1 /*******************************************************************************
2  * Copyright (c) 2000, 2013 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Lars Vogel <Lars.Vogel@gmail.com> - Adds generic type arguments https://bugs.eclipse.org/412836
14  *******************************************************************************/
15 package org.eclipse.core.runtime;
16 
17 import java.io.PrintWriter;
18 import java.util.*;
19 import org.eclipse.core.internal.runtime.InternalPlatform;
20 import org.eclipse.core.internal.runtime.PerformanceStatsProcessor;
21 
22 /**
23  * PerformanceStats collects and aggregates timing data about events such as
24  * a builder running, an editor opening, etc.  This data is collected for the
25  * purpose of performance analysis, and is not intended to be used as
26  * a generic event tracking and notification system.
27  * <p>
28  * Each performance event can have an associated maximum acceptable
29  * duration that is specified in the platform debug options file (.options).
30  * Events that take longer than this maximum are logged as errors.  Along
31  * with option file entries for each debug event, there are some global debug
32  * options for enabling or disabling performance event gathering and reporting.
33  * See the "org.eclipse.core.runtime/perf*" debug options in the .options file
34  * for the org.eclipse.core.runtime plugin for more details.
35  * </p><p>
36  * A performance event can optionally have additional context information
37  * ({@link #getContext}).  This information is only stored in the case
38  * of a performance failure, and can be used to provide further diagnostic
39  * information that can help track down the cause of the failure.
40  * </p><p>
41  * Performance events and performance failures are batched up and periodically
42  * sent to interested performance event listeners.
43  * </p><p>
44  * This class is not intended to be subclassed or instantiated by clients.
45  * </p>
46  * @since 3.1
47  */
48 public class PerformanceStats {
49 	/**
50 	 * A performance listener is periodically notified after performance events occur
51 	 * or after events fail.
52 	 * <p>
53 	 * This class is intended to be subclassed.
54 	 * </p>
55 	 *
56 	 * @see PerformanceStats#addListener(PerformanceStats.PerformanceListener)
57 	 */
58 	public static abstract class PerformanceListener {
59 		/**
60 		 * Creates a new listener.
61 		 */
PerformanceListener()62 		protected PerformanceListener() {
63 			super();
64 		}
65 
66 		/**
67 		 * Notifies than an event exceeded the maximum duration for that event type.
68 		 * <p>
69 		 * This default implementation does nothing. Subclasses may override.
70 		 * </p>
71 		 *
72 		 * @param event The event that failed
73 		 * @param duration The duration of the failed event, in milliseconds
74 		 */
eventFailed(PerformanceStats event, long duration)75 		public void eventFailed(PerformanceStats event, long duration) {
76 			//default implementation does nothing
77 		}
78 
79 		/**
80 		 * Notifies that an event occurred.  Notification might not occur
81 		 * in the same thread or near the time of the actual event.
82 		 * <p>
83 		 * This default implementation does nothing. Subclasses may override.
84 		 * </p>
85 		 *
86 		 * @param event The event that occurred
87 		 */
eventsOccurred(PerformanceStats[] event)88 		public void eventsOccurred(PerformanceStats[] event) {
89 			//default implementation does nothing
90 		}
91 	}
92 
93 	/**
94 	 * An empty stats object that is returned when tracing is turned off
95 	 */
96 	private static final PerformanceStats EMPTY_STATS = new PerformanceStats("", ""); //$NON-NLS-1$ //$NON-NLS-2$
97 
98 	/**
99 	 * Constant indicating whether or not tracing is enabled
100 	 */
101 	public static final boolean ENABLED;
102 
103 	/**
104 	 * A constant indicating that the timer has not been started.
105 	 */
106 	private static final long NOT_STARTED = -1;
107 
108 	/**
109 	 * All known event statistics.
110 	 */
111 	private final static Map<PerformanceStats, PerformanceStats> statMap =
112 				Collections.synchronizedMap(new HashMap<PerformanceStats,PerformanceStats>());
113 
114 	/**
115 	 * Maximum allowed durations for each event.
116 	 * Maps String (event name) -&gt; Long (threshold)
117 	 */
118 	private final static Map<String, Long> thresholdMap = Collections.synchronizedMap(new HashMap<String, Long>());
119 
120 	/**
121 	 * Whether non-failure statistics should be retained.
122 	 */
123 	private static final boolean TRACE_SUCCESS;
124 
125 	/**
126 	 * An identifier that can be used to figure out who caused the event. This is
127 	 * typically a string representation of the object whose code was running when
128 	 * the event occurred or a <code>String</code> describing the event.
129 	 */
130 	private String blame;
131 
132 	/**
133 	 * The id of the plugin that defined the blame object for this event, or
134 	 * <code>null</code> if it could not be determined.
135 	 */
136 	private String blamePluginId;
137 
138 	/**
139 	 * An optional context for the event (may be <code>null</code>).
140 	 * The context can provide extra information about an event, such as the
141 	 * name of a project being built, or the input of an editor being opened.
142 	 */
143 	private String context;
144 
145 	/**
146 	 * The starting time of the current occurrence of this event.
147 	 */
148 	private long currentStart = NOT_STARTED;
149 
150 	/**
151 	 * The symbolic name of the event that occurred. This is usually the name of
152 	 * the debug option for this event.
153 	 */
154 	private String event;
155 
156 	/**
157 	 * Whether this is a performance failure event
158 	 */
159 	private boolean isFailure;
160 
161 	/**
162 	 * The total number of times this event has occurred.
163 	 */
164 	private int runCount = 0;
165 
166 	/**
167 	 * The total time in milliseconds taken by all occurrences of this event.
168 	 */
169 	private long runningTime = 0;
170 
171 	static {
172 		ENABLED = InternalPlatform.getDefault().getBooleanOption(Platform.PI_RUNTIME + "/perf", false);//$NON-NLS-1$
173 		//turn these on by default if the global trace flag is turned on
174 		TRACE_SUCCESS = InternalPlatform.getDefault().getBooleanOption(Platform.PI_RUNTIME + "/perf/success", ENABLED); //$NON-NLS-1$
175 	}
176 
177 	/**
178 	 * Adds a listener that is notified when performance events occur.  If
179 	 * an equal listener is already installed, it will be replaced.
180 	 *
181 	 * @param listener The listener to be added
182 	 * @see #removeListener(PerformanceStats.PerformanceListener)
183 	 */
addListener(PerformanceListener listener)184 	public static void addListener(PerformanceListener listener) {
185 		if (ENABLED)
186 			PerformanceStatsProcessor.addListener(listener);
187 	}
188 
189 	/**
190 	 * Discards all known performance event statistics.
191 	 */
clear()192 	public static void clear() {
193 		statMap.clear();
194 	}
195 
196 	/**
197 	 * Returns all performance event statistics.
198 	 *
199 	 * @return An array of known performance event statistics.  The array
200 	 * will be empty if there are no recorded statistics.
201 	 */
getAllStats()202 	public static PerformanceStats[] getAllStats() {
203 		return statMap.values().toArray(new PerformanceStats[statMap.size()]);
204 	}
205 
206 	/**
207 	 * Returns the stats object corresponding to the given parameters.
208 	 * A stats object is created and added to the global list of events if it did not
209 	 * already exist.
210 	 *
211 	 * @param eventName A symbolic event name.  This is usually the name of
212 	 * the debug option for this event. An example event name from
213 	 * the org.eclipse.core.resources plugin describing a build event might look like:
214 	 * 		<code>"org.eclipse.core.resources/perf/building"</code>"
215 	 * @param blameObject The blame for the event.  This is typically the object
216 	 * whose code was running when the event occurred.  If a blame object cannot
217 	 * be obtained, a <code>String</code> describing the event should be supplied
218 	 */
getStats(String eventName, Object blameObject)219 	public static PerformanceStats getStats(String eventName, Object blameObject) {
220 		if (!ENABLED || eventName == null || blameObject == null)
221 			return EMPTY_STATS;
222 		PerformanceStats newStats = new PerformanceStats(eventName, blameObject);
223 		if (!TRACE_SUCCESS)
224 			return newStats;
225 		//use existing stats object if available
226 		PerformanceStats oldStats = statMap.get(newStats);
227 		if (oldStats != null)
228 			return oldStats;
229 		statMap.put(newStats, newStats);
230 		return newStats;
231 	}
232 
233 	/**
234 	 * Returns whether monitoring of a given performance event is enabled.
235 	 * <p>
236 	 * For frequent performance events, the result of this method call should
237 	 * be cached by the caller to minimize overhead when performance monitoring
238 	 * is turned off.  It is not possible for enablement to change during the life
239 	 * of this invocation of the platform.
240 	 * </p>
241 	 *
242 	 * @param eventName The name of the event to determine enablement for
243 	 * @return <code>true</code>If the performance event with the given
244 	 * name is enabled, and <code>false</code> otherwise.
245 	 */
isEnabled(String eventName)246 	public static boolean isEnabled(String eventName) {
247 		if (!ENABLED)
248 			return false;
249 		String option = Platform.getDebugOption(eventName);
250 		return option != null && !"false".equalsIgnoreCase(option) && !"-1".equalsIgnoreCase(option); //$NON-NLS-1$ //$NON-NLS-2$
251 	}
252 
253 	/**
254 	 * Prints all statistics to the standard output.
255 	 */
printStats()256 	public static void printStats() {
257 		if (!ENABLED)
258 			return;
259 		PrintWriter writer = new PrintWriter(System.out);
260 		PerformanceStatsProcessor.printStats(writer);
261 		writer.flush();
262 	}
263 
264 	/**
265 	 * Writes all statistics using the provided writer
266 	 *
267 	 * @param out The writer to print stats to.
268 	 */
printStats(PrintWriter out)269 	public static void printStats(PrintWriter out) {
270 		if (!ENABLED)
271 			return;
272 		PerformanceStatsProcessor.printStats(out);
273 	}
274 
275 	/**
276 	 * Removes an event listener. Has no effect if an equal
277 	 * listener object is not currently registered.
278 	 *
279 	 * @param listener The listener to remove
280 	 * @see #addListener(PerformanceStats.PerformanceListener)
281 	 */
removeListener(PerformanceListener listener)282 	public static void removeListener(PerformanceListener listener) {
283 		if (ENABLED)
284 			PerformanceStatsProcessor.removeListener(listener);
285 	}
286 
287 	/**
288 	 * Removes statistics for a given event and blame
289 	 *
290 	 * @param eventName The name of the event to remove
291 	 * @param blameObject The blame for the event to remove
292 	 */
removeStats(String eventName, Object blameObject)293 	public static void removeStats(String eventName, Object blameObject) {
294 		synchronized (statMap) {
295 			for (Iterator<PerformanceStats> it = statMap.keySet().iterator(); it.hasNext();) {
296 				PerformanceStats stats = it.next();
297 				if (stats.getEvent().equals(eventName) && stats.getBlame().equals(blameObject))
298 					it.remove();
299 			}
300 		}
301 	}
302 
303 	/**
304 	 * Creates a new PerformanceStats object.  Private to prevent client instantiation.
305 	 */
PerformanceStats(String event, Object blame)306 	private PerformanceStats(String event, Object blame) {
307 		this(event, blame, null);
308 	}
309 
310 	/**
311 	 * Creates a new PerformanceStats object.  Private to prevent client instantiation.
312 	 */
PerformanceStats(String event, Object blameObject, String context)313 	private PerformanceStats(String event, Object blameObject, String context) {
314 		this.event = event;
315 		this.blame = blameObject instanceof String ? (String) blameObject : blameObject.getClass().getName();
316 		this.blamePluginId = InternalPlatform.getDefault().getBundleId(blameObject);
317 		this.context = context;
318 	}
319 
320 	/**
321 	 * Adds an occurrence of this event to the cumulative counters. This method
322 	 * can be used as an alternative to <code>startRun</code> and <code>endRun</code>
323 	 * for clients that want to track the context and execution time separately.
324 	 *
325 	 * @param elapsed The elapsed time of the new occurrence in milliseconds
326 	 * @param contextName The context for the event to return, or <code>null</code>.
327 	 * The context optionally provides extra information about an event, such as the
328 	 * name of a project being built, or the input of an editor being opened.
329 	 */
addRun(long elapsed, String contextName)330 	public void addRun(long elapsed, String contextName) {
331 		if (!ENABLED)
332 			return;
333 		runCount++;
334 		runningTime += elapsed;
335 		if (elapsed > getThreshold(event))
336 			PerformanceStatsProcessor.failed(createFailureStats(contextName, elapsed), blamePluginId, elapsed);
337 		if (TRACE_SUCCESS)
338 			PerformanceStatsProcessor.changed(this);
339 	}
340 
341 	/**
342 	 * Creates a stats object representing a performance failure
343 	 *
344 	 * @param contextName The failure context information.
345 	 * @param elapsed The elapsed time in milliseconds
346 	 * @return The failure stats
347 	 */
createFailureStats(String contextName, long elapsed)348 	private PerformanceStats createFailureStats(String contextName, long elapsed) {
349 		PerformanceStats failedStat = new PerformanceStats(event, blame, contextName);
350 		PerformanceStats old = statMap.get(failedStat);
351 		if (old == null)
352 			statMap.put(failedStat, failedStat);
353 		else
354 			failedStat = old;
355 		failedStat.isFailure = true;
356 		failedStat.runCount++;
357 		failedStat.runningTime += elapsed;
358 		return failedStat;
359 	}
360 
361 	/**
362 	 * Stops timing the occurrence of this event that was started by the previous
363 	 * call to <code>startRun</code>.  The event is automatically added to
364 	 * the cumulative counters for this event and listeners are notified.
365 	 * <p>
366 	 * Note that this facility guards itself against runs that start but fail to stop,
367 	 * so it is not necessary to call this method from a finally block.  Tracking
368 	 * performance of failure cases is generally not of interest.
369 	 * </p>
370 	 *
371 	 * @see #startRun()
372 	 */
endRun()373 	public void endRun() {
374 		if (!ENABLED || currentStart == NOT_STARTED)
375 			return;
376 		addRun(System.currentTimeMillis() - currentStart, context);
377 		currentStart = NOT_STARTED;
378 	}
379 
380 	/* (non-Javadoc)
381 	 * @see java.lang.Object#equals()
382 	 */
383 	@Override
equals(Object obj)384 	public boolean equals(Object obj) {
385 		//count and time are not considered part of equality
386 		if (!(obj instanceof PerformanceStats))
387 			return false;
388 		PerformanceStats that = (PerformanceStats) obj;
389 		return this.event.equals(that.event) && this.getBlameString().equals(that.getBlameString())
390 				&& Objects.equals(this.context, that.context);
391 	}
392 
393 	/**
394 	 * Returns an object that can be used to figure out who caused the event,
395 	 * or a string describing the cause of the event.
396 	 *
397 	 * @return The blame for this event
398 	 */
getBlame()399 	public Object getBlame() {
400 		return blame;
401 	}
402 
403 	/**
404 	 * Returns a string describing the blame for this event.
405 	 *
406 	 * @return A string describing the blame.
407 	 */
getBlameString()408 	public String getBlameString() {
409 		return blame;
410 	}
411 
412 	/**
413 	 * Returns the optional event context, such as the input of an editor, or the target project
414 	 * of a build event.
415 	 *
416 	 * @return The context, or <code>null</code> if there is none
417 	 */
getContext()418 	public String getContext() {
419 		return context;
420 	}
421 
422 	/**
423 	 * Returns the symbolic name of the event that occurred.
424 	 *
425 	 * @return The name of the event.
426 	 */
getEvent()427 	public String getEvent() {
428 		return event;
429 	}
430 
431 	/**
432 	 * Returns the total number of times this event has occurred.
433 	 *
434 	 * @return The number of occurrences of this event.
435 	 */
getRunCount()436 	public int getRunCount() {
437 		return runCount;
438 	}
439 
440 	/**
441 	 * Returns the total execution time in milliseconds for all occurrences
442 	 * of this event.
443 	 *
444 	 * @return The total running time in milliseconds.
445 	 */
getRunningTime()446 	public long getRunningTime() {
447 		return runningTime;
448 	}
449 
450 	/**
451 	 * Returns the performance threshold for this event.
452 	 */
getThreshold(String eventName)453 	private long getThreshold(String eventName) {
454 		Long value = thresholdMap.get(eventName);
455 		if (value == null) {
456 			String option = InternalPlatform.getDefault().getOption(eventName);
457 			if (option != null) {
458 				try {
459 					value = Long.valueOf(option);
460 				} catch (NumberFormatException e) {
461 					//invalid option, just ignore
462 				}
463 			}
464 			if (value == null)
465 				value = Long.valueOf(Long.MAX_VALUE);
466 			thresholdMap.put(eventName, value);
467 		}
468 		return value.longValue();
469 	}
470 
471 	@Override
hashCode()472 	public int hashCode() {
473 		//count and time are not considered part of equality
474 		int hash = event.hashCode() * 37 + getBlameString().hashCode();
475 		if (context != null)
476 			hash = hash * 37 + context.hashCode();
477 		return hash;
478 	}
479 
480 	/**
481 	 * Returns whether this performance event represents a performance failure.
482 	 *
483 	 * @return <code>true</code> if this is a performance failure, and
484 	 * <code>false</code> otherwise.
485 	 */
isFailure()486 	public boolean isFailure() {
487 		return isFailure;
488 	}
489 
490 	/**
491 	 * Resets count and running time for this particular stats event.
492 	 */
reset()493 	public void reset() {
494 		runningTime = 0;
495 		runCount = 0;
496 	}
497 
498 	/**
499 	 * Starts timing an occurrence of this event. This is a convenience method,
500 	 * fully equivalent to <code>startRun(null)</code>.
501 	 */
startRun()502 	public void startRun() {
503 		if (ENABLED)
504 			startRun(null);
505 	}
506 
507 	/**
508 	 * Starts timing an occurrence of this event.  The event should be stopped
509 	 * by a subsequent call to <code>endRun</code>.
510 	 *
511 	 * @param contextName The context for the event to return, or <code>null</code>.
512 	 * The context optionally provides extra information about an event, such as the
513 	 * name of a project being built, or the input of an editor being opened.
514 	 * @see #endRun
515 	 */
startRun(String contextName)516 	public void startRun(String contextName) {
517 		if (!ENABLED)
518 			return;
519 		this.context = contextName;
520 		this.currentStart = System.currentTimeMillis();
521 	}
522 
523 	/**
524 	 * For debugging purposes only.
525 	 */
526 	@Override
toString()527 	public String toString() {
528 		StringBuilder result = new StringBuilder("PerformanceStats("); //$NON-NLS-1$
529 		result.append(event);
530 		result.append(',');
531 		result.append(blame);
532 		if (context != null) {
533 			result.append(',');
534 			result.append(context);
535 		}
536 		result.append(')');
537 		return result.toString();
538 	}
539 }