| /******************************************************************************* |
| * Copyright (c) 2000, 2013 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Lars Vogel <Lars.Vogel@gmail.com> - Adds generic type arguments https://bugs.eclipse.org/412836 |
| *******************************************************************************/ |
| package org.eclipse.core.runtime; |
| |
| import java.io.PrintWriter; |
| import java.util.*; |
| import org.eclipse.core.internal.runtime.InternalPlatform; |
| import org.eclipse.core.internal.runtime.PerformanceStatsProcessor; |
| |
| /** |
| * PerformanceStats collects and aggregates timing data about events such as |
| * a builder running, an editor opening, etc. This data is collected for the |
| * purpose of performance analysis, and is not intended to be used as |
| * a generic event tracking and notification system. |
| * <p> |
| * Each performance event can have an associated maximum acceptable |
| * duration that is specified in the platform debug options file (.options). |
| * Events that take longer than this maximum are logged as errors. Along |
| * with option file entries for each debug event, there are some global debug |
| * options for enabling or disabling performance event gathering and reporting. |
| * See the "org.eclipse.core.runtime/perf*" debug options in the .options file |
| * for the org.eclipse.core.runtime plugin for more details. |
| * </p><p> |
| * A performance event can optionally have additional context information |
| * ({@link #getContext}). This information is only stored in the case |
| * of a performance failure, and can be used to provide further diagnostic |
| * information that can help track down the cause of the failure. |
| * </p><p> |
| * Performance events and performance failures are batched up and periodically |
| * sent to interested performance event listeners. |
| * </p><p> |
| * This class is not intended to be subclassed or instantiated by clients. |
| * </p> |
| * @since 3.1 |
| */ |
| public class PerformanceStats { |
| /** |
| * A performance listener is periodically notified after performance events occur |
| * or after events fail. |
| * <p> |
| * This class is intended to be subclassed. |
| * </p> |
| * |
| * @see PerformanceStats#addListener(PerformanceStats.PerformanceListener) |
| */ |
| public static abstract class PerformanceListener { |
| /** |
| * Creates a new listener. |
| */ |
| protected PerformanceListener() { |
| super(); |
| } |
| |
| /** |
| * Notifies than an event exceeded the maximum duration for that event type. |
| * <p> |
| * This default implementation does nothing. Subclasses may override. |
| * </p> |
| * |
| * @param event The event that failed |
| * @param duration The duration of the failed event, in milliseconds |
| */ |
| public void eventFailed(PerformanceStats event, long duration) { |
| //default implementation does nothing |
| } |
| |
| /** |
| * Notifies that an event occurred. Notification might not occur |
| * in the same thread or near the time of the actual event. |
| * <p> |
| * This default implementation does nothing. Subclasses may override. |
| * </p> |
| * |
| * @param event The event that occurred |
| */ |
| public void eventsOccurred(PerformanceStats[] event) { |
| //default implementation does nothing |
| } |
| } |
| |
| /** |
| * An empty stats object that is returned when tracing is turned off |
| */ |
| private static final PerformanceStats EMPTY_STATS = new PerformanceStats("", ""); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| /** |
| * Constant indicating whether or not tracing is enabled |
| */ |
| public static final boolean ENABLED; |
| |
| /** |
| * A constant indicating that the timer has not been started. |
| */ |
| private static final long NOT_STARTED = -1; |
| |
| /** |
| * All known event statistics. |
| */ |
| private final static Map<PerformanceStats, PerformanceStats> statMap = |
| Collections.synchronizedMap(new HashMap<PerformanceStats,PerformanceStats>()); |
| |
| /** |
| * Maximum allowed durations for each event. |
| * Maps String (event name) -> Long (threshold) |
| */ |
| private final static Map<String, Long> thresholdMap = Collections.synchronizedMap(new HashMap<String, Long>()); |
| |
| /** |
| * Whether non-failure statistics should be retained. |
| */ |
| private static final boolean TRACE_SUCCESS; |
| |
| /** |
| * An identifier that can be used to figure out who caused the event. This is |
| * typically a string representation of the object whose code was running when |
| * the event occurred or a <code>String</code> describing the event. |
| */ |
| private String blame; |
| |
| /** |
| * The id of the plugin that defined the blame object for this event, or |
| * <code>null</code> if it could not be determined. |
| */ |
| private String blamePluginId; |
| |
| /** |
| * An optional context for the event (may be <code>null</code>). |
| * The context can provide extra information about an event, such as the |
| * name of a project being built, or the input of an editor being opened. |
| */ |
| private String context; |
| |
| /** |
| * The starting time of the current occurrence of this event. |
| */ |
| private long currentStart = NOT_STARTED; |
| |
| /** |
| * The symbolic name of the event that occurred. This is usually the name of |
| * the debug option for this event. |
| */ |
| private String event; |
| |
| /** |
| * Whether this is a performance failure event |
| */ |
| private boolean isFailure; |
| |
| /** |
| * The total number of times this event has occurred. |
| */ |
| private int runCount = 0; |
| |
| /** |
| * The total time in milliseconds taken by all occurrences of this event. |
| */ |
| private long runningTime = 0; |
| |
| static { |
| ENABLED = InternalPlatform.getDefault().getBooleanOption(Platform.PI_RUNTIME + "/perf", false);//$NON-NLS-1$ |
| //turn these on by default if the global trace flag is turned on |
| TRACE_SUCCESS = InternalPlatform.getDefault().getBooleanOption(Platform.PI_RUNTIME + "/perf/success", ENABLED); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Adds a listener that is notified when performance events occur. If |
| * an equal listener is already installed, it will be replaced. |
| * |
| * @param listener The listener to be added |
| * @see #removeListener(PerformanceStats.PerformanceListener) |
| */ |
| public static void addListener(PerformanceListener listener) { |
| if (ENABLED) |
| PerformanceStatsProcessor.addListener(listener); |
| } |
| |
| /** |
| * Discards all known performance event statistics. |
| */ |
| public static void clear() { |
| statMap.clear(); |
| } |
| |
| /** |
| * Returns all performance event statistics. |
| * |
| * @return An array of known performance event statistics. The array |
| * will be empty if there are no recorded statistics. |
| */ |
| public static PerformanceStats[] getAllStats() { |
| return statMap.values().toArray(new PerformanceStats[statMap.values().size()]); |
| } |
| |
| /** |
| * Returns the stats object corresponding to the given parameters. |
| * A stats object is created and added to the global list of events if it did not |
| * already exist. |
| * |
| * @param eventName A symbolic event name. This is usually the name of |
| * the debug option for this event. An example event name from |
| * the org.eclipse.core.resources plugin describing a build event might look like: |
| * <code>"org.eclipse.core.resources/perf/building"</code>" |
| * @param blameObject The blame for the event. This is typically the object |
| * whose code was running when the event occurred. If a blame object cannot |
| * be obtained, a <code>String</code> describing the event should be supplied |
| */ |
| public static PerformanceStats getStats(String eventName, Object blameObject) { |
| if (!ENABLED || eventName == null || blameObject == null) |
| return EMPTY_STATS; |
| PerformanceStats newStats = new PerformanceStats(eventName, blameObject); |
| if (!TRACE_SUCCESS) |
| return newStats; |
| //use existing stats object if available |
| PerformanceStats oldStats = statMap.get(newStats); |
| if (oldStats != null) |
| return oldStats; |
| statMap.put(newStats, newStats); |
| return newStats; |
| } |
| |
| /** |
| * Returns whether monitoring of a given performance event is enabled. |
| * <p> |
| * For frequent performance events, the result of this method call should |
| * be cached by the caller to minimize overhead when performance monitoring |
| * is turned off. It is not possible for enablement to change during the life |
| * of this invocation of the platform. |
| * </p> |
| * |
| * @param eventName The name of the event to determine enablement for |
| * @return <code>true</code>If the performance event with the given |
| * name is enabled, and <code>false</code> otherwise. |
| */ |
| public static boolean isEnabled(String eventName) { |
| if (!ENABLED) |
| return false; |
| String option = Platform.getDebugOption(eventName); |
| return option != null && !option.equalsIgnoreCase("false") && !option.equalsIgnoreCase("-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Prints all statistics to the standard output. |
| */ |
| public static void printStats() { |
| if (!ENABLED) |
| return; |
| PrintWriter writer = new PrintWriter(System.out); |
| PerformanceStatsProcessor.printStats(writer); |
| writer.flush(); |
| } |
| |
| /** |
| * Writes all statistics using the provided writer |
| * |
| * @param out The writer to print stats to. |
| */ |
| public static void printStats(PrintWriter out) { |
| if (!ENABLED) |
| return; |
| PerformanceStatsProcessor.printStats(out); |
| } |
| |
| /** |
| * Removes an event listener. Has no effect if an equal |
| * listener object is not currently registered. |
| * |
| * @param listener The listener to remove |
| * @see #addListener(PerformanceStats.PerformanceListener) |
| */ |
| public static void removeListener(PerformanceListener listener) { |
| if (ENABLED) |
| PerformanceStatsProcessor.removeListener(listener); |
| } |
| |
| /** |
| * Removes statistics for a given event and blame |
| * |
| * @param eventName The name of the event to remove |
| * @param blameObject The blame for the event to remove |
| */ |
| public static void removeStats(String eventName, Object blameObject) { |
| synchronized (statMap) { |
| for (Iterator<PerformanceStats> it = statMap.keySet().iterator(); it.hasNext();) { |
| PerformanceStats stats = it.next(); |
| if (stats.getEvent().equals(eventName) && stats.getBlame().equals(blameObject)) |
| it.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Creates a new PerformanceStats object. Private to prevent client instantiation. |
| */ |
| private PerformanceStats(String event, Object blame) { |
| this(event, blame, null); |
| } |
| |
| /** |
| * Creates a new PerformanceStats object. Private to prevent client instantiation. |
| */ |
| private PerformanceStats(String event, Object blameObject, String context) { |
| this.event = event; |
| this.blame = blameObject instanceof String ? (String) blameObject : blameObject.getClass().getName(); |
| this.blamePluginId = InternalPlatform.getDefault().getBundleId(blameObject); |
| this.context = context; |
| } |
| |
| /** |
| * Adds an occurrence of this event to the cumulative counters. This method |
| * can be used as an alternative to <code>startRun</code> and <code>endRun</code> |
| * for clients that want to track the context and execution time separately. |
| * |
| * @param elapsed The elapsed time of the new occurrence in milliseconds |
| * @param contextName The context for the event to return, or <code>null</code>. |
| * The context optionally provides extra information about an event, such as the |
| * name of a project being built, or the input of an editor being opened. |
| */ |
| public void addRun(long elapsed, String contextName) { |
| if (!ENABLED) |
| return; |
| runCount++; |
| runningTime += elapsed; |
| if (elapsed > getThreshold(event)) |
| PerformanceStatsProcessor.failed(createFailureStats(contextName, elapsed), blamePluginId, elapsed); |
| if (TRACE_SUCCESS) |
| PerformanceStatsProcessor.changed(this); |
| } |
| |
| /** |
| * Creates a stats object representing a performance failure |
| * |
| * @param contextName The failure context information. |
| * @param elapsed The elapsed time in milliseconds |
| * @return The failure stats |
| */ |
| private PerformanceStats createFailureStats(String contextName, long elapsed) { |
| PerformanceStats failedStat = new PerformanceStats(event, blame, contextName); |
| PerformanceStats old = statMap.get(failedStat); |
| if (old == null) |
| statMap.put(failedStat, failedStat); |
| else |
| failedStat = old; |
| failedStat.isFailure = true; |
| failedStat.runCount++; |
| failedStat.runningTime += elapsed; |
| return failedStat; |
| } |
| |
| /** |
| * Stops timing the occurrence of this event that was started by the previous |
| * call to <code>startRun</code>. The event is automatically added to |
| * the cumulative counters for this event and listeners are notified. |
| * <p> |
| * Note that this facility guards itself against runs that start but fail to stop, |
| * so it is not necessary to call this method from a finally block. Tracking |
| * performance of failure cases is generally not of interest. |
| * </p> |
| * |
| * @see #startRun() |
| */ |
| public void endRun() { |
| if (!ENABLED || currentStart == NOT_STARTED) |
| return; |
| addRun(System.currentTimeMillis() - currentStart, context); |
| currentStart = NOT_STARTED; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals() |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| //count and time are not considered part of equality |
| if (!(obj instanceof PerformanceStats)) |
| return false; |
| PerformanceStats that = (PerformanceStats) obj; |
| if (!this.event.equals(that.event)) |
| return false; |
| if (!this.getBlameString().equals(that.getBlameString())) |
| return false; |
| return this.context == null ? that.context == null : this.context.equals(that.context); |
| } |
| |
| /** |
| * Returns an object that can be used to figure out who caused the event, |
| * or a string describing the cause of the event. |
| * |
| * @return The blame for this event |
| */ |
| public Object getBlame() { |
| return blame; |
| } |
| |
| /** |
| * Returns a string describing the blame for this event. |
| * |
| * @return A string describing the blame. |
| */ |
| public String getBlameString() { |
| return blame; |
| } |
| |
| /** |
| * Returns the optional event context, such as the input of an editor, or the target project |
| * of a build event. |
| * |
| * @return The context, or <code>null</code> if there is none |
| */ |
| public String getContext() { |
| return context; |
| } |
| |
| /** |
| * Returns the symbolic name of the event that occurred. |
| * |
| * @return The name of the event. |
| */ |
| public String getEvent() { |
| return event; |
| } |
| |
| /** |
| * Returns the total number of times this event has occurred. |
| * |
| * @return The number of occurrences of this event. |
| */ |
| public int getRunCount() { |
| return runCount; |
| } |
| |
| /** |
| * Returns the total execution time in milliseconds for all occurrences |
| * of this event. |
| * |
| * @return The total running time in milliseconds. |
| */ |
| public long getRunningTime() { |
| return runningTime; |
| } |
| |
| /** |
| * Returns the performance threshold for this event. |
| */ |
| private long getThreshold(String eventName) { |
| Long value = thresholdMap.get(eventName); |
| if (value == null) { |
| String option = InternalPlatform.getDefault().getOption(eventName); |
| if (option != null) { |
| try { |
| value = Long.valueOf(option); |
| } catch (NumberFormatException e) { |
| //invalid option, just ignore |
| } |
| } |
| if (value == null) |
| value = Long.valueOf(Long.MAX_VALUE); |
| thresholdMap.put(eventName, value); |
| } |
| return value.longValue(); |
| } |
| |
| @Override |
| public int hashCode() { |
| //count and time are not considered part of equality |
| int hash = event.hashCode() * 37 + getBlameString().hashCode(); |
| if (context != null) |
| hash = hash * 37 + context.hashCode(); |
| return hash; |
| } |
| |
| /** |
| * Returns whether this performance event represents a performance failure. |
| * |
| * @return <code>true</code> if this is a performance failure, and |
| * <code>false</code> otherwise. |
| */ |
| public boolean isFailure() { |
| return isFailure; |
| } |
| |
| /** |
| * Resets count and running time for this particular stats event. |
| */ |
| public void reset() { |
| runningTime = 0; |
| runCount = 0; |
| } |
| |
| /** |
| * Starts timing an occurrence of this event. This is a convenience method, |
| * fully equivalent to <code>startRun(null)</code>. |
| */ |
| public void startRun() { |
| if (ENABLED) |
| startRun(null); |
| } |
| |
| /** |
| * Starts timing an occurrence of this event. The event should be stopped |
| * by a subsequent call to <code>endRun</code>. |
| * |
| * @param contextName The context for the event to return, or <code>null</code>. |
| * The context optionally provides extra information about an event, such as the |
| * name of a project being built, or the input of an editor being opened. |
| * @see #endRun |
| */ |
| public void startRun(String contextName) { |
| if (!ENABLED) |
| return; |
| this.context = contextName; |
| this.currentStart = System.currentTimeMillis(); |
| } |
| |
| /** |
| * For debugging purposes only. |
| */ |
| @Override |
| public String toString() { |
| StringBuilder result = new StringBuilder("PerformanceStats("); //$NON-NLS-1$ |
| result.append(event); |
| result.append(','); |
| result.append(blame); |
| if (context != null) { |
| result.append(','); |
| result.append(context); |
| } |
| result.append(')'); |
| return result.toString(); |
| } |
| } |