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) -> 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 }