blob: 3d8353adf067c8f26347175335dc67322954873d [file] [log] [blame]
/**
* Copyright (c) 2015 Codetrails GmbH.
* 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
*/
package org.eclipse.epp.logging.aeri.core.util;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Arrays.asList;
import static org.apache.commons.lang3.ArrayUtils.isEmpty;
import static org.apache.commons.lang3.ArrayUtils.subarray;
import static org.apache.commons.lang3.StringUtils.*;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.Nullable;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.hash.Hasher;
public class Statuses {
private static final String NAME_DISPLAY = "org.eclipse.swt.widgets.Display";
private static final String NAME_EVENT_TABLE = "org.eclipse.swt.widgets.EventTable";
private static final String NAME_WIDGET = "org.eclipse.swt.widgets.Widget";
private static final String NAME_MAIN = "org.eclipse.equinox.launcher.Main";
private static final String NAME_WINDOW = "org.eclipse.jface.window.Window";
private static final String NAME_WORKSPACE = "org.eclipse.core.internal.resources.Workspace";
private static final String NAME_METHOD_REQUESTOR = "org.eclipse.e4.core.internal.di.MethodRequestor";
private static final StackTraceElement DISPLAY__FILTER_EVENT = new StackTraceElement(NAME_DISPLAY, "filterEvent", null, -1);
private static final StackTraceElement DISPLAY__READ_AND_DISPATCH = new StackTraceElement(NAME_DISPLAY, "readAndDispatch", null, -1);
private static final StackTraceElement DISPLAY__RUN_DEFERRED_EVENTS = new StackTraceElement(NAME_DISPLAY, "runDeferredEvents", null,
-1);
private static final StackTraceElement DISPLAY__RUN_ASYNC_MESSAGES = new StackTraceElement(NAME_DISPLAY, "runAsyncMessages", null, -1);
private static final StackTraceElement DISPLAY__RUN_SETTINGS = new StackTraceElement(NAME_DISPLAY, "runSettings", null, -1);
private static final StackTraceElement DISPLAY__RUN_POPUPS = new StackTraceElement(NAME_DISPLAY, "runPopups", null, -1);
private static final StackTraceElement MAIN__RUN = new StackTraceElement(NAME_MAIN, "run", null, -1);
private static final StackTraceElement DISPLAY__SEND_EVENT = new StackTraceElement(NAME_DISPLAY, "sendEvent", null, -1);
private static final StackTraceElement EVENT_TABLE__SEND_EVENT = new StackTraceElement(NAME_EVENT_TABLE, "sendEvent", null, -1);
private static final StackTraceElement WIDGET__SEND_EVENT = new StackTraceElement(NAME_WIDGET, "sendEvent", null, -1);
private static final StackTraceElement WINDOW__RUN_EVENT_LOOP = new StackTraceElement(NAME_WINDOW, "runEventLoop", null, -1);
private static final StackTraceElement WINDOW__OPEN = new StackTraceElement(NAME_WINDOW, "open", null, -1);
private static final StackTraceElement WORKSPACE__BUILD = new StackTraceElement(NAME_WORKSPACE, "build", null, -1);
private static final StackTraceElement METHOD_REQUESTOR_EXECUTE = new StackTraceElement(NAME_METHOD_REQUESTOR, "execute", null, -1);
private static StackTraceElement[] rule(StackTraceElement... sequence) {
return sequence;
}
public static StackTraceElement[] removeClassesFromTop(StackTraceElement[] trace, Set<String> classNames) {
checkNotNull(trace);
checkNotNull(classNames);
int index = findStart(trace, classNames);
StackTraceElement[] res = subarray(trace, index, trace.length);
return res;
}
/**
* Returns the first index that was not in the ignored classes list - or trace.length if all classes were ignored.
*
* @param ignoredClasses
* list of fully qualified class names
*/
private static int findStart(StackTraceElement[] trace, Set<String> ignoredClasses) {
for (int i = 0; i < trace.length; i++) {
StackTraceElement frame = trace[i];
if (!ignoredClasses.contains(frame.getClassName())) {
// This is the first not-blacklisted frame return that index:
return i;
}
}
// This trace apparently only consists of blacklisted frames
return trace.length;
}
/**
* Gets a {@link Throwable} cause chain as a list. The first entry in the list will be {@code throwable} followed by its cause
* hierarchy.
*/
public static Throwable[] getCausalChain(Throwable throwable) {
checkNotNull(throwable);
return Throwables.getCausalChain(throwable).toArray(new Throwable[0]);
}
public static StackTraceElement[] normalize(Throwable throwable) {
checkNotNull(throwable);
Throwable[] chain = getCausalChain(throwable);
List<StackTraceElement> normalized = Lists.newLinkedList();
// go from the bottom up to create a normalized stacktrace
for (int i = chain.length; i-- > 0;) {
StackTraceElement[] causeTrace = chain[i].getStackTrace();
if (i == 0) {
// remaining chain.length is 0 -> copy everything and return
normalized.addAll(Arrays.asList(causeTrace));
break;
}
// starting at the bottom of the current stacktrace and its parent, find the first frame not in the parent stacktrace:
StackTraceElement[] parentTrace = chain[i - 1].getStackTrace();
int ci = causeTrace.length - 1;
int ei = parentTrace.length - 1;
while (ci >= 0 && ei >= 0 && equal(causeTrace[ci], parentTrace[ei])) {
ci--;
ei--;
}
// add those unique frames to the final stacktrace:
StackTraceElement[] uniqueFrames = subarray(causeTrace, 0, Math.max(0, ci + 1));
normalized.addAll(Arrays.asList(causeTrace));
}
StackTraceElement[] res = normalized.toArray(new StackTraceElement[0]);
return res;
}
public static StackTraceElement[] truncate(StackTraceElement[] trace) {
checkNotNull(trace);
int index = findCutOffIndexForRelevantFrames(trace);
return ArrayUtils.subarray(trace, 0, index);
}
private static int findCutOffIndexForRelevantFrames(StackTraceElement[] trace) {
// rule(WIDGET__SEND_EVENT, WIDGET__SEND_EVENT, WIDGET__SEND_EVENT),
// rule(EVENT__TABLE_SEND_EVENT, DISPLAY__SEND_EVENT, WIDGET__SEND_EVENT),
// rule(DISPLAY__RUN_ASYNC_MESSAGES),
// rule(DISPLAY__RUN_DEFERRED_EVENTS),
// rule(DISPLAY__READ_AND_DISPATCH, NOT_WINDOW_RUN__EVENT_LOOP, NOT_WINDOW__OPEN),
// rule(DISPLAY__RUN_POPUPS),
// rule(DISPLAY__RUN_SETTINGS),
// rule(DISPLAY__FILTER_EVENT),
// rule(WORKSPACE__BUILD));
// a set of yet hardcoded rules
for (int i = 0; i < trace.length; i++) {
StackTraceElement frame = trace[i];
if (isSameFrame(trace, i, WIDGET__SEND_EVENT, WIDGET__SEND_EVENT, WIDGET__SEND_EVENT)) {
return i + 3;
} else if (isSameFrame(trace, i, EVENT_TABLE__SEND_EVENT, DISPLAY__SEND_EVENT, WIDGET__SEND_EVENT)) {
return i + 3;
} else if (isSameFrame(frame, DISPLAY__READ_AND_DISPATCH)) {
if (isSameFrame(trace, i + 1, WINDOW__RUN_EVENT_LOOP, WINDOW__OPEN)) {
// Typical dialog startup sequence - DO NOT CUT OFF HERE
} else {
return i + 1;
}
} else if (isSameFrame(frame, DISPLAY__RUN_ASYNC_MESSAGES)) {
return i + 1;
} else if (isSameFrame(frame, DISPLAY__RUN_DEFERRED_EVENTS)) {
return i + 1;
} else if (isSameFrame(frame, DISPLAY__RUN_POPUPS)) {
return i + 1;
} else if (isSameFrame(frame, DISPLAY__RUN_SETTINGS)) {
return i + 1;
} else if (isSameFrame(frame, DISPLAY__FILTER_EVENT)) {
return i + 1;
} else if (isSameFrame(frame, WORKSPACE__BUILD)) {
return i + 1;
} else if (isSameFrame(frame, METHOD_REQUESTOR_EXECUTE)) {
return i + 1;
}
}
return trace.length;
}
private static boolean isMain(StackTraceElement[] trace) {
if (isEmpty(trace)) {
return false;
}
StackTraceElement last = trace[trace.length - 1];
return isSameFrame(last, MAIN__RUN, true);
}
private static boolean isSameFrame(StackTraceElement frame, StackTraceElement test) {
return isSameFrame(frame, test, true);
}
private static boolean isSameFrame(StackTraceElement last, StackTraceElement test, boolean ignoreLineNumber) {
return equal(last.getClassName(), test.getClassName()) && equal(last.getMethodName(), test.getMethodName())
&& (ignoreLineNumber || last.getLineNumber() == test.getLineNumber());
}
private static boolean isSameFrame(StackTraceElement[] trace, int index, StackTraceElement test) {
if (index >= trace.length) {
return false;
}
StackTraceElement frame = trace[index];
return isSameFrame(frame, test);
}
private static boolean isSameFrame(StackTraceElement[] trace, int index, StackTraceElement... test) {
if (test.length > trace.length - index) {
return false;
}
for (int i = 0; i < test.length; i++) {
if (!isSameFrame(trace[index + i], test[i])) {
return false;
}
}
return true;
}
public static boolean isEmptyJobStatus(IStatus status) {
return isEmptyJobThrowable(status.getException());
}
public static boolean isEmptyJobThrowable(Throwable throwable) {
return isEmptyJobStackTrace(throwable.getStackTrace()) && throwable.getCause() == null;
}
public static boolean isEmptyJobStackTrace(StackTraceElement[] trace) {
// expected:
// at org.eclipse.core.internal.jobs.JobManager.endJob(JobManager.java:701)
// at org.eclipse.core.internal.jobs.WorkerPool.endJob(WorkerPool.java:105)
// at org.eclipse.core.internal.jobs.Worker.run(Worker.java:72)
for (StackTraceElement frame : trace) {
if (!startsWith(frame.getClassName(), "org.eclipse.core.internal.jobs.")) {
return false;
}
}
return true;
}
public static String newFingerprint(Throwable input, final boolean includeMessages, final boolean includeLineNumbers) {
final Hasher hasher = Reports.newHasher();
new StatusSwitch<Hasher>() {
@Override
public Hasher caseStackTraceElement(StackTraceElement object) {
hasher.putString(stripToEmpty(object.getClassName()), UTF_8);
hasher.putString(stripToEmpty(object.getMethodName()), UTF_8);
if (includeLineNumbers) {
hasher.putInt(object.getLineNumber());
}
return null;
}
@Override
public Hasher caseThrowable(Throwable object) {
hasher.putString(stripToEmpty(object.getClass().getName()), UTF_8);
if (includeMessages) {
hasher.putString(stripToEmpty(object.getMessage()), UTF_8);
}
return null;
}
}.doSwitch(input);
return hasher.hash().toString();
}
public static String newFingerprint(StackTraceElement[] trace, final boolean includeLineNumbers) {
final Hasher hasher = Reports.newHasher();
for (StackTraceElement frame : trace) {
hasher.putString(stripToEmpty(frame.getClassName()), UTF_8);
hasher.putString(stripToEmpty(frame.getMethodName()), UTF_8);
if (includeLineNumbers) {
hasher.putInt(frame.getLineNumber());
}
}
return hasher.hash().toString();
}
/**
* Traverses the status hierarchy until it finds a status with a meaningful exception. Empty Worker statuses are considered as
* not-meaningful and most likely replaced by one of its children.
*
* If no better child could be found the input status is returned.
*/
public static IStatus findRelevantStatus(IStatus status) {
if (isEmptyJobStatus(status)) {
Queue<IStatus> bsf = Lists.newLinkedList();
bsf.addAll(asList(status.getChildren()));
while (!bsf.isEmpty()) {
IStatus poll = bsf.poll();
if (poll.getException() != null) {
return poll;
} else {
bsf.addAll(asList(poll.getChildren()));
}
}
}
return status;
}
public static boolean isUiFreeze(IStatus status) {
return equal("org.eclipse.ui.monitoring", status.getPlugin());
}
public static String newThrowableFingerprint(@Nullable Throwable input, final boolean includeMessages,
final boolean includeLineNumbers) {
final Hasher hasher = Reports.newHasher();
new TraceFingerprintComputer(hasher, includeLineNumbers, includeMessages).doSwitch(input);
return hasher.hash().toString();
}
public static String traceIdentityHash(@Nullable IStatus report) {
final Hasher hasher = Reports.newHasher();
new TraceFingerprintComputer(hasher, true, false).doSwitch(report);
return hasher.hash().toString();
}
private static final class TraceFingerprintComputer extends StatusSwitch<Hasher> {
private final Hasher hasher;
private final boolean includeLineNumbers;
private final boolean includeMessages;
private TraceFingerprintComputer(Hasher hasher, boolean includeLineNumbers, boolean includeMessages) {
this.hasher = hasher;
this.includeLineNumbers = includeLineNumbers;
this.includeMessages = includeMessages;
}
@Override
public Hasher caseStackTraceElement(StackTraceElement object) {
hasher.putString(stripToEmpty(object.getClassName()), UTF_8);
hasher.putString(stripToEmpty(object.getMethodName()), UTF_8);
if (includeLineNumbers) {
hasher.putInt(object.getLineNumber());
}
return null;
}
@Override
public Hasher caseThrowable(Throwable object) {
if (includeMessages) {
hasher.putString(stripToEmpty(object.getMessage()), UTF_8);
}
return null;
}
}
}