| /** |
| * 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; |
| } |
| } |
| } |