/* * Copyright 2011-2017 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.impl; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import com.google.common.base.Strings; import com.google.common.base.Ticker; import com.google.common.collect.ImmutableList; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.collector.Collector.EntryVisitor; import org.glowroot.agent.model.AsyncTimerImpl; import org.glowroot.agent.model.DetailMapWriter; import org.glowroot.agent.model.ErrorMessage; import org.glowroot.agent.model.QueryData; import org.glowroot.agent.model.QueryEntryBase; import org.glowroot.agent.plugin.api.AsyncQueryEntry; import org.glowroot.agent.plugin.api.MessageSupplier; import org.glowroot.agent.plugin.api.QueryMessageSupplier; import org.glowroot.agent.plugin.api.ThreadContext; import org.glowroot.agent.plugin.api.Timer; import org.glowroot.agent.plugin.api.internal.NopTransactionService.NopTimer; import org.glowroot.agent.plugin.api.internal.ReadableMessage; import org.glowroot.agent.plugin.api.internal.ReadableQueryMessage; import org.glowroot.agent.util.Tickers; import org.glowroot.wire.api.model.Proto; import org.glowroot.wire.api.model.TraceOuterClass.Trace; import static com.google.common.base.Preconditions.checkNotNull; // this supports updating by a single thread and reading by multiple threads class TraceEntryImpl extends QueryEntryBase implements AsyncQueryEntry, Timer { private static final Logger logger = LoggerFactory.getLogger(TraceEntryImpl.class); private static final Ticker ticker = Tickers.getTicker(); private final ThreadContextImpl threadContext; private final @Nullable TraceEntryImpl parentTraceEntry; private final @Nullable Object messageSupplier; // volatile so it can be set from another thread (needed for async trace entries) private volatile @Nullable ErrorMessage errorMessage; private final long startTick; // these fields are not volatile, so depends on memory barrier in Transaction for visibility private long revisedStartTick; private int selfNestingLevel; private long endTick; private boolean initialComplete; // this is for maintaining linear list of trace entries private @Nullable TraceEntryImpl nextTraceEntry; // only null for trace entries added using addEntryEntry() private final @Nullable TimerImpl syncTimer; private final @Nullable AsyncTimerImpl asyncTimer; // not volatile, so depends on memory barrier in Transaction for visibility private @Nullable ImmutableList<StackTraceElement> stackTrace; // only used by transaction thread private long stackTraceThreshold; // only used by transaction thread private @MonotonicNonNull TimerImpl extendedTimer; TraceEntryImpl(ThreadContextImpl threadContext, @Nullable TraceEntryImpl parentTraceEntry, @Nullable Object messageSupplier, @Nullable QueryData queryData, long queryExecutionCount, long startTick, @Nullable TimerImpl syncTimer, @Nullable AsyncTimerImpl asyncTimer) { super(queryData); this.threadContext = threadContext; this.parentTraceEntry = parentTraceEntry; this.messageSupplier = messageSupplier; this.startTick = startTick; this.syncTimer = syncTimer; this.asyncTimer = asyncTimer; revisedStartTick = startTick; selfNestingLevel = 1; if (queryData != null) { queryData.start(startTick, queryExecutionCount); } } @Override public @Nullable Object getMessageSupplier() { return messageSupplier; } @Nullable ErrorMessage getErrorMessage() { return errorMessage; } void accept(int depth, long transactionStartTick, long captureTick, EntryVisitor entryVisitor) throws Exception { long offsetNanos = startTick - transactionStartTick; long durationNanos; boolean active; if (isCompleted() && Tickers.lessThanOrEqual(endTick, captureTick)) { // total time is calculated relative to revised start tick durationNanos = endTick - revisedStartTick; active = false; } else { // total time is calculated relative to revised start tick // max with zero to prevent negative value which is possible here under race condition durationNanos = Math.max(captureTick - revisedStartTick, 0); active = true; } Object messageSupplier = getMessageSupplier(); Trace.Entry.Builder builder = Trace.Entry.newBuilder() .setDepth(depth) .setStartOffsetNanos(offsetNanos) .setDurationNanos(durationNanos) .setActive(active); // async root entry always has empty message and empty detail if (messageSupplier instanceof MessageSupplier) { ReadableMessage readableMessage = (ReadableMessage) ((MessageSupplier) messageSupplier).get(); builder.setMessage(readableMessage.getText()); builder.addAllDetailEntry(DetailMapWriter.toProto(readableMessage.getDetail())); } else if (messageSupplier instanceof QueryMessageSupplier) { String queryText = checkNotNull(getQueryText()); int sharedQueryTextIndex = entryVisitor.visitSharedQueryText(queryText); ReadableQueryMessage readableQueryMessage = (ReadableQueryMessage) ((QueryMessageSupplier) messageSupplier).get(); Trace.QueryEntryMessage.Builder queryMessage = Trace.QueryEntryMessage.newBuilder() .setSharedQueryTextIndex(sharedQueryTextIndex) .setPrefix(readableQueryMessage.getPrefix()); String rowCountSuffix = getRowCountSuffix(); if (rowCountSuffix.isEmpty()) { // optimization to avoid creating new string when concatenating empty string queryMessage.setSuffix(readableQueryMessage.getSuffix()); } else { queryMessage.setSuffix(readableQueryMessage.getSuffix() + rowCountSuffix); } builder.setQueryEntryMessage(queryMessage); builder.addAllDetailEntry(DetailMapWriter.toProto(readableQueryMessage.getDetail())); } ErrorMessage errorMessage = this.errorMessage; if (errorMessage != null) { Trace.Error.Builder errorBuilder = builder.getErrorBuilder(); errorBuilder.setMessage(errorMessage.message()); Proto.Throwable throwable = errorMessage.throwable(); if (throwable != null) { errorBuilder.setException(throwable); } errorBuilder.build(); } if (stackTrace != null) { for (StackTraceElement stackTraceElement : stackTrace) { builder.addLocationStackTraceElementBuilder() .setClassName(stackTraceElement.getClassName()) .setMethodName(Strings.nullToEmpty(stackTraceElement.getMethodName())) .setFileName(Strings.nullToEmpty(stackTraceElement.getFileName())) .setLineNumber(stackTraceElement.getLineNumber()) .build(); } } entryVisitor.visitEntry(builder.build()); } long getStartTick() { return startTick; } @Override public void end() { if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } long endTick = ticker.read(); endInternal(endTick, null); } @Override public void endWithStackTrace(long threshold, TimeUnit unit) { if (threshold < 0) { logger.error("endWithStackTrace(): argument 'threshold' must be non-negative"); end(); return; } if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } if (isAsync()) { // it is not helpful to capture stack trace at end of async trace entry since it is // ended by a different thread (and by not capturing, it reduces thread safety needs) endInternal(endTick, null); return; } long endTick = ticker.read(); long thresholdNanos = unit.toNanos(threshold); if (endTick - startTick >= thresholdNanos) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // strip up through this method, plus 1 additional method (the plugin advice method) int index = ThreadContextImpl.getNormalizedStartIndex(stackTrace, "endWithStackTrace", 1); setStackTrace(ImmutableList.copyOf(stackTrace).subList(index, stackTrace.length)); } else { // store threshold in case this trace entry is extended, see extend() below stackTraceThreshold = thresholdNanos; } endInternal(endTick, null); } @Override public void endWithError(Throwable t) { if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } endWithErrorInternal(null, t); } @Override public void endWithError(@Nullable String message) { if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } endWithErrorInternal(message, null); } @Override public void endWithError(@Nullable String message, Throwable t) { if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } endWithErrorInternal(message, t); } @Override public void endWithInfo(Throwable t) { if (initialComplete) { // this guards against end*() being called multiple times on async trace entries return; } endWithErrorInternal(null, t); } // for async trace entries, extend must be called by the same thread that started the async // trace entry @Override public Timer extend() { // timer is only null for trace entries added using addEntryEntry(), and these trace entries // are not returned from plugin api so no way for extend() to be called when timer is null checkNotNull(syncTimer); if (selfNestingLevel++ == 0) { long priorDurationNanos = endTick - revisedStartTick; long currTick = ticker.read(); revisedStartTick = currTick - priorDurationNanos; extendedTimer = syncTimer.extend(currTick); extendQueryData(currTick); } return this; } // this is called for stopping an extension @Override public void stop() { // the timer interface for this class is only expose through return value of extend() if (--selfNestingLevel == 0) { endTick = ticker.read(); checkNotNull(extendedTimer); extendedTimer.end(endTick); endQueryData(endTick); // it is not helpful to capture stack trace at end of async trace entry since it is // ended by a different thread (and by not capturing, it reduces thread safety needs) if (!isAsync() && stackTrace == null && stackTraceThreshold != 0 && endTick - revisedStartTick >= stackTraceThreshold) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // strip up through this method, plus 1 additional method (the plugin advice method) int index = ThreadContextImpl.getNormalizedStartIndex(stackTrace, "stop", 1); setStackTrace(ImmutableList.copyOf(stackTrace).subList(index, stackTrace.length)); } } } void setStackTrace(ImmutableList<StackTraceElement> stackTrace) { this.stackTrace = stackTrace; } ThreadContextImpl getThreadContext() { return threadContext; } @Nullable TraceEntryImpl getParentTraceEntry() { return parentTraceEntry; } @Nullable TraceEntryImpl getNextTraceEntry() { return nextTraceEntry; } void setNextTraceEntry(TraceEntryImpl nextTraceEntry) { this.nextTraceEntry = nextTraceEntry; } void immediateEndAsErrorEntry(ErrorMessage errorMessage, long endTick) { this.errorMessage = errorMessage; this.endTick = endTick; selfNestingLevel--; initialComplete = true; } boolean isAuxThreadRoot() { // TODO this is a little hacky depending on timer name return syncTimer != null && syncTimer.getName().equals("auxiliary thread"); } private boolean isCompleted() { // initialComplete is needed for async trace entries which have selfNestingLevel = 0 after // calling stopSyncTimer(), but are not complete until end() is called return initialComplete && selfNestingLevel == 0; } private boolean isAsync() { return asyncTimer != null; } private void endWithErrorInternal(@Nullable String message, @Nullable Throwable t) { ErrorMessage errorMessage = ErrorMessage.create(message, t, threadContext.getTransaction().getThrowableFrameLimitCounter()); endInternal(ticker.read(), errorMessage); // it is not helpful to capture stack trace at end of async trace entry since it is // ended by a different thread (and by not capturing, it reduces thread safety needs) if (!isAsync() && t == null) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // strip up through this method, plus 2 additional methods: // TraceEntryImpl.endWithError() and the plugin advice method int index = ThreadContextImpl.getNormalizedStartIndex(stackTrace, "endWithErrorInternal", 2); setStackTrace(ImmutableList.copyOf(stackTrace).subList(index, stackTrace.length)); } } private void endInternal(long endTick, @Nullable ErrorMessage errorMessage) { // timer is only null for trace entries added using addEntryEntry(), and these trace entries // are not returned from plugin api so no way for end...() to be called checkNotNull(syncTimer); if (asyncTimer == null) { syncTimer.end(endTick); } else { asyncTimer.end(endTick); } endQueryData(endTick); this.errorMessage = errorMessage; this.endTick = endTick; if (isAsync()) { threadContext.getTransaction().memoryBarrierWrite(); } else { selfNestingLevel--; threadContext.popEntry(this, endTick); } initialComplete = true; } private String getRowCountSuffix() { if (!isRowNavigationAttempted()) { return ""; } long rowCount = getRowCount(); if (rowCount == 1) { return " => 1 row"; } else { return " => " + rowCount + " rows"; } } @Override public void stopSyncTimer() { // syncTimer is only null for trace entries added using addEntryEntry(), and these trace // entries are not returned from plugin api so no way for stopSyncTimer() to be called checkNotNull(syncTimer); syncTimer.stop(); selfNestingLevel--; threadContext.popNonRootEntry(this); } @Override public Timer extendSyncTimer(ThreadContext currThreadContext) { if (currThreadContext != threadContext) { return NopTimer.INSTANCE; } // syncTimer is only null for trace entries added using addEntryEntry(), and these trace // entries are not returned from plugin api so no way for extendSyncTimer() to be called checkNotNull(syncTimer); return syncTimer.extend(); } // this is used for logging, in particular in TraceEntryComponent.popEntryBailout() // and in ThreadContextImpl.populateParentChildMap() @Override public String toString() { if (messageSupplier instanceof MessageSupplier) { return ((ReadableMessage) ((MessageSupplier) messageSupplier).get()).getText(); } else if (messageSupplier instanceof QueryMessageSupplier) { ReadableQueryMessage readableQueryMessage = (ReadableQueryMessage) ((QueryMessageSupplier) messageSupplier).get(); return readableQueryMessage.getPrefix() + checkNotNull(getQueryText()) + readableQueryMessage.getSuffix(); } if (errorMessage != null) { return errorMessage.message(); } return super.toString(); } }