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

org.glowroot.agent.model.TraceEntryComponent Maven / Gradle / Ivy

There is a newer version: 0.9.28
Show 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.agent.model;

import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import org.glowroot.agent.shaded.google.common.base.Ticker;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.collect.Lists;
import org.glowroot.agent.shaded.google.common.collect.Maps;
import org.glowroot.agent.shaded.slf4j.Logger;
import org.glowroot.agent.shaded.slf4j.LoggerFactory;

import org.glowroot.agent.plugin.api.transaction.MessageSupplier;
import org.glowroot.agent.util.Tickers;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;

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

// this supports updating by a single thread and reading by multiple threads
class TraceEntryComponent {

    private static final Logger logger = LoggerFactory.getLogger(TraceEntryComponent.class);

    private final long startTick;
    // not volatile, so depends on memory barrier in Transaction for visibility
    private boolean completed;
    // not volatile, so depends on memory barrier in Transaction for visibility
    private long endTick;

    private final TraceEntryImpl rootEntry;

    @Nullable
    private TraceEntryImpl activeEntry;

    private TraceEntryImpl tailEntry;

    // entries.size() is accessed a lot, but only by transaction thread, so storing size separately
    // so it can be accessed without synchronization
    private int entryCount;

    // this doesn't need to be volatile since it is only accessed by the transaction thread
    private boolean entryLimitExceeded;

    private final Ticker ticker;

    TraceEntryComponent(MessageSupplier messageSupplier, TimerImpl timer, long startTick,
            Ticker ticker) {
        this.startTick = startTick;
        this.ticker = ticker;
        rootEntry = new TraceEntryImpl(null, messageSupplier, null, 0, startTick, timer);
        activeEntry = rootEntry;
        tailEntry = rootEntry;
    }

    TraceEntryImpl getRootEntry() {
        return rootEntry;
    }

    // this does not include the root trace entry
    public List toProtobuf() {
        long captureTick;
        if (completed) {
            captureTick = endTick;
        } else {
            captureTick = ticker.read();
        }
        Map> childEntryMap = Maps.newHashMap();
        TraceEntryImpl entry = rootEntry.getNextTraceEntry();
        // filter out entries that started after the capture tick
        // checking completed is short circuit optimization for the common case
        while (entry != null
                && (completed || Tickers.lessThanOrEqual(entry.getStartTick(), captureTick))) {
            // checkNotNull is safe because only the root entry has null parent
            TraceEntryImpl parentTraceEntry = checkNotNull(entry.getParentTraceEntry());
            List childEntries = childEntryMap.get(parentTraceEntry);
            if (childEntries == null) {
                childEntries = Lists.newArrayList();
                childEntryMap.put(parentTraceEntry, childEntries);
            }
            childEntries.add(entry);
            entry = entry.getNextTraceEntry();
        }

        return getProtobufChildEntries(rootEntry, childEntryMap, startTick, captureTick);
    }

    // this does not include root trace entry
    int getEntryCount() {
        return entryCount;
    }

    long getStartTick() {
        return startTick;
    }

    boolean isCompleted() {
        return completed;
    }

    long getEndTick() {
        return endTick;
    }

    long getDurationNanos() {
        return completed ? endTick - startTick : ticker.read() - startTick;
    }

    TraceEntryImpl pushEntry(long startTick, MessageSupplier messageSupplier,
            @Nullable QueryData queryData, long queryExecutionCount, TimerImpl timer) {
        TraceEntryImpl entry = createEntry(startTick, messageSupplier, queryData,
                queryExecutionCount, null, timer, false);
        tailEntry.setNextTraceEntry(entry);
        tailEntry = entry;
        activeEntry = entry;
        entryCount++;
        return entry;
    }

    // typically pop() methods don't require the objects to pop, but for safety, the entry is
    // passed in just to make sure it is the one on top (and if not, then pop until it is found,
    // preventing any nasty bugs from a missed pop, e.g. an entry never being marked as complete)
    void popEntry(TraceEntryImpl entry, long endTick) {
        popEntrySafe(entry);
        if (activeEntry == null) {
            this.endTick = endTick;
            this.completed = true;
        }
    }

    TraceEntryImpl addEntry(long startTick, long endTick, @Nullable MessageSupplier messageSupplier,
            @Nullable ErrorMessage errorMessage, boolean limitBypassed) {
        TraceEntryImpl entry =
                createEntry(startTick, messageSupplier, null, 1, errorMessage, null, limitBypassed);
        tailEntry.setNextTraceEntry(entry);
        tailEntry = entry;
        entryCount++;
        entry.setEndTick(endTick);
        return entry;
    }

    void setEntryLimitExceeded() {
        entryLimitExceeded = true;
    }

    boolean isEntryLimitExceeded() {
        return entryLimitExceeded;
    }

    private TraceEntryImpl createEntry(long startTick, @Nullable MessageSupplier messageSupplier,
            @Nullable QueryData queryData, long queryExecutionCount,
            @Nullable ErrorMessage errorMessage, @Nullable TimerImpl timer, boolean limitBypassed) {
        if (entryLimitExceeded && !limitBypassed) {
            // just in case the entryLimit property is changed in the middle of a trace this resets
            // the flag so that it can be triggered again (and possibly then a second limit marker)
            entryLimitExceeded = false;
        }
        TraceEntryImpl entry = new TraceEntryImpl(activeEntry, messageSupplier, queryData,
                queryExecutionCount, startTick, timer);
        entry.setErrorMessage(errorMessage);
        return entry;
    }

    private void popEntrySafe(TraceEntryImpl entry) {
        if (activeEntry == null) {
            logger.error("entry stack is empty, cannot pop entry: {}", entry);
            return;
        }
        if (activeEntry == entry) {
            activeEntry = activeEntry.getParentTraceEntry();
        } else {
            // somehow(?) a pop was missed (or maybe too many pops), this is just damage control
            popEntryBailout(entry);
        }
    }

    // split typically unused path into separate method to not affect inlining budget
    private void popEntryBailout(TraceEntryImpl expectingEntry) {
        logger.error("found entry {} at top of stack when expecting entry {}", activeEntry,
                expectingEntry);
        while (activeEntry != null && activeEntry != expectingEntry) {
            activeEntry = activeEntry.getParentTraceEntry();
        }
        if (activeEntry != null) {
            // now perform pop
            activeEntry = activeEntry.getParentTraceEntry();
        } else {
            logger.error("popped entire stack, never found entry: {}", expectingEntry);
        }
    }

    private static List getProtobufChildEntries(TraceEntryImpl entry,
            Map> childEntryMap, long transactionStartTick,
            long captureTick) {
        List childEntries = childEntryMap.get(entry);
        if (childEntries == null) {
            return ImmutableList.of();
        }
        List protobufChildEntries =
                Lists.newArrayListWithCapacity(childEntries.size());
        for (TraceEntryImpl childEntry : childEntries) {
            List subChildEntries = getProtobufChildEntries(childEntry, childEntryMap,
                    transactionStartTick, captureTick);
            protobufChildEntries
                    .add(childEntry.toProtobuf(transactionStartTick, captureTick, subChildEntries));
        }
        return protobufChildEntries;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy