1 | package de.uka.ipd.sdq.measurement.strategies.activeresource; |
2 | |
3 | import java.io.File; |
4 | import java.util.Arrays; |
5 | import java.util.Properties; |
6 | |
7 | import javax.measure.quantity.Dimensionless; |
8 | import javax.measure.quantity.Duration; |
9 | import javax.measure.quantity.Quantity; |
10 | import javax.measure.unit.BaseUnit; |
11 | import javax.measure.unit.NonSI; |
12 | import javax.measure.unit.ProductUnit; |
13 | import javax.measure.unit.SI; |
14 | import javax.measure.unit.Unit; |
15 | |
16 | import org.apache.log4j.Logger; |
17 | import org.jscience.physics.amount.Amount; |
18 | |
19 | /** |
20 | * Abstract superclass of all active demand strategies. |
21 | * |
22 | * c.f.: Steffen Becker, Tobias Dencker, and Jens Happe. |
23 | * Model-Driven Generation of Performance Prototypes. |
24 | * In Performance Evaluation: Metrics, Models and Benchmarks (SIPEW 2008), volume 5119 of Lecture Notes in Computer Science, pages 79-98. |
25 | * Springer-Verlag Berlin Heidelberg, 2008. |
26 | * |
27 | * @author Tobias Denker, Anne Koziolek, Steffen Becker, Thomas Zolynski |
28 | */ |
29 | public abstract class AbstractDemandStrategy implements IDemandStrategy { |
30 | |
31 | private static final int RIGHT_ENDPOINT = 1; |
32 | |
33 | private static final int LEFT_ENDPOINT = 0; |
34 | |
35 | public static final Unit<Work> WORKUNITS = new BaseUnit<Work>("WU"); |
36 | public interface Work extends Quantity { |
37 | public static final Unit<Work> UNIT = WORKUNITS; |
38 | } |
39 | |
40 | public interface ProcessingRate extends Quantity { |
41 | public static final Unit<ProcessingRate> UNIT = new ProductUnit<ProcessingRate>(Work.UNIT.divide(SI.SECOND)); |
42 | } |
43 | |
44 | public static final String CALIBRATION_PATH_CONFIG_KEY = "CalibrationPath"; |
45 | |
46 | private static final int MIN_CALIBRATION_CYCLES = 5; |
47 | |
48 | private CalibrationTable calibrationTable; |
49 | |
50 | private static final Amount<Duration> ONE_MILLISECOND = Amount.valueOf(1,SI.MILLI(SI.SECOND)); |
51 | |
52 | private static final int DEFAULT_ACCURACY = 8; |
53 | |
54 | private final int warmUpCycles; |
55 | |
56 | /** Modifier for low, medium and high calibration. */ |
57 | private final int low, medium, high; |
58 | |
59 | /** Iteration count for calibration */ |
60 | protected long defaultIterationCount; |
61 | |
62 | private Properties properties; |
63 | |
64 | private Amount<ProcessingRate> processingRate; |
65 | |
66 | private File configFile = null; |
67 | |
68 | protected DegreeOfAccuracyEnum degreeOfAccuracy; |
69 | private static Logger logger = Logger.getLogger(AbstractDemandStrategy.class.getName()); |
70 | |
71 | private static final String CONFIG_PATH = "./conf/"; |
72 | |
73 | // define constants |
74 | private static final int[] CALIBRATION_CYCLES = { 1024, 512, 256, 128, 64, 50, 40, 30, 25, 20, 15, 10 }; |
75 | |
76 | /** Amount of outlier when calculating the mean. elements/OUTLIER_RATE of lowest and highest values are discarded */ |
77 | private static final int OUTLIER_RATE = 5; |
78 | |
79 | /** |
80 | * Constructor. Configures a demand strategy with low, medium and high modifier, as well |
81 | * as number of standard and warm-up cycles |
82 | * |
83 | * @param low accuracy modifier for low precision calibration |
84 | * @param medium accuracy modifier for medium precision calibration |
85 | * @param high accuracy modifier for high precision calibration |
86 | * @param iterationCount |
87 | * @param warmups |
88 | */ |
89 | public AbstractDemandStrategy(int low, int medium, int high, int iterationCount, int warmups) { |
90 | super(); |
91 | |
92 | /** |
93 | * Initialise the calibration algorithm's parameter set |
94 | */ |
95 | this.low = low; |
96 | this.medium = medium; |
97 | this.high = high; |
98 | this.defaultIterationCount = iterationCount; |
99 | this.warmUpCycles = warmups; |
100 | } |
101 | |
102 | /** |
103 | * @see IDemandStrategy#initializeStrategy(DegreeOfAccuracyEnum, double) |
104 | */ |
105 | @Override |
106 | public void initializeStrategy(DegreeOfAccuracyEnum degree, double initProcessingRate) { |
107 | logger.info("Initialising " + getName() + " " + getStrategysResource().name() + " strategy with accuracy "+degree.name()); |
108 | |
109 | this.degreeOfAccuracy = degree; |
110 | this.processingRate = Amount.valueOf(initProcessingRate,ProcessingRate.UNIT); |
111 | this.configFile = new File(getCalibrationFileName()); |
112 | |
113 | CalibrationTable loadedCalibration = CalibrationTable.load(configFile); |
114 | |
115 | if (loadedCalibration != null) { |
116 | calibrationTable = loadedCalibration; |
117 | |
118 | } else { |
119 | calibrate(); |
120 | } |
121 | logger.debug(getName() + " " + getStrategysResource().name() + " strategy initialised"); |
122 | } |
123 | |
124 | /** |
125 | * @see IDemandStrategy#initializeStrategy(DegreeOfAccuracyEnum, double, String) |
126 | */ |
127 | @Override |
128 | public void initializeStrategy(DegreeOfAccuracyEnum degreeOfAccuracy, double processingRate, String calibrationPath) { |
129 | Properties props = new Properties(); |
130 | props.setProperty(CALIBRATION_PATH_CONFIG_KEY, calibrationPath); |
131 | setProperties(props); |
132 | |
133 | initializeStrategy(degreeOfAccuracy, processingRate); |
134 | } |
135 | |
136 | /** |
137 | * @see IDemandStrategy#setProperties(Properties) |
138 | */ |
139 | @Override |
140 | public void setProperties(Properties properties) { |
141 | this.properties = properties; |
142 | } |
143 | |
144 | /** |
145 | * @see IDemandStrategy#consume(double) |
146 | */ |
147 | @Override |
148 | public void consume(double demand) { |
149 | if (calibrationTable == null) { |
150 | logger.fatal("No calibration found - STRATEGY HAS TO BE INITIALIZED FIRST!"); |
151 | throw new RuntimeException("No calibration found - STRATEGY HAS TO BE INITIALIZED FIRST!"); |
152 | } |
153 | |
154 | Amount<Work> demandedWork = Amount.valueOf(demand,Work.UNIT); |
155 | Amount<Duration> millisec = demandedWork.divide(processingRate).to(SI.SECOND); |
156 | if (logger.isDebugEnabled()) { |
157 | logger.debug("Consume called, demand is : " + demandedWork + ", " + millisec); |
158 | } |
159 | |
160 | long[] factors = fillTimeFrame(millisec); |
161 | |
162 | for (int i = 0; i < factors.length; i++) { |
163 | long loopCount = factors[i]; |
164 | for (int j = 0; j < loopCount; j++) { |
165 | run(calibrationTable.getEntry(i).getParameter()); |
166 | } |
167 | } |
168 | logger.debug("Demand consumed"); |
169 | } |
170 | |
171 | /** |
172 | * Template method to return the real hardware resource type simulated by this strategy |
173 | * @see de.uka.ipd.sdq.measurement.strategies.activeresource.IDemandStrategy#getStrategysResource() |
174 | */ |
175 | public abstract ResourceTypeEnum getStrategysResource(); |
176 | |
177 | /** |
178 | * Template method to return the name of this strategy |
179 | * @see de.uka.ipd.sdq.measurement.strategies.activeresource.IDemandStrategy#getName() |
180 | */ |
181 | public abstract String getName(); |
182 | |
183 | /** Returns the name of the file used to store the calibration table |
184 | * Filename depends on paramters of this class |
185 | * @return The calibration table file name |
186 | */ |
187 | protected String getCalibrationFileName() { |
188 | return getCalibrationPath() + getName() + "_" |
189 | + CalibrationTable.DEFAULT_CALIBRATION_TABLE_SIZE + "_" + this.degreeOfAccuracy.name() + ".ser"; |
190 | } |
191 | |
192 | /** |
193 | * Query the calibration path from the properties of this object |
194 | * @return The file system path used to load and store the calibration data, or the current working directory if it is not set |
195 | */ |
196 | protected String getCalibrationPath() { |
197 | String result = null; |
198 | |
199 | // Test whether properties have been set externally |
200 | if (properties != null) { |
201 | result = properties.getProperty(CALIBRATION_PATH_CONFIG_KEY); |
202 | if ((result != null) && (!result.equals(""))) { |
203 | return result + "/"; |
204 | } |
205 | } |
206 | |
207 | return CONFIG_PATH; |
208 | } |
209 | |
210 | /** |
211 | * Template method. This starts running the strategy with the parameter load |
212 | * @param load Complexity parameter. Algorithm should take longer if parameter is larger, |
213 | * i.e., ideally run(a) < run(b) <==> a < b |
214 | */ |
215 | protected abstract void run(long load); |
216 | |
217 | /** |
218 | * Create a new calibration table for this host by measuring the execution times of |
219 | * our algorithm and creating an according calibration table |
220 | */ |
221 | private void calibrate() { |
222 | this.calibrationTable = new CalibrationTable(); |
223 | |
224 | for (int i = 0; i < warmUpCycles; i++) { |
225 | run(defaultIterationCount); |
226 | } |
227 | |
228 | logger.info("The timetable with the corresponding parameters:"); |
229 | for (int i = 0; i < calibrationTable.size(); i++) { |
230 | Amount<Duration> targetTime = Amount.valueOf(1 << i,SI.MILLI(SI.SECOND)); |
231 | long parameter = getRoot(targetTime); |
232 | |
233 | if (i > 2) { //TODO: Why 2? |
234 | //TODO: This is smart, but absolutely not maintainable... |
235 | targetTime = recalibrate(parameter, i); |
236 | } |
237 | |
238 | calibrationTable.addEntry(i, targetTime, parameter); |
239 | logger.info(calibrationTable.getEntry(i)); |
240 | } |
241 | calibrationTable.save(configFile); |
242 | } |
243 | |
244 | /** |
245 | * Iteratively approximates the best input value to reach a specified execution time. |
246 | * Let the result of this method be parameter. Then this method determines a parameter, |
247 | * s.t. exec_alg(parameter) = targetTime |
248 | * |
249 | * The accepted tolerance is one millisecond. |
250 | * |
251 | * @param targetTime target time in milliseconds |
252 | * @return exec_alg^(-1)(targetTime) |
253 | */ |
254 | private long getRoot(Amount<Duration> targetTime) { |
255 | final int numberOfRepetitions = 2; // TODO: Why 2? Configurable? |
256 | return getRoot(targetTime, numberOfRepetitions); |
257 | } |
258 | |
259 | /** |
260 | * Iteratively approximates the best input value to reach a specified execution time. |
261 | * Let the result of this method be parameter. Then this method determines a parameter, |
262 | * s.t. exec_alg(parameter) = targetTime |
263 | * |
264 | * The accepted tolerance is one millisecond. |
265 | * |
266 | * @param targetTime target time in milliseconds |
267 | * @param numberOfRepetitions number of times the algorithm determines the root. This is needed |
268 | * as the function is only approximated by running measurements |
269 | * @return exec_alg^(-1)(targetTime) |
270 | */ |
271 | private long getRoot(Amount<Duration> targetTime, int numberOfRepetitions) { |
272 | // approximated parameters |
273 | long[] targetParameter = new long[numberOfRepetitions]; |
274 | |
275 | // run a couple of times and calculate mean |
276 | for (int i = 0; i < numberOfRepetitions; i++) { |
277 | targetParameter[i] = getRootOnce(targetTime); |
278 | } |
279 | return mean(targetParameter); |
280 | } |
281 | |
282 | /** |
283 | * Mathematical root finding algorithm. Calculation based on bisection method. |
284 | * |
285 | * @param targetTime |
286 | * @return root |
287 | */ |
288 | private long getRootOnce(Amount<Duration> targetTime) { |
289 | long[] intervalEndpoints = new long[2]; |
290 | Amount<Duration>[] intervalFunctionValues = new Amount[2]; |
291 | initialiseInterval(targetTime,intervalEndpoints,intervalFunctionValues); |
292 | if (!hasRoot(intervalFunctionValues[LEFT_ENDPOINT], intervalFunctionValues[RIGHT_ENDPOINT]) || intervalFunctionValues[LEFT_ENDPOINT].isGreaterThan(intervalFunctionValues[RIGHT_ENDPOINT])) { |
293 | logger.error("PROBLEM: No root found. Special algorithm" |
294 | + " without monotonically increasing load !?!"); |
295 | logger.error("f_n_left = "+intervalFunctionValues[LEFT_ENDPOINT]); |
296 | logger.error("f_n_right = " +intervalFunctionValues[RIGHT_ENDPOINT]); |
297 | throw new RuntimeException("PROBLEM: No root found. Special algorithm" |
298 | + " without monotonically increasing load !?!"); |
299 | } |
300 | |
301 | logger.debug("--- Running bisection method ----"); |
302 | Amount<Duration> epsilon = getEpsilon(targetTime); |
303 | while (Math.abs(intervalEndpoints[LEFT_ENDPOINT]-intervalEndpoints[RIGHT_ENDPOINT]) > 2 && |
304 | intervalFunctionValues[RIGHT_ENDPOINT].minus(intervalFunctionValues[LEFT_ENDPOINT]).abs().isLargerThan(epsilon)) { |
305 | if (logger.isDebugEnabled()) { |
306 | logger.debug("["+intervalEndpoints[LEFT_ENDPOINT]+", "+intervalEndpoints[RIGHT_ENDPOINT]+"] --> "+ |
307 | "["+formatDuration(intervalFunctionValues[LEFT_ENDPOINT])+", "+formatDuration(intervalFunctionValues[RIGHT_ENDPOINT])+"]"); |
308 | } |
309 | long intervalMedian = (intervalEndpoints[LEFT_ENDPOINT] + intervalEndpoints[RIGHT_ENDPOINT]) / 2; |
310 | Amount<Duration> f_n_median = calcRunTimeFunction(intervalMedian, targetTime); |
311 | if (hasSameSign(intervalFunctionValues[LEFT_ENDPOINT].getEstimatedValue(), f_n_median.getEstimatedValue())) { |
312 | intervalEndpoints[LEFT_ENDPOINT] = intervalMedian; |
313 | intervalFunctionValues[LEFT_ENDPOINT] = f_n_median; |
314 | } else { |
315 | intervalEndpoints[RIGHT_ENDPOINT] = intervalMedian; |
316 | intervalFunctionValues[RIGHT_ENDPOINT] = f_n_median; |
317 | } |
318 | } |
319 | return (intervalEndpoints[LEFT_ENDPOINT] + intervalEndpoints[RIGHT_ENDPOINT]) / 2; |
320 | |
321 | } |
322 | |
323 | private Amount<Duration> getEpsilon(Amount<Duration> targetTime) { |
324 | Amount<Duration> result = targetTime.times(0.01d); |
325 | if (result.to(SI.MILLI(SI.SECOND)).isGreaterThan(ONE_MILLISECOND)) |
326 | return ONE_MILLISECOND; |
327 | return result; |
328 | } |
329 | |
330 | /** |
331 | * @param a |
332 | * @param b |
333 | * @return true if a and b have the same sign |
334 | */ |
335 | private boolean hasSameSign(double a, double b) { |
336 | return a * b > 0; |
337 | } |
338 | |
339 | |
340 | private Amount<Duration> recalibrate(long parameter, int index) { |
341 | int cycles = CALIBRATION_CYCLES[index]; |
342 | return getRunTime(parameter, Amount.valueOf(cycles,SI.MILLI(SI.SECOND))); |
343 | } |
344 | |
345 | /** |
346 | * The initial value of f(n_right) has to be greater than 0. |
347 | * |
348 | * @param targetTime |
349 | * @return n_right with f(n_right) > 0 |
350 | */ |
351 | private void initialiseInterval(Amount<Duration> targetTime, long[] intervalEndpoints, Amount<Duration>[] intervalFunctionValues) { |
352 | if (logger.isDebugEnabled()){ |
353 | logger.debug("Find inital interval for target time "+formatDuration(targetTime)); |
354 | } |
355 | long z = 0; |
356 | do { |
357 | intervalEndpoints[LEFT_ENDPOINT] = intervalEndpoints[RIGHT_ENDPOINT]; |
358 | intervalFunctionValues[LEFT_ENDPOINT] = intervalFunctionValues[RIGHT_ENDPOINT]; |
359 | intervalEndpoints[RIGHT_ENDPOINT] = z * defaultIterationCount; |
360 | intervalFunctionValues[RIGHT_ENDPOINT] = calcRunTimeFunction(intervalEndpoints[RIGHT_ENDPOINT], targetTime); |
361 | z = z == 0 ? 1 : z << 1; |
362 | if (logger.isDebugEnabled()) { |
363 | logger.debug("["+intervalEndpoints[LEFT_ENDPOINT]+", "+intervalEndpoints[RIGHT_ENDPOINT]+"] --> "+ |
364 | "["+formatDuration(intervalFunctionValues[LEFT_ENDPOINT])+", "+formatDuration(intervalFunctionValues[RIGHT_ENDPOINT])+"]"); |
365 | } |
366 | } while (intervalFunctionValues[RIGHT_ENDPOINT].isLessThan(Amount.valueOf(0L, SI.SECOND))); |
367 | } |
368 | |
369 | /** |
370 | * Checks whether there is a root (Nullstelle) between the two function values |
371 | * @param f_n_left Left interval end point function value |
372 | * @param f_n_right Right interval end point function value |
373 | * @return true if there is a root between the two function values |
374 | */ |
375 | private boolean hasRoot(Amount<Duration> f_n_left, Amount<Duration> f_n_right) { |
376 | return (!hasSameSign(f_n_left.getEstimatedValue(), f_n_right.getEstimatedValue())); |
377 | } |
378 | |
379 | |
380 | /** |
381 | * Derives a function f(n) = exec_alg(n) - targetTime, whose root is at |
382 | * targetTime, i.e, f(targetTime) = 0 |
383 | * |
384 | * @param parameter |
385 | * @param targetTime |
386 | * @return |
387 | */ |
388 | private Amount<Duration> calcRunTimeFunction(long parameter, Amount<Duration> targetTime) { |
389 | return getRunTime(parameter, targetTime).minus(targetTime); |
390 | } |
391 | |
392 | /** |
393 | * Returns mean algorithm run time depending on the parameter. The approximation |
394 | * accuracy of the algorithm run time depends on the targetTime. For small |
395 | * targetTime several approximation cycles are executed and their mean is returned. |
396 | * For larger targetTime just a single cycle is executed. |
397 | * |
398 | * @param parameter characterising parameter of the load generating algorithm |
399 | * @param targetTime target time (used to determine approximation accuracy, s.a.) |
400 | * @return approximated run time in nanoseconds, i.e., exec_alg(parameter) |
401 | */ |
402 | private Amount<Duration> getRunTime(long parameter, Amount<Duration> targetTime) { |
403 | int cycles = getCalibrationCycles(getAccuracyValue(), targetTime); |
404 | |
405 | long[] approximation = new long[cycles]; |
406 | |
407 | for (int i = 0; i < cycles; i++) { |
408 | if (logger.isTraceEnabled()) { |
409 | logger.trace("Measuring calibration run " + i + " of " + cycles); |
410 | } |
411 | long start = System.nanoTime(); |
412 | run(parameter); |
413 | approximation[i] = (System.nanoTime() - start); |
414 | } |
415 | |
416 | long mean = mean(approximation); |
417 | logger.debug("Mean time for parameter " + parameter + " is " + mean); |
418 | return Amount.valueOf(mean,SI.NANO(SI.SECOND)); |
419 | } |
420 | |
421 | /** |
422 | * Calculates mean value of array p. If p has more than five elements, the lowest |
423 | * and highest 'length / OUTLIER_RATE' are removed. |
424 | * |
425 | * @param p array of numbers |
426 | * @return mean value |
427 | */ |
428 | private long mean(long[] p) { |
429 | long sum = 0; |
430 | Arrays.sort(p); |
431 | int start = p.length > OUTLIER_RATE ? p.length / OUTLIER_RATE : 0; |
432 | for (int i = start; i < p.length - start; i++) { |
433 | sum += p[i]; |
434 | } |
435 | |
436 | return sum / (p.length - start * 2); |
437 | } |
438 | |
439 | /** |
440 | * Returns number of iterations to calculate the mean from. It is aimed |
441 | * at being reverse proportional to the targetTime. |
442 | * |
443 | * For long target times only one cycle will be executed. |
444 | * Example: MEDIUM accuracy: Exponent = DEFAULT_ACCURACY (8) + 0 = 8 |
445 | * TargetTime >= 2^8 => len = 1 |
446 | * < 2^8 => len = 2^8/TargetTime |
447 | * |
448 | * @param exponent |
449 | * @param targetTime |
450 | * @return |
451 | */ |
452 | private int getCalibrationCycles(int exponent, Amount<Duration> targetTime) { |
453 | Amount<Duration> threshold = Amount.valueOf(1 << exponent,SI.MILLI(SI.SECOND)); |
454 | |
455 | return Math.max((int)Math.floor(threshold.divide(targetTime).getEstimatedValue()), MIN_CALIBRATION_CYCLES); |
456 | } |
457 | |
458 | /** |
459 | * Maps an accuracy (LOW, MEDIUM, HIGH) to the values specified during the |
460 | * configuration (in the constructor). |
461 | * |
462 | * @return accuracy modifier |
463 | */ |
464 | private int getAccuracyValue() { |
465 | int result = DEFAULT_ACCURACY; |
466 | |
467 | switch(this.degreeOfAccuracy) { |
468 | case HIGH: |
469 | result += high; |
470 | break; |
471 | case MEDIUM: |
472 | result += medium; |
473 | break; |
474 | case LOW: |
475 | result += low; |
476 | break; |
477 | default: |
478 | throw new IllegalArgumentException("Unsupported degree of accuracy"); |
479 | } |
480 | return result; |
481 | } |
482 | |
483 | /** |
484 | * Computes a vector of (scaling) factors for each entry in the |
485 | * calibration table. These factors give the number of repetitions |
486 | * of each of the calibration entries to reach a given target time. |
487 | * Use greedy strategy to fill time frame with smaller run times |
488 | * |
489 | * @param millisec The target time to factorise |
490 | * @return An array of scaling factors for the calibration table entries |
491 | */ |
492 | private long[] fillTimeFrame(Amount<Duration> millisec) { |
493 | long[] result = new long[CalibrationTable.DEFAULT_CALIBRATION_TABLE_SIZE]; |
494 | Amount<Duration> sum = millisec; |
495 | |
496 | for (int i = CalibrationTable.DEFAULT_CALIBRATION_TABLE_SIZE - 1; i >= 0; i--) { |
497 | CalibrationEntry calibrationEntry = calibrationTable.getEntry(i); |
498 | |
499 | result[i] = (long) Math.floor(((Amount<Dimensionless>) (sum.divide(calibrationEntry.getTargetTime())).to(Unit.ONE)).getEstimatedValue()); |
500 | if (result[i] >= 1) { |
501 | sum = sum.minus(calibrationEntry.getTargetTime().times(result[i])); |
502 | } |
503 | if (logger.isTraceEnabled()) { |
504 | logger.trace(formatDuration(calibrationEntry.getTargetTime()) + " | " |
505 | + calibrationEntry.getParameter() + " | " + result[i] + "|" + formatDuration(sum)); |
506 | } |
507 | } |
508 | return result; |
509 | } |
510 | |
511 | /** |
512 | * Consumes demands (only used for testing purpose!) |
513 | * @param demand |
514 | */ |
515 | @Deprecated |
516 | public void watchConsume(double demand) { |
517 | final int repetitionCount = 10; |
518 | if (calibrationTable == null) { |
519 | logger.fatal("No calibration found - STRATEGY HAS TO BE INITIALIZED FIRST!"); |
520 | throw new RuntimeException("No calibration found - STRATEGY HAS TO BE INITIALIZED FIRST!"); |
521 | } |
522 | |
523 | Amount<Work> demandedWork = Amount.valueOf(demand,Work.UNIT); |
524 | Amount<Duration> expectedTime = demandedWork.divide(processingRate).to(SI.SECOND); |
525 | logger.info("Request issued to consume " + demandedWork); |
526 | logger.info("Expected duration is " + formatDuration(expectedTime)); |
527 | long theTime = System.nanoTime(); |
528 | |
529 | for (int h = 0; h < repetitionCount; h++) { |
530 | consume(demand); |
531 | } |
532 | Amount<Duration> measuredTime = Amount.valueOf((System.nanoTime() - theTime) / repetitionCount,SI.NANO(SI.SECOND)); |
533 | logger.info("Demand of "+formatDuration(expectedTime)+" consumed at an average value of " + formatDuration(measuredTime) |
534 | + ". Abs. difference is "+formatDuration(measuredTime.minus(expectedTime).abs())); |
535 | } |
536 | |
537 | @SuppressWarnings("unchecked") |
538 | public static String formatDuration(Amount<Duration> t) { |
539 | if (t == null) |
540 | return "null"; |
541 | |
542 | Unit<Duration>[] units = new Unit[] {SI.NANO(SI.SECOND), SI.MICRO(SI.SECOND), SI.MILLI(SI.SECOND), SI.SECOND, NonSI.MINUTE, NonSI.HOUR}; |
543 | for (Unit<Duration> u : units) { |
544 | double value = t.to(u).getEstimatedValue(); |
545 | if (Math.abs(value) < 1000) { |
546 | return value + " " + u; |
547 | } |
548 | } |
549 | return t.toText().toString(); |
550 | } |
551 | } |