1 package org.djutils.profile;
2
3 import java.util.Collections;
4 import java.util.LinkedHashMap;
5 import java.util.Map;
6 import java.util.Map.Entry;
7 import java.util.TreeMap;
8
9 import org.djutils.exceptions.Throw;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 public final class Profile
29 {
30
31
32 private static final Map<String, ProfileInfo> INFOS = new LinkedHashMap<>();
33
34
35 private static final Map<String, String> LINES = new LinkedHashMap<>();
36
37
38 private static long printInterval = 1000;
39
40
41 private static long lastPrint = 0;
42
43
44 private Profile()
45 {
46
47 }
48
49
50
51
52 public static void start()
53 {
54 start0(null, System.nanoTime());
55 }
56
57
58
59
60
61 public static void start(final String name)
62 {
63 start0(name, System.nanoTime());
64 }
65
66
67
68
69
70
71 private static void start0(final String name, final Long nanoTime)
72 {
73 getProfileInfo(name, true).start(nanoTime);
74 }
75
76
77
78
79 public static void end()
80 {
81 end0(null, System.nanoTime());
82 }
83
84
85
86
87
88 public static void end(final String name)
89 {
90 end0(name, System.nanoTime());
91 }
92
93
94
95
96
97
98 private static void end0(final String name, final Long nanoTime)
99 {
100 getProfileInfo(name, false).end(nanoTime);
101 }
102
103
104
105
106
107
108
109 private static ProfileInfo getProfileInfo(final String name, final boolean start)
110 {
111 StackTraceElement element = Thread.currentThread().getStackTrace()[4];
112 String classMethodId = element.getClassName() + ":" + element.getMethodName();
113 String partId;
114 if (name == null)
115 {
116 if (start)
117 {
118 partId = ":" + String.valueOf(element.getLineNumber());
119 LINES.put(classMethodId, partId);
120 }
121 else
122 {
123 partId = LINES.get(classMethodId);
124 }
125 }
126 else
127 {
128 partId = ":" + name;
129 }
130 classMethodId += partId;
131
132 ProfileInfo info = INFOS.get(classMethodId);
133 if (info == null)
134 {
135 info = new ProfileInfo(name);
136 INFOS.put(classMethodId, info);
137 }
138 return info;
139 }
140
141
142
143
144
145 public static String statistics()
146 {
147
148 double sum = 0;
149 int maxInvocations = 0;
150 int maxNameLength = 4;
151 Map<Double, Entry<String, ProfileInfo>> sorted = new TreeMap<>(Collections.reverseOrder());
152 for (Entry<String, ProfileInfo> entry : INFOS.entrySet())
153 {
154 String id = entry.getKey();
155 ProfileInfo info = INFOS.get(id);
156 sorted.put(info.getTotal(), entry);
157 sum += info.getTotal();
158 maxInvocations = maxInvocations > info.getInvocations() ? maxInvocations : info.getInvocations();
159 int nameLength = (info.getName() == null ? id : info.getName()).length();
160 maxNameLength = maxNameLength > nameLength ? maxNameLength : nameLength;
161 }
162 int lengthInvoke = String.valueOf(maxInvocations).length();
163 lengthInvoke = lengthInvoke > 6 ? lengthInvoke : 6;
164 String invokeHeaderFormat = String.format("%%%d.%ds", lengthInvoke, lengthInvoke);
165 String invokeLineFormat = String.format("%%%ds", lengthInvoke);
166 String nameHeaderFormat = String.format("%%%d.%ds", maxNameLength, maxNameLength);
167 String nameLineFormat = String.format("%%%ds", maxNameLength);
168 String line = new String(new char[80 + lengthInvoke + maxNameLength]).replace("\0", "-");
169
170
171 StringBuilder builder = new StringBuilder();
172 builder.append("-").append(line).append("-\n");
173 builder.append(String.format("| %7.7s | ", "Perc."));
174 builder.append(String.format(invokeHeaderFormat, "#Calls"));
175 builder.append(String.format(" | %10.10s | %10.10s | %10.10s | %10.10s | %10.10s | ", "TotTime", "MinTime", "MaxTime",
176 "AvgTime", "StdTime"));
177 builder.append(String.format(nameHeaderFormat, "Name")).append(" |\n");
178 builder.append("|").append(line).append("|\n");
179
180
181 for (Entry<String, ProfileInfo> entry : sorted.values())
182 {
183 String id = entry.getKey();
184 ProfileInfo info = entry.getValue();
185 if (info.getInvocations() > 0)
186 {
187 double perc = 100.0 * info.getTotal() / sum;
188 builder.append(String.format("| %6.2f%% | ", perc));
189 builder.append(String.format(invokeLineFormat, info.getInvocations()));
190 builder.append(String.format(" | %9.4fs | %9.6fs | %9.6fs | %9.6fs | ", info.getTotal(), info.getMin(),
191 info.getMax(), info.getMean()));
192
193 if (info.getInvocations() > 1)
194 {
195 builder.append(String.format("%9.6fs", info.getStandardDeviation()));
196 }
197 else
198 {
199 builder.append(" ");
200 }
201
202 builder.append(" | ").append(String.format(nameLineFormat, info.getName() == null ? id : info.getName()))
203 .append(" |\n");
204 }
205 }
206 return builder.append("-").append(line).append("-\n").toString();
207 }
208
209
210
211
212
213
214 public static String statistics(final String name)
215 {
216 Throw.whenNull(name, "name may not be null");
217 for (ProfileInfo result : INFOS.values())
218 {
219 if (null == result)
220 {
221 return null;
222 }
223 if (name.equals(result.getName()))
224 {
225 return result.toString();
226 }
227 }
228 return null;
229 }
230
231
232
233
234
235
236
237 public static void print()
238 {
239 long t = System.currentTimeMillis();
240 if (t - lastPrint > printInterval)
241 {
242 lastPrint = t;
243 System.out.print(statistics());
244 }
245 }
246
247
248
249
250
251 public static void setPrintInterval(final long printInterval)
252 {
253 Profile.printInterval = printInterval;
254 }
255
256
257
258
259 public static void clear()
260 {
261 INFOS.clear();
262 LINES.clear();
263 }
264
265
266
267
268 public static void reset()
269 {
270 for (ProfileInfo pi : INFOS.values())
271 {
272 pi.reset();
273 }
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287
288 private static class ProfileInfo
289 {
290
291 private static final double NANOSECONDSPERSECOND = 1000000000.0;
292
293
294 private final String name;
295
296
297 private Long start = null;
298
299
300 private long total;
301
302
303 private long totalSquared;
304
305
306 private long minTime;
307
308
309 private long maxTime;
310
311
312 private int invocations;
313
314
315
316
317
318 ProfileInfo(final String name)
319 {
320 this.name = name;
321 }
322
323
324
325
326 public void reset()
327 {
328 this.start = null;
329 this.total = 0;
330 this.totalSquared = 0;
331 this.minTime = 0;
332 this.maxTime = 0;
333 this.invocations = 0;
334 }
335
336
337
338
339
340 public void start(final long startTime)
341 {
342 Throw.when(this.start != null, IllegalStateException.class,
343 "Can not start profiling because it is already running.");
344 this.start = startTime;
345 }
346
347
348
349
350
351 public void end(final long endTime)
352 {
353 Throw.when(this.start == null, IllegalStateException.class, "Can not end profiling because it is not running.");
354 long duration = endTime - this.start;
355 this.total += duration;
356 this.totalSquared += duration * duration;
357 if (this.invocations == 0)
358 {
359 this.minTime = duration;
360 this.maxTime = duration;
361 }
362 else
363 {
364 this.minTime = this.minTime < duration ? this.minTime : duration;
365 this.maxTime = this.maxTime > duration ? this.maxTime : duration;
366 }
367 this.invocations++;
368 this.start = null;
369 }
370
371
372
373
374
375 public String getName()
376 {
377 return this.name;
378 }
379
380
381
382
383
384 public double getTotal()
385 {
386 return this.total / NANOSECONDSPERSECOND;
387 }
388
389
390
391
392
393 public double getStandardDeviation()
394 {
395 if (this.invocations < 2)
396 {
397 return Double.NaN;
398 }
399 double squared = this.totalSquared - this.total * this.total / this.invocations;
400 return Math.sqrt(squared / (this.invocations - 1)) / NANOSECONDSPERSECOND;
401 }
402
403
404
405
406
407 public int getInvocations()
408 {
409 return this.invocations;
410 }
411
412
413
414
415
416 public double getMean()
417 {
418 return getTotal() / getInvocations();
419 }
420
421
422
423
424
425 public double getMin()
426 {
427 return this.minTime / NANOSECONDSPERSECOND;
428 }
429
430
431
432
433
434 public double getMax()
435 {
436 return this.maxTime / NANOSECONDSPERSECOND;
437 }
438
439
440 @Override
441 public String toString()
442 {
443 return "ProfileInfo [name=" + this.name + ", start=" + this.start + ", total=" + this.total + ", totalSquared="
444 + this.totalSquared + ", minTime=" + this.minTime + ", maxTime=" + this.maxTime + ", invocations="
445 + this.invocations + "]";
446 }
447
448 }
449
450 }