blob: 5d01905f1665ff7ab8ff20c52f0ce0d05575d9f4 [file] [log] [blame]
Pierre De Rop3a00a212015-03-01 09:27:46 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dm.benchmark.controller.impl;
20
21import static java.lang.System.out;
22import static java.util.stream.Collectors.joining;
23import static java.util.stream.Collectors.toList;
24import static org.apache.felix.dm.benchmark.scenario.Artist.ALBUMS;
25import static org.apache.felix.dm.benchmark.scenario.Artist.ARTISTS;
26import static org.apache.felix.dm.benchmark.scenario.Artist.TRACKS;
27import static org.apache.felix.dm.benchmark.scenario.Helper.debug;
28
29import java.text.DecimalFormat;
30import java.text.DecimalFormatSymbols;
31import java.text.NumberFormat;
32import java.util.Arrays;
33import java.util.List;
34import java.util.Locale;
35import java.util.Optional;
36import java.util.concurrent.CountDownLatch;
37import java.util.concurrent.ThreadLocalRandom;
38import java.util.concurrent.TimeUnit;
39import java.util.function.Consumer;
40import java.util.stream.LongStream;
41import java.util.stream.Stream;
42
43import org.apache.felix.dm.benchmark.controller.ScenarioController;
44import org.apache.felix.dm.benchmark.scenario.Album;
45import org.apache.felix.dm.benchmark.scenario.Artist;
46import org.apache.felix.dm.benchmark.scenario.Helper;
47import org.apache.felix.dm.benchmark.scenario.Track;
48import org.apache.felix.dm.benchmark.scenario.Unchecked;
49import org.osgi.framework.Bundle;
50import org.osgi.framework.BundleContext;
51
52/**
53 * The controller which perform microbenchmarks on some scenario bundles.
54 *
55 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
56 */
57public class ScenarioControllerImpl implements Runnable, ScenarioController {
58 /**
59 * List of bundles to be executed by the benchmark.
60 */
61 final List<String> TESTS = Arrays.asList(
62 "org.apache.felix.dependencymanager.benchmark.dependencymanager",
63 "org.apache.felix.dependencymanager.benchmark.dependencymanager.parallel"
64 );
65
66 /**
67 * Our injected bundle context, used to lookup the bundles to benchmark.
68 */
69 private volatile BundleContext m_bctx;
70
71 /**
72 * Latches used to detect when expected services are registered, or unregistered.
73 */
74 private volatile CountDownLatch m_startLatch, m_stopLatch;
75
76 /**
77 * When a components is called in its start or stop method, we'll perform some processing if the following
78 * attribute is true.
79 */
80 private volatile boolean m_doProcessingInStartStop;
81
82 /**
83 * Our component is starting: we'll first stop all bundles participating in the benchmark, then we'll
84 * fire a thread, and from that thread we'll iterate on all bundles in order to do a benchmark on each.
85 * (we'll call start/stop N times, and will display the elapsed times for each bundle).
86 */
87 void start() {
88 new Thread(this).start();
89 }
90
91 void stop() {
92 }
93
94 @Override
95 public void run() {
96 // wait a bit in order to let the gogo banner be displayed before we start the bench.
97 Unchecked.run(() -> Thread.sleep(500));
98
99 out.println("Starting benchmarks (each tested bundle will add/remove " + (ARTISTS + (ARTISTS * (ALBUMS + (ALBUMS * TRACKS))))
100 + " components during bundle activation).");
101
102 // Stop all tested bundles.
103 forEachScenarioBundle(TESTS, Unchecked.consumer(bundle -> {
104 debug(() -> "Stopping bundle " + bundle.getSymbolicName());
105 bundle.stop();
106 }));
107
108 // Register our controller service
109 m_bctx.registerService(ScenarioController.class.getName(), this, null);
110
111 // Start/stop several times the tested bundles. (no processing done in components start methods).
112 m_doProcessingInStartStop = false;
113 out.println("\n\t[Starting benchmarks with no processing done in components start methods]");
114 startStopScenarioBundles(TESTS, 50);
115
116 // Start/stop several times the tested bundles (processing is done in components start methods).
117 m_doProcessingInStartStop = true;
118 out.println("\n\t[Starting benchmarks with processing done in components start methods]");
119 startStopScenarioBundles(TESTS, 5);
120 }
121
122 @Override
123 public void artistAdded(Artist artist) {
124 int size = artist.getAlbums().size();
125 if (size != Artist.ALBUMS) {
126 throw new IllegalStateException("Artist has not created expected number of albums:" + size);
127 }
128 artist.play();
129 componentAdded();
130 Helper.debug(() -> "Artist added : " + artist);
131 }
132
133 @Override
134 public void artistRemoved(Artist artist) {
135 componentRemoved();
136 Helper.debug(() -> "Artist removed : " + artist);
137 }
138
139 @Override
140 public void albumAdded(Album album) {
141 int size = album.getMusicTracks().size();
142 if (size != Artist.TRACKS) {
143 throw new IllegalStateException("Album does not contain expected number of music tracks:" + size);
144 }
145 componentAdded();
146 Helper.debug(() -> "Album added : " + album);
147 }
148
149 @Override
150 public void albumRemoved(Album album) {
151 componentRemoved();
152 Helper.debug(() -> "Album removed : " + album);
153 }
154
155 @Override
156 public void trackAdded(Track track) {
157 componentAdded();
158 Helper.debug(() -> "Track added : " + track);
159 }
160
161 @Override
162 public void trackRemoved(Track track) {
163 componentRemoved();
164 Helper.debug(() -> "Track removed : " + track);
165 }
166
167 // ------------------- Private methods -----------------------------------------------------
168
169 private void startStopScenarioBundles(List<String> tests, int iterations) {
170 forEachScenarioBundle(tests, bundle -> {
171 out.print("\nBenchmarking bundle: " + bundle.getSymbolicName() + " ");
172 List<Long> sortedResults = LongStream.range(0, iterations)
173 .peek(i -> out.print("."))
174 .map(n -> durationOf(() -> startAndStop(bundle)))
175 .sorted().boxed().collect(toList());
176 out.println();
177 displaySortedResults(sortedResults);
178 Unchecked.run(() -> Thread.sleep(500));
179 });
180 }
181
182 /**
183 * Displays meaningful values in the sorted results (first=fastest, midle=average, last entry=slowest)
184 * @param sortedResults
185 */
186 private void displaySortedResults(List<Long> sortedResults) {
187 // We don't display an average of the duration times; Instead, we sort the results,
188 // and we display the significant results (the first entry is the fastest, the middle entry is the
189 // average, the last entry is the slowest ...)
190 out.printf("-> results in nanos: [%s]%n",
191 Stream.of(0f, 24.99f, 49.99f, 74.99f, 99.99f)
192 .mapToInt(perc -> (int) (perc * sortedResults.size() / 100))
193 .mapToObj(sortedResults::get)
194 .map(this::formatNano)
195 .collect(joining(" | ")));
196 }
197
198 /**
199 * Displays a nanosecond value using thousands separator.
200 * Example: 1000000 -> 1,000,000
201 */
202 private String formatNano(Long nanoseconds) {
203 DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
204 DecimalFormatSymbols symbols = formatter.getDecimalFormatSymbols();
205 symbols.setGroupingSeparator(',');
206 return formatter.format(nanoseconds);
207 }
208
209 private void componentAdded() {
210 doProcessing();
211 m_startLatch.countDown();
212 }
213
214 private void componentRemoved() {
215 //doProcessing();
216 m_stopLatch.countDown();
217 }
218
219 private void doProcessing() {
220 if (m_doProcessingInStartStop) {
221 long duration = TimeUnit.MILLISECONDS.toNanos(ThreadLocalRandom.current().nextLong(5));
222 long t1 = System.nanoTime();
223 while (System.nanoTime() - t1 < duration)
224 ;
225 }
226 }
227
228 /**
229 * Maps a function to all bundles participating in the benchmark.
230 */
231 private void forEachScenarioBundle(List<String> tests, Consumer<Bundle> consumer) {
232 tests.stream().forEach(test -> {
233 Optional<Bundle> bundle = Stream.of(m_bctx.getBundles()).filter(b -> b.getSymbolicName().equals(test)).findFirst();
234 bundle.ifPresent(b -> {
235 consumer.accept(b);
236 });
237 });
238 }
239
240 /**
241 * This function does this:
242 *
243 * 1) start a bundle, and register the ScenarioController service (this will trigger all components activation)
244 * 2) wait for all expected components to be fully started
245 * 3) stop the bundle and wait for all expected components to be fully stopped
246 *
247 * @param b the benchmarked scenario bundle
248 */
249 void startAndStop(Bundle b) {
250 try {
251 initLatches();
252
253 debug(() -> "starting bundle " + b.getSymbolicName());
254 b.start();
255
256 if (! m_startLatch.await(60, TimeUnit.SECONDS)) {
257 out.println("Could not start components timely: current start latch=" + m_startLatch.getCount() + ", stop latch=" + m_stopLatch.getCount());
258 Unchecked.run(() -> Thread.sleep(Integer.MAX_VALUE));
259 }
Pierre De Rop93e47322015-05-19 16:01:36 +0000260
261 // Make sure the threadpool is quiescent and has finished to register all components
262 if (! Helper.getThreadPool().awaitQuiescence(5, TimeUnit.SECONDS)) {
263 out.println("could not start components timely (thread pool is still active after 5 seconds)");
264 Unchecked.run(() -> Thread.sleep(Integer.MAX_VALUE));
265 }
Pierre De Rop3a00a212015-03-01 09:27:46 +0000266
267 debug(() -> "stopping bundle " + b.getSymbolicName());
268 b.stop();
269
270 // Wait for all component deactivations
271 if (! m_stopLatch.await(60, TimeUnit.SECONDS)) {
272 out.println("Could not stop components timely: current start latch=" + m_startLatch.getCount() + ", stop latch=" + m_stopLatch.getCount());
273 Unchecked.run(() -> Thread.sleep(Integer.MAX_VALUE));
274 }
275 } catch (Throwable t) {
276 t.printStackTrace();
277 }
278 }
279
280 /**
281 * Initialize the latches used to track when all scenario bundle components are started or stopped.
282 */
283 private void initLatches() {
284 m_startLatch = new CountDownLatch(ARTISTS
285 + (ARTISTS * (ALBUMS + (ALBUMS * TRACKS))));
286
287 m_stopLatch = new CountDownLatch(ARTISTS
288 + (ARTISTS * (ALBUMS + (ALBUMS * TRACKS))));
289 }
290
291 /**
292 * Returns the time consumed by the given runnable, ²ch is executed by this method.
293 */
294 private long durationOf(Runnable scenario) {
295 long start = System.nanoTime();
296 long end = 0;
297 try {
298 scenario.run();
299 end = System.nanoTime();
300 } catch (Throwable t) {
301 t.printStackTrace();
302 }
303 return (end - start);
304 }
305}