diff options
| author | Sergey Prigogin | 2014-08-04 20:48:32 +0000 |
|---|---|---|
| committer | Sergey Prigogin | 2014-08-21 22:04:47 +0000 |
| commit | 9b4722b71f6463402aa8d0cd490370682952288a (patch) | |
| tree | e5e0bca14b48ce4c20fc1b820303f06e4bad48b3 | |
| parent | 3a2f0f2139c723275b09d21e489af62fffa45788 (diff) | |
| download | eclipse.platform.ui-9b4722b71f6463402aa8d0cd490370682952288a.tar.gz eclipse.platform.ui-9b4722b71f6463402aa8d0cd490370682952288a.tar.xz eclipse.platform.ui-9b4722b71f6463402aa8d0cd490370682952288a.zip | |
Bug 441015 - Added event loop monitoring plugin
Change-Id: Ie98bbdc53c35aecbb9d56e73398c041402361314
Signed-off-by: Marcus Eng <marcuseng23@gmail.com>
Signed-off-by: Sergey Prigogin <eclipse.sprigogin@gmail.com>
48 files changed, 4125 insertions, 0 deletions
diff --git a/bundles/org.eclipse.ui.monitoring/.classpath b/bundles/org.eclipse.ui.monitoring/.classpath new file mode 100644 index 00000000000..ad32c83a788 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.ui.monitoring/.options b/bundles/org.eclipse.ui.monitoring/.options new file mode 100644 index 00000000000..7f703c4e9a9 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/.options @@ -0,0 +1,4 @@ +# Debugging options for the org.eclipse.ui.monitoring plugin + +# Turn on general debugging for the org.eclipse.ui.monitoring plugin. +org.eclipse.ui.monitoring/debug/event_monitor=false diff --git a/bundles/org.eclipse.ui.monitoring/.project b/bundles/org.eclipse.ui.monitoring/.project new file mode 100644 index 00000000000..38d1a9e6308 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/.project @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.ui.monitoring</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature> + </natures> +</projectDescription> diff --git a/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..c537b63063c --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.pde.prefs b/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.pde.prefs new file mode 100644 index 00000000000..3b2510f64c7 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.pde.prefs @@ -0,0 +1,14 @@ +#Tue Nov 16 14:10:21 EST 2004 +compilers.p.unused-element-or-attribute=1 +compilers.p.unresolved-ex-points=0 +compilers.p.deprecated=0 +compilers.p.unknown-element=1 +compilers.p.unknown-resource=1 +compilers.p.unknown-class=1 +compilers.p.unknown-attribute=0 +compilers.p.no-required-att=0 +eclipse.preferences.version=1 +compilers.p.unresolved-import=0 +compilers.p.not-externalized-att=0 +compilers.p.illegal-att-value=0 +compilers.use-project=true diff --git a/bundles/org.eclipse.ui.monitoring/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.monitoring/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..04cb2a42f7e --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/META-INF/MANIFEST.MF @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +Bundle-ActivationPolicy: lazy +Bundle-Activator: org.eclipse.ui.internal.monitoring.MonitoringPlugin +Bundle-ClassPath: . +Bundle-Localization: plugin +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-SymbolicName: org.eclipse.ui.monitoring;singleton:=true +Bundle-Vendor: %Bundle-Vendor +Bundle-Version: 1.0.0.qualifier +Export-Package: org.eclipse.ui.internal.monitoring;x-internal:=true, + org.eclipse.ui.internal.monitoring.preferences;x-internal:=true, + org.eclipse.ui.monitoring;x-internal:=true +Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.10.0,4.0.0)", + org.eclipse.jface;bundle-version="[3.10.0,4.0.0)", + org.eclipse.ui;bundle-version="[3.106.0,4.0.0)" diff --git a/bundles/org.eclipse.ui.monitoring/README.md b/bundles/org.eclipse.ui.monitoring/README.md new file mode 100644 index 00000000000..89c9ff0ab4a --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/README.md @@ -0,0 +1,21 @@ +org.eclipse.ui.monitoring +========================= + +org.eclipse.ui.monitoring provides a way to monitor and log freeze events from the UI thread. + +org.eclipse.ui.monitoring plug-in usage +--------------------------------------- + +Enable the plug-in from Eclipse preferences under General > Tracing > Event Loop Monitor. + +Once this plug-in is enabled, events on the UI thread that take longer than a specified threshold value will be logged to the Eclipse error log. + +The information captured to the Eclipse error log includes information on the thread as well as the stack trace, which then can be easily reported. + +License +------- + +[Eclipse Public License (EPL) v1.0][2] + +[1]: http://wiki.eclipse.org/Platform_UI +[2]: http://wiki.eclipse.org/EPL diff --git a/bundles/org.eclipse.ui.monitoring/build.properties b/bundles/org.eclipse.ui.monitoring/build.properties new file mode 100644 index 00000000000..3c1b6d9ead0 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/build.properties @@ -0,0 +1,17 @@ +############################################################################### +# Copyright (C) 2014, Google Inc. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Steve Foreman (Google) - initial API and implementation +# Marcus Eng (Google) +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + plugin.xml diff --git a/bundles/org.eclipse.ui.monitoring/plugin.properties b/bundles/org.eclipse.ui.monitoring/plugin.properties new file mode 100644 index 00000000000..992d0eea43e --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/plugin.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2014 Google, Inc and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Marcus Eng (Google) - initial API and implementation +############################################################################### + +Bundle-Name=Event Loop Monitor +Bundle-Vendor=Google Inc. +eventLoopMonitorPreferencePage.name=Event Loop Monitor +UiFreezeEventLogger.name=UI Freeze Event Logger
\ No newline at end of file diff --git a/bundles/org.eclipse.ui.monitoring/plugin.xml b/bundles/org.eclipse.ui.monitoring/plugin.xml new file mode 100644 index 00000000000..8e3e171906d --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/plugin.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="4.4"?> +<!-- + Copyright (C) 2014, Google Inc and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Public License v1.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/legal/epl-v10.html + + Contributors: + Steve Foreman (Google) - initial API and implementation + Marcus Eng (Google) +--> +<plugin> + <extension-point + id="logger" + name="%UiFreezeEventLogger.name" + schema="schema/org.eclipse.ui.monitoring.logger.exsd"/> + + <extension point="org.eclipse.ui.startup"> + <startup class="org.eclipse.ui.internal.monitoring.MonitoringStartup"/> + </extension> + <extension + point="org.eclipse.core.runtime.preferences"> + <initializer + class="org.eclipse.ui.internal.monitoring.preferences.MonitoringPreferenceInitializer"> + </initializer> + </extension> + <extension + point="org.eclipse.ui.preferencePages"> + <page + category="org.eclipse.ui.trace.tracingPage" + class="org.eclipse.ui.internal.monitoring.preferences.MonitoringPreferencePage" + id="org.eclipse.ui.monitoring.page" + name="%eventLoopMonitorPreferencePage.name"> + </page> + </extension> +</plugin> diff --git a/bundles/org.eclipse.ui.monitoring/pom.xml b/bundles/org.eclipse.ui.monitoring/pom.xml new file mode 100644 index 00000000000..baea8515bc2 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/pom.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2014, Google Inc and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Public License v1.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/legal/epl-v10.html + + Contributors: + Marcus Eng (Google) - initial API and implementation +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>eclipse.platform.ui</artifactId> + <groupId>eclipse.platform.ui</groupId> + <version>4.5.0-SNAPSHOT</version> + <relativePath>../../</relativePath> + </parent> + <groupId>org.eclipse.ui</groupId> + <artifactId>org.eclipse.ui.monitoring</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>eclipse-plugin</packaging> +</project>
\ No newline at end of file diff --git a/bundles/org.eclipse.ui.monitoring/schema/org.eclipse.ui.monitoring.logger.exsd b/bundles/org.eclipse.ui.monitoring/schema/org.eclipse.ui.monitoring.logger.exsd new file mode 100644 index 00000000000..bf093c5a9db --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/schema/org.eclipse.ui.monitoring.logger.exsd @@ -0,0 +1,102 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!-- Schema file written by PDE --> +<schema targetNamespace="org.eclipse.ui.monitoring" xmlns="http://www.w3.org/2001/XMLSchema"> +<annotation> + <appinfo> + <meta.schema plugin="org.eclipse.ui.monitoring" id="org.eclipse.ui.monitoring.logger" name="UiFreezeEventLogger"/> + </appinfo> + <documentation> + An extension point that allows for a UiFreezeEvent to be processed differently in addition to logging to the Eclipse error log. + </documentation> + </annotation> + + <element name="extension"> + <annotation> + <appinfo> + <meta.element /> + </appinfo> + </annotation> + <complexType> + <choice minOccurs="1" maxOccurs="unbounded"> + <element ref="logger"/> + </choice> + <attribute name="point" type="string" use="required"> + <annotation> + <documentation> + + </documentation> + </annotation> + </attribute> + <attribute name="id" type="string"> + <annotation> + <documentation> + + </documentation> + </annotation> + </attribute> + <attribute name="name" type="string"> + <annotation> + <documentation> + + </documentation> + <appinfo> + <meta.attribute translatable="true"/> + </appinfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="logger"> + <complexType> + <attribute name="class" type="string"> + <annotation> + <documentation> + + </documentation> + <appinfo> + <meta.attribute kind="java" basedOn=":org.eclipse.ui.monitoring.IUiFreezeEventLogger"/> + </appinfo> + </annotation> + </attribute> + </complexType> + </element> + + <annotation> + <appinfo> + <meta.section type="since"/> + </appinfo> + <documentation> + 1.0.0 + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="examples"/> + </appinfo> + <documentation> + Receiving and logging a UiFreezeEvent to a remote server. + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="apiinfo"/> + </appinfo> + <documentation> + A UiFreezeEvent is passed to a class implementing the interface IUiFreezeEventLogger whenever an event needs to be logged. + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="implementation"/> + </appinfo> + <documentation> + A class implementing IUiFreezeEventLogger will have the log method be invoked everytime a UiFreezeEvent is ready to be processed. + </documentation> + </annotation> + + +</schema> diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/DefaultUiFreezeEventLogger.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/DefaultUiFreezeEventLogger.java new file mode 100644 index 00000000000..8fea35a1cbb --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/DefaultUiFreezeEventLogger.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.osgi.util.NLS; +import org.eclipse.ui.monitoring.IUiFreezeEventLogger; +import org.eclipse.ui.monitoring.PreferenceConstants; +import org.eclipse.ui.monitoring.StackSample; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.lang.management.LockInfo; +import java.lang.management.ThreadInfo; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Writes {@link UiFreezeEvent}s to the Eclipse error log. + */ +public class DefaultUiFreezeEventLogger implements IUiFreezeEventLogger { + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + + private static class SeverityMultiStatus extends MultiStatus { + public SeverityMultiStatus(int severity, String pluginId, String message, Throwable exception) { + super(pluginId, OK, message, exception); + setSeverity(severity); + } + } + + /** + * Parses the given {@link UiFreezeEvent} into a {@link MultiStatus} and saves it to the log. + * + * @param event the event that caused the UI thread to freeze + */ + @Override + public void log(UiFreezeEvent event) { + long lastTimestamp = event.getStartTimestamp(); + String startTime = dateFormat.format(new Date(lastTimestamp)); + + String pattern = event.isStillRunning() + ? Messages.DefaultUiFreezeEventLogger_ui_delay_header_running_2 + : Messages.DefaultUiFreezeEventLogger_ui_delay_header_non_running_2; + String header = NLS.bind(pattern, + String.format("%.2f", event.getTotalDuration() / 1000.0), startTime); //$NON-NLS-1$ + + MultiStatus loggedEvent = + new SeverityMultiStatus(IStatus.WARNING, PreferenceConstants.PLUGIN_ID, header, null); + + for (int i = 0; i < event.getSampleCount(); i++) { + StackSample sample = event.getStackTraceSamples()[i]; + + double deltaInSeconds = (sample.getTimestamp() - lastTimestamp) / 1000.0; + ThreadInfo[] threads = sample.getStackTraces(); + + // The first thread is guaranteed to be the display thread. + Exception stackTrace = new Exception( + Messages.DefaultUiFreezeEventLogger_stack_trace_header); + stackTrace.setStackTrace(threads[0].getStackTrace()); + String traceText = NLS.bind( + Messages.DefaultUiFreezeEventLogger_sample_header_2, + dateFormat.format(sample.getTimestamp()), + String.format("%.3f", deltaInSeconds)); //$NON-NLS-1$ + MultiStatus traceStatus = new SeverityMultiStatus(IStatus.INFO, + PreferenceConstants.PLUGIN_ID, + String.format("%s\n%s", traceText, createThreadMessage(threads[0])), //$NON-NLS-1$ + stackTrace); + loggedEvent.add(traceStatus); + + for (int j = 1; j < threads.length; j++) { + traceStatus.add(createThreadStatus(threads[j])); + } + + lastTimestamp = sample.getTimestamp(); + } + + MonitoringPlugin.getDefault().getLog().log(loggedEvent); + } + + private static IStatus createThreadStatus(ThreadInfo thread) { + Exception stackTrace = new Exception( + Messages.DefaultUiFreezeEventLogger_stack_trace_header); + stackTrace.setStackTrace(thread.getStackTrace()); + + StringBuilder threadText = createThreadMessage(thread); + String lockName = thread.getLockName(); + if (lockName != null && !lockName.isEmpty()) { + LockInfo lock = thread.getLockInfo(); + threadText.append(NLS.bind( + Messages.DefaultUiFreezeEventLogger_waiting_for_1, + getClassAndHashCode(lock))); + String lockOwnerName = thread.getLockOwnerName(); + if (lockOwnerName != null && !lockOwnerName.isEmpty()) { + threadText.append(NLS.bind( + Messages.DefaultUiFreezeEventLogger_lock_owner_2, + lockOwnerName, thread.getLockOwnerId())); + } + } + + for (LockInfo lockInfo : thread.getLockedSynchronizers()) { + threadText.append(NLS.bind( + Messages.DefaultUiFreezeEventLogger_holding_1, + getClassAndHashCode(lockInfo))); + } + + return new Status(IStatus.INFO, PreferenceConstants.PLUGIN_ID, threadText.toString(), + stackTrace); + } + + private static StringBuilder createThreadMessage(ThreadInfo thread) { + String threadDetails = NLS.bind( + Messages.DefaultUiFreezeEventLogger_thread_details, + thread.getThreadId(), thread.getThreadState()); + + StringBuilder threadText = new StringBuilder(NLS.bind( + Messages.DefaultUiFreezeEventLogger_thread_header_2, + thread.getThreadName(), threadDetails)); + + return threadText; + } + + private static String getClassAndHashCode(LockInfo info) { + return String.format("%s@%08x", info.getClassName(), info.getIdentityHashCode()); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThread.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThread.java new file mode 100644 index 00000000000..696dd3ce50f --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThread.java @@ -0,0 +1,630 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.monitoring.IUiFreezeEventLogger; +import org.eclipse.ui.monitoring.StackSample; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * SWT long event monitoring thread. Can be used to report freezes on the UI thread. + */ +public class EventLoopMonitorThread extends Thread implements Listener { + private static final String EXTENSION_ID = "org.eclipse.ui.monitoring.logger"; //$NON-NLS-1$ + private static final String NEW_LINE_AND_BULLET = "\n* "; //$NON-NLS-1$ + + /* NOTE: All time-related values in this class are in milliseconds. */ + /** + * Helper object for passing preference-based arguments by name to the constructor, making the + * code more readable than a large parameter list of ints and bools. + */ + public static class Parameters { + /** Milliseconds after which a long event should get logged */ + public int loggingThreshold; + /** Milliseconds to wait before collecting the first sample */ + public int samplingThreshold; + /** + * If true, includes call stacks of all threads into the logged message. Otherwise, only the + * stack of the watched thread is included. + */ + public boolean dumpAllThreads; + /** + * Milliseconds at which stack traces should be sampled. Down-sampling of a long event may + * extend the polling delay for that event. + */ + public int minimumPollingDelay; + /** Maximum number of stack samples to log */ + public int loggedTraceCount; + /** Milliseconds after which a long event should logged as a deadlock */ + public long deadlockDelta; + /** + * If true, log freeze events to the Eclipse error log on the local machine. + */ + public boolean logLocally; + /** + * Contains the list of fully qualified methods to filter out. + */ + public String filterTraces; + + + /** + * Checks if parameters for plug-in are valid before startup. + * + * @throws IllegalArgumentException if the parameter values are invalid or inconsistent. + */ + public void checkParameters() throws IllegalArgumentException { + StringBuilder error = new StringBuilder(); + if (!(this.loggingThreshold > 0)) { + error.append(NEW_LINE_AND_BULLET + NLS.bind( + Messages.EventLoopMonitorThread_logging_threshold_error_1, this.loggingThreshold)); + } + if (!(this.minimumPollingDelay > 0)) { + error.append(NEW_LINE_AND_BULLET + NLS.bind( + Messages.EventLoopMonitorThread_sample_interval_error_1, this.minimumPollingDelay)); + } + if (!(this.loggedTraceCount > 0)) { + error.append(NEW_LINE_AND_BULLET + NLS.bind( + Messages.EventLoopMonitorThread_max_log_count_error_1, this.loggedTraceCount)); + } + if (!(this.samplingThreshold > 0)) { + error.append(NEW_LINE_AND_BULLET + NLS.bind( + Messages.EventLoopMonitorThread_capture_threshold_error_1, this.samplingThreshold)); + } + if (this.loggingThreshold < this.samplingThreshold) { + error.append(NEW_LINE_AND_BULLET + Messages.EventLoopMonitorThread_invalid_threshold_error); + } + if (!(this.deadlockDelta > 0)) { + error.append(NEW_LINE_AND_BULLET + + NLS.bind(Messages.EventLoopMonitorThread_deadlock_error_1, this.deadlockDelta)); + } + + String errorString = error.toString(); + if (!errorString.isEmpty()) { + throw new IllegalArgumentException( + Messages.EventLoopMonitorThread_invalid_argument_error + errorString); + } + } + } + + /* + * Tracks when the current event was started, or if the event has nested {@link Event#sendEvent} + * calls, then the time when the most recent nested call returns and the current event is resumed. + * + * Accessed by both the UI and monitoring threads. Updated by the UI thread and read by the + * polling thread. Changing this in the UI thread causes the polling thread to reset its stalled + * event state. The UI thread sets this value to zero to indicate a sleep state and to a positive + * value to represent a dispatched state. (Using zero as an invalid event start time will be wrong + * for a 1 millisecond window when the 32-bit system clock rolls over in 2038, but we can live + * with skipping any events that fall in that window). + */ + private volatile long eventStartOrResumeTime; + + // Accessed by both the UI and monitoring threads. + private final int loggingThreshold; + private final AtomicBoolean cancelled = new AtomicBoolean(false); + private final AtomicReference<LongEventInfo> publishEvent = + new AtomicReference<LongEventInfo>(null); + + // Accessed only on the polling thread. + private ArrayList<IUiFreezeEventLogger> externalLoggers; + private final DefaultUiFreezeEventLogger defaultLogger; + private final Tracer localTraceLog; + private final Display display; + private final FilterHandler filterHandler; + private final long samplingThreshold; + private final long minimumPollingDelay; + private final int maxTraceCount; + private final int loggedTraceCount; + private final long deadlockDelta; + private final long uiThreadId; + private final Object sleepMonitor; + private final boolean dumpAllThreads; + private final boolean logLocally; + + /** + * A helper class to track and report potential deadlocks. + */ + private class DeadlockTracker { + private boolean haveAlreadyLoggedPossibleDeadlock; + + // The last time a state transition between events or sleep/wake was seen. May be set to zero + // to indicate that deadlocks should not be tracked. + private long lastActive; + + /** + * Logs a possible deadlock to the remote log. {@code lastActive} is zero if the interval is for + * a sleep, in which case we don't log a deadlock. + * + * @param currTime the current time + * @param stackTraces stack traces for the currently stalled event + * @param numStacks the number of valid traces for the currently stalled event + */ + public void logPossibleDeadlock(long currTime, StackSample[] stackTraces, int numStacks) { + long totalDuration = currTime - lastActive; + + if (!haveAlreadyLoggedPossibleDeadlock && lastActive > 0 && totalDuration > deadlockDelta) { + logEvent(new UiFreezeEvent(lastActive, totalDuration, stackTraces, numStacks, true)); + haveAlreadyLoggedPossibleDeadlock = true; + Arrays.fill(stackTraces, null); + } + } + + /** + * Resets the deadlock tracker's state. + */ + public void reset(long lastActive) { + this.lastActive = lastActive; + haveAlreadyLoggedPossibleDeadlock = false; + } + } + + /** + * Initializes the static state of the monitoring thread. + * + * @param args parameters derived from preferences + * @throws IllegalArgumentException if monitoring thread cannot be initialized due to an error. + */ + public EventLoopMonitorThread(Parameters args) throws IllegalArgumentException { + super("Event Loop Monitor"); //$NON-NLS-1$ + + Assert.isNotNull(args); + + args.checkParameters(); + + setDaemon(true); + setPriority(NORM_PRIORITY + 1); + this.display = getDisplay(); + this.uiThreadId = this.display.getThread().getId(); + this.filterHandler = new FilterHandler(args.filterTraces); + this.samplingThreshold = args.samplingThreshold; + this.minimumPollingDelay = args.minimumPollingDelay; + this.loggedTraceCount = args.loggedTraceCount; + this.maxTraceCount = 2 * (args.loggedTraceCount - 1); + this.loggingThreshold = args.loggingThreshold; + this.dumpAllThreads = args.dumpAllThreads; + this.localTraceLog = getTracer(); + this.deadlockDelta = args.deadlockDelta; + this.logLocally = args.logLocally; + this.sleepMonitor = new Object(); + defaultLogger = new DefaultUiFreezeEventLogger(); + + loadLoggerExtensions(); + + if (!logLocally && externalLoggers.isEmpty()) { + MonitoringPlugin.logWarning(Messages.EventLoopMonitorThread_logging_disabled_error); + } + } + + /** + * Shuts down the monitoring thread. Must be called on the display thread. + */ + public void shutdown() throws SWTException { + cancelled.set(true); + if (!display.isDisposed()) { + display.removeListener(SWT.PreEvent, this); + display.removeListener(SWT.PostEvent, this); + display.removeListener(SWT.Sleep, this); + display.removeListener(SWT.Wakeup, this); + } + wakeUp(); + } + + @Override + public void handleEvent(Event event) { + /* + * Freeze monitoring involves seeing long intervals between BeginEvent/EndEvent messages, + * regardless of the level of event nesting. For example: + * 1) Log if a top-level or nested dispatch takes too long (interval is between BeginEvent and + * EndEvent). + * 2) Log if preparation before popping up a dialog takes too long (interval is between two + * BeginEvent messages). + * 3) Log if processing after dismissing a dialog takes too long (interval is between two + * EndEvent messages). + * 4) Log if there is a long delay between nested calls (interval is between EndEvent and + * BeginEvent). This could happen after a dialog is dismissed, does too much processing on + * the UI thread, and then pops up a notification dialog. + * 5) Don't log for long delays between top-level events (interval is between EndEvent and + * BeginEvent at the top level), which should involve sleeping. + * + * Calls to Display.sleep() make the UI responsive, whether or not events are actually + * dispatched, so items 1-4 above assume that there are no intervening calls to sleep() between + * the event transitions. Treating the BeginSleep event as an event transition lets us + * accurately capture true freeze intervals. + * + * Correct management of BeginSleep/EndSleep events allow us to handle items 4 and 5 above + * since we can tell if a long delay between an EndEvent and a BeginEvent are due to an idle + * state (in Display.sleep()) or a UI freeze. + * + * Since an idle system can potentially sleep for a long time, we need to avoid logging long + * delays that are due to sleeps. The eventStartOrResumeTime variable is set to zero + * when the thread is sleeping so that deadlock logging can be avoided for this case. + */ + switch (event.type) { + case SWT.PreEvent: + beginEvent(); + break; + case SWT.PostEvent: + endEvent(); + break; + case SWT.Sleep: + beginSleep(); + break; + case SWT.Wakeup: + endSleep(); + break; + default: + } + } + + // Called on the UI thread! + // VisibleForTesting + public void beginEvent() { + // Log a long interval, not entering sleep + handleEventTransition(true, false); + } + + // Called on the UI thread! + // VisibleForTesting + public void endEvent() { + // Log a long interval, not entering sleep + handleEventTransition(true, false); + } + + // Called on the UI thread! + // VisibleForTesting + public void beginSleep() { + // Log a long interval, entering sleep + handleEventTransition(true, true); + } + + // Called on the UI thread! + // VisibleForTesting + public void endSleep() { + // Don't log the long sleep interval, not entering sleep + handleEventTransition(false, false); + } + + // Called on the UI thread! + private void handleEventTransition(boolean attemptToLogLongDelay, boolean isEnteringSleep) { + /* + * On transition between events or sleeping/wake up, we need to reset the delay tracking state + * and possibly publish a long delay message. Updating eventStartOrResumeTime causes the polling + * thread to reset its stack traces, so it should always be changed *after* the event is + * published. The indeterminacy of threading may cause the polling thread to see both changes or + * only the (first) publishEvent change, but the only difference is a small window where if an + * additional stack trace was scheduled to be sampled, a bogus stack trace sample will be + * appended to the end of the samples. Analysis code needs to be aware that the last sample may + * not be relevant to the issue which caused the freeze. + */ + long currTime = getTimestamp(); + if (attemptToLogLongDelay) { + long startTime = eventStartOrResumeTime; + if (startTime != 0) { + int duration = (int) (currTime - startTime); + if (duration >= loggingThreshold) { + LongEventInfo info = new LongEventInfo(startTime, duration); + publishEvent.set(info); + wakeUp(); + } + } + } + // Using zero as an invalid event time will be wrong for a 1 millisecond window when the system + // clock rolls over in 2038, but we can live with that. + eventStartOrResumeTime = !isEnteringSleep ? currTime : 0; + } + + @Override + public void run() { + /* + * If this event loop starts in the middle of a UI freeze, it will succeed in capturing the + * portion of that UI freeze that it sees. + * + * Our timer resolution is, at best, 1 millisecond so we can never try to catch events of a + * duration less than that. + */ + boolean resetStalledEventState = true; + + DeadlockTracker deadlockTracker = new DeadlockTracker(); + + final long pollingNyquistDelay = minimumPollingDelay / 2; + long pollingDelay = 0; // immediately updated by resetStalledEventState + long grabStackTraceAt = 0; // immediately updated by resetStalledEventState + long lastEventStartOrResumeTime = 0; // immediately updated by resetStalledEventState + + StackSample[] stackTraces = new StackSample[maxTraceCount]; + int numStacks = 0; + + ThreadMXBean jvmThreadManager = ManagementFactory.getThreadMXBean(); + boolean dumpLockedMonitors = jvmThreadManager.isObjectMonitorUsageSupported(); + boolean dumpLockedSynchronizers = jvmThreadManager.isSynchronizerUsageSupported(); + if (dumpAllThreads && jvmThreadManager.isThreadContentionMonitoringSupported()) { + jvmThreadManager.setThreadContentionMonitoringEnabled(true); + } + + // Register for events + display.asyncExec(new Runnable() { + @Override + public void run() { + registerDisplayListeners(); + } + }); + + long currTime = getTimestamp(); + + while (!cancelled.get()) { + long sleepFor; + if (resetStalledEventState) { + long eventTime = eventStartOrResumeTime; + deadlockTracker.reset(eventTime); + if (eventTime == 0) { + eventTime = currTime; + } + grabStackTraceAt = eventTime + samplingThreshold; + numStacks = 0; + pollingDelay = minimumPollingDelay; + sleepFor = pollingNyquistDelay; + resetStalledEventState = false; + } else if (lastEventStartOrResumeTime == 0) { + sleepFor = pollingNyquistDelay; + } else { + sleepFor = Math.min(pollingNyquistDelay, Math.max(1, grabStackTraceAt - currTime)); + } + + // This is the top of the polling loop. + long sleepAt = getTimestamp(); + + /* + * Check for starvation outside of sleeping. If we sleep or process much longer than expected + * (e.g. > threshold/2 longer), then the polling thread has been starved and it's very likely + * that the UI thread has been as well. Starvation freezes do not have useful information, so + * don't log them. + */ + long awakeDuration = currTime - sleepAt; + boolean starvedAwake = awakeDuration > (sleepFor + loggingThreshold / 2); + sleepForMillis(sleepFor); + currTime = getTimestamp(); + long currEventStartOrResumeTime = eventStartOrResumeTime; + long sleepDuration = currTime - sleepAt; + boolean starvedSleep = sleepDuration > (sleepFor + loggingThreshold / 2); + boolean starved = starvedSleep || starvedAwake; + + /* + * If after sleeping we see that a new event has been dispatched, mark that we should update + * the stalled event state. Otherwise, check if we have surpassed our threshold and collect a + * stack trace. + */ + if (lastEventStartOrResumeTime != currEventStartOrResumeTime || starved) { + resetStalledEventState = true; + if (localTraceLog != null && starved) { + if (starvedAwake) { + localTraceLog.trace(String.format( + "Starvation detected! Polling loop took a significant amount of threshold: %dms", //$NON-NLS-1$ + awakeDuration)); + } + + if (starvedSleep) { + localTraceLog.trace(String.format( + "Starvation detected! Expected a sleep of %dms but actually slept for %dms", //$NON-NLS-1$ + sleepFor, sleepDuration)); + } + } + } else if (lastEventStartOrResumeTime != 0) { + deadlockTracker.logPossibleDeadlock(currTime, stackTraces, numStacks); + + // Collect additional stack traces if enough time has elapsed. + if (maxTraceCount > 0 && currTime - grabStackTraceAt > 0) { + if (numStacks == maxTraceCount) { + numStacks = maxTraceCount / 2; + decimate(stackTraces, maxTraceCount, numStacks, 0); + pollingDelay *= 2; + } + + try { + ThreadInfo[] rawThreadStacks = dumpAllThreads + ? jvmThreadManager.dumpAllThreads(dumpLockedMonitors, dumpLockedSynchronizers) + : new ThreadInfo[] { + jvmThreadManager.getThreadInfo(uiThreadId, Integer.MAX_VALUE) + }; + + ThreadInfo[] threadStacks = rawThreadStacks; + // If all threads were dumped, we remove the info for the monitoring thread. + if (dumpAllThreads) { + int index = 0; + threadStacks = new ThreadInfo[rawThreadStacks.length - 1]; + + for (int i = 0; i < rawThreadStacks.length; i++) { + ThreadInfo currentThread = rawThreadStacks[i]; + + // Skip if stack trace is from the current (UI monitoring) thread. + if (!isCurrentThread(currentThread.getThreadId())) { + if (currentThread.getThreadId() == uiThreadId && i > 0) { + // Swap main thread to first slot in array if it is not already. + currentThread = threadStacks[0]; + threadStacks[0] = rawThreadStacks[i]; + } + threadStacks[index++] = currentThread; + } + } + } + + stackTraces[numStacks++] = new StackSample(getTimestamp(), threadStacks); + grabStackTraceAt += pollingDelay; + } catch (SWTException e) { + // Display is disposed so start terminating + cancelled.set(true); + resetStalledEventState = true; + } + } + } + + // If a stalled event has finished, publish it and mark that the information should be reset. + LongEventInfo eventSnapshot = publishEvent.getAndSet(null); + if (starved || eventSnapshot != null) { + if (eventSnapshot != null) { + // Trim last stack trace if it is too close to the end of the event. + int trimLast = 0; + if (numStacks - 1 > loggedTraceCount) { + long eventEnd = eventSnapshot.start + eventSnapshot.duration; + if (eventEnd - stackTraces[numStacks - 1].getTimestamp() < minimumPollingDelay) { + trimLast = 1; + } + } + + if (numStacks > loggedTraceCount) { + decimate(stackTraces, numStacks, loggedTraceCount, trimLast); + numStacks = loggedTraceCount; + } + + logEvent(new UiFreezeEvent(eventSnapshot.start, eventSnapshot.duration, stackTraces, + numStacks, false)); + } + + resetStalledEventState = true; + Arrays.fill(stackTraces, null); + } + + lastEventStartOrResumeTime = currEventStartOrResumeTime; + } + } + + private static Display getDisplay() throws IllegalArgumentException { + IWorkbench workbench = MonitoringPlugin.getDefault().getWorkbench(); + if (workbench == null) { + throw new IllegalArgumentException(Messages.EventLoopMonitorThread_workbench_was_null); + } + + Display display = workbench.getDisplay(); + if (display == null) { + throw new IllegalArgumentException(Messages.EventLoopMonitorThread_display_was_null); + } + + return display; + } + + // VisibleForTesting + protected long getTimestamp() { + return System.currentTimeMillis(); + } + + // VisibleForTesting + protected void sleepForMillis(long milliseconds) { + if (milliseconds > 0) { + try { + synchronized (sleepMonitor) { + // Spurious wake ups are OK; they will just burn a few extra CPU cycles. + sleepMonitor.wait(milliseconds); + } + } catch (InterruptedException e) { + // Wake up. + } + } + } + + private Tracer getTracer() { + return MonitoringPlugin.getTracer(); + } + + private void loadLoggerExtensions() { + externalLoggers = new ArrayList<IUiFreezeEventLogger>(); + IConfigurationElement[] configElements = + Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_ID); + + for (IConfigurationElement element : configElements) { + try { + Object object = element.createExecutableExtension("class"); //$NON-NLS-1$ + if (object instanceof IUiFreezeEventLogger) { + externalLoggers.add((IUiFreezeEventLogger) object); + } else { + MonitoringPlugin.logWarning(String.format( + Messages.EventLoopMonitorThread_invalid_logger_type_error_4, + object.getClass().getName(), IUiFreezeEventLogger.class.getClass().getSimpleName(), + EXTENSION_ID, element.getContributor().getName())); + } + } catch (CoreException e) { + MonitoringPlugin.logError(e.getMessage(), e); + } + } + } + + /** + * Returns {@code true} if given thread is the same as the current thread. + */ + private static boolean isCurrentThread(long threadId) { + return threadId == Thread.currentThread().getId(); + } + + private void registerDisplayListeners() { + display.addListener(SWT.PreEvent, EventLoopMonitorThread.this); + display.addListener(SWT.PostEvent, EventLoopMonitorThread.this); + display.addListener(SWT.Sleep, EventLoopMonitorThread.this); + display.addListener(SWT.Wakeup, EventLoopMonitorThread.this); + } + + private static void decimate(Object[] list, int fromSize, int toSize, int trimTail) { + fromSize -= trimTail; + for (int i = 1; i < toSize; ++i) { + int j = (i * fromSize + toSize / 2) / toSize; // == floor(i*(from/to)+0.5) == round(i*from/to) + list[i] = list[j]; + } + } + + private void wakeUp() { + synchronized (sleepMonitor) { + sleepMonitor.notify(); + } + } + + /** + * Writes the snapshot and stack captures to the workspace log. + */ + private void logEvent(UiFreezeEvent event) { + if (!filterHandler.shouldLogEvent(event, uiThreadId)) { + return; + } + + if (logLocally) { + defaultLogger.log(event); + } + + for (int i = 0; i < externalLoggers.size(); i++) { + IUiFreezeEventLogger currentLogger = externalLoggers.get(i); + try { + currentLogger.log(event); + } catch (Throwable t) { + externalLoggers.remove(i); + i--; + MonitoringPlugin.logError(NLS.bind( + Messages.EventLoopMonitorThread_external_exception_error_1, + currentLogger.getClass().getName()), t); + } + } + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/FilterHandler.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/FilterHandler.java new file mode 100644 index 00000000000..ec2e9e2fbc4 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/FilterHandler.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.ui.monitoring.StackSample; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.lang.management.ThreadInfo; +import java.util.Arrays; + +/** + * Checks if the {@link UiFreezeEvent} matches any defined filters. + */ +public class FilterHandler { + /** + * Groups the class name and method name defined in the filter. + */ + private class Filter implements Comparable<Filter> { + final String className; + final String methodName; + + public Filter(String className, String methodName) { + this.className = className; + this.methodName = methodName; + } + + @Override + public int compareTo(Filter other) { + int c = methodName.compareTo(other.methodName); + if (c != 0) { + return c; + } + return className.compareTo(other.className); + } + } + + private final Filter[] filters; + + public FilterHandler(String unparsedFilters) { + String[] rawFilters = unparsedFilters.split(","); //$NON-NLS-1$ + filters = new Filter[rawFilters.length]; + + for (int i = 0; i < rawFilters.length; i++) { + String currentFilter = rawFilters[i]; + int period = currentFilter.lastIndexOf('.'); + + if (period < 0) { + filters[i] = new Filter("", currentFilter); //$NON-NLS-1$ + continue; + } + + filters[i] = new Filter(currentFilter.substring(0, period), + currentFilter.substring(period + 1)); + } + + Arrays.sort(filters); + } + + /** + * Returns {@code true} if the {@link UiFreezeEvent} can be logged after checking the + * contained {@link StackSample}s against the defined filters. + */ + public boolean shouldLogEvent(UiFreezeEvent event, long displayThreadId) { + for (int i = 0; i < event.getSampleCount(); i++) { + StackSample sample = event.getStackTraceSamples()[i]; + if (hasFilteredTraces(sample.getStackTraces(), displayThreadId)) { + return false; + } + } + return true; + } + + /** + * Checks if the stack trace contains fully qualified methods that were specified to be ignored. + */ + private boolean hasFilteredTraces(ThreadInfo[] stackTraces, long displayThreadId) { + for (ThreadInfo threadInfo : stackTraces) { + if (threadInfo.getThreadId() == displayThreadId) { + return matchesFilter(threadInfo.getStackTrace()); + } + } + + MonitoringPlugin.logError(Messages.FilterHandler_missing_thread_error, null); + return false; + } + + private boolean matchesFilter(StackTraceElement[] stackTraces) { + for (StackTraceElement element : stackTraces) { + String methodName = element.getMethodName(); + String className = element.getClassName(); + // Binary search. + int low = 0; + int high = filters.length; + while (low < high) { + int mid = (low + high) >>> 1; + Filter filter = filters[mid]; + int c = methodName.compareTo(filter.methodName); + if (c == 0) { + c = className.compareTo(filter.className); + } + if (c == 0) { + return true; + } else if (c < 0) { + high = mid; + } else { + low = mid + 1; + } + } + } + return false; + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/LongEventInfo.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/LongEventInfo.java new file mode 100644 index 00000000000..379772b2d3d --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/LongEventInfo.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +/** + * Information captured about a set of events. + */ +public class LongEventInfo { + /** + * The start time of the first event, in milliseconds since 00:00 of 1 January 1970 UTC. + * + * @see System#currentTimeMillis + */ + public final long start; + + /** + * The total duration of all events, in milliseconds + */ + public final long duration; + + /** + * Constructs an event snapshot object from a contiguous range of events. + * + * @param start the start timestamp in milliseconds since 00:00 of 1 Jan 1970 + * @param duration the duration of the captured events, in milliseconds + */ + public LongEventInfo(long start, long duration) { + this.start = start; + this.duration = duration; + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.java new file mode 100644 index 00000000000..474c21530ac --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2014 Google, Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.osgi.util.NLS; + +public final class Messages extends NLS { + public static String DefaultUiFreezeEventLogger_holding_1; + public static String DefaultUiFreezeEventLogger_lock_owner_2; + public static String DefaultUiFreezeEventLogger_sample_header_2; + public static String DefaultUiFreezeEventLogger_stack_trace_header; + public static String DefaultUiFreezeEventLogger_thread_details; + public static String DefaultUiFreezeEventLogger_thread_header_2; + public static String DefaultUiFreezeEventLogger_ui_delay_header_non_running_2; + public static String DefaultUiFreezeEventLogger_ui_delay_header_running_2; + public static String DefaultUiFreezeEventLogger_waiting_for_1; + public static String EventLoopMonitorThread_capture_threshold_error_1; + public static String EventLoopMonitorThread_deadlock_error_1; + public static String EventLoopMonitorThread_display_was_null; + public static String EventLoopMonitorThread_external_exception_error_1; + public static String EventLoopMonitorThread_invalid_argument_error; + public static String EventLoopMonitorThread_invalid_parameters_error; + public static String EventLoopMonitorThread_invalid_threshold_error; + public static String EventLoopMonitorThread_logging_disabled_error; + public static String EventLoopMonitorThread_logging_threshold_error_1; + public static String EventLoopMonitorThread_max_log_count_error_1; + public static String EventLoopMonitorThread_invalid_logger_type_error_4; + public static String EventLoopMonitorThread_sample_interval_error_1; + public static String EventLoopMonitorThread_workbench_was_null; + public static String FilterHandler_missing_thread_error; + public static String MonitoringStartup_initialization_error; + + private Messages() { + // Do not instantiate. + } + + static { + NLS.initializeMessages(Messages.class.getName(), Messages.class); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.properties b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.properties new file mode 100644 index 00000000000..d9dc58b5adc --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.properties @@ -0,0 +1,35 @@ +############################################################################### +# Copyright (c) 2014 Google, Inc and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Marcus Eng (Google) - initial API and implementation +############################################################################### + +DefaultUiFreezeEventLogger_holding_1=\nHolding: {0} +DefaultUiFreezeEventLogger_lock_owner_2=Lock owner=''{0}'' tid={1} +DefaultUiFreezeEventLogger_sample_header_2=Sample at {0} (+{1}s) +DefaultUiFreezeEventLogger_stack_trace_header=UI Event Stack Trace +DefaultUiFreezeEventLogger_thread_details= tid={0} ({1}) +DefaultUiFreezeEventLogger_thread_header_2=Thread ''{0}'' {1} +DefaultUiFreezeEventLogger_ui_delay_header_non_running_2=UI Delay of {0}s at {1} +DefaultUiFreezeEventLogger_ui_delay_header_running_2=UI Delay of {0}s at {1} (running) +DefaultUiFreezeEventLogger_waiting_for_1=\nWaiting for: {0} +EventLoopMonitorThread_deadlock_error_1=\Deadlock threshold must be greater than 0. It is currently {0}. +EventLoopMonitorThread_display_was_null=Display was null. +EventLoopMonitorThread_external_exception_error_1=Exception in {0}. The logger has been disabled. +EventLoopMonitorThread_invalid_argument_error=Arguments for Event Loop Monitor are invalid: +EventLoopMonitorThread_invalid_parameters_error=Invalid parameters for event loop monitor. +EventLoopMonitorThread_invalid_threshold_error=Capture threshold must be less than log threshold. +EventLoopMonitorThread_logging_disabled_error=Event loop monitoring is enabled but logging of long-running events is disabled. +EventLoopMonitorThread_logging_threshold_error_1=Logging threshold must be greater than 0. It is currently {0}. +EventLoopMonitorThread_max_log_count_error_1=Max log trace count must be greater than 0. It is currently {0}. +EventLoopMonitorThread_invalid_logger_type_error_4={0} is not an instance of {1} in {2} extension defined by {3} plug-in. +EventLoopMonitorThread_sample_interval_error_1=Sample interval must be greater than 0. It is currently {0}. +EventLoopMonitorThread_workbench_was_null=Workbench was null. +EventLoopMonitorThread_capture_threshold_error_1=Capture threshold must be greater than 0. It is currently {0}. +FilterHandler_missing_thread_error=Did not encounter the UI Thread in stack traces. +MonitoringStartup_initialization_error=Error initializing monitoring thread. diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringPlugin.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringPlugin.java new file mode 100644 index 00000000000..b9895cc6843 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringPlugin.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ui.monitoring.PreferenceConstants; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class that controls the plug-in life cycle. + */ +public class MonitoringPlugin extends AbstractUIPlugin { + private static MonitoringPlugin plugin; + private static final String TRACE_EVENT_MONITOR = "/debug/event_monitor"; //$NON-NLS-1$ + private static final String TRACE_PREFIX = "Event Loop Monitor"; //$NON-NLS-1$ + private static Tracer tracer; + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + tracer = Tracer.create(TRACE_PREFIX, PreferenceConstants.PLUGIN_ID + TRACE_EVENT_MONITOR); + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + public static MonitoringPlugin getDefault() { + return plugin; + } + + public static Tracer getTracer() { + return tracer; + } + + public static void logError(String message, Throwable e) { + log(new Status(IStatus.ERROR, PreferenceConstants.PLUGIN_ID, message, e)); + } + + public static void logWarning(String message) { + log(new Status(IStatus.WARNING, PreferenceConstants.PLUGIN_ID, message)); + } + + private static void log(IStatus status) { + plugin.getLog().log(status); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringStartup.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringStartup.java new file mode 100644 index 00000000000..b94ce0cd699 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringStartup.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.internal.monitoring.preferences.MonitoringPreferenceListener; +import org.eclipse.ui.monitoring.PreferenceConstants; + +/** + * Starts the event loop monitoring thread. Initializes preferences from {@link IPreferenceStore}. + */ +public class MonitoringStartup implements IStartup { + private EventLoopMonitorThread monitoringThread; + + @Override + public void earlyStartup() { + setupPlugin(); + } + + private void setupPlugin() { + if (monitoringThread != null) { + return; + } + + IPreferenceStore preferences = MonitoringPlugin.getDefault().getPreferenceStore(); + if (preferences.getBoolean(PreferenceConstants.MONITORING_ENABLED)) { + monitoringThread = createAndStartMonitorThread(); + } + + preferences.addPropertyChangeListener(new MonitoringPreferenceListener(monitoringThread)); + } + + /** + * Creates and starts a new monitoring thread. + */ + public static EventLoopMonitorThread createAndStartMonitorThread() { + EventLoopMonitorThread.Parameters args = loadPreferences(); + EventLoopMonitorThread temporaryThread = null; + + try { + temporaryThread = new EventLoopMonitorThread(args); + } catch (IllegalArgumentException e) { + MonitoringPlugin.logError(Messages.MonitoringStartup_initialization_error, e); + return null; + } + + final EventLoopMonitorThread thread = temporaryThread; + final Display display = MonitoringPlugin.getDefault().getWorkbench().getDisplay(); + // Final setup and start synced on display thread + display.asyncExec(new Runnable() { + @Override + public void run() { + // If we're still running when display gets disposed, shutdown the thread. + display.disposeExec(new Runnable() { + @Override + public void run() { + thread.shutdown(); + } + }); + thread.start(); + } + }); + + return thread; + } + + private static EventLoopMonitorThread.Parameters loadPreferences() { + IPreferenceStore preferences = MonitoringPlugin.getDefault().getPreferenceStore(); + EventLoopMonitorThread.Parameters args = new EventLoopMonitorThread.Parameters(); + + args.loggingThreshold = preferences.getInt(PreferenceConstants.MAX_EVENT_LOG_TIME_MILLIS); + args.samplingThreshold = preferences.getInt(PreferenceConstants.MAX_EVENT_SAMPLE_TIME_MILLIS); + args.dumpAllThreads = preferences.getBoolean(PreferenceConstants.DUMP_ALL_THREADS); + args.minimumPollingDelay = preferences.getInt(PreferenceConstants.SAMPLE_INTERVAL_TIME_MILLIS); + args.loggedTraceCount = preferences.getInt(PreferenceConstants.MAX_LOG_TRACE_COUNT); + args.deadlockDelta = preferences.getInt(PreferenceConstants.FORCE_DEADLOCK_LOG_TIME_MILLIS); + args.logLocally = preferences.getBoolean(PreferenceConstants.LOG_TO_ERROR_LOG); + args.filterTraces = preferences.getString(PreferenceConstants.FILTER_TRACES); + + return args; + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Tracer.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Tracer.java new file mode 100644 index 00000000000..078d6898e12 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Tracer.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Terry Parker (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.core.runtime.Platform; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Simple helper class for Eclipse debug tracing. + * + * @see <a href= "http://wiki.eclipse.org/FAQ_How_do_I_use_the_platform_debug_tracing_facility%3F" + * >Eclipse Wiki: FAQ How do I use the platform debug tracing facility?</a> + */ +public class Tracer { + private static final Calendar localChronology = Calendar.getInstance(); + private static final SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$ + private final String prefix; + private final PrintStream out = System.out; + + private static String getTimestamp() { + return timeFormatter.format(new Date(localChronology.getTimeInMillis())); + } + + /** + * Returns {@code true} if the debug option is set, but only if the platform is running in debug + * mode (e.g., not in a unit test.) + */ + public static boolean isTracingEnabled(String debugOption) { + return Platform.isRunning() && Boolean.parseBoolean(Platform.getDebugOption(debugOption)); + } + + /** + * Returns a tracer object if the given debug option is enabled, or {@code null} if it is not. + */ + public static Tracer create(String prefix, String debugOption) { + if (isTracingEnabled(debugOption)) { + return new Tracer(prefix); + } + return null; + } + + /** + * Creates a tracer object that assists in debug tracing. + * + * @param prefix a string to be prefixed to every trace line (may be {@code null}) + */ + protected Tracer(String prefix) { + this.prefix = prefix; + } + + /** + * Prints out the given Object. + */ + public void trace(Object o) { + out.printf("%s %s: %s\n", getTimestamp(), prefix, o); //$NON-NLS-1$ + } + + /** + * Prints out the given message and object. + */ + public void trace(String msg, Object... params) { + trace(String.format(msg, params)); + } + + /** + * Prints the stack trace of a given {@code Throwable} object + * + * @param t a {@code Throwable} that cannot be null. + */ + public void traceStackTrace(Throwable t) { + StringWriter writer = new StringWriter(); + PrintWriter printer = new PrintWriter(writer); + t.printStackTrace(printer); + printer.flush(); + trace(writer.toString()); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/FilterInputDialog.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/FilterInputDialog.java new file mode 100644 index 00000000000..ac7f2ebf344 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/FilterInputDialog.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * A dialog box used to input new stack traces to filter out. + */ +public class FilterInputDialog extends TitleAreaDialog { + private Text textFilter; + private String filter; + + public FilterInputDialog(Shell parentShell) { + super(parentShell); + this.create(); + } + + @Override + public void create() { + super.create(); + setTitle(Messages.FilterInputDialog_filter_input_header); + setMessage(Messages.FilterInputDialog_filter_input_message, + IMessageProvider.INFORMATION); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite) super.createDialogArea(parent); + Composite container = new Composite(area, SWT.NONE); + container.setLayoutData(new GridData(GridData.FILL_BOTH)); + GridLayout layout = new GridLayout(2, false); + container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + container.setLayout(layout); + + createFilterText(container); + + return area; + } + + private void createFilterText(Composite container) { + Label filterLabel = new Label(container, SWT.NONE); + filterLabel.setText(Messages.FilterInputDialog_filter_input_label); + + GridData dataFilterInput = new GridData(); + dataFilterInput.grabExcessHorizontalSpace = true; + dataFilterInput.horizontalAlignment = GridData.FILL; + + textFilter = new Text(container, SWT.BORDER); + textFilter.setLayoutData(dataFilterInput); + } + + @Override + protected boolean isResizable() { + return false; + } + + private void saveInput() { + filter = textFilter.getText(); + } + + @Override + protected void okPressed() { + saveInput(); + textFilter.clearSelection(); + super.okPressed(); + } + + /** + * Returns a {@code String} of the user-defined filter. + */ + public String getInput() { + return filter; + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/ListFieldEditor.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/ListFieldEditor.java new file mode 100644 index 00000000000..307cc053ec8 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/ListFieldEditor.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.jface.preference.ListEditor; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.internal.monitoring.MonitoringPlugin; +import org.eclipse.ui.monitoring.PreferenceConstants; + +/** + * Displays the list of traces to filter out and ignore. + */ +public class ListFieldEditor extends ListEditor { + private FilterInputDialog dialog; + + ListFieldEditor(String name, String labelText, Composite parent) { + super(name, labelText, parent); + super.getAddButton().setText(Messages.ListFieldEditor_add_filter_button_label); + super.getUpButton().setVisible(false); + super.getDownButton().setVisible(false); + dialog = new FilterInputDialog(super.getShell()); + this.setEnabled(MonitoringPlugin.getDefault().getPreferenceStore() + .getBoolean(PreferenceConstants.MONITORING_ENABLED), parent); + } + + /** + * Handles the parsing of defined traces to be filtered. + */ + @Override + protected String createList(String[] items) { + StringBuilder mergedItems = new StringBuilder(); + + for (String item : items) { + item.trim(); + if (mergedItems.length() != 0) { + mergedItems.append(','); + } + mergedItems.append(item); + } + + return mergedItems.toString(); + } + + @Override + protected String getNewInputObject() { + dialog.open(); + String input = dialog.getInput(); + if (input != null && !input.isEmpty() && !input.contains(",")) { //$NON-NLS-1$ + input.trim(); + return dialog.getInput(); + } + return null; + } + + @Override + protected String[] parseString(String stringList) { + return stringList.split(","); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.java new file mode 100644 index 00000000000..75c1f5f656e --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2014 Google, Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.osgi.util.NLS; + +public final class Messages extends NLS { + public static String FilterInputDialog_filter_input_header; + public static String FilterInputDialog_filter_input_message; + public static String FilterInputDialog_filter_input_label; + public static String ListFieldEditor_add_filter_button_label; + public static String MonitoringPreferenceListener_preference_error_header; + public static String MonitoringPreferenceListener_preference_error; + public static String MonitoringPreferencePage_capture_threshold_error; + public static String MonitoringPreferencePage_deadlock_label; + public static String MonitoringPreferencePage_dump_all_threads_label; + public static String MonitoringPreferencePage_enable_thread_label; + public static String MonitoringPreferencePage_event_log_label; + public static String MonitoringPreferencePage_filter_label; + public static String MonitoringPreferencePage_first_stack_label; + public static String MonitoringPreferencePage_invalid_number_error; + public static String MonitoringPreferencePage_log_freeze_events_label; + public static String MonitoringPreferencePage_log_threshold_error; + public static String MonitoringPreferencePage_sample_interval_label; + public static String MonitoringPreferencePage_stack_sample_label; + + private Messages() { + // Do not instantiate. + } + + static { + NLS.initializeMessages(Messages.class.getName(), Messages.class); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.properties b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.properties new file mode 100644 index 00000000000..03553386d36 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.properties @@ -0,0 +1,29 @@ +############################################################################### +# Copyright (c) 2014 Google, Inc and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Marcus Eng (Google) - initial API and implementation +############################################################################### + +FilterInputDialog_filter_input_message=Enter the fully qualified method name of a stack trace to filter out. +FilterInputDialog_filter_input_label=Stack Frame to Ignore: +FilterInputDialog_filter_input_header=New Stack Trace Filter +ListFieldEditor_add_filter_button_label=Add &Filter +MonitoringPreferenceListener_preference_error_header=Invalid Preferences +MonitoringPreferenceListener_preference_error=The specified preferences could not be updated. See error log for details. +MonitoringPreferencePage_capture_threshold_error=Capture threshold must be less than log threshold. +MonitoringPreferencePage_deadlock_label=Maximum time to wait in deadl&ock before log (ms): +MonitoringPreferencePage_dump_all_threads_label=Dump stacks of all t&hreads +MonitoringPreferencePage_enable_thread_label=&Enable event loop monitoring thread +MonitoringPreferencePage_event_log_label=Long event &threshold (ms): +MonitoringPreferencePage_filter_label=Stack Tra&ces to Filter Out: +MonitoringPreferencePage_first_stack_label=Maximum time to wait in event &before first stack sample (ms): +MonitoringPreferencePage_invalid_number_error=Value must be an integer greater than 0. +MonitoringPreferencePage_log_freeze_events_label=&Log freeze events to Eclipse error log +MonitoringPreferencePage_log_threshold_error=Log threshold must be greater or equal to capture threshold. +MonitoringPreferencePage_sample_interval_label=Stack sample &interval (ms): +MonitoringPreferencePage_stack_sample_label=Maximum stack &samples logged: diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceInitializer.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceInitializer.java new file mode 100644 index 00000000000..6ae9cae7e42 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceInitializer.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.ui.internal.monitoring.MonitoringPlugin; +import org.eclipse.ui.monitoring.PreferenceConstants; + +/** + * Initializes the default values for monitoring plug-in preferences. + */ +public class MonitoringPreferenceInitializer extends AbstractPreferenceInitializer { + /** Force a logged event for a possible deadlock when an event hangs for longer than this */ + private static final int DEFAULT_FORCE_DEADLOCK_LOG_TIME_MILLIS = 10 * 60 * 1000; // == 10 minutes + private static final String DEFAULT_FILTER_TRACES = + "org.eclipse.swt.internal.gtk.OS.gtk_dialog_run," //$NON-NLS-1$ + + "org.eclipse.e4.ui.workbench.addons.dndaddon.DnDManager.startDrag"; //$NON-NLS-1$ + + @Override + public void initializeDefaultPreferences() { + IPreferenceStore store = MonitoringPlugin.getDefault().getPreferenceStore(); + + store.setDefault(PreferenceConstants.MONITORING_ENABLED, false); + store.setDefault(PreferenceConstants.FORCE_DEADLOCK_LOG_TIME_MILLIS, + DEFAULT_FORCE_DEADLOCK_LOG_TIME_MILLIS); + store.setDefault(PreferenceConstants.MAX_LOG_TRACE_COUNT, 3); + store.setDefault(PreferenceConstants.MAX_EVENT_LOG_TIME_MILLIS, 500); + store.setDefault(PreferenceConstants.MAX_EVENT_SAMPLE_TIME_MILLIS, 500); + store.setDefault(PreferenceConstants.SAMPLE_INTERVAL_TIME_MILLIS, 300); + store.setDefault(PreferenceConstants.DUMP_ALL_THREADS, false); + store.setDefault(PreferenceConstants.LOG_TO_ERROR_LOG, true); + store.setDefault(PreferenceConstants.FILTER_TRACES, DEFAULT_FILTER_TRACES); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceListener.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceListener.java new file mode 100644 index 00000000000..1eef0a3b331 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceListener.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.internal.monitoring.EventLoopMonitorThread; +import org.eclipse.ui.internal.monitoring.MonitoringPlugin; +import org.eclipse.ui.internal.monitoring.MonitoringStartup; +import org.eclipse.ui.monitoring.PreferenceConstants; + +/** + * Listens to preference changes and restarts the monitoring thread when necessary. + */ +public class MonitoringPreferenceListener implements IPropertyChangeListener { + private EventLoopMonitorThread monitoringThread; + /** + * A flag to handle the resetting of the {@link EventLoopMonitorThread}. The method + * {@link #refreshMonitoringThread()} can be called multiple times if multiple preferences are + * changed via the preference page. {@code monitorThreadRestartInProgress} is set on the first + * call to {@link #refreshMonitoringThread()}. Subsequent calls to restartMonitorThread do not + * schedule more resets while the flag is enabled. Once the scheduled asyncExec event executes, + * the flag is reset. + */ + private boolean monitorThreadRestartInProgress; + + public MonitoringPreferenceListener(EventLoopMonitorThread thread) { + monitoringThread = thread; + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + String property = event.getProperty(); + if (!property.equals(PreferenceConstants.MONITORING_ENABLED) + && !property.equals(PreferenceConstants.FORCE_DEADLOCK_LOG_TIME_MILLIS) + && !property.equals(PreferenceConstants.MAX_LOG_TRACE_COUNT) + && !property.equals(PreferenceConstants.MAX_EVENT_LOG_TIME_MILLIS) + && !property.equals(PreferenceConstants.MAX_EVENT_SAMPLE_TIME_MILLIS) + && !property.equals(PreferenceConstants.SAMPLE_INTERVAL_TIME_MILLIS) + && !property.equals(PreferenceConstants.DUMP_ALL_THREADS) + && !property.equals(PreferenceConstants.LOG_TO_ERROR_LOG) + && !property.equals(PreferenceConstants.FILTER_TRACES)) { + return; + } + + synchronized (this) { + if (monitorThreadRestartInProgress) { + return; + } + + monitorThreadRestartInProgress = true; + + final Display display = MonitoringPlugin.getDefault().getWorkbench().getDisplay(); + // Schedule the event to restart the thread after all preferences have had enough time + // to propagate. + display.asyncExec(new Runnable() { + @Override + public void run() { + refreshMonitoringThread(); + } + }); + } + } + + private synchronized void refreshMonitoringThread() { + if (monitoringThread != null) { + monitoringThread.shutdown(); + monitoringThread = null; + } + monitorThreadRestartInProgress = false; + + MonitoringPlugin plugin = MonitoringPlugin.getDefault(); + IPreferenceStore preferences = plugin.getPreferenceStore(); + if (preferences.getBoolean(PreferenceConstants.MONITORING_ENABLED)) { + EventLoopMonitorThread thread = MonitoringStartup.createAndStartMonitorThread(); + // If thread is null, the newly-defined preferences are invalid. + if (thread == null) { + MessageDialog.openError( + plugin.getWorkbench().getActiveWorkbenchWindow().getShell(), + Messages.MonitoringPreferenceListener_preference_error_header, + Messages.MonitoringPreferenceListener_preference_error); + return; + } + + monitoringThread = thread; + } + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferencePage.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferencePage.java new file mode 100644 index 00000000000..6b1bcd08450 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferencePage.java @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring.preferences; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.ui.internal.monitoring.MonitoringPlugin; +import org.eclipse.ui.monitoring.PreferenceConstants; + +import java.util.HashMap; +import java.util.Map; + +/** + * Preference page that allows user to toggle plug in settings from Eclipse preferences. + */ +public class MonitoringPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + private static final IPreferenceStore preferences = MonitoringPlugin.getDefault().getPreferenceStore(); + private boolean pluginEnabled = preferences.getBoolean(PreferenceConstants.MONITORING_ENABLED); + private Map<FieldEditor, Composite> editors; + + /** + * Checks that the capture threshold is less than the log threshold. + */ + private class LogThresholdFieldEditor extends IntegerFieldEditor { + IntegerFieldEditor maxEventSampleTime; + + public LogThresholdFieldEditor(String name, String textLabel, Composite parent) { + super(name, textLabel, parent); + this.setupField(parent); + this.fillIntoGrid(parent, 2); + this.setEnabled(pluginEnabled, parent); + } + + @Override + protected boolean checkState() { + try { + if (maxEventSampleTime.getIntValue() > this.getIntValue()) { + showErrorMessage(); + return false; + } + } catch (NumberFormatException e) { + // With a number exception, fall through and allow the parent class handle state + } + return super.checkState(); + } + + public void setSampleFieldEditor(IntegerFieldEditor field) { + this.maxEventSampleTime = field; + } + + private void setupField(Composite parent) { + super.setValidRange(1, Integer.MAX_VALUE); + super.setErrorMessage(Messages.MonitoringPreferencePage_log_threshold_error); + addField(this, parent); + } + } + + public MonitoringPreferencePage() { + super(GRID); + editors = new HashMap<FieldEditor, Composite>(); + } + + @Override + public void createFieldEditors() { + Composite parent = getFieldEditorParent(); + final GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + parent.setLayout(layout); + + Composite groupContainer = new Composite(parent, SWT.NONE); + GridLayout groupLayout = new GridLayout(1, false); + groupLayout.marginWidth = 0; + groupLayout.marginHeight = 0; + groupContainer.setLayout(groupLayout); + groupContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Composite topGroup = new Composite(groupContainer, SWT.NONE); + GridLayout innerGroupLayout = new GridLayout(2, false); + innerGroupLayout.marginWidth = 0; + innerGroupLayout.marginHeight = 0; + topGroup.setLayout(innerGroupLayout); + topGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + createBooleanFieldEditor(PreferenceConstants.MONITORING_ENABLED, + Messages.MonitoringPreferencePage_enable_thread_label, topGroup); + + final LogThresholdFieldEditor maxEventLogTime = new LogThresholdFieldEditor( + PreferenceConstants.MAX_EVENT_LOG_TIME_MILLIS, + Messages.MonitoringPreferencePage_event_log_label, topGroup); + createIntegerFieldEditor(PreferenceConstants.MAX_LOG_TRACE_COUNT, + Messages.MonitoringPreferencePage_stack_sample_label, topGroup); + createIntegerFieldEditor(PreferenceConstants.SAMPLE_INTERVAL_TIME_MILLIS, + Messages.MonitoringPreferencePage_sample_interval_label, topGroup); + topGroup.setLayout(innerGroupLayout); + + IntegerFieldEditor maxEventSampleTime = new IntegerFieldEditor( + PreferenceConstants.MAX_EVENT_SAMPLE_TIME_MILLIS, + Messages.MonitoringPreferencePage_first_stack_label, topGroup) { + @Override + protected boolean checkState() { + try { + if (maxEventLogTime.getIntValue() < this.getIntValue()) { + showErrorMessage(); + return false; + } + } catch (NumberFormatException e) { + // With a number exception, fall through and allow the parent class handle state. + } + return super.checkState(); + } + }; + + maxEventSampleTime.setValidRange(1, Integer.MAX_VALUE); + maxEventSampleTime.setErrorMessage(Messages.MonitoringPreferencePage_capture_threshold_error); + maxEventLogTime.setSampleFieldEditor(maxEventSampleTime); + maxEventSampleTime.fillIntoGrid(topGroup, 2); + addField(maxEventSampleTime, topGroup); + maxEventSampleTime.setEnabled(pluginEnabled, topGroup); + + createIntegerFieldEditor(PreferenceConstants.FORCE_DEADLOCK_LOG_TIME_MILLIS, + Messages.MonitoringPreferencePage_deadlock_label, topGroup); + + createBooleanFieldEditor(PreferenceConstants.DUMP_ALL_THREADS, + Messages.MonitoringPreferencePage_dump_all_threads_label, topGroup); + topGroup.setLayout(innerGroupLayout); + + createBooleanFieldEditor(PreferenceConstants.LOG_TO_ERROR_LOG, + Messages.MonitoringPreferencePage_log_freeze_events_label, topGroup); + topGroup.setLayout(innerGroupLayout); + + final Composite bottomGroup = new Composite(groupContainer, SWT.NONE); + bottomGroup.setLayout(innerGroupLayout); + bottomGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addField(new ListFieldEditor(PreferenceConstants.FILTER_TRACES, + Messages.MonitoringPreferencePage_filter_label, bottomGroup), bottomGroup); + } + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(preferences); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + super.propertyChange(event); + Object object = event.getSource(); + if (object instanceof BooleanFieldEditor + && ((BooleanFieldEditor) object).getPreferenceName().equals(PreferenceConstants.MONITORING_ENABLED)) { + for (Map.Entry<FieldEditor, Composite> editor : editors.entrySet()) { + editor.getKey().setEnabled(event.getNewValue().equals(true), + editor.getValue()); + } + } + } + + private void addField(FieldEditor editor, Composite parent) { + super.addField(editor); + + if (!editor.getPreferenceName().equals(PreferenceConstants.MONITORING_ENABLED)) { + editors.put(editor, parent); + } + } + + private void createBooleanFieldEditor(String name, String labelText, Composite parent) { + BooleanFieldEditor field = new BooleanFieldEditor(name, labelText, parent); + field.fillIntoGrid(parent, 2); + addField(field, parent); + if (!name.equals(PreferenceConstants.MONITORING_ENABLED)) { + field.setEnabled(pluginEnabled, parent); + } + } + + private void createIntegerFieldEditor(String name, String labelText, Composite parent) { + IntegerFieldEditor field = new IntegerFieldEditor(name, labelText, parent); + field.setValidRange(1, Integer.MAX_VALUE); + field.setErrorMessage(Messages.MonitoringPreferencePage_invalid_number_error); + field.fillIntoGrid(parent, 2); + addField(field, parent); + field.setEnabled(pluginEnabled, parent); + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/IUiFreezeEventLogger.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/IUiFreezeEventLogger.java new file mode 100644 index 00000000000..e7020cf8941 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/IUiFreezeEventLogger.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.monitoring; + +import org.eclipse.ui.internal.monitoring.EventLoopMonitorThread; + +/** + * All classes logging {@link UiFreezeEvent}s have to implement this interface. + * + * @since 1.0 + */ +public interface IUiFreezeEventLogger { + /** + * Invoked from the {@link EventLoopMonitorThread} whenever a {@link UiFreezeEvent} is ready to + * be logged. Implementations of this function must end quickly or else it will impact system + * performance. All time-consuming tasks should be executed asynchronously. + */ + void log(UiFreezeEvent event); +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/PreferenceConstants.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/PreferenceConstants.java new file mode 100644 index 00000000000..f51937d0f4f --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/PreferenceConstants.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.monitoring; + +/** + * Definitions of the preference constants. + * + * @since 1.0 + */ +public class PreferenceConstants { + public static final String PLUGIN_ID = "org.eclipse.ui.monitoring"; //$NON-NLS-1$ + /** + * If true, enables the monitoring thread which logs when the user sees a Blocked Jobs dialog or + * when the UI thread becomes unresponsive. + */ + public static final String MONITORING_ENABLED = "monitoring_enabled"; //$NON-NLS-1$ + /** Sample interval to capture the traces of an unresponsive event. */ + public static final String FORCE_DEADLOCK_LOG_TIME_MILLIS = "force_deadlock_log"; //$NON-NLS-1$ + /** Maximum number of traces to write out to the log. */ + public static final String MAX_LOG_TRACE_COUNT = "max_log_trace_count"; //$NON-NLS-1$ + /** Log events that took longer than the specified duration in milliseconds. */ + public static final String MAX_EVENT_LOG_TIME_MILLIS = "max_event_log_time"; //$NON-NLS-1$ + /** + * Start capturing traces if an event takes longer than the specified duration in + * milliseconds. + */ + public static final String MAX_EVENT_SAMPLE_TIME_MILLIS = "max_event_sample_time"; //$NON-NLS-1$ + /** Sample collection interval to capture the traces of an unresponsive event. */ + public static final String SAMPLE_INTERVAL_TIME_MILLIS = "sample_interval"; //$NON-NLS-1$ + /** + * If true, includes call stacks of all threads into the logged message. Otherwise, only the stack + * of the main thread is included. Disabled by default due to additional performance overhead. + */ + public static final String DUMP_ALL_THREADS = "dump_all_threads"; //$NON-NLS-1$ + /** + * If true, log freeze events to the Eclipse error log. + */ + public static final String LOG_TO_ERROR_LOG = "log_to_error_log"; //$NON-NLS-1$ + /** Stack traces to filter out. Any event with a filter matching any sample will be ignored. */ + public static final String FILTER_TRACES = "filter_traces"; //$NON-NLS-1$ + + private PreferenceConstants() {} +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/StackSample.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/StackSample.java new file mode 100644 index 00000000000..590b313ff28 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/StackSample.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.monitoring; + +import java.lang.management.ThreadInfo; + +/** + * A sample of the stack that contains the stack traces and the time stamp. + * + * @since 1.0 + */ +public class StackSample { + private final long timestamp; + private final ThreadInfo[] traces; + + public StackSample(long timestamp, ThreadInfo[] traces) { + this.timestamp = timestamp; + this.traces = traces; + } + + /** + * Returns the time stamp for this {@code StackSample}. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Returns an array of {@code ThreadInfo} for this {@code StackSample}. The display thread is + * always the first in the array. + */ + public ThreadInfo[] getStackTraces() { + return traces; + } +} diff --git a/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/UiFreezeEvent.java b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/UiFreezeEvent.java new file mode 100644 index 00000000000..fdb42eb9373 --- /dev/null +++ b/bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/UiFreezeEvent.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.monitoring; + +/** + * Responsible for holding the stack traces for a UI event. + * + * @since 1.0 + */ +public class UiFreezeEvent { + private final long startTimestamp; + private final long totalDuration; + private final StackSample[] stackTraceSamples; + private final int numSamples; + private final boolean isRunning; + + public UiFreezeEvent(long startTime, long totalTime, StackSample[] samples, int sampleCount, + boolean stillRunning) { + this.startTimestamp = startTime; + this.stackTraceSamples = samples; + this.numSamples = sampleCount; + this.totalDuration = totalTime; + this.isRunning = stillRunning; + } + + /** + * Returns the time when the UI thread froze. + */ + public long getStartTimestamp() { + return startTimestamp; + } + + /** + * Returns the total amount of time the UI thread remained frozen. + */ + public long getTotalDuration() { + return totalDuration; + } + + /** + * Returns a list of stack trace samples obtained during the event. + */ + public StackSample[] getStackTraceSamples() { + return stackTraceSamples; + } + + /** + * Returns the number stack traces obtained when the UI thread was frozen. + */ + public int getSampleCount() { + return numSamples; + } + + /** + * Returns {@code true} if this event is still running. + */ + public boolean isStillRunning() { + return isRunning; + } +} @@ -103,6 +103,7 @@ <module>bundles/org.eclipse.ui.views</module> <module>bundles/org.eclipse.ui.views.properties.tabbed</module> <module>bundles/org.eclipse.ui.workbench</module> + <module>bundles/org.eclipse.ui.monitoring</module> <module>bundles/org.eclipse.ui.navigator</module> <module>bundles/org.eclipse.ui.themes</module> <module>features/org.eclipse.e4.rcp</module> diff --git a/tests/org.eclipse.ui.monitoring.tests/.classpath b/tests/org.eclipse.ui.monitoring.tests/.classpath new file mode 100644 index 00000000000..ad32c83a788 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/tests/org.eclipse.ui.monitoring.tests/.project b/tests/org.eclipse.ui.monitoring.tests/.project new file mode 100644 index 00000000000..f18e3e2da84 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.ui.monitoring.tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/tests/org.eclipse.ui.monitoring.tests/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.ui.monitoring.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..5e0fd61c269 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/tests/org.eclipse.ui.monitoring.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.monitoring.tests/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..39229b5bf10 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Google Eclipse Monitoring Tests +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-SymbolicName: org.eclipse.ui.monitoring.tests;singleton:=true +Bundle-Vendor: Google Inc. +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.ui.monitoring;bundle-version="1.0.0" +Require-Bundle: org.eclipse.jface;bundle-version="[3.10.0,4.0.0)", + org.eclipse.ui.workbench;bundle-version="[3.106.0,4.0.0)", + org.junit;bundle-version="[4.11.0,5.0.0)" diff --git a/tests/org.eclipse.ui.monitoring.tests/README.md b/tests/org.eclipse.ui.monitoring.tests/README.md new file mode 100644 index 00000000000..a02badc6192 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/README.md @@ -0,0 +1,27 @@ +org.eclipse.ui.tests +==================== + +Contains the tests org.eclipse.ui.monitoring plugin. + +For more information, refer to the [Platform UI wiki page] [1]. + + +Running the tests +----------------- + +Use the following command to run the tests via Maven: +mvn clean verify -Pbuild-individual-bundles -Dmaven.test.skip=false + + +See [https://wiki.eclipse.org/Platform_UI/How_to_Contribute#Unit_Testing][2] for more information. + +License +------- + +[Eclipse Public License (EPL) v1.0][3] + + + +[1]: http://wiki.eclipse.org/Platform_UI +[2]: https://wiki.eclipse.org/Platform_UI/How_to_Contribute#Unit_Testing +[3]: http://wiki.eclipse.org/EPL diff --git a/tests/org.eclipse.ui.monitoring.tests/build.properties b/tests/org.eclipse.ui.monitoring.tests/build.properties new file mode 100644 index 00000000000..93375cd0df9 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/build.properties @@ -0,0 +1,18 @@ +############################################################################### +# Copyright (C) 2014, Google Inc. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Steve Foreman (Google) - initial API and implementation +# Marcus Eng (Google) +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + fragment.xml +jre.compilation.profile = JavaSE-1.6 diff --git a/tests/org.eclipse.ui.monitoring.tests/fragment.xml b/tests/org.eclipse.ui.monitoring.tests/fragment.xml new file mode 100644 index 00000000000..eaf18d5d916 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/fragment.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="4.4"?> +<!-- + Copyright (C) 2014, Google Inc and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Public License v1.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/legal/epl-v10.html + + Contributors: + Steve Foreman (Google) - initial API and implementation + Marcus Eng (Google) +--> +<fragment> + <extension point="org.eclipse.ui.commands"> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.delay" + name="Delay" + defaultHandler="org.eclipse.ui.internal.monitoring.DelayHandler"> + <commandParameter + id="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" + name="Synchronous" + optional="false" /> + <commandParameter + id="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" + name="Duration" + optional="false" /> + </command> + </extension> + + <extension point="org.eclipse.ui.menus"> + <menuContribution + locationURI="menu:org.eclipse.ui.main.menu?after=additions"> + <menu label="Delays" id="org.eclipse.ui.monitoring.manualtesting.menus.delayMenu"> + <menu label="Sync" id="org.eclipse.ui.monitoring.manualtesting.menus.delayMenu.sync"> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay1" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="1" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay10" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 10ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="10" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay100" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 100ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="100" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay1000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="1000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay5000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 5s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="5000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay30000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 30s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="30000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay60000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="60000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay180000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 3m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="180000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.syncDelay300000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 5m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="true" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="300000" /> + </command> + </menu> + <menu label="Async" id="org.eclipse.ui.monitoring.manualtesting.menus.delayMenu.async"> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay1" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="1" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay10" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 10ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="10" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay100" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 100ms"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="100" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay1000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="1000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay5000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 5s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="5000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay30000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 30s"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="30000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay60000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 1m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="60000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay180000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 3m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="180000" /> + </command> + <command + id="org.eclipse.ui.monitoring.manualtesting.commands.asyncDelay3000000" + commandId="org.eclipse.ui.monitoring.manualtesting.commands.delay" + label="Delay 5m"> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.sync" value="false" /> + <parameter name="org.eclipse.ui.monitoring.manualtesting.commands.delay.duration" value="300000" /> + </command> + </menu> + </menu> + </menuContribution> + </extension> + <extension + point="org.eclipse.ui.monitoring.logger"> + <logger + class="org.eclipse.ui.internal.monitoring.MockUiFreezeEventLogger"> + </logger> + </extension> +</fragment> diff --git a/tests/org.eclipse.ui.monitoring.tests/pom.xml b/tests/org.eclipse.ui.monitoring.tests/pom.xml new file mode 100644 index 00000000000..9ffc56c2b5a --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/pom.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2014, Google Inc and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Public License v1.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/legal/epl-v10.html + + Contributors: + Marcus Eng (Google) - initial API and implementation +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>eclipse.platform.ui.tests</artifactId> + <groupId>eclipse.platform.ui</groupId> + <version>4.5.0-SNAPSHOT</version> + </parent> + <groupId>org.eclipse.ui</groupId> + <artifactId>org.eclipse.ui.monitoring.tests</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>eclipse-test-plugin</packaging> + + <properties> + <testSuiteMonitoring>**/org/eclipse/ui/internal/monitoring/MonitoringTestSuite.java</testSuiteMonitoring> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.eclipse.tycho</groupId> + <artifactId>tycho-surefire-plugin</artifactId> + <version>${tycho.version}</version> + <configuration> + <includes> + <include>${testSuiteMonitoring}</include> + </includes> + <useUIHarness>true</useUIHarness> + <useUIThread>true</useUIThread> + <dependencies> + <dependency> + <type>eclipse-plugin</type> + <artifactId>org.eclipse.ui.monitoring</artifactId> + <version>0.0.0</version> + </dependency> + </dependencies> + </configuration> + </plugin> + </plugins> + </build> +</project>
\ No newline at end of file diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DefaultLoggerTest.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DefaultLoggerTest.java new file mode 100644 index 00000000000..230c5e13616 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DefaultLoggerTest.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import junit.framework.TestCase; + +import org.eclipse.core.runtime.ILogListener; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.ui.monitoring.PreferenceConstants; +import org.eclipse.ui.monitoring.StackSample; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * JUnit test for the {@link DefaultUiFreezeEventLogger}. + */ +public class DefaultLoggerTest extends TestCase { + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); + private static final String RUNTIME_ID = "org.eclipse.core.runtime"; + private static final long TIME = 120000000; + private static final long DURATION = 500; + private DefaultUiFreezeEventLogger logger; + private ThreadInfo thread; + private IStatus loggedStatus; + + @Override + public void setUp() { + logger = new DefaultUiFreezeEventLogger(); + createLogListener(); + } + + private void createLogListener() { + Platform.addLogListener(new ILogListener() { + @Override + public void logging(IStatus status, String plugin) { + if (plugin.equals(RUNTIME_ID)) { + loggedStatus = status; + } + } + }); + } + + private UiFreezeEvent createFreezeEvent() { + ThreadMXBean jvmThreadManager = ManagementFactory.getThreadMXBean(); + thread = jvmThreadManager.getThreadInfo(Thread.currentThread().getId(), Integer.MAX_VALUE); + + StackSample[] samples = { new StackSample(TIME, new ThreadInfo[] { thread }) }; + UiFreezeEvent event = new UiFreezeEvent(TIME, DURATION, samples, 1, false); + return event; + } + + public void testLogEvent() throws Exception { + UiFreezeEvent event = createFreezeEvent(); + String expectedTime = dateFormat.format(new Date(TIME)); + String expectedHeader = + String.format("UI Delay of %.2fs at %s", DURATION / 1000.0, expectedTime); + String expectedEventMessage = String.format("Sample at %s (+%.3fs)", expectedTime, 0.000); + + logger.log(event); + + assertEquals(PreferenceConstants.PLUGIN_ID, loggedStatus.getPlugin()); + assertTrue("Logged status was not a MultiStatus", loggedStatus.isMultiStatus()); + assertEquals(expectedHeader, loggedStatus.getMessage()); + assertEquals("One nested IStatus did not get logged correctly.", 1, + loggedStatus.getChildren().length); + + IStatus freezeEvent = loggedStatus.getChildren()[0]; + assertTrue(freezeEvent.getMessage().contains(expectedEventMessage)); + + StackTraceElement[] threadStackTrace = thread.getStackTrace(); + StackTraceElement[] loggedStackTrace = freezeEvent.getException().getStackTrace(); + assertEquals(threadStackTrace.length, loggedStackTrace.length); + + for (int i = 0; i < threadStackTrace.length; i++) { + assertEquals(threadStackTrace[i], loggedStackTrace[i]); + } + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DelayHandler.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DelayHandler.java new file mode 100644 index 00000000000..5db148fe859 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DelayHandler.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.swt.widgets.Display; + +/** + * Used by 'Delays' menu to trigger an intentional delay on the UI thread to + * test the logging of UI freezes. + */ +public class DelayHandler extends AbstractHandler { + private static final int NS_PER_MS = 1000 * 1000; + private static final Display display = Display.getDefault(); + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + String syncStr = + event.getParameter("org.eclipse.ui.monitoring.manualtesting.commands.delay.sync"); + final boolean sync = syncStr != null && !syncStr.isEmpty() && Boolean.parseBoolean(syncStr); + + String durationStr = + event.getParameter("org.eclipse.ui.monitoring.manualtesting.commands.delay.duration"); + if (durationStr == null) { + throw new IllegalArgumentException( + "org.eclipse.ui.monitoring.manualtesting.commands.delay.duration parameter not provided"); + } + final long durationNs = Long.parseLong(durationStr) * NS_PER_MS; + + Runnable doDelay = new Runnable() { + @Override + public void run() { + double durationMs = (double) durationNs / (double) NS_PER_MS; + System.out.printf("Starting delay for %.6fms%n", durationMs); + long startTime = System.nanoTime(); + monitoringTestSleep(durationNs); + long actualDuration = System.nanoTime() - startTime; + System.out.printf("Delay for %.6fms complete. Actual duration: %.6fms%n", + durationMs, (double) actualDuration / (double) NS_PER_MS); + } + }; + + if (sync) { + display.syncExec(doDelay); + } else { + display.asyncExec(doDelay); + } + + return null; + } + + private static void monitoringTestSleep(long nanoseconds) { + long endTime = System.nanoTime() + nanoseconds; + + try { + while (true) { + long nsRemaining = endTime - System.nanoTime(); + if (nsRemaining > NS_PER_MS) { + Thread.sleep(nsRemaining / NS_PER_MS, (int) (nsRemaining % NS_PER_MS)); + } else if (nsRemaining <= 0) { + return; + } // < 1ms remaining, just spin-wait + } + } catch (InterruptedException e) { + // Wake up + } + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadManualJUnitPluginTests.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadManualJUnitPluginTests.java new file mode 100644 index 00000000000..0e80374e16c --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadManualJUnitPluginTests.java @@ -0,0 +1,640 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import junit.framework.TestCase; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.monitoring.PreferenceConstants; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * A test that measures performance overhead of {@link EventLoopMonitorThread}. + * This test is not included into {@link MonitoringTestSuite} due to its + * low reliability and the amount of time it takes. + */ +public class EventLoopMonitorThreadManualJUnitPluginTests extends TestCase { + /** Change to {@code true} to enable printing of detailed information to the console. */ + private static final boolean PRINT_TO_CONSOLE = false; + + // Test Parameters + /** Time each measurement should run for. This will affect the number of samples collected. */ + protected static final double TARGET_RUNNING_TIME_PER_MEASUREMENT = 5.0; // seconds + + /** Maximum allowable relative increase due to taking traces on the UI thread. */ + protected static final double MAX_RELATIVE_INCREASE_ONE_STACK_PERCENT = 2.5; // % + + /** Maximum allowable relative increase per thread due to taking traces on all threads. */ + protected static final double MAX_RELATIVE_INCREASE_PER_EXTRA_THREAD_PERCENT = 0.25; // % + + /** Number of times to repeat the control measurement. This should always be at least 2. */ + protected static final int NUM_CONTROL_MEASUREMENTS = 5; + + /** Number of times to repeat the UI stack sampling measurement. */ + protected static final int NUM_UI_STACK_MEASUREMENTS = 5; + + /** Number of times to repeat the all stacks sampling measurement. */ + protected static final int NUM_ALL_STACKS_MEASUREMENTS = 5; + + // Calibration Tuning Parameters + /** + * Number of work units to integrate over while calibrating. This parameter is used to adjust how + * much work is done to capture some jitter but too large of value will oscillate with large + * events (e.g. GC) instead of converging. + */ + protected static final long WORK_INTEGRATION_ITERATIONS = 70000; // iterations + + /** + * Number of consecutive iterations below the error threshold (defined by + * {@link #CAL_MAX_RELATIVE_ERROR}) before accepting a calibration result. Due to occasional + * jitter (e.g. OS scheduling, GC, etc), large values may not ever converge. + */ + protected static final int MIN_CONVERGING_ITERATIONS = 256; // iterations + + /** + * Number of outliers, relative to samples, to tolerate while calculating convergence during + * calibration. Tolerated points are ignored (e.g. not factored into the rolling average). + */ + protected static final double MAX_OUTLIER_RATIO = 0.03; + + /** + * Weight of new elements added to the exponential averaging of the mean during calibration. The + * optimal value is [2/(N-1)] where N is the desired number of samples to average. + */ + protected static final double CAL_EMA_ALPHA = 2.0 / (MIN_CONVERGING_ITERATIONS - 1.0); + + /** + * Max relative error between each new estimated mean and the previous estimate. Once the relative + * error stabilizes below this threshold for long enough (defined by + * {@link #MIN_CONVERGING_ITERATIONS} iterations) the calibration is complete. + */ + protected static final double CAL_MAX_RELATIVE_ERROR = 0.003; + + /** + * Pseudo-random Noise LFSR generator polynomial, degree = 64. Polynomial for maximum sequence + * length by Wayne Stahnke, Primitive Polynomials Modulo Two, + * http://www.ams.org/journals/mcom/1973-27-124/S0025-5718-1973-0327722-7/S0025-5718-1973-0327722-7.pdf + * + * <p> + * At 80ns/call, the sequence will have a period of >23382 years (== (2^63-1) * 80ns) + */ + protected static final long PN63_GENERATOR_POLY = (3L << 62) | 1; + + @Override + public void setUp() { + getPreferences().setValue(PreferenceConstants.MONITORING_ENABLED, false); + } + + @Override + public void tearDown() throws Exception { + getPreferences().setToDefault(PreferenceConstants.MONITORING_ENABLED); + } + + protected static long pn63(long pnSequence) { + int nextBit = 0; + for (int i = 0; i < 64; ++i) { + if (((PN63_GENERATOR_POLY >> i) & 1) == 1) { + nextBit ^= (pnSequence >> i) & 1; + } + } + + return (pnSequence << 1) | nextBit; + } + + protected static long doWork(long pnSeqeuence, long iterations) { + LinkedList<String> messages = new LinkedList<String>(); + + for (long i = 0; i < iterations; ++i) { + pnSeqeuence = pn63(pnSeqeuence); + + if (i % 10000 == 0) { + messages.add("10000!"); + } else if (i % 1000 == 0) { + for (int c = messages.size() / 2; c >= 0; --c) { + messages.removeFirst(); + } + } else if (i % 100 == 0) { + messages.add(String.format("Iteration %d", i)); + } + } + + for (String s : messages) { + pnSeqeuence ^= s.hashCode(); + } + + return pnSeqeuence; + } + + /** + * Performance test for {@link EventLoopMonitorThread}. This test verifies that the monitoring + * doesn't interfere too much with the real work being done. + */ + public void testFixedWork() throws Exception { + final Display display = Display.getDefault(); + assertNotNull("No SWT Display available.", display); + + final MockUiFreezeEventLogger logger = new MockUiFreezeEventLogger(); + final CountDownLatch backgroundJobsDone = new CountDownLatch(1); + Queue<Thread> threads = startBackgroundThreads(backgroundJobsDone); + + double nsPerWork = calibrate(display); + final long numIterations = Math.round(1e9 * TARGET_RUNNING_TIME_PER_MEASUREMENT / nsPerWork); + + final double[] tWork = {0}; + final long[] workOutput = {0}; + final Runnable doFixedAmountOfWork = new Runnable() { + @Override + public void run() { + long start = System.nanoTime(); + long result = doWork(start, numIterations); + tWork[0] = System.nanoTime() - start; + workOutput[0] ^= result; + } + }; + + // Fetch the total number of threads. + ThreadMXBean jvmThreadManager = ManagementFactory.getThreadMXBean(); + boolean dumpLockedMonitors = jvmThreadManager.isObjectMonitorUsageSupported(); + boolean dumpLockedSynchronizers = jvmThreadManager.isSynchronizerUsageSupported(); + int totalThreadCount = + jvmThreadManager.dumpAllThreads(dumpLockedMonitors, dumpLockedSynchronizers).length; + + List<Double> controlResults = new ArrayList<Double>(NUM_CONTROL_MEASUREMENTS); + List<Double> uiStackResults = new ArrayList<Double>(NUM_UI_STACK_MEASUREMENTS); + List<Double> allStacksResults = new ArrayList<Double>(NUM_ALL_STACKS_MEASUREMENTS); + double expectedRunningTime = 0.0; + double maxRunningTime = 0.0; + + /* + * This could be made more precise by measuring the control loop many times, calculating the + * mean and standard deviation, measuring each of the test case a few times, calculating the + * probability of the result, and comparing that probability to some acceptable bound. + */ + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Starting %d control measurements without monitoring.", + NUM_CONTROL_MEASUREMENTS)); + } + for (int i = 1; i <= NUM_CONTROL_MEASUREMENTS; ++i) { + display.syncExec(doFixedAmountOfWork); + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Control measurement %d/%d finished. tWork = %fs", + i, NUM_CONTROL_MEASUREMENTS, tWork[0] / 1e9)); + } + controlResults.add(tWork[0]); + expectedRunningTime += tWork[0] / NUM_CONTROL_MEASUREMENTS; + if (tWork[0] > maxRunningTime) { + maxRunningTime = tWork[0]; + } + } + + // Calculate error bound between mean & max control runs + double controlDiff = Math.abs((maxRunningTime - expectedRunningTime) / expectedRunningTime); + double maxRelativeIncreaseOneStackAllowed = + (MAX_RELATIVE_INCREASE_ONE_STACK_PERCENT / 100) * (1 + controlDiff); + double maxRelativeIncreaseAllStacksAllowed = + ((MAX_RELATIVE_INCREASE_ONE_STACK_PERCENT + MAX_RELATIVE_INCREASE_PER_EXTRA_THREAD_PERCENT + * (totalThreadCount - 1)) / 100) * (1 + controlDiff); + + Thread monitor1 = createAndStartMonitoringThread(display, false); + double worstRelativeDiffOneThread = Double.MIN_NORMAL; + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Starting %d measurements while collecting UI thread stacks.", + NUM_UI_STACK_MEASUREMENTS)); + } + for (int i = 1; i <= NUM_UI_STACK_MEASUREMENTS; ++i) { + display.syncExec(doFixedAmountOfWork); + uiStackResults.add(tWork[0]); + double relativeDiffOneThread = (tWork[0] - expectedRunningTime) / expectedRunningTime; + if (relativeDiffOneThread > worstRelativeDiffOneThread) { + worstRelativeDiffOneThread = relativeDiffOneThread; + } + if (PRINT_TO_CONSOLE) { + System.out.println(String.format( + "Measurement %d/%d finished. tWork = %fs, Relative increase = %f%% (allowed < %f%%)", + i, NUM_UI_STACK_MEASUREMENTS, tWork[0] / 1e9, relativeDiffOneThread * 100, + maxRelativeIncreaseOneStackAllowed * 100)); + } + assertTrue( + String.format("Relative increase with monitoring thread surpassed threshold for " + + "measurement %d/%d. It took %fs with a relative increase of %f%%", + i, NUM_UI_STACK_MEASUREMENTS, tWork[0] / 1e9, relativeDiffOneThread * 100), + relativeDiffOneThread < maxRelativeIncreaseOneStackAllowed); + } + killMonitorThread(monitor1, display); + + Thread monitor2 = createAndStartMonitoringThread(display, true); + double worstRelativeDiffAllThreads = Double.MIN_NORMAL; + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Starting %d measurements while collecting all thread stacks.", + NUM_ALL_STACKS_MEASUREMENTS)); + } + for (int i = 1; i <= NUM_ALL_STACKS_MEASUREMENTS; ++i) { + display.syncExec(doFixedAmountOfWork); + allStacksResults.add(tWork[0]); + double relativeDiffAllThreads = (tWork[0] - expectedRunningTime) / expectedRunningTime; + if (relativeDiffAllThreads > worstRelativeDiffAllThreads) { + worstRelativeDiffAllThreads = relativeDiffAllThreads; + } + if (PRINT_TO_CONSOLE) { + System.out.println(String.format( + "Measurement %d/%d finished. tWork = %fs, Relative increase = %f%% (allowed < %f%%)", + i, NUM_ALL_STACKS_MEASUREMENTS, tWork[0] / 1e9, relativeDiffAllThreads * 100, + maxRelativeIncreaseAllStacksAllowed * 100)); + } + assertTrue( + String.format("Relative increase with monitoring thread (with all threads) surpassed " + + "threshold for measurement %d/%d. It took %fs with a relative increase of %f%%", + i, NUM_ALL_STACKS_MEASUREMENTS, tWork[0] / 1e9, relativeDiffAllThreads * 100), + relativeDiffAllThreads < maxRelativeIncreaseAllStacksAllowed); + } + killMonitorThread(monitor2, display); + + backgroundJobsDone.countDown(); + + boolean gotFreezeEvents = + logger.getLoggedEvents().size() == NUM_UI_STACK_MEASUREMENTS + NUM_ALL_STACKS_MEASUREMENTS; + + boolean oneThreadOk = worstRelativeDiffOneThread < maxRelativeIncreaseOneStackAllowed; + boolean allThreadsOk = worstRelativeDiffAllThreads < maxRelativeIncreaseAllStacksAllowed; + + if (PRINT_TO_CONSOLE) { + // Tabulate final results while waiting for monitor to finish + double controlMean = 0; + double controlMin = Double.MAX_VALUE; + double controlMax = Double.MIN_NORMAL; + double controlM2 = 0; + for (int n = 0; n < controlResults.size(); ) { + double v = controlResults.get(n) / 1e9; + controlResults.set(n, v); + ++n; + double delta = v - controlMean; + controlMean += delta / n; + controlM2 += delta * (v - controlMean); + controlMin = Math.min(controlMin, v); + controlMax = Math.max(controlMax, v); + } + double controlStD = Math.sqrt(controlM2 / controlResults.size()); + + double uiStackMean = 0; + double uiStackMin = Double.MAX_VALUE; + double uiStackMax = Double.MIN_NORMAL; + double uiStackM2 = 0; + for (int n = 0; n < uiStackResults.size(); ) { + double v = uiStackResults.get(n) / 1e9; + uiStackResults.set(n, v); + ++n; + double delta = v - uiStackMean; + uiStackMean += delta / n; + uiStackM2 += delta * (v - uiStackMean); + uiStackMin = Math.min(uiStackMin, v); + uiStackMax = Math.max(uiStackMax, v); + } + + double allStacksMean = 0; + double allStacksMin = Double.MAX_VALUE; + double allStacksMax = Double.MIN_NORMAL; + double allStacksM2 = 0; + for (int n = 0; n < allStacksResults.size(); ) { + double v = allStacksResults.get(n) / 1e9; + allStacksResults.set(n, v); + ++n; + double delta = v - allStacksMean; + allStacksMean += delta / n; + allStacksM2 += delta * (v - allStacksMean); + allStacksMin = Math.min(allStacksMin, v); + allStacksMax = Math.max(allStacksMax, v); + } + + // Ensure the work cannot be optimized away completely + System.out.println(String.format("Work result = %016X", workOutput[0])); + + if (!oneThreadOk) { + System.out.println(" * Monitoring UI thread slowed down work too much"); + } + if (!allThreadsOk) { + System.out.println(" * Monitoring all threads slowed down work too much"); + } + if (!gotFreezeEvents) { + System.out.println(String.format(" * Didn't get expected freeze events (got %d/%d)", + logger.getLoggedEvents().size(), + NUM_UI_STACK_MEASUREMENTS + NUM_ALL_STACKS_MEASUREMENTS)); + } + + System.out.println("\nRaw results (in seconds): "); + System.out.println(String.format( + " * Control (min = %f max = %f avg = %f stD = %f): %s", + controlMin, controlMax, controlMean, controlStD, joinItems(controlResults))); + System.out.println(String.format( + " * UI stack (min = %f max = %f avg = %f stD = %f, vsC = %f): %s", + uiStackMin, uiStackMax, uiStackMean, Math.sqrt(uiStackM2 / uiStackResults.size()), + Math.abs(uiStackMean - controlMean) / controlStD, joinItems(uiStackResults))); + System.out.println(String.format( + " * All stacks (min = %f max = %f avg = %f stD = %f, vsC = %f): %s", + allStacksMin, allStacksMax, allStacksMean, Math.sqrt(allStacksM2 / allStacksResults.size()), + Math.abs(allStacksMean - controlMean) / controlStD, + joinItems(allStacksResults))); + } + + // Join all threads + while (!threads.isEmpty()) { + Thread t = threads.poll(); + try { + t.join(); + } catch (InterruptedException e) { + threads.offer(t); // Retry + } + } + + assertTrue(oneThreadOk && allThreadsOk && gotFreezeEvents && !logger.getLoggedEvents().isEmpty()); + assertTrue("Monitoring slowed down cases too much", oneThreadOk || allThreadsOk); + assertTrue("Monitoring UI thread slowed down work too much", oneThreadOk); + assertTrue("Did not log expected freeze events", gotFreezeEvents); + assertTrue("Monitoring all threads slowed down work too much", allThreadsOk); + } + + private Thread createAndStartMonitoringThread(Display display, boolean dumpAll) throws Exception { + CountDownLatch monitorStarted = new CountDownLatch(1); + Thread monitor = createTestMonitor(dumpAll, monitorStarted); + startMontoring(display, monitor, monitorStarted); + return monitor; + } + + private void killMonitorThread(final Thread thread, Display display) throws Exception { + display.syncExec(new Runnable() { + @Override + public void run() { + shutdownThread(thread); + } + }); + + thread.join(); + } + + protected static EventLoopMonitorThread.Parameters createDefaultParameters() { + IPreferenceStore preferences = MonitoringPlugin.getDefault().getPreferenceStore(); + EventLoopMonitorThread.Parameters params = new EventLoopMonitorThread.Parameters(); + + params.loggingThreshold = preferences.getInt(PreferenceConstants.MAX_EVENT_LOG_TIME_MILLIS); + params.samplingThreshold = preferences.getInt(PreferenceConstants.MAX_EVENT_SAMPLE_TIME_MILLIS); + params.dumpAllThreads = preferences.getBoolean(PreferenceConstants.DUMP_ALL_THREADS); + params.minimumPollingDelay = preferences.getInt( + PreferenceConstants.SAMPLE_INTERVAL_TIME_MILLIS); + params.loggedTraceCount = preferences.getInt(PreferenceConstants.MAX_LOG_TRACE_COUNT); + params.deadlockDelta = preferences.getInt(PreferenceConstants.FORCE_DEADLOCK_LOG_TIME_MILLIS); + params.filterTraces = preferences.getString(PreferenceConstants.FILTER_TRACES); + + params.checkParameters(); + return params; + } + + protected Thread createTestMonitor(boolean dumpAllThreads, final CountDownLatch monitorStarted) + throws Exception { + EventLoopMonitorThread.Parameters args = createDefaultParameters(); + args.samplingThreshold = 100; + args.minimumPollingDelay = 100; + args.dumpAllThreads = dumpAllThreads; + + return new EventLoopMonitorThread(args) { + /** + * Replaces the super-class implementation with a no-op. This breaks the implicit contract + * that some amount of time should have passed when sleepForMillis is called with a non-zero + * argument, but in this testing environment giving the unit tests complete control over the + * elapsed time allows the tests to be more deterministic. + */ + @Override + protected void sleepForMillis(long nanoseconds) { + monitorStarted.countDown(); + super.sleepForMillis(nanoseconds); + } + }; + } + + protected Queue<Thread> startBackgroundThreads(final CountDownLatch backgroundJobsDone) { + final Runnable backgroundTaskRunnable = new Runnable() { + @Override + public void run() { + final double dutyCycle = 0.10; + + final double min = 100; // ns + final double max = 1e9; // ns + final double skew = 0.1; // the degree to which the values cluster around the mode + final double bias = -1e5; // bias the mode to approach the min (< 0) vs max (> 0) + + double range = max - min; + double mid = min + range / 2.0; + double biasFactor = Math.exp(bias); + Random rng = new Random(); + + while (true) { + double rv = rng.nextGaussian(); + double runFor = mid + (range * (biasFactor / (biasFactor + Math.exp(-rv / skew)) - 0.5)); + + long endTime = System.nanoTime() + (long) runFor; + do { + doWork(rng.nextInt(), (int) runFor); + } while (endTime - System.nanoTime() > 0); + + double sleepScale = Math.abs(rng.nextGaussian() / dutyCycle); + try { + if (backgroundJobsDone.await((int) Math.round(runFor * sleepScale), + TimeUnit.NANOSECONDS)) { + return; + } + } catch (InterruptedException e) { + // Wake up. + } + } + } + }; + + final Runnable backgroundIdle = new Runnable() { + @Override + public void run() { + while (true) { + try { + backgroundJobsDone.await(); + return; + } catch (InterruptedException e) { + // Wake up. + } + } + } + }; + + Random rng = new Random(); + int activeThreads = Thread.activeCount(); + int numBackgroundTasks = activeThreads <= 2 ? 5 + rng.nextInt(5) : 1; + int numBackgroundIdlers = 8 + rng.nextInt(30); + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Starting %d background tasks and %d background idlers", + numBackgroundTasks, numBackgroundIdlers)); + } + Queue<Thread> threads = new ArrayDeque<Thread>(numBackgroundIdlers + numBackgroundTasks); + for (int i = 0; i < numBackgroundTasks; i++) { + Thread t = new Thread(backgroundTaskRunnable); + threads.add(t); + t.start(); + } + + for (int i = 0; i < numBackgroundIdlers; i++) { + Thread t = new Thread(backgroundIdle); + threads.add(t); + t.start(); + } + return threads; + } + + protected void startMontoring(final Display display, final Thread swtThread, + CountDownLatch eventsRegistered) throws Exception { + display.syncExec(new Runnable() { + @Override + public void run() { + swtThread.start(); + + // If we're still running when display gets disposed, shutdown the thread. + display.disposeExec(new Runnable() { + @Override + public void run() { + shutdownThread(swtThread); + } + }); + } + }); + + for (boolean eventsReady = false; !eventsReady;) { + while (display.readAndDispatch()) { /* keep invoking events */ + } + + eventsReady |= eventsRegistered.await(1, TimeUnit.MILLISECONDS); + } + } + + protected void shutdownThread(Thread monitor) { + ((EventLoopMonitorThread) monitor).shutdown(); + } + + /** + * @returns nanoseconds/unitWork + */ + protected double calibrate(final Display display) { + if (PRINT_TO_CONSOLE) { + System.out.println("Starting calibration..."); + } + final double[] tWork = {0}; + display.syncExec(new Runnable() { + @Override + public void run() { + tWork[0] = measureAvgTimePerWorkItem(); + } + }); + if (PRINT_TO_CONSOLE) { + System.out.println(String.format("Calibration finished. tWorkUnit = %fns", tWork[0])); + } + return tWork[0]; + } + + protected double measureAvgTimePerWorkItem() { + int consecutiveGood = 0; + int outliers = 0; + int hash = 0; + double avgAbsRelErr = 0; + + double numSamplesAveraged = 2.0 / CAL_EMA_ALPHA + 1.0; + + // Ensure everything is in memory, JITed, and ready to go. + doWork(0, 1000); + + // Initialize a meaningful mean + long start = System.nanoTime(); + long result = doWork(0, WORK_INTEGRATION_ITERATIONS); + double mean = System.nanoTime() - start; + double oldMean; + + long n = 0; + long startWallTime = System.currentTimeMillis(); + long nextPrintAt = startWallTime + 5000; // 5s + while (++n < Long.MAX_VALUE + && (consecutiveGood < MIN_CONVERGING_ITERATIONS || avgAbsRelErr > CAL_MAX_RELATIVE_ERROR)) { + start = System.nanoTime(); + result = doWork(result, WORK_INTEGRATION_ITERATIONS); + long duration = System.nanoTime() - start; + hash ^= result; + + double deltaDuration = duration - mean; + oldMean = mean; + mean += CAL_EMA_ALPHA * (deltaDuration - mean); // EMA + double absRelErr = Math.abs((oldMean - mean) / mean); + if (absRelErr < CAL_MAX_RELATIVE_ERROR) { + consecutiveGood++; + avgAbsRelErr = avgAbsRelErr * ((consecutiveGood - 1) / consecutiveGood) + + (absRelErr / consecutiveGood); + } else if (outliers < (int) (MAX_OUTLIER_RATIO * Math.min(n, numSamplesAveraged))) { + ++outliers; + } else { + consecutiveGood = 1; + outliers = 0; + avgAbsRelErr = absRelErr; + } + + if (nextPrintAt - System.currentTimeMillis() < 0) { + nextPrintAt += 2000; // 2s + if (PRINT_TO_CONSOLE) { + System.out.println(String.format( + "Still calibrating... n = %3d\t\ttWork = %f ns\t\tRelErr = %f\t\tOutliers = %d/%d", + n, 2.0 * mean / WORK_INTEGRATION_ITERATIONS, avgAbsRelErr, outliers, + (int) (MAX_OUTLIER_RATIO * Math.min(n, numSamplesAveraged)))); + } + } + } + + double tWork = 2.0 * mean / WORK_INTEGRATION_ITERATIONS; + + // Passing the hash to println method ensures it cannot be optimized away completely. + System.out.println(String.format("Measurement converged in %d ms (%d loops) " + + "tWork = %fns, relErr = %f, outliers = %d", + System.currentTimeMillis() - startWallTime, + n, + tWork, + avgAbsRelErr, + outliers, + hash)); + return tWork; + } + + private String joinItems(List<Double> items) { + StringBuilder mergedItems = new StringBuilder(); + + for (double item : items) { + if (mergedItems.length() != 0) { + mergedItems.append(','); + } + mergedItems.append(item); + } + + return mergedItems.toString(); + } + + private static IPreferenceStore getPreferences() { + return MonitoringPlugin.getDefault().getPreferenceStore(); + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadTests.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadTests.java new file mode 100644 index 00000000000..c3295f7ebd0 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadTests.java @@ -0,0 +1,543 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ + +package org.eclipse.ui.internal.monitoring; + +import junit.framework.TestCase; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.ui.internal.monitoring.EventLoopMonitorThread.Parameters; +import org.eclipse.ui.monitoring.PreferenceConstants; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.util.List; + +/** + * JUnit tests for {@link EventLoopMonitorThread}. + */ +public class EventLoopMonitorThreadTests extends TestCase { + /** + * A mock event loop monitor thread used for JUnit tests. + */ + private static class MockEventLoopMonitorThread extends EventLoopMonitorThread { + public MockEventLoopMonitorThread(Parameters args) throws Exception { + super(args); + } + + /** + * Overridden to provide an artificial time scale for testing. + */ + @Override + protected long getTimestamp() { + return timestamp; + } + + /** + * Replaces the super-class implementation with a no-op. This breaks the implicit contract + * that some amount of time should have passed when sleepForMillis is called with a non-zero + * argument, but in this testing environment giving the unit tests complete control over the + * elapsed time allows the tests to be more deterministic. + */ + @Override + protected void sleepForMillis(long nanoseconds) { + synchronized (sleepLock) { + ++numSleeps; + sleepLock.notifyAll(); + } + } + } + + /* NOTE: All time-related values in this class are in milliseconds. */ + public static final int FORCE_DEADLOCK_LOG_TIME_MILLIS = 10 * 60 * 1000; // == 10 minutes + public static final String FILTER_TRACES = + "org.eclipse.swt.internal.gtk.OS.gtk_dialog_run," + + "org.eclipse.e4.ui.workbench.addons.dndaddon.DnDManager.startDrag"; + private static final long MAX_TIMEOUT_MS = 1 * 1000; // 1 second + private static final int THRESHOLD_MS = 100; + private static final int POLLING_RATE_MS = THRESHOLD_MS / 2; + private static final int MIN_STACK_TRACES = 5; + private static final int MAX_STACK_TRACES = 11; + private static final int MIN_MAX_STACK_TRACE_DELTA = MAX_STACK_TRACES - MIN_STACK_TRACES; + MockEventLoopMonitorThread testThread; + private MockUiFreezeEventLogger logger; + private static Object sleepLock; + private static long numSleeps; + private static volatile long timestamp; + + @Override + public void setUp() { + getPreferences().setValue(PreferenceConstants.MONITORING_ENABLED, false); + logger = new MockUiFreezeEventLogger(); + sleepLock = new Object(); + numSleeps = 0; + timestamp = 1; + } + + @Override + public void tearDown() throws Exception { + shutdownThread(testThread); + testThread = null; + getLoggedEvents().clear(); + getPreferences().setToDefault(PreferenceConstants.MONITORING_ENABLED); + } + + private static IPreferenceStore getPreferences() { + return MonitoringPlugin.getDefault().getPreferenceStore(); + } + + /** + * Creates and returns a EventLoopMonitorThread that fakes out the timer management to enable + * testing various long event scenarios. + */ + private static MockEventLoopMonitorThread createTestThread(int threshold) throws Exception { + EventLoopMonitorThread.Parameters args = new Parameters(); + args.loggingThreshold = threshold - 1; + args.samplingThreshold = POLLING_RATE_MS - 1; + args.dumpAllThreads = true; + args.minimumPollingDelay = POLLING_RATE_MS - 1; + args.loggedTraceCount = MIN_STACK_TRACES; + args.deadlockDelta = FORCE_DEADLOCK_LOG_TIME_MILLIS; + args.filterTraces = FILTER_TRACES; + + return new MockEventLoopMonitorThread(args); + } + + /** + * Shuts down the given monitoring thread. + */ + private static void shutdownThread(EventLoopMonitorThread thread) throws Exception { + thread.endSleep(); + thread.endEvent(); + thread.shutdown(); + thread.join(); + } + + /** + * Runs the current thread for a specified amount of time for delays. + */ + private static void runForCycles(long numCycles) throws Exception { + runForTime(POLLING_RATE_MS * numCycles); + } + + /** + * Runs the current thread for a specified amount of time in milliseconds. + */ + private static void runForTime(long millis) throws Exception { + synchronized (sleepLock) { + while (millis > 0) { + long next = Math.min(millis, POLLING_RATE_MS); + timestamp += next; + + long sleeps = numSleeps; + long endTime = System.currentTimeMillis() + MAX_TIMEOUT_MS; + do { + sleepLock.wait(MAX_TIMEOUT_MS); + } while (sleeps == numSleeps && endTime - System.currentTimeMillis() > 0); + + millis -= next; + } + } + } + + /** + * Returns the expected number of stack traces captured. + */ + private static int expectedStackCount(long runningTimeMs) { + return Math.min((int) (runningTimeMs / POLLING_RATE_MS), MIN_STACK_TRACES); + } + + private List<UiFreezeEvent> getLoggedEvents() { + return logger.getLoggedEvents(); + } + + public void testStackDecimation() throws Exception { + UiFreezeEvent snapshot; + + testThread = createTestThread(THRESHOLD_MS * 2); + testThread.start(); + testThread.beginEvent(); + + // Cycle a few events + synchronized (sleepLock) { + for (int i = 0; i < 3; ++i) { + testThread.beginEvent(); + runForCycles(1); + testThread.endEvent(); + } + } + + // Test going one beyond the MAX_STACK_TRACES count to see that the count is decimated. + int eventLength = MAX_STACK_TRACES + 2; + synchronized (sleepLock) { + testThread.beginEvent(); + runForCycles(eventLength); + testThread.endEvent(); + runForCycles(3); + } + + snapshot = getLoggedEvents().get(0); + assertNotNull("A long running event was not automatically published", snapshot); + assertEquals("Decimation did not resize the stack trace array properly", MIN_STACK_TRACES, + snapshot.getSampleCount()); + + // Decimation slows down the sampling rate by a factor of 2, so test the resampling reduction. + eventLength = MAX_STACK_TRACES + (MIN_MAX_STACK_TRACE_DELTA - 1) * 2; + synchronized (sleepLock) { + testThread.beginEvent(); + runForCycles(eventLength); + testThread.endEvent(); + runForCycles(3); + } + + snapshot = getLoggedEvents().get(1); + assertNotNull("A long running event was not automatically published", snapshot); + assertEquals("Decimation did not reset the sampiling rate properly", MIN_STACK_TRACES, + snapshot.getSampleCount()); + + // Test the resampling reduction after two decimations. + eventLength = + MAX_STACK_TRACES + (MIN_MAX_STACK_TRACE_DELTA) * 2 + (MIN_MAX_STACK_TRACE_DELTA - 2) * 4; + synchronized (sleepLock) { + testThread.beginEvent(); + runForCycles(eventLength); + testThread.endEvent(); + runForCycles(3); + } + + snapshot = getLoggedEvents().get(2); + assertNotNull("A long running event was not automatically published", snapshot); + assertEquals("Decimation did not reset the sampiling rate properly", MIN_STACK_TRACES, + snapshot.getSampleCount()); + } + + public void testPublishPossibleDeadlock() throws Exception { + testThread = createTestThread(Integer.MAX_VALUE - 1); + testThread.start(); + long maxDeadlock = FORCE_DEADLOCK_LOG_TIME_MILLIS; + testThread.beginEvent(); + + synchronized (sleepLock) { + // Cycle a few events to make sure the monitoring event thread is running. + for (int i = 0; i < 3; ++i) { + testThread.beginEvent(); + runForCycles(1); + testThread.endEvent(); + } + long startTime = timestamp; + + // Wait for the end of the event to propagate to the deadlock tracker. + runForCycles(1); + + long remaining = maxDeadlock - (timestamp - startTime); + runForTime(remaining - 1); + assertTrue("No deadlock should get logged before its time", getLoggedEvents().isEmpty()); + + // March time forward to trigger the possible deadlock logging. + runForCycles(4); + + assertEquals("Incorrect number of events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertTrue("Possible deadlock logging should have stack a valid number of stack traces", + snapshot.getSampleCount() >= MIN_STACK_TRACES); + + // Extending the UI freeze shouldn't log any more events. + runForTime(maxDeadlock * 2); + runForCycles(3); + } + + assertEquals("No more deadlock events should get logged", 1, getLoggedEvents().size()); + } + + public void testPublishNoDeadlocksWhenSleeping() throws Exception { + testThread = createTestThread(Integer.MAX_VALUE - 1); + testThread.start(); + testThread.beginEvent(); + + synchronized (sleepLock) { + // Cycle a few events to make sure the monitoring event thread is running. + for (int i = 0; i < 3; ++i) { + testThread.beginEvent(); + runForCycles(1); + testThread.endEvent(); + } + testThread.beginSleep(); + + // Wait for the end of the event to propagate to the deadlock tracker. + runForTime(FORCE_DEADLOCK_LOG_TIME_MILLIS * 2); + runForCycles(3); + } + + assertTrue("No deadlock events should get logged", getLoggedEvents().isEmpty()); + } + + public void testNoLoggingForSleep() throws Exception { + int eventFactor = 5; + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + + // One level deep + synchronized (sleepLock) { + testThread.beginSleep(); + runForTime(eventFactor * POLLING_RATE_MS); + testThread.endSleep(); + runForCycles(3); + } + + assertTrue("Sleeping should not trigger a long running event", getLoggedEvents().isEmpty()); + } + + public void testEventLogging() throws Exception { + int eventFactor = 5; + long eventStartTime = 0; + long eventStallDuration = 0; + + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + + // One level deep + synchronized (sleepLock) { + testThread.beginEvent(); // level 1 + eventStartTime = timestamp; + runForTime(eventFactor * THRESHOLD_MS); + eventStallDuration = timestamp - eventStartTime; + testThread.endEvent(); + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event log has an incorrect start time", eventStartTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration was incorrect", eventStallDuration, + snapshot.getTotalDuration()); + assertEquals("A long running event didn't capture a good range of stack traces", + expectedStackCount(eventStallDuration), snapshot.getSampleCount()); + } + + public void testNestedEventLogging() throws Exception { + int eventFactor = 6; + + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + long eventStartTime = 0; + long eventStallDuration = 0; + + // Two levels deep + synchronized (sleepLock) { + testThread.beginEvent(); // level 1 + runForCycles(1); + testThread.beginEvent(); // level 2 + eventStartTime = timestamp; + runForTime(eventFactor * THRESHOLD_MS); + eventStallDuration = timestamp - eventStartTime; + testThread.endEvent(); + testThread.endEvent(); + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event log has an incorrect start time", eventStartTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration was incorrect", eventStallDuration, + snapshot.getTotalDuration()); + assertEquals("A long running event didn't capture a good range of stack traces", + expectedStackCount(eventStallDuration), snapshot.getSampleCount()); + } + + public void testDoublyNestedEventLogging() throws Exception { + int eventFactor = 7; + + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + long eventStartTime = 0; + long eventStallDuration = 0; + + // Three levels deep + synchronized (sleepLock) { + testThread.beginEvent(); // level 1 + runForCycles(1); + testThread.beginEvent(); // level 2 + runForCycles(1); + testThread.beginEvent(); // level 3 + eventStartTime = timestamp; + runForTime(eventFactor * THRESHOLD_MS); + eventStallDuration = timestamp - eventStartTime; + testThread.endEvent(); + testThread.endEvent(); + testThread.endEvent(); + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event log has an incorrect start time", eventStartTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration was incorrect", eventStallDuration, + snapshot.getTotalDuration()); + assertEquals("A long running event didn't capture a good range of stack traces", + expectedStackCount(eventStallDuration), snapshot.getSampleCount()); + } + + public void testSeeLongEventInContinuationAfterNestedCall() throws Exception { + int eventFactor = 4; + + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + long eventResumeTime = 0; + long eventStallDuration = 0; + + // Exceed the threshold after the thread is started in the middle of an event, then end the + // event and validate that no long event was logged. + synchronized (sleepLock) { + testThread.beginEvent(); + // Initially the outer thread is invoking nested events that are responsive. + for (int i = 0; i < 4; i++) { + runForCycles(1); + testThread.beginEvent(); + testThread.endEvent(); + } + + eventResumeTime = timestamp; + runForTime(eventFactor * THRESHOLD_MS); + testThread.endEvent(); + eventStallDuration = timestamp - eventResumeTime; + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event didn't start from the nested return point", eventResumeTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration was incorrect", eventStallDuration, + snapshot.getTotalDuration()); + assertEquals("A long running event didn't capture a good range of stack traces", + expectedStackCount(eventStallDuration), snapshot.getSampleCount()); + } + + public void testSeeLongEventInTheMiddleOfNestedCalls() throws Exception { + int eventFactor = 4; + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + long eventResumeTime = 0; + long eventStallDuration = 0; + + // Exceed the threshold after the thread is started in the middle of an event, then end the + // event and validate that no long event was logged. + synchronized (sleepLock) { + testThread.beginEvent(); + // Initially the outer thread is invoking nested events that are responsive. + for (int i = 0; i < 3; i++) { + runForCycles(1); + testThread.beginEvent(); + testThread.endEvent(); + } + + // This is the nested event UI freeze + eventResumeTime = timestamp; + runForTime(eventFactor * THRESHOLD_MS); + eventStallDuration = timestamp - eventResumeTime; + + // Before exiting the outer thread is invoking nested events that are responsive. + for (int i = 0; i < 3; i++) { + testThread.beginEvent(); + testThread.endEvent(); + runForCycles(1); + } + + testThread.endEvent(); + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event didn't start from the nested return point", eventResumeTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration was incorrect", eventStallDuration, + snapshot.getTotalDuration()); + assertEquals("A long running event didn't capture a good range of stack traces", + expectedStackCount(eventStallDuration), snapshot.getSampleCount()); + } + + public void testSeeSleepInTheMiddleOfNestedCalls() throws Exception { + int eventFactor = 4; + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + + // Exceed the threshold after the thread is started in the middle of an event, then end the + // event and validate that no long event was logged. + synchronized (sleepLock) { + testThread.beginEvent(); + // Initially the outer thread is invoking nested events that are responsive. + for (int i = 0; i < 3; i++) { + runForCycles(1); + testThread.beginEvent(); + testThread.endEvent(); + } + + // Nested events + for (int i = 0; i < eventFactor; ++i) { + runForCycles(1); + testThread.beginSleep(); + testThread.endSleep(); + } + + // Before exiting the outer thread is invoking nested events that are responsive. + for (int i = 0; i < 3; i++) { + testThread.beginEvent(); + testThread.endEvent(); + runForCycles(1); + } + testThread.endEvent(); + runForCycles(3); + } + + assertTrue("A long running event should not be published if Display.sleep() was called", + getLoggedEvents().isEmpty()); + } + + public void testConsecutiveSleeps() throws Exception { + int eventFactor = 5; + long eventStartTime = 0; + long eventDuration = 0; + + testThread = createTestThread(THRESHOLD_MS); + testThread.start(); + + synchronized (sleepLock) { + testThread.beginSleep(); + runForTime(THRESHOLD_MS); + testThread.endSleep(); + eventStartTime = timestamp; + runForCycles(3); + } + + assertTrue("A long running event shold not be published during a sleep", + getLoggedEvents().isEmpty()); + + // Let a long time elapse between the last endSleep() and the next beginSleep(). + synchronized (sleepLock) { + runForTime(THRESHOLD_MS * eventFactor); + eventDuration = timestamp - eventStartTime; + testThread.beginSleep(); + testThread.endSleep(); + runForCycles(3); + } + + assertEquals("Incorrect number of long events was logged", 1, getLoggedEvents().size()); + UiFreezeEvent snapshot = getLoggedEvents().get(0); + assertEquals("A long running event log has an incorrect start time", eventStartTime, + snapshot.getStartTimestamp()); + assertEquals("A long running event's duration is incorrect", eventDuration, + snapshot.getTotalDuration()); + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/FilterHandlerTests.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/FilterHandlerTests.java new file mode 100644 index 00000000000..9d8da49b587 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/FilterHandlerTests.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Steve Foreman (Google) - initial API and implementation + * Marcus Eng (Google) + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import junit.framework.TestCase; + +import org.eclipse.ui.monitoring.StackSample; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +/** + * Tests for {@link FilterHandler} class. + */ +public class FilterHandlerTests extends TestCase { + private static final String FILTER_TRACES = + "org.eclipse.ui.internal.monitoring.FilterHandlerTests.createFilteredUiFreezeEvent"; + private static final long THREAD_ID = Thread.currentThread().getId(); + + private UiFreezeEvent createUiFreezeEvent() { + ThreadMXBean jvmThreadManager = ManagementFactory.getThreadMXBean(); + ThreadInfo thread = + jvmThreadManager.getThreadInfo(Thread.currentThread().getId(), Integer.MAX_VALUE); + StackSample[] samples = { new StackSample(0, new ThreadInfo[] { thread }) }; + return new UiFreezeEvent(0, 0, samples, 1, false); + } + + /** + * Creates a {@link UiFreezeEvent} with a stack trace that is not filtered. + */ + private UiFreezeEvent createUnfilteredUiFreezeEvent() { + return createUiFreezeEvent(); + } + + /** + * Creates a {@link UiFreezeEvent} with a stack trace that should be filtered. + */ + private UiFreezeEvent createFilteredUiFreezeEvent() { + return createUiFreezeEvent(); + } + + public void testUnfilteredEventLogging() { + FilterHandler filterHandler = new FilterHandler(FILTER_TRACES); + UiFreezeEvent unfilteredEvent = createUnfilteredUiFreezeEvent(); + + assertTrue(filterHandler.shouldLogEvent(unfilteredEvent, THREAD_ID)); + } + + public void testFilteredEventLogging() { + FilterHandler filterHandler = new FilterHandler(FILTER_TRACES); + UiFreezeEvent filteredEvent = createFilteredUiFreezeEvent(); + + assertFalse(filterHandler.shouldLogEvent(filteredEvent, THREAD_ID)); + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MockUiFreezeEventLogger.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MockUiFreezeEventLogger.java new file mode 100644 index 00000000000..fb71aad9905 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MockUiFreezeEventLogger.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marcus Eng (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import org.eclipse.ui.monitoring.IUiFreezeEventLogger; +import org.eclipse.ui.monitoring.UiFreezeEvent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Captures and stores {@link UiFreezeEvent}s during JUnit tests. + */ +public class MockUiFreezeEventLogger implements IUiFreezeEventLogger { + private static final List<UiFreezeEvent> loggedEvents = + Collections.synchronizedList(new ArrayList<UiFreezeEvent>()); + + @Override + public void log(UiFreezeEvent event) { + loggedEvents.add(event); + } + + public List<UiFreezeEvent> getLoggedEvents() { + return loggedEvents; + } +} diff --git a/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MonitoringTestSuite.java b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MonitoringTestSuite.java new file mode 100644 index 00000000000..e97c8d2ff59 --- /dev/null +++ b/tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MonitoringTestSuite.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (C) 2014, Google Inc and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Sergey Prigogin (Google) - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.monitoring; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test suite for {@code org.eclipse.ui.monitoring} plug-in. + * The tests in {@link EventLoopMonitorThreadManualJUnitPluginTests} are not included in this + * suite due to their flakiness. + */ +public class MonitoringTestSuite extends TestSuite { + public static Test suite() { + return new MonitoringTestSuite(); + } + + public MonitoringTestSuite() { + addTestSuite(EventLoopMonitorThreadTests.class); + addTestSuite(FilterHandlerTests.class); + addTestSuite(DefaultLoggerTest.class); + } +} |
