/******************************************************************************* * 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 * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.ui.internal.monitoring; import java.lang.management.ThreadInfo; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.core.runtime.Assert; import org.eclipse.ui.monitoring.StackSample; import org.eclipse.ui.monitoring.UiFreezeEvent; /** * Checks if the {@link UiFreezeEvent} matches any defined filters. * <p> * <strong>This class is not thread safe.<strong> * </p> */ public class FilterHandler { private static final String DOUBLE_BACKSLASH = "\\\\"; //$NON-NLS-1$ /** Reusable object used to avoid object creation in filtering methods. */ private final CompoundName compoundName = new CompoundName("", ""); //$NON-NLS-1$//$NON-NLS-2$ /** * Groups the class name and method name defined in the filter. */ private static class StackFrame implements Comparable<StackFrame> { final String className; final String methodName; public StackFrame(String className, String methodName) { this.className = className; this.methodName = methodName; } @Override public int compareTo(StackFrame other) { int c = methodName.compareTo(other.methodName); if (c != 0) { return c; } return className.compareTo(other.className); } } private static class CompoundName implements CharSequence { private String first; private String last; CompoundName(String first, String last) { Assert.isNotNull(first); Assert.isNotNull(last); this.first = first; this.last = last; } void reset(String first, String last) { Assert.isNotNull(first); Assert.isNotNull(last); this.first = first; this.last = last; } @Override public int length() { return first.length() + 1 + last.length(); } @Override public char charAt(int index) { int firstLen = first.length(); if (index < firstLen) { return first.charAt(index); } else if (index == firstLen) { return '.'; } else { return last.charAt(index - firstLen - 1); } } @Override public CharSequence subSequence(int start, int end) { int lastOffset = first.length() + 1; // Offset of the last name in the sequence. if (end < lastOffset) { return first.subSequence(start, end); } else if (start < lastOffset) { return new CompoundName(first.substring(start), last.substring(0, end - lastOffset)); } else { return last.subSequence(start - lastOffset, end - lastOffset); } } @Override public String toString() { return first + '.' + last; } } private final StackFrame[] filterFrames; private final Pattern[] filterPatterns; /** * Creates the filter. * * @param commaSeparatedMethods comma separated fully qualified method names to filter on. * Method names may contain wildcard characters '*' and '?'. */ public FilterHandler(String commaSeparatedMethods) { String[] filters = commaSeparatedMethods.split(","); //$NON-NLS-1$ List<StackFrame> stackFrames = new ArrayList<StackFrame>(filters.length); List<Pattern> stackPatterns = new ArrayList<Pattern>(filters.length); for (String filter : filters) { if (containsWildcards(filter)) { Pattern pattern = createPattern(filter); stackPatterns.add(pattern); } else { int lastDot = filter.lastIndexOf('.'); stackFrames.add(lastDot >= 0 ? new StackFrame(filter.substring(0, lastDot), filter.substring(lastDot + 1)) : new StackFrame("", filter)); //$NON-NLS-1$ } } Collections.sort(stackFrames); filterFrames = stackFrames.toArray(new StackFrame[stackFrames.size()]); filterPatterns = stackPatterns.toArray(new Pattern[stackPatterns.size()]); } /** * Returns {@code true} if the stack samples do not contain filtered stack frames in the stack * traces of the display thread. * * @param stackSamples the array containing stack trace samples for a long event in the first * {@code numSamples} elements * @param numSamples the number of valid stack trace samples in the {@code stackSamples} array * @param displayThreadId the ID of the display thread */ public boolean shouldLogEvent(StackSample[] stackSamples, int numSamples, long displayThreadId) { if (filterFrames.length != 0 || filterPatterns.length != 0) { for (int i = 0; i < numSamples; i++) { if (hasFilteredTraces(stackSamples[i].getStackTraces(), displayThreadId)) { return false; } } } return true; } /** * Checks if the stack trace of the display thread contains any frame that matches the filter. */ private boolean hasFilteredTraces(ThreadInfo[] stackTraces, long displayThreadId) { for (ThreadInfo threadInfo : stackTraces) { if (threadInfo.getThreadId() == displayThreadId) { for (StackTraceElement element : threadInfo.getStackTrace()) { if (matchesFilter(element)) { return true; } } return false; } } MonitoringPlugin.logError(Messages.FilterHandler_missing_thread_error, null); return false; } /** * Checks whether the given stack frame matches the filter. */ boolean matchesFilter(StackTraceElement stackFrame) { String className = stackFrame.getClassName(); String methodName = stackFrame.getMethodName(); if (filterPatterns.length != 0) { // Match against patterns in filterPatterns. compoundName.reset(className, methodName); for (Pattern pattern : filterPatterns) { if (pattern.matcher(compoundName).matches()) { return true; } } } // Binary search in filterFrames. int low = 0; int high = filterFrames.length; while (low < high) { int mid = (low + high) >>> 1; StackFrame filter = filterFrames[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; } private boolean containsWildcards(String pattern) { for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); if (c != '.' && !Character.isJavaIdentifierPart(c)) { return true; } } return false; } /** * Converts a glob pattern to a regular expression. * * @param pattern The glob pattern to convert * @return the equivalent regular expression pattern * @throws PatternSyntaxException if compilation of the regular expression fails */ private static Pattern createPattern(String pattern) throws PatternSyntaxException { int len = pattern.length(); StringBuilder buf = new StringBuilder(len * 2); boolean isEscaped = false; for (int i = 0; i < len; i++) { char c = pattern.charAt(i); switch (c) { case '\\': // The backslash is an escape character. if (!isEscaped) { isEscaped = true; } else { buf.append(DOUBLE_BACKSLASH); isEscaped = false; } break; // Characters that have to be escaped in a regular expression. case '(': case ')': case '{': case '}': case '.': case '[': case ']': case '$': case '^': case '+': case '|': if (isEscaped) { buf.append(DOUBLE_BACKSLASH); isEscaped = false; } buf.append('\\'); buf.append(c); break; case '?': if (!isEscaped) { buf.append('.'); } else { buf.append('\\'); buf.append(c); isEscaped = false; } break; case '*': if (!isEscaped) { buf.append(".*"); //$NON-NLS-1$ } else { buf.append('\\'); buf.append(c); isEscaped = false; } break; default: if (isEscaped) { buf.append(DOUBLE_BACKSLASH); isEscaped = false; } buf.append(c); break; } } if (isEscaped) { buf.append(DOUBLE_BACKSLASH); isEscaped= false; } return Pattern.compile(buf.toString()); } }