All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.transaction.model.Profile Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2011-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.glowroot.transaction.model;

import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;

import org.glowroot.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.Lists;
import org.immutables.value.Value;

import org.glowroot.common.Styles;

import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;

public class Profile {

    private static final Pattern timerMarkerMethodPattern =
            Pattern.compile("^.*\\$glowroot\\$timer\\$(.*)\\$[0-9]+$");

    private final Object lock = new Object();
    // optimized for trace captures which are never read
    @GuardedBy("lock")
    private final List> unmergedStackTraces = Lists.newArrayList();
    @GuardedBy("lock")
    private final List unmergedStackTraceThreadStates = Lists.newArrayList();
    @GuardedBy("lock")
    private final ProfileNode syntheticRootNode = ProfileNode.createSyntheticRoot();

    private final boolean mayHaveSyntheticTimerMethods;

    @VisibleForTesting
    public Profile(boolean mayHaveSyntheticTimerMethods) {
        this.mayHaveSyntheticTimerMethods = mayHaveSyntheticTimerMethods;
    }

    public Object getLock() {
        return lock;
    }

    // must be holding lock to call and can only use resulting node tree inside the same
    // synchronized block
    public ProfileNode getSyntheticRootNode() {
        mergeTheUnmergedStackTraces();
        return syntheticRootNode;
    }

    public long getSampleCount() {
        synchronized (lock) {
            return syntheticRootNode.getSampleCount() + unmergedStackTraces.size();
        }
    }

    public boolean mayHaveSyntheticTimerMethods() {
        return mayHaveSyntheticTimerMethods;
    }

    // limit is just to cap memory consumption for a single transaction profile in case it runs for
    // a very very very long time
    void addStackTrace(ThreadInfo threadInfo, int limit) {
        synchronized (lock) {
            if (syntheticRootNode.getSampleCount() + unmergedStackTraces.size() >= limit) {
                return;
            }
            List stackTrace = Arrays.asList(threadInfo.getStackTrace());
            unmergedStackTraces.add(stackTrace);
            unmergedStackTraceThreadStates.add(threadInfo.getThreadState().name());
            if (unmergedStackTraces.size() >= 10) {
                // merged stack tree takes up less memory, so merge from time to time
                mergeTheUnmergedStackTraces();
            }
        }
    }

    // must be holding lock to call
    private void mergeTheUnmergedStackTraces() {
        for (int i = 0; i < unmergedStackTraces.size(); i++) {
            List stackTrace = unmergedStackTraces.get(i);
            String threadState = unmergedStackTraceThreadStates.get(i);
            if (mayHaveSyntheticTimerMethods) {
                addToStackTree(stripSyntheticTimerMethods(stackTrace), threadState);
            } else {
                // fast path for common case
                addToStackTree(stackTrace, threadState);
            }
        }
        unmergedStackTraces.clear();
        unmergedStackTraceThreadStates.clear();
    }

    // must be holding lock to call
    @VisibleForTesting
    public void addToStackTree(List stackTrace, String threadState) {
        syntheticRootNode.incrementSampleCount(1);
        ProfileNode lastMatchedNode = syntheticRootNode;
        Iterable nextChildNodes = syntheticRootNode.getChildNodes();
        int nextIndex;
        // navigate the stack tree nodes
        // matching the new stack trace as far as possible
        for (nextIndex = stackTrace.size() - 1; nextIndex >= 0; nextIndex--) {
            Object element = stackTrace.get(nextIndex);
            // look for matching node
            ProfileNode matchingNode = null;
            for (ProfileNode childNode : nextChildNodes) {
                if (matches(getStackTraceElement(element), childNode, nextIndex == 0,
                        threadState)) {
                    matchingNode = childNode;
                    break;
                }
            }
            if (matchingNode == null) {
                break;
            }
            // match found, update lastMatchedNode and break out of the inner loop
            matchingNode.incrementSampleCount(1);
            // the timer names for a given stack element should always match, unless
            // the line numbers aren't available and overloaded methods are matched up, or
            // the stack trace was captured while one of the synthetic $glowroot$timer$
            // methods was executing in which case one of the timer names may be a
            // subset of the other, in which case, the superset wins:
            ImmutableList timerNames = getTimerNames(element);
            if (timerNames.size() > matchingNode.getTimerNames().size()) {
                matchingNode.setTimerNames(timerNames);
            }
            lastMatchedNode = matchingNode;
            nextChildNodes = lastMatchedNode.getChildNodes();
        }
        // add remaining stack trace elements
        for (int i = nextIndex; i >= 0; i--) {
            Object element = stackTrace.get(i);
            ProfileNode nextNode;
            StackTraceElement stackTraceElement = getStackTraceElement(element);
            if (i == 0) {
                // leaf node
                nextNode = ProfileNode.create(stackTraceElement, threadState);
            } else {
                nextNode = ProfileNode.create(stackTraceElement, null);
            }
            nextNode.setTimerNames(getTimerNames(element));
            nextNode.incrementSampleCount(1);
            lastMatchedNode.addChildNode(nextNode);
            lastMatchedNode = nextNode;
        }
    }

    // recreate the stack trace as it would have been without the synthetic $glowroot$timer$
    // methods
    public static List stripSyntheticTimerMethods(
            List stackTrace) {

        List stackTracePlus =
                Lists.newArrayListWithCapacity(stackTrace.size());
        for (Iterator i = stackTrace.iterator(); i.hasNext();) {
            StackTraceElement element = i.next();
            String originalMethodName = element.getMethodName();
            if (originalMethodName == null) {
                // methodName can be null after hotswapping under Eclipse debugger
                continue;
            }
            String timerName = getTimerName(originalMethodName);
            if (timerName == null) {
                stackTracePlus.add(StackTraceElementPlus.of(element, ImmutableList.of()));
                continue;
            }
            List timerNames = Lists.newArrayListWithCapacity(2);
            timerNames.add(timerName);
            // skip over successive $glowroot$timer$ methods up to and including the "original"
            // method
            while (i.hasNext()) {
                StackTraceElement skipElement = i.next();
                String skipMethodName = skipElement.getMethodName();
                if (skipMethodName == null) {
                    // methodName can be null after hotswapping under Eclipse debugger
                    continue;
                }
                timerName = getTimerName(skipMethodName);
                if (timerName == null) {
                    // loop should always terminate here since synthetic $glowroot$timer$
                    // methods should never be the last element (the last element is the first
                    // element in the call stack)
                    originalMethodName = skipMethodName;
                    break;
                }
                timerNames.add(timerName);
            }
            // "original" in the sense that this is what it would have been without the
            // synthetic $timer$ methods
            StackTraceElement originalElement = new StackTraceElement(element.getClassName(),
                    originalMethodName, element.getFileName(), element.getLineNumber());
            stackTracePlus.add(StackTraceElementPlus.of(originalElement, timerNames));
        }
        return stackTracePlus;
    }

    private static @Nullable String getTimerName(String methodName) {
        if (!methodName.contains("$glowroot$timer$")) {
            // fast contains check for common case
            return null;
        }
        Matcher matcher = timerMarkerMethodPattern.matcher(methodName);
        if (matcher.matches()) {
            String group = matcher.group(1);
            checkNotNull(group);
            return group.replace("$", " ");
        } else {
            return null;
        }
    }

    private static boolean matches(StackTraceElement stackTraceElement, ProfileNode childNode,
            boolean leaf, String threadState) {
        String leafThreadState = childNode.getLeafThreadState();
        if (leafThreadState != null && leaf) {
            // only consider thread state when matching the leaf node
            return childNode.isSameStackTraceElement(stackTraceElement)
                    && leafThreadState.equals(threadState);
        } else {
            return leafThreadState == null && !leaf
                    && childNode.isSameStackTraceElement(stackTraceElement);
        }
    }

    private static StackTraceElement getStackTraceElement(Object stackTraceElementOrPlus) {
        StackTraceElement stackTraceElement;
        if (stackTraceElementOrPlus instanceof StackTraceElement) {
            stackTraceElement = (StackTraceElement) stackTraceElementOrPlus;
        } else {
            stackTraceElement =
                    ((StackTraceElementPlus) stackTraceElementOrPlus).stackTraceElement();
        }
        return stackTraceElement;
    }

    private static ImmutableList getTimerNames(Object stackTraceElementOrPlus) {
        if (stackTraceElementOrPlus instanceof StackTraceElement) {
            return ImmutableList.of();
        } else {
            return ((StackTraceElementPlus) stackTraceElementOrPlus).timerNames();
        }
    }

    @Value.Immutable
    @Styles.AllParameters
    public abstract static class StackTraceElementPlusBase {
        public abstract StackTraceElement stackTraceElement();
        abstract ImmutableList timerNames();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy