diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2013-04-16 16:46:35 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2013-04-16 16:49:13 +0200 |
commit | d8d846936cb37aafccb672074e3b8ee1d995f608 (patch) | |
tree | f0c7446b68cad9d2f89b32a5624d683eb9fb0624 /sonar-batch | |
parent | fb7248d86bc6b540567a2dcc8ee9cad6a615e254 (diff) | |
download | sonarqube-d8d846936cb37aafccb672074e3b8ee1d995f608.tar.gz sonarqube-d8d846936cb37aafccb672074e3b8ee1d995f608.zip |
SONAR-4147 Support a new profiling option "sonar.profiling.log=true"
Diffstat (limited to 'sonar-batch')
17 files changed, 1046 insertions, 43 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java index d7459b0e7a7..b2314ee7053 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrapper/Batch.java @@ -76,7 +76,7 @@ public final class Batch { } private void startBatch() { - List all = Lists.newArrayList(components); + List<Object> all = Lists.newArrayList(components); all.add(new BootstrapProperties(bootstrapProperties)); if (projectReactor != null) { all.add(projectReactor); diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/Phases.java b/sonar-batch/src/main/java/org/sonar/batch/phases/Phases.java index 2ccb0da0d05..6694153325e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/Phases.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/Phases.java @@ -27,7 +27,18 @@ import java.util.Set; public class Phases { public static enum Phase { - MAVEN, INIT, SENSOR, DECORATOR, POSTJOB + MAVEN("Maven"), INIT("Initializers"), SENSOR("Sensors"), DECORATOR("Decorators"), POSTJOB("Post-Jobs"); + + private final String label; + + private Phase(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } } private final Set<Phase> enabled = Sets.newHashSet(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java new file mode 100644 index 00000000000..05eb06718f6 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobExecutionEvent.java @@ -0,0 +1,50 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.events.PostJobExecutionHandler; + +class PostJobExecutionEvent extends AbstractPhaseEvent<PostJobExecutionHandler> + implements org.sonar.api.batch.events.PostJobExecutionHandler.PostJobExecutionEvent { + + private final PostJob postJob; + + PostJobExecutionEvent(PostJob postJob, boolean start) { + super(start); + this.postJob = postJob; + } + + @Override + public PostJob getPostJob() { + return postJob; + } + + @Override + public void dispatch(PostJobExecutionHandler handler) { + handler.onPostJobExecution(this); + } + + @Override + public Class getType() { + return PostJobExecutionHandler.class; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java new file mode 100644 index 00000000000..1dc88497501 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobPhaseEvent.java @@ -0,0 +1,51 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.phases; + +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.events.PostJobsPhaseHandler; + +import java.util.List; + +class PostJobPhaseEvent extends AbstractPhaseEvent<PostJobsPhaseHandler> + implements org.sonar.api.batch.events.PostJobsPhaseHandler.PostJobsPhaseEvent { + + private final List<PostJob> postJobs; + + PostJobPhaseEvent(List<PostJob> postJobs, boolean start) { + super(start); + this.postJobs = postJobs; + } + + public List<PostJob> getPostJobs() { + return postJobs; + } + + @Override + protected void dispatch(PostJobsPhaseHandler handler) { + handler.onPostJobsPhase(this); + } + + @Override + protected Class getType() { + return PostJobsPhaseHandler.class; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java index ee89e9ac2da..5a1736552c4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PostJobsExecutor.java @@ -19,6 +19,7 @@ */ package org.sonar.batch.phases; +import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,9 +30,10 @@ import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.maven.DependsUponMavenPlugin; import org.sonar.api.batch.maven.MavenPluginHandler; import org.sonar.api.resources.Project; -import org.sonar.batch.scan.maven.MavenPluginExecutor; +import org.sonar.batch.events.EventBus; import org.sonar.batch.local.DryRunExporter; import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.maven.MavenPluginExecutor; import java.util.Collection; @@ -43,20 +45,25 @@ public class PostJobsExecutor implements BatchComponent { private final DefaultModuleFileSystem fs; private final MavenPluginExecutor mavenExecutor; private final DryRunExporter localModeExporter; + private final EventBus eventBus; public PostJobsExecutor(BatchExtensionDictionnary selector, Project project, DefaultModuleFileSystem fs, MavenPluginExecutor mavenExecutor, - DryRunExporter localModeExporter) { + DryRunExporter localModeExporter, EventBus eventBus) { this.selector = selector; this.project = project; this.fs = fs; this.mavenExecutor = mavenExecutor; this.localModeExporter = localModeExporter; + this.eventBus = eventBus; } public void execute(SensorContext context) { Collection<PostJob> postJobs = selector.select(PostJob.class, project, true); + + eventBus.fireEvent(new PostJobPhaseEvent(Lists.newArrayList(postJobs), true)); execute(context, postJobs); exportLocalModeResults(context); + eventBus.fireEvent(new PostJobPhaseEvent(Lists.newArrayList(postJobs), false)); } private void execute(SensorContext context, Collection<PostJob> postJobs) { @@ -64,8 +71,10 @@ public class PostJobsExecutor implements BatchComponent { for (PostJob postJob : postJobs) { LOG.info("Executing post-job {}", postJob.getClass()); + eventBus.fireEvent(new PostJobExecutionEvent(postJob, true)); executeMavenPlugin(postJob); postJob.executeOn(project, context); + eventBus.fireEvent(new PostJobExecutionEvent(postJob, false)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java index 9428884f068..47f1eff8e2a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java @@ -66,9 +66,9 @@ public class SensorsExecutor implements BatchComponent { for (Sensor sensor : sensors) { // SONAR-2965 In case the sensor takes too much time we close the session to not face a timeout session.commitAndClose(); - executeMavenPlugin(sensor); eventBus.fireEvent(new SensorExecutionEvent(sensor, true)); + executeMavenPlugin(sensor); sensor.analyse(project, context); eventBus.fireEvent(new SensorExecutionEvent(sensor, false)); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java new file mode 100644 index 00000000000..5ff269a69ac --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/AbstractTimeProfiling.java @@ -0,0 +1,93 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public abstract class AbstractTimeProfiling { + + private final long startTime; + + private long totalTime; + + public AbstractTimeProfiling() { + this.startTime = System.currentTimeMillis(); + } + + public long startTime() { + return startTime; + } + + public void stop() { + this.totalTime = System.currentTimeMillis() - startTime; + } + + public long totalTime() { + return totalTime; + } + + public String totalTimeAsString() { + if (totalTime < 1000) { + return String.format("%sms", totalTime); + } + else { + long sec = totalTime / 1000; + // long remainingMs = totalTime - (sec * 1000); + if (sec < 60) { + return String.format("%ss", sec); + } + else { + long min = sec / 60; + long remainingSec = sec - (min * 60); + if (remainingSec > 0) { + return String.format("%smin %ss", min, remainingSec); + } + else { + return String.format("%smin", min); + } + } + } + } + + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } + + protected void add(AbstractTimeProfiling other) { + this.setTotalTime(this.totalTime() + other.totalTime()); + } + + protected <G extends AbstractTimeProfiling> List<G> sortByDescendingTotalTime(Collection<G> unsorted) { + List<G> result = new ArrayList<G>(unsorted.size()); + result.addAll(unsorted); + Collections.sort(result, new Comparator<G>() { + @Override + public int compare(G o1, G o2) { + return Long.valueOf(o2.totalTime()).compareTo(o1.totalTime()); + } + }); + return result; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/ItemProfiling.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/ItemProfiling.java new file mode 100644 index 00000000000..18998ab6b10 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/ItemProfiling.java @@ -0,0 +1,34 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +public class ItemProfiling extends AbstractTimeProfiling { + + private final String itemName; + + public ItemProfiling(String itemName) { + this.itemName = itemName; + } + + public String itemName() { + return itemName; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java new file mode 100644 index 00000000000..40c00be5050 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/ModuleProfiling.java @@ -0,0 +1,64 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +import org.sonar.batch.phases.Phases; +import org.sonar.batch.phases.Phases.Phase; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class ModuleProfiling extends AbstractTimeProfiling { + + private Map<Phases.Phase, PhaseProfiling> profilingPerPhase = new HashMap<Phases.Phase, PhaseProfiling>(); + + public PhaseProfiling getProfilingPerPhase(Phase phase) { + return profilingPerPhase.get(phase); + } + + public void addPhaseProfiling(Phase phase) { + profilingPerPhase.put(phase, PhaseProfiling.create(phase)); + } + + public void dump() { + for (PhaseProfiling phaseProfiling : sortByDescendingTotalTime(profilingPerPhase.values())) { + System.out.println(" * " + phaseProfiling.phase() + " execution time: " + phaseProfiling.totalTimeAsString()); + } + for (Phase phase : Phases.Phase.values()) { + if (profilingPerPhase.containsKey(phase)) { + System.out.println(""); + System.out.println(" * " + phase + " execution time breakdown"); + getProfilingPerPhase(phase).dump(); + } + } + } + + public void merge(ModuleProfiling other) { + super.add(other); + for (Entry<Phases.Phase, PhaseProfiling> entry : other.profilingPerPhase.entrySet()) { + if (!this.profilingPerPhase.containsKey(entry.getKey())) { + this.addPhaseProfiling(entry.getKey()); + } + this.getProfilingPerPhase(entry.getKey()).merge(entry.getValue()); + } + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java new file mode 100644 index 00000000000..5ed818970f2 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhaseProfiling.java @@ -0,0 +1,106 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +import org.sonar.batch.phases.Phases.Phase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class PhaseProfiling extends AbstractTimeProfiling { + + private final Phase phase; + + private Map<String, ItemProfiling> profilingPerItem = new HashMap<String, ItemProfiling>(); + + public PhaseProfiling(Phase phase) { + this.phase = phase; + } + + public static PhaseProfiling create(Phase phase) { + return new PhaseProfiling(phase); + } + + public Phase phase() { + return phase; + } + + public ItemProfiling getProfilingPerItem(Object item) { + String stringOrSimpleName = toStringOrSimpleName(item); + return profilingPerItem.get(stringOrSimpleName); + } + + public void newItemProfiling(Object item) { + String stringOrSimpleName = toStringOrSimpleName(item); + profilingPerItem.put(stringOrSimpleName, new ItemProfiling(stringOrSimpleName)); + } + + public void newItemProfiling(String itemName) { + profilingPerItem.put(itemName, new ItemProfiling(itemName)); + } + + public void merge(PhaseProfiling other) { + super.add(other); + for (Entry<String, ItemProfiling> entry : other.profilingPerItem.entrySet()) { + if (!this.profilingPerItem.containsKey(entry.getKey())) { + newItemProfiling(entry.getKey()); + } + this.getProfilingPerItem(entry.getKey()).add(entry.getValue()); + } + } + + public void dump() { + for (ItemProfiling itemProfiling : truncateList(sortByDescendingTotalTime(profilingPerItem.values()))) { + System.out.println(" o " + itemProfiling.itemName() + ": " + itemProfiling.totalTimeAsString()); + } + } + + /** + * Try to use toString if it is not the default {@link Object#toString()}. Else use {@link Class#getSimpleName()} + * @param o + * @return + */ + private String toStringOrSimpleName(Object o) { + String toString = o.toString(); + if (toString == null || toString.startsWith(o.getClass().getName())) { + return o.getClass().getSimpleName(); + } + return toString; + } + + private List<ItemProfiling> truncateList(List<ItemProfiling> sortedFullList) { + int maxSize = 10; + List<ItemProfiling> result = new ArrayList<ItemProfiling>(maxSize); + int i = 0; + for (ItemProfiling item : sortedFullList) { + if (i++ >= maxSize || item.totalTime() == 0) { + return result; + } + else { + result.add(item); + } + } + return result; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java new file mode 100644 index 00000000000..88f3c0de6dd --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/PhasesSumUpTimeProfiler.java @@ -0,0 +1,162 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.events.DecoratorExecutionHandler; +import org.sonar.api.batch.events.DecoratorsPhaseHandler; +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.batch.phases.Phases; + +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +public class PhasesSumUpTimeProfiler implements ProjectAnalysisHandler, SensorExecutionHandler, DecoratorExecutionHandler, PostJobExecutionHandler, DecoratorsPhaseHandler, + SensorsPhaseHandler, PostJobsPhaseHandler { + + @VisibleForTesting + ModuleProfiling currentModuleProfiling; + @VisibleForTesting + ModuleProfiling totalProfiling = new ModuleProfiling(); + private DecoratorsProfiler decoratorsProfiler; + + @Override + public void onProjectAnalysis(ProjectAnalysisEvent event) { + Project module = event.getProject(); + if (event.isStart()) { + decoratorsProfiler = new DecoratorsProfiler(); + currentModuleProfiling = new ModuleProfiling(); + } + else { + currentModuleProfiling.stop(); + System.out.println("\n -------- Profiling for module " + module.getName() + " --------\n"); + currentModuleProfiling.dump(); + System.out.println("\n -------- End of profiling for module " + module.getName() + " --------\n"); + totalProfiling.merge(currentModuleProfiling); + if (module.isRoot() && !module.getModules().isEmpty()) { + totalProfiling.stop(); + System.out.println("\n ======== Profiling for total execution ========\n"); + totalProfiling.dump(); + System.out.println("\n ======== End of profiling for total execution ========\n"); + } + } + } + + public void onSensorsPhase(SensorsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phases.Phase.SENSOR); + } + else { + currentModuleProfiling.getProfilingPerPhase(Phases.Phase.SENSOR).stop(); + } + } + + public void onSensorExecution(SensorExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.SENSOR); + if (event.isStart()) { + profiling.newItemProfiling(event.getSensor()); + } else { + profiling.getProfilingPerItem(event.getSensor()).stop(); + } + } + + public void onDecoratorExecution(DecoratorExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR); + if (event.isStart()) { + if (profiling.getProfilingPerItem(event.getDecorator()) == null) { + profiling.newItemProfiling(event.getDecorator()); + } + decoratorsProfiler.start(event.getDecorator()); + } else { + decoratorsProfiler.stop(); + } + } + + public void onDecoratorsPhase(DecoratorsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phases.Phase.DECORATOR); + } + else { + for (Decorator decorator : decoratorsProfiler.getDurations().keySet()) { + currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR) + .getProfilingPerItem(decorator).setTotalTime(decoratorsProfiler.getDurations().get(decorator)); + } + currentModuleProfiling.getProfilingPerPhase(Phases.Phase.DECORATOR).stop(); + } + } + + public void onPostJobsPhase(PostJobsPhaseEvent event) { + if (event.isStart()) { + currentModuleProfiling.addPhaseProfiling(Phases.Phase.POSTJOB); + } + else { + currentModuleProfiling.getProfilingPerPhase(Phases.Phase.POSTJOB).stop(); + } + } + + public void onPostJobExecution(PostJobExecutionEvent event) { + PhaseProfiling profiling = currentModuleProfiling.getProfilingPerPhase(Phases.Phase.POSTJOB); + if (event.isStart()) { + profiling.newItemProfiling(event.getPostJob()); + } else { + profiling.getProfilingPerItem(event.getPostJob()).stop(); + } + } + + static class DecoratorsProfiler { + List<Decorator> decorators = Lists.newArrayList(); + Map<Decorator, Long> durations = new IdentityHashMap<Decorator, Long>(); + long startTime; + Decorator currentDecorator; + + DecoratorsProfiler() { + } + + void start(Decorator decorator) { + this.startTime = System.currentTimeMillis(); + this.currentDecorator = decorator; + } + + void stop() { + final Long cumulatedDuration; + if (durations.containsKey(currentDecorator)) { + cumulatedDuration = durations.get(currentDecorator); + } else { + decorators.add(currentDecorator); + cumulatedDuration = 0L; + } + durations.put(currentDecorator, cumulatedDuration + (System.currentTimeMillis() - startTime)); + } + + public Map<Decorator, Long> getDurations() { + return durations; + } + + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/profiling/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/profiling/package-info.java new file mode 100644 index 00000000000..6c52d959784 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/profiling/package-info.java @@ -0,0 +1,24 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.profiling; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index e6d7cdd5003..ed9c5bba0c5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -20,7 +20,9 @@ package org.sonar.batch.scan; import org.sonar.api.BatchExtension; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.batch.DefaultFileLinesContextFactory; @@ -31,12 +33,22 @@ import org.sonar.batch.bootstrap.ExtensionInstaller; import org.sonar.batch.bootstrap.ExtensionMatcher; import org.sonar.batch.bootstrap.ExtensionUtils; import org.sonar.batch.bootstrap.MetricProvider; -import org.sonar.batch.index.*; +import org.sonar.batch.index.DefaultIndex; +import org.sonar.batch.index.DefaultPersistenceManager; +import org.sonar.batch.index.DefaultResourcePersister; +import org.sonar.batch.index.DependencyPersister; +import org.sonar.batch.index.EventPersister; +import org.sonar.batch.index.LinkPersister; +import org.sonar.batch.index.MeasurePersister; +import org.sonar.batch.index.MemoryOptimizer; +import org.sonar.batch.index.SnapshotCache; +import org.sonar.batch.index.SourcePersister; import org.sonar.batch.issue.DeprecatedViolations; import org.sonar.batch.issue.IssueCache; import org.sonar.batch.issue.IssuePersister; import org.sonar.batch.issue.ScanIssueActions; import org.sonar.batch.phases.GraphPersister; +import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; import org.sonar.batch.scan.maven.FakeMavenPluginExecutor; import org.sonar.batch.scan.maven.MavenPluginExecutor; import org.sonar.batch.scan.source.SourceDataPersister; @@ -59,44 +71,47 @@ public class ProjectScanContainer extends ComponentContainer { addBatchComponents(); fixMavenExecutor(); addBatchExtensions(); + Settings settings = getComponentByType(Settings.class); + if (settings != null && settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)) { + add(PhasesSumUpTimeProfiler.class); + } } private void addBatchComponents() { add( - DefaultResourceCreationLock.class, - DefaultPersistenceManager.class, - DependencyPersister.class, - EventPersister.class, - LinkPersister.class, - MeasurePersister.class, - MemoryOptimizer.class, - DefaultResourcePersister.class, - SourcePersister.class, - DefaultNotificationManager.class, - MetricProvider.class, - ProjectConfigurator.class, - DefaultIndex.class, - DefaultFileLinesContextFactory.class, - ProjectLock.class, - LastSnapshots.class, - SnapshotCache.class, + DefaultResourceCreationLock.class, + DefaultPersistenceManager.class, + DependencyPersister.class, + EventPersister.class, + LinkPersister.class, + MeasurePersister.class, + MemoryOptimizer.class, + DefaultResourcePersister.class, + SourcePersister.class, + DefaultNotificationManager.class, + MetricProvider.class, + ProjectConfigurator.class, + DefaultIndex.class, + DefaultFileLinesContextFactory.class, + ProjectLock.class, + LastSnapshots.class, + SnapshotCache.class, - ScanIssueActions.class, - DeprecatedViolations.class, - IssueCache.class, - IssuePersister.class, + ScanIssueActions.class, + DeprecatedViolations.class, + IssueCache.class, + IssuePersister.class, - TestPlanPerspectiveLoader.class, - TestablePerspectiveLoader.class, - TestPlanBuilder.class, - TestableBuilder.class, - ScanGraph.create(), - GraphPersister.class, + TestPlanPerspectiveLoader.class, + TestablePerspectiveLoader.class, + TestPlanBuilder.class, + TestableBuilder.class, + ScanGraph.create(), + GraphPersister.class, - SyntaxHighlightingCache.class, - SymbolDataCache.class, - SourceDataPersister.class - ); + SyntaxHighlightingCache.class, + SymbolDataCache.class, + SourceDataPersister.class); } private void fixMavenExecutor() { diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapContainerTest.java index db3a4d21bfe..347ff07f085 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapContainerTest.java @@ -62,11 +62,11 @@ public class BootstrapContainerTest { PluginMetadata metadata = mock(PluginMetadata.class); FakePlugin plugin = new FakePlugin(); BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(ImmutableMap.<PluginMetadata, Plugin>of( - metadata, plugin - )); + when(pluginRepository.getPluginsByMetadata()).thenReturn(ImmutableMap.<PluginMetadata, Plugin> of( + metadata, plugin + )); - BootstrapContainer container = spy(BootstrapContainer.create(Lists.newArrayList(pluginRepository))); + BootstrapContainer container = spy(BootstrapContainer.create(Lists.<Object> newArrayList(pluginRepository))); doNothing().when(container).executeTask(); container.doAfterStart(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java b/sonar-batch/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java index 4f23c04a2c9..5b1472b2c88 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/phases/PostJobsExecutorTest.java @@ -25,9 +25,10 @@ import org.sonar.api.batch.BatchExtensionDictionnary; import org.sonar.api.batch.PostJob; import org.sonar.api.batch.SensorContext; import org.sonar.api.resources.Project; -import org.sonar.batch.scan.maven.MavenPluginExecutor; +import org.sonar.batch.events.EventBus; import org.sonar.batch.local.DryRunExporter; import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.maven.MavenPluginExecutor; import java.util.Arrays; @@ -48,7 +49,7 @@ public class PostJobsExecutorTest { @Before public void setUp() { - executor = new PostJobsExecutor(selector, project, mock(DefaultModuleFileSystem.class), mavenPluginExecutor, localModeExporter); + executor = new PostJobsExecutor(selector, project, mock(DefaultModuleFileSystem.class), mavenPluginExecutor, localModeExporter, mock(EventBus.class)); } @Test diff --git a/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java b/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java new file mode 100644 index 00000000000..e5b586979a0 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/profiling/PhasesSumUpTimeProfilerTest.java @@ -0,0 +1,360 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.profiling; + +import org.junit.Test; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.PostJob; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.events.DecoratorExecutionHandler; +import org.sonar.api.batch.events.DecoratorsPhaseHandler; +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.ProjectAnalysisHandler.ProjectAnalysisEvent; +import org.sonar.api.batch.events.SensorExecutionHandler; +import org.sonar.api.batch.events.SensorExecutionHandler.SensorExecutionEvent; +import org.sonar.api.batch.events.SensorsPhaseHandler; +import org.sonar.api.batch.events.SensorsPhaseHandler.SensorsPhaseEvent; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.phases.Phases.Phase; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PhasesSumUpTimeProfilerTest { + + @Test + public void testSimpleProject() throws InterruptedException { + PhasesSumUpTimeProfiler profiler = new PhasesSumUpTimeProfiler(); + final Project project = mockProject("project", true); + when(project.getModules()).thenReturn(Collections.<Project> emptyList()); + + fakeAnalysis(profiler, project); + + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isIn(delta(10L, 5)); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR).getProfilingPerItem(new FakeDecorator1()).totalTime()).isIn(delta(20L, 5)); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isIn(delta(30L, 5)); + + } + + @Test + public void testMultimoduleProject() throws InterruptedException { + PhasesSumUpTimeProfiler profiler = new PhasesSumUpTimeProfiler(); + final Project project = mockProject("project root", true); + final Project moduleA = mockProject("moduleA", false); + final Project moduleB = mockProject("moduleB", false); + when(project.getModules()).thenReturn(Arrays.asList(moduleA, moduleB)); + + fakeAnalysis(profiler, moduleA); + fakeAnalysis(profiler, moduleB); + fakeAnalysis(profiler, project); + + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isIn(delta(10L, 5)); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR).getProfilingPerItem(new FakeDecorator1()).totalTime()).isIn(delta(20L, 5)); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.DECORATOR).getProfilingPerItem(new FakeDecorator2()).totalTime()).isIn(delta(10L, 5)); + assertThat(profiler.currentModuleProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isIn(delta(30L, 5)); + + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.SENSOR).getProfilingPerItem(new FakeSensor()).totalTime()).isIn(delta(30L, 5)); + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.DECORATOR).getProfilingPerItem(new FakeDecorator1()).totalTime()).isIn(delta(60L, 10)); + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.DECORATOR).getProfilingPerItem(new FakeDecorator2()).totalTime()).isIn(delta(30L, 5)); + assertThat(profiler.totalProfiling.getProfilingPerPhase(Phase.POSTJOB).getProfilingPerItem(new FakePostJob()).totalTime()).isIn(delta(90L, 10)); + } + + @Test + public void testDisplayTimings() { + AbstractTimeProfiling profiling = new AbstractTimeProfiling() { + }; + + profiling.setTotalTime(5); + assertThat(profiling.totalTimeAsString()).isEqualTo("5ms"); + + profiling.setTotalTime(5 * 1000 + 12); + assertThat(profiling.totalTimeAsString()).isEqualTo("5s"); + + profiling.setTotalTime(5 * 60 * 1000 + 12 * 1000); + assertThat(profiling.totalTimeAsString()).isEqualTo("5min 12s"); + + profiling.setTotalTime(5 * 60 * 1000); + assertThat(profiling.totalTimeAsString()).isEqualTo("5min"); + } + + private Object[] delta(long value, int delta) { + Long[] result = new Long[2 * delta + 1]; + int index = 0; + for (long i = value - delta; i <= value + delta; i++) { + result[index++] = i; + } + return result; + } + + private Project mockProject(String name, boolean isRoot) { + final Project project = mock(Project.class); + when(project.isRoot()).thenReturn(isRoot); + when(project.getName()).thenReturn(name); + return project; + } + + private void fakeAnalysis(PhasesSumUpTimeProfiler profiler, final Project module) throws InterruptedException { + // Start of moduleA + profiler.onProjectAnalysis(projectEvent(module, true)); + sensorPhase(profiler); + decoratorPhase(profiler); + postJobPhase(profiler); + // End of moduleA + profiler.onProjectAnalysis(projectEvent(module, false)); + } + + private void decoratorPhase(PhasesSumUpTimeProfiler profiler) throws InterruptedException { + Decorator decorator1 = new FakeDecorator1(); + Decorator decorator2 = new FakeDecorator2(); + // Start of decorator phase + profiler.onDecoratorsPhase(decoratorsEvent(true)); + // Start of decorator 1 + profiler.onDecoratorExecution(decoratorEvent(decorator1, true)); + Thread.sleep(10); + // End of decorator 1 + profiler.onDecoratorExecution(decoratorEvent(decorator1, false)); + // Start of decorator 2 + profiler.onDecoratorExecution(decoratorEvent(decorator2, true)); + Thread.sleep(5); + // End of decorator 2 + profiler.onDecoratorExecution(decoratorEvent(decorator2, false)); + // Start of decorator 1 + profiler.onDecoratorExecution(decoratorEvent(decorator1, true)); + Thread.sleep(10); + // End of decorator 1 + profiler.onDecoratorExecution(decoratorEvent(decorator1, false)); + // Start of decorator 2 + profiler.onDecoratorExecution(decoratorEvent(decorator2, true)); + Thread.sleep(5); + // End of decorator 2 + profiler.onDecoratorExecution(decoratorEvent(decorator2, false)); + // End of decorator phase + profiler.onDecoratorsPhase(decoratorsEvent(false)); + } + + private void sensorPhase(PhasesSumUpTimeProfiler profiler) throws InterruptedException { + Sensor sensor = new FakeSensor(); + // Start of sensor phase + profiler.onSensorsPhase(sensorsEvent(true)); + // Start of a Sensor + profiler.onSensorExecution(sensorEvent(sensor, true)); + Thread.sleep(10); + // End of a Sensor + profiler.onSensorExecution(sensorEvent(sensor, false)); + // End of sensor phase + profiler.onSensorsPhase(sensorsEvent(false)); + } + + private void postJobPhase(PhasesSumUpTimeProfiler profiler) throws InterruptedException { + PostJob postJob = new FakePostJob(); + // Start of sensor phase + profiler.onPostJobsPhase(postJobsEvent(true)); + // Start of a Sensor + profiler.onPostJobExecution(postJobEvent(postJob, true)); + Thread.sleep(30); + // End of a Sensor + profiler.onPostJobExecution(postJobEvent(postJob, false)); + // End of sensor phase + profiler.onPostJobsPhase(postJobsEvent(false)); + } + + private SensorExecutionEvent sensorEvent(final Sensor sensor, final boolean start) { + return new SensorExecutionHandler.SensorExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Sensor getSensor() { + return sensor; + } + }; + } + + private DecoratorExecutionHandler.DecoratorExecutionEvent decoratorEvent(final Decorator decorator, final boolean start) { + return new DecoratorExecutionHandler.DecoratorExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Decorator getDecorator() { + return decorator; + } + }; + } + + private PostJobExecutionHandler.PostJobExecutionEvent postJobEvent(final PostJob postJob, final boolean start) { + return new PostJobExecutionHandler.PostJobExecutionEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public PostJob getPostJob() { + return postJob; + } + }; + } + + private SensorsPhaseEvent sensorsEvent(final boolean start) { + return new SensorsPhaseHandler.SensorsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<Sensor> getSensors() { + return null; + } + }; + } + + private PostJobsPhaseHandler.PostJobsPhaseEvent postJobsEvent(final boolean start) { + return new PostJobsPhaseHandler.PostJobsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<PostJob> getPostJobs() { + return null; + } + }; + } + + private DecoratorsPhaseHandler.DecoratorsPhaseEvent decoratorsEvent(final boolean start) { + return new DecoratorsPhaseHandler.DecoratorsPhaseEvent() { + + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public List<Decorator> getDecorators() { + return null; + } + }; + } + + private ProjectAnalysisEvent projectEvent(final Project project, final boolean start) { + return new ProjectAnalysisHandler.ProjectAnalysisEvent() { + @Override + public boolean isStart() { + return start; + } + + @Override + public boolean isEnd() { + return !start; + } + + @Override + public Project getProject() { + return project; + } + }; + } + + public class FakeSensor implements Sensor { + @Override + public void analyse(Project project, SensorContext context) { + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + public class FakeDecorator1 implements Decorator { + public void decorate(Resource resource, DecoratorContext context) { + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + public class FakeDecorator2 implements Decorator { + public void decorate(Resource resource, DecoratorContext context) { + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + } + + public class FakePostJob implements PostJob { + @Override + public void executeOn(Project project, SensorContext context) { + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java index 3ec36cd3313..9cd35fd0c8d 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java @@ -21,11 +21,14 @@ package org.sonar.batch.scan; import org.junit.Test; import org.sonar.api.BatchExtension; +import org.sonar.api.CoreProperties; import org.sonar.api.ServerExtension; import org.sonar.api.batch.InstantiationStrategy; +import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.task.TaskExtension; import org.sonar.batch.bootstrap.ExtensionInstaller; +import org.sonar.batch.profiling.PhasesSumUpTimeProfiler; import org.sonar.batch.scan.maven.MavenPluginExecutor; import static org.fest.assertions.Assertions.assertThat; @@ -55,6 +58,26 @@ public class ProjectScanContainerTest { } @Test + public void should_activate_profiling() { + ComponentContainer parentContainer = new ComponentContainer(); + Settings settings = new Settings(); + parentContainer.add(settings); + ProjectScanContainer container = new ProjectScanContainer(parentContainer); + container.add(mock(ExtensionInstaller.class)); + container.doBeforeStart(); + + assertThat(container.getComponentsByType(PhasesSumUpTimeProfiler.class)).hasSize(0); + + settings.setProperty(CoreProperties.PROFILING_LOG_PROPERTY, "true"); + + container = new ProjectScanContainer(parentContainer); + container.add(mock(ExtensionInstaller.class)); + container.doBeforeStart(); + + assertThat(container.getComponentsByType(PhasesSumUpTimeProfiler.class)).hasSize(1); + } + + @Test public void should_add_only_batch_extensions() { ProjectScanContainer.BatchExtensionFilter filter = new ProjectScanContainer.BatchExtensionFilter(); |