/*******************************************************************************
* Copyright (C) 2014, 2015 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)
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.ui.internal.monitoring;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
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.PreferenceConstants;
import org.eclipse.ui.monitoring.StackSample;
import org.eclipse.ui.monitoring.UiFreezeEvent;
/**
* Event loop monitoring thread. Detects events that take long time to process, collects stack
* traces of the UI thread during processing of those events, and logs the long events to the error
* log.
*/
public class EventLoopMonitorThread extends Thread {
private static final int EVENT_HISTORY_SIZE = 100;
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$
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 final Tracer tracer =
Tracer.create(TRACE_PREFIX, PreferenceConstants.PLUGIN_ID + TRACE_EVENT_MONITOR);
/* 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 compared to a large parameter list of integers and booleans.
*/
public static class Parameters {
/** Events that took longer than the specified duration are logged as warnings. */
public int longEventWarningThreshold;
/** Events that took longer than the specified duration are logged as errors. */
public int longEventErrorThreshold;
/**
* Events that took longer than the specified duration are reported as deadlocks without
* waiting for the event to finish.
*/
public long deadlockThreshold;
/** Maximum number of stack samples to log */
public int maxStackSamples;
/** If true, log freeze events to the Eclipse error log on the local machine. */
public boolean logToErrorLog;
/** @see org.eclipse.ui.monitoring.PreferenceConstants#UI_THREAD_FILTER */
public String uiThreadFilter;
/** @see org.eclipse.ui.monitoring.PreferenceConstants#NONINTERESTING_THREAD_FILTER */
public String noninterestingThreadFilter;
/**
* Checks if the values of parameters for UI responsiveness monitoring are valid.
*
* @throws IllegalArgumentException if the parameter values are invalid or inconsistent.
*/
public void checkParameters() throws IllegalArgumentException {
StringBuilder problems = new StringBuilder();
if (longEventWarningThreshold <= 0) {
problems.append(NEW_LINE_AND_BULLET +
NLS.bind(Messages.EventLoopMonitorThread_warning_threshold_error_1,
longEventWarningThreshold));
}
if (longEventErrorThreshold < longEventWarningThreshold) {
problems.append(NEW_LINE_AND_BULLET +
NLS.bind(Messages.EventLoopMonitorThread_error_threshold_too_low_error_2,
longEventErrorThreshold, longEventWarningThreshold));
}
if (deadlockThreshold <= 0) {
problems.append(NEW_LINE_AND_BULLET +
NLS.bind(Messages.EventLoopMonitorThread_deadlock_error_1,
deadlockThreshold));
} else if (deadlockThreshold <= longEventErrorThreshold) {
problems.append(NEW_LINE_AND_BULLET +
NLS.bind(Messages.EventLoopMonitorThread_deadlock_threshold_too_low_error_2,
deadlockThreshold, longEventErrorThreshold));
}
if (problems.length() != 0) {
throw new IllegalArgumentException(
NLS.bind(Messages.EventLoopMonitorThread_invalid_argument_error_1,
problems.toString()));
}
}
}
/**
* Describes the state of the event loop. Visible for testing.
*/
private class EventLoopState implements Listener {
/**
* The number of {@link SWT#PreEvent PreEvent}s minus the number of
* {@link SWT#PostEvent PostEvent}s since the last
* {@link SWT#PreExternalEventDispatch PreExternalEventDispatch}.
*/
private int nestingLevel;
/**
* The stack of nesting levels. The current nesting level is pushed to the stack on
* {@link SWT#PreExternalEventDispatch PreExternalEventDispatch} event and popped from
* the stack on {@link SWT#PostExternalEventDispatch PostExternalEventDispatch} event.
*/
private int[] nestingLevelStack = new int[64];
private int nestingLevelStackSize;
@Override
public void handleEvent(Event event) {
/*
* Freeze monitoring involves seeing long intervals between PreEvent/PostEvent messages.
* For example:
* 1) Log if a top-level or nested dispatch takes too long (interval is between PreEvent
* and PostEvent).
* 2) Log if preparation before popping up a dialog takes too long (interval is between
* two PreEvent messages).
* 3) Log if processing after dismissing a dialog takes too long (interval is between
* two PostEvent messages).
* 4) Log if there is a long delay between nested calls (interval is between PostEvent
* and PreEvent). This could happen after a dialog is dismissed, the code does too
* much processing on the UI thread, and then pops up another dialog.
* 5) Don't log for long delays between top-level events (interval is between PostEvent
* and PreEvent at the top level), which should involve sleeping.
*
* Tracking of PreExternalEventDispatch/PostExternalEventDispatch events allows us to
* handle items 4 and 5 above since we can tell if a long delay between an PostEvent and
* a PreEvent are due to an idle state (e.g. in Display.sleep()) or a UI freeze.
*
* Since an idle system can potentially block in an external event loop for a long time,
* we need to avoid logging long delays during that time. The eventStartOrResumeTime
* field is set to zero when the thread is in an external event loop so that deadlock
* logging can be avoided for this case.
*/
switch (event.type) {
case SWT.PreEvent:
if (!doesEventIndicateResponsiveUI(event.detail)) {
break; // Ignore events that may be produced during a UI freeze.
}
nestingLevel++;
if (eventHistory != null) {
eventHistory.recordEvent(event.type, event.detail, nestingLevel);
}
// Log a long interval, start the timer.
handleEventTransition(true, true);
break;
case SWT.PostEvent:
if (!doesEventIndicateResponsiveUI(event.detail)) {
break; // Ignore events that may be produced during a UI freeze.
}
if (--nestingLevel < 0) {
// This may happen if some PreEvent events had occurred before we
// started listening to SWT events.
nestingLevel = 0;
}
if (eventHistory != null) {
eventHistory.recordEvent(event.type, event.detail, nestingLevel);
}
// Log a long interval, start the timer if inside another event.
handleEventTransition(true, nestingLevel > 0);
break;
case SWT.PreExternalEventDispatch:
saveAndResetNestingLevel();
if (eventHistory != null) {
eventHistory.recordEvent(event.type, event.detail, nestingLevel);
}
// Log a long interval, stop the timer.
handleEventTransition(true, false);
break;
case SWT.PostExternalEventDispatch:
restoreNestingLevel();
if (eventHistory != null) {
eventHistory.recordEvent(event.type, event.detail, nestingLevel);
}
// Don't log a long interval, start the timer if inside another event.
handleEventTransition(false, nestingLevel > 0);
break;
default:
break;
}
}
/**
* Returns {@code true} if dispatching of an event of the given type indicates that the UI
* is responsive. Events that may be produced during UI freezes are irrelevant to UI
* responsiveness monitoring.
*/
private boolean doesEventIndicateResponsiveUI(int eventType) {
switch (eventType) {
case SWT.Skin:
case SWT.MeasureItem:
case SWT.Dispose:
return false;
default:
return true;
}
}
private void saveAndResetNestingLevel() {
if (nestingLevelStackSize < nestingLevelStack.length) {
nestingLevelStack[nestingLevelStackSize++] = nestingLevel;
nestingLevel = 0;
} else {
MonitoringPlugin.logError(
NLS.bind(Messages.EventLoopMonitorThread_max_event_loop_depth_exceeded_1,
nestingLevelStack.length), null);
shutdown();
}
}
private void restoreNestingLevel() {
if (nestingLevelStackSize > 0) {
nestingLevel = nestingLevelStack[--nestingLevelStackSize];
} else {
// This may happen if some PreExternalEventDispatch events had occurred before we
// started listening to SWT events.
nestingLevel = 0;
}
}
}
/**
* Circular buffer recording SWT events. Used for tracing.
*/
private static class EventHistory {
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); //$NON-NLS-1$
private static class EventInfo {
long timestamp;
int eventType;
int detail;
int nestingLevel;
}
private final EventInfo[] buffer;
private int start; // Index of the first recorded event.
private int size; // Number of recorded events.
EventHistory(int capacity) {
buffer = new EventInfo[capacity];
for (int i = 0; i < capacity; i++) {
buffer[i] = new EventInfo();
}
}
synchronized void recordEvent(int eventType, int detail, int nestingLevel) {
int j = (start + size) % buffer.length;
EventInfo event = buffer[j];
event.timestamp = System.currentTimeMillis();
event.eventType = eventType;
event.detail = detail;
event.nestingLevel = nestingLevel;
if (size < buffer.length) {
size++;
} else if (++start >= buffer.length) {
start = 0;
}
}
synchronized String extractAndClear() {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < size; i++) {
int j = (start + i) % buffer.length;
EventInfo eventInfo = buffer[j];
buf.append(TIME_FORMAT.format(new Date(eventInfo.timestamp)));
buf.append(": "); //$NON-NLS-1$
switch (eventInfo.eventType) {
case SWT.PreEvent:
buf.append("PreEvent"); //$NON-NLS-1$
break;
case SWT.PostEvent:
buf.append("PostEvent"); //$NON-NLS-1$
break;
case SWT.PreExternalEventDispatch:
buf.append("PreExternalEventDispatch"); //$NON-NLS-1$
break;
case SWT.PostExternalEventDispatch:
buf.append("PostExternalEventDispatch"); //$NON-NLS-1$
break;
default:
buf.append("Event "); //$NON-NLS-1$
buf.append(eventInfo.eventType);
}
buf.append(' ');
buf.append(eventInfo.detail);
buf.append(" nesting level: "); //$NON-NLS-1$
buf.append(eventInfo.nestingLevel);
buf.append('\n');
}
size = 0;
return buf.toString();
}
}
// Accessed only by the UI thread. */
private final EventLoopState eventLoopState = new EventLoopState();
/*
* 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 the monitoring thread. 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.
*/
private volatile long eventStartOrResumeTime;
// Accessed by both the UI and monitoring threads.
private final int longEventWarningThreshold;
private final AtomicBoolean cancelled = new AtomicBoolean(false);
private final AtomicReference<LongEventInfo> eventToPublish =
new AtomicReference<LongEventInfo>(null);
// Accessed only by the monitoring thread.
private final List<IUiFreezeEventLogger> externalLoggers =
new ArrayList<IUiFreezeEventLogger>();
private DefaultUiFreezeEventLogger defaultLogger;
private final Display display;
private final FilterHandler uiThreadFilter;
private final FilterHandler noninterestingThreadFilter;
private final int longEventErrorThreshold;
private final long sampleInterval;
private final long allThreadsSampleInterval;
private final int maxStackSamples;
private final int maxLoggedStackSamples;
private final long deadlockThreshold;
private final long uiThreadId;
private final Object sleepMonitor;
private final boolean logToErrorLog;
private EventHistory eventHistory;
private ThreadMXBean threadMXBean;
private boolean dumpLockedMonitors;
private boolean dumpLockedSynchronizers;
private long monitoringThreadId;
/**
* 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$
if (tracer != null) {
eventHistory = new EventHistory(EVENT_HISTORY_SIZE);
}
Assert.isNotNull(args);
args.checkParameters();
setDaemon(true);
setPriority(NORM_PRIORITY + 1);
display = getDisplay();
uiThreadId = this.display.getThread().getId();
longEventWarningThreshold = Math.max(args.longEventWarningThreshold, 3);
longEventErrorThreshold = Math.max(args.longEventErrorThreshold, longEventWarningThreshold);
maxLoggedStackSamples = Math.max(args.maxStackSamples, 0);
maxStackSamples = 2 * maxLoggedStackSamples;
sampleInterval = longEventWarningThreshold * 2 / 3;
allThreadsSampleInterval = longEventErrorThreshold * 2 / 3;
deadlockThreshold = args.deadlockThreshold;
logToErrorLog = args.logToErrorLog;
uiThreadFilter = new FilterHandler(args.uiThreadFilter);
noninterestingThreadFilter = new FilterHandler(args.noninterestingThreadFilter);
sleepMonitor = new Object();
}
/**
* 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, eventLoopState);
display.removeListener(SWT.PostEvent, eventLoopState);
display.removeListener(SWT.PreExternalEventDispatch, eventLoopState);
display.removeListener(SWT.PostExternalEventDispatch, eventLoopState);
}
wakeUp();
}
/**
* For testing only.
*/
final void handleEvent(Event event) {
eventLoopState.handleEvent(event);
}
// Called on the UI thread!
private void handleEventTransition(boolean attemptToLogLongDelay, boolean startEventTimer) {
/*
* 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) eventToPublish 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. This bogus sample is
* removed before logging.
*/
long currTime = getTimestamp();
if (attemptToLogLongDelay) {
long startTime = eventStartOrResumeTime;
if (startTime != 0) {
int duration = (int) (currTime - startTime);
if (duration >= longEventWarningThreshold) {
LongEventInfo info = new LongEventInfo(startTime, duration);
eventToPublish.set(info);
wakeUp();
}
}
}
eventStartOrResumeTime = startEventTimer ? currTime : 0;
}
@Override
public void run() {
if (logToErrorLog) {
defaultLogger = new DefaultUiFreezeEventLogger(longEventErrorThreshold);
}
loadLoggerExtensions();
if (!logToErrorLog && externalLoggers.isEmpty()) {
MonitoringPlugin.logWarning(Messages.EventLoopMonitorThread_logging_disabled_error);
}
monitoringThreadId = Thread.currentThread().getId();
threadMXBean = ManagementFactory.getThreadMXBean();
dumpLockedMonitors = threadMXBean.isObjectMonitorUsageSupported();
dumpLockedSynchronizers = threadMXBean.isSynchronizerUsageSupported();
boolean contentionMonitoringSupported = threadMXBean.isThreadContentionMonitoringSupported();
/*
* 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;
long deadlockTimerStart = 0;
final long pollingNyquistDelay = sampleInterval / 2;
long pollingDelay = 0; // Immediately updated by resetStalledEventState.
long grabStackSampleAt = 0; // Immediately updated by resetStalledEventState.
long lastEventStartOrResumeTime = 0; // Immediately updated by resetStalledEventState.
StackSample[] stackSamples = new StackSample[maxStackSamples];
int numSamples = 0;
boolean starvedAwake = false;
boolean starvedAsleep = false;
boolean dumpAllThreads = false;
// Register for events
display.asyncExec(() -> registerDisplayListeners());
long currTime = getTimestamp();
while (!cancelled.get()) {
long sleepFor;
if (resetStalledEventState) {
long eventTime = eventStartOrResumeTime;
deadlockTimerStart = eventTime;
if (eventTime == 0) {
eventTime = currTime;
}
grabStackSampleAt = eventTime + sampleInterval;
numSamples = 0;
starvedAwake = false;
starvedAsleep = false;
if (dumpAllThreads) {
// Stop capturing stacks of all threads.
dumpAllThreads = false;
if (contentionMonitoringSupported) {
threadMXBean.setThreadContentionMonitoringEnabled(false);
}
}
pollingDelay = sampleInterval;
sleepFor = pollingNyquistDelay;
resetStalledEventState = false;
} else if (lastEventStartOrResumeTime == 0) {
sleepFor = pollingNyquistDelay;
} else {
sleepFor = Math.min(pollingNyquistDelay, Math.max(1, grabStackSampleAt - currTime));
}
// Allow the discarded stack samples to be garbage collected.
for (int i = numSamples; i < stackSamples.length && stackSamples[i] != null; i++) {
stackSamples[i] = null;
}
// 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 starvedAwakeCurrentCycle = awakeDuration > (sleepFor + longEventWarningThreshold / 2);
if (starvedAwakeCurrentCycle) {
starvedAwake = true;
}
sleepForMillis(sleepFor);
currTime = getTimestamp();
long currEventStartOrResumeTime = eventStartOrResumeTime;
long sleepDuration = currTime - sleepAt;
boolean starvedAsleepCurrentCycle = sleepDuration > (sleepFor + longEventWarningThreshold / 2);
if (starvedAsleepCurrentCycle) {
starvedAsleep = true;
}
boolean starved = starvedAsleepCurrentCycle || starvedAwakeCurrentCycle;
/*
* 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 (tracer != null && starved) {
if (starvedAwakeCurrentCycle) {
tracer.trace(String.format(
"Starvation detected! Polling loop took a significant amount of threshold: %dms", //$NON-NLS-1$
awakeDuration));
}
if (starvedAsleepCurrentCycle) {
tracer.trace(String.format(
"Starvation detected! Expected to sleep for %dms but actually slept for %dms", //$NON-NLS-1$
sleepFor, sleepDuration));
}
}
} else if (lastEventStartOrResumeTime != 0) {
if (!dumpAllThreads && currTime >= lastEventStartOrResumeTime + allThreadsSampleInterval) {
// Start capturing stacks of all threads.
dumpAllThreads = true;
if (contentionMonitoringSupported) {
threadMXBean.setThreadContentionMonitoringEnabled(true);
}
}
if (deadlockTimerStart != 0) {
long totalDuration = currTime - deadlockTimerStart;
if (totalDuration >= deadlockThreshold) {
if (numSamples > maxLoggedStackSamples) {
decimate(stackSamples, numSamples, maxLoggedStackSamples);
numSamples = maxLoggedStackSamples;
}
if (uiThreadFilter.shouldLogEvent(stackSamples, numSamples, uiThreadId)) {
logEvent(new UiFreezeEvent(deadlockTimerStart, totalDuration,
Arrays.copyOf(stackSamples, numSamples),
true, starvedAwake, starvedAsleep));
deadlockTimerStart = 0; // Don't log potential deadlock more than once.
}
}
}
// Collect additional stack traces if enough time has elapsed.
if (maxStackSamples > 0 && currTime >= grabStackSampleAt) {
if (numSamples == maxStackSamples) {
numSamples = maxStackSamples / 2;
decimate(stackSamples, maxStackSamples, numSamples);
}
ThreadInfo[] threadStacks = captureThreadStacks(dumpAllThreads);
stackSamples[numSamples++] = new StackSample(getTimestamp(), threadStacks);
if (numSamples == maxStackSamples) {
pollingDelay *= 2; // Reduce polling frequency.
}
grabStackSampleAt += pollingDelay;
}
}
// If a stalled event has finished, publish it and mark that the information should
// be reset.
LongEventInfo eventSnapshot = eventToPublish.getAndSet(null);
if (eventSnapshot != null) {
long eventEnd = eventSnapshot.start + eventSnapshot.duration;
// Remove stack samples collected after the end of the event.
while (numSamples > 0 && eventEnd <= stackSamples[numSamples - 1].getTimestamp()) {
--numSamples;
}
if (numSamples > maxLoggedStackSamples) {
// Remove the last stack sample if it is too close to the end of the event.
if (eventEnd - stackSamples[numSamples - 1].getTimestamp() < sampleInterval) {
--numSamples;
}
}
if (numSamples > maxLoggedStackSamples) {
decimate(stackSamples, numSamples, maxLoggedStackSamples);
numSamples = maxLoggedStackSamples;
}
if (uiThreadFilter.shouldLogEvent(stackSamples, numSamples, uiThreadId)) {
logEvent(new UiFreezeEvent(eventSnapshot.start, eventSnapshot.duration,
Arrays.copyOf(stackSamples, numSamples),
false, starvedAwake, starvedAsleep));
}
resetStalledEventState = true;
}
lastEventStartOrResumeTime = currEventStartOrResumeTime;
}
}
private ThreadInfo[] captureThreadStacks(boolean dumpAllThreads) {
if (dumpAllThreads) {
ThreadInfo[] threadStacks =
threadMXBean.dumpAllThreads(dumpLockedMonitors, dumpLockedSynchronizers);
// Remove the info for the monitoring thread.
int index = 0;
for (int i = 0; i < threadStacks.length; i++) {
ThreadInfo thread = threadStacks[i];
long threadId = thread.getThreadId();
// Skip the stack trace of the event loop monitoring thread.
if (threadId != monitoringThreadId) {
if (threadId == uiThreadId) {
// Swap the UI thread to first slot in the array if it is not there already.
if (index != 0) {
thread = threadStacks[0];
threadStacks[0] = threadStacks[i];
}
} else if (!isInteresting(thread)) {
continue; // Skip the non-interesting thread.
}
threadStacks[index++] = thread;
}
}
return Arrays.copyOf(threadStacks, index);
} else {
return new ThreadInfo[] { threadMXBean.getThreadInfo(uiThreadId, Integer.MAX_VALUE) };
}
}
/**
* A thread is considered interesting if its stack trace includes at least one frame not
* matching any of the methods in {@link #noninterestingThreadFilter}.
*/
private boolean isInteresting(ThreadInfo thread) {
for (StackTraceElement element : thread.getStackTrace()) {
if (!noninterestingThreadFilter.matchesFilter(element)) {
return true;
}
}
return false;
}
private static Display getDisplay() throws IllegalStateException {
IWorkbench workbench = MonitoringPlugin.getDefault().getWorkbench();
if (workbench == null) {
throw new IllegalStateException(Messages.EventLoopMonitorThread_workbench_was_null);
}
Display display = workbench.getDisplay();
if (display == null) {
throw new IllegalStateException(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 void loadLoggerExtensions() {
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(NLS.bind(
Messages.EventLoopMonitorThread_invalid_logger_type_error_4,
new Object[] { object.getClass().getName(),
IUiFreezeEventLogger.class.getClass().getSimpleName(),
EXTENSION_ID, element.getContributor().getName() }));
}
} catch (CoreException e) {
MonitoringPlugin.logError(e.getMessage(), e);
}
}
}
private void registerDisplayListeners() {
display.addListener(SWT.PreEvent, eventLoopState);
display.addListener(SWT.PostEvent, eventLoopState);
display.addListener(SWT.PreExternalEventDispatch, eventLoopState);
display.addListener(SWT.PostExternalEventDispatch, eventLoopState);
}
/**
* Reduces number of samples by weeding some of them out. The remaining samples are chosen to
* represent the whole range of samples with a slight preference given to later samples that
* are more likely to contain stacks of all threads.
*
* @param samples the array of samples
* @param fromSize the number of samples to choose from in the array
* @param toSize the number of samples to select
*/
private static void decimate(StackSample[] samples, int fromSize, int toSize) {
for (int i = 0; i < toSize; ++i) {
int j = ((i + 1) * fromSize - 1) / toSize;
samples[i] = samples[j];
}
}
private void wakeUp() {
synchronized (sleepMonitor) {
sleepMonitor.notify();
}
}
/**
* Writes a UI freeze event to the log.
*/
private void logEvent(UiFreezeEvent event) {
if (tracer != null) {
tracer.trace("Logging " + event + "Prior events:\n" + eventHistory.extractAndClear()); //$NON-NLS-1$//$NON-NLS-2$
}
if (logToErrorLog) {
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);
}
}
}
}