Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSergey Prigogin2014-08-04 20:48:32 +0000
committerSergey Prigogin2014-08-21 22:04:47 +0000
commit9b4722b71f6463402aa8d0cd490370682952288a (patch)
treee5e0bca14b48ce4c20fc1b820303f06e4bad48b3
parent3a2f0f2139c723275b09d21e489af62fffa45788 (diff)
downloadeclipse.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>
-rw-r--r--bundles/org.eclipse.ui.monitoring/.classpath7
-rw-r--r--bundles/org.eclipse.ui.monitoring/.options4
-rw-r--r--bundles/org.eclipse.ui.monitoring/.project34
-rw-r--r--bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.jdt.core.prefs7
-rw-r--r--bundles/org.eclipse.ui.monitoring/.settings/org.eclipse.pde.prefs14
-rw-r--r--bundles/org.eclipse.ui.monitoring/META-INF/MANIFEST.MF17
-rw-r--r--bundles/org.eclipse.ui.monitoring/README.md21
-rw-r--r--bundles/org.eclipse.ui.monitoring/build.properties17
-rw-r--r--bundles/org.eclipse.ui.monitoring/plugin.properties15
-rw-r--r--bundles/org.eclipse.ui.monitoring/plugin.xml38
-rw-r--r--bundles/org.eclipse.ui.monitoring/pom.xml24
-rw-r--r--bundles/org.eclipse.ui.monitoring/schema/org.eclipse.ui.monitoring.logger.exsd102
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/DefaultUiFreezeEventLogger.java134
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThread.java630
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/FilterHandler.java120
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/LongEventInfo.java40
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.java48
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Messages.properties35
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringPlugin.java61
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/MonitoringStartup.java93
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/Tracer.java92
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/FilterInputDialog.java92
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/ListFieldEditor.java67
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.java42
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/Messages.properties29
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceInitializer.java43
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferenceListener.java99
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/internal/monitoring/preferences/MonitoringPreferencePage.java200
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/IUiFreezeEventLogger.java27
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/PreferenceConstants.java51
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/StackSample.java43
-rw-r--r--bundles/org.eclipse.ui.monitoring/src/org/eclipse/ui/monitoring/UiFreezeEvent.java68
-rw-r--r--pom.xml1
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/.classpath7
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/.project28
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/.settings/org.eclipse.jdt.core.prefs8
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/META-INF/MANIFEST.MF11
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/README.md27
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/build.properties18
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/fragment.xml174
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/pom.xml51
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DefaultLoggerTest.java92
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/DelayHandler.java79
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadManualJUnitPluginTests.java640
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/EventLoopMonitorThreadTests.java543
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/FilterHandlerTests.java66
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MockUiFreezeEventLogger.java35
-rw-r--r--tests/org.eclipse.ui.monitoring.tests/src/org/eclipse/ui/internal/monitoring/MonitoringTestSuite.java31
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;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2619813d0fe..cab59e38cf6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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);
+ }
+}

Back to the top