/*******************************************************************************
* 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:
* Marcus Eng (Google) - initial API and implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.ui.internal.monitoring;
import java.lang.management.LockInfo;
import java.lang.management.ThreadInfo;
import java.text.SimpleDateFormat;
import java.util.Date;
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;
/**
* 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 final long longEventErrorThresholdMillis;
private static class StackTrace extends Throwable {
private static final long serialVersionUID = -2829405667536819137L;
StackTrace(StackTraceElement[] stackTraceElements) {
setStackTrace(stackTraceElements);
}
@Override
public String toString() {
return Messages.DefaultUiFreezeEventLogger_stack_trace_header;
}
}
private static class SeverityMultiStatus extends MultiStatus {
public SeverityMultiStatus(int severity, String pluginId, String message, Throwable exception) {
super(pluginId, OK, message, exception);
setSeverity(severity);
}
}
public DefaultUiFreezeEventLogger(long longEventErrorThresholdMillis) {
this.longEventErrorThresholdMillis = longEventErrorThresholdMillis;
}
/**
* Converts the given {@link UiFreezeEvent} into a {@link MultiStatus} and writes 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 template = event.isStillRunning()
? Messages.DefaultUiFreezeEventLogger_ui_freeze_ongoing_header_2
: Messages.DefaultUiFreezeEventLogger_ui_freeze_finished_header_2;
long duration = event.getTotalDuration();
String format = duration >= 100000 ? "%.0f" : duration >= 10 ? "%.2g" : "%.1g"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String header = NLS.bind(template, String.format(format, duration / 1000.0), startTime);
StackSample[] stackTraceSamples = event.getStackTraceSamples();
if (stackTraceSamples.length == 0 && (event.isStarvedAwake() || event.isStarvedAsleep())) {
String note =
(event.isStarvedAwake() || event.isStarvedAsleep()) ?
Messages.DefaultUiFreezeEventLogger_starved_awake_and_asleep :
event.isStarvedAwake() ?
Messages.DefaultUiFreezeEventLogger_starved_awake :
Messages.DefaultUiFreezeEventLogger_starved_asleep;
header += note;
}
int severity = duration >= longEventErrorThresholdMillis ?
IStatus.ERROR : IStatus.WARNING;
MultiStatus loggedEvent =
new SeverityMultiStatus(severity, PreferenceConstants.PLUGIN_ID, header, null);
for (StackSample sample : stackTraceSamples) {
double deltaInSeconds = (sample.getTimestamp() - lastTimestamp) / 1000.0;
ThreadInfo[] threads = sample.getStackTraces();
// The first thread is guaranteed to be the display thread.
Throwable stackTrace = new StackTrace(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) {
Throwable stackTrace = new StackTrace(thread.getStackTrace());
StringBuilder threadText = createThreadMessage(thread);
String lockName = thread.getLockName();
if (lockName != null && !lockName.isEmpty()) {
LockInfo lock = thread.getLockInfo();
String lockOwnerName = thread.getLockOwnerName();
if (lockOwnerName == null) {
threadText.append(NLS.bind(
Messages.DefaultUiFreezeEventLogger_waiting_for_1,
getClassAndHashCode(lock)));
} else {
threadText.append(NLS.bind(
Messages.DefaultUiFreezeEventLogger_waiting_for_with_lock_owner_3,
new Object[] { getClassAndHashCode(lock), 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$
}
}