![JAR search and dependency download from the Maven repository](/logo.png)
org.glowroot.transaction.TransactionServiceImpl 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;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.glowroot.shaded.google.common.base.Strings;
import org.glowroot.shaded.google.common.base.Ticker;
import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.primitives.Ints;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.shaded.slf4j.Logger;
import org.glowroot.shaded.slf4j.LoggerFactory;
import org.glowroot.common.Clock;
import org.glowroot.config.AdvancedConfig;
import org.glowroot.config.ConfigService;
import org.glowroot.config.PluginConfig;
import org.glowroot.jvm.ThreadAllocatedBytes;
import org.glowroot.plugin.api.config.ConfigListener;
import org.glowroot.plugin.api.internal.NopTransactionService.NopQueryEntry;
import org.glowroot.plugin.api.internal.NopTransactionService.NopTimer;
import org.glowroot.plugin.api.internal.NopTransactionService.NopTraceEntry;
import org.glowroot.plugin.api.transaction.MessageSupplier;
import org.glowroot.plugin.api.transaction.QueryEntry;
import org.glowroot.plugin.api.transaction.Timer;
import org.glowroot.plugin.api.transaction.TimerName;
import org.glowroot.plugin.api.transaction.TraceEntry;
import org.glowroot.plugin.api.transaction.TransactionService;
import org.glowroot.transaction.model.QueryData;
import org.glowroot.transaction.model.TimerImpl;
import org.glowroot.transaction.model.TimerNameImpl;
import org.glowroot.transaction.model.Transaction;
import org.glowroot.transaction.model.Transaction.CompletionCallback;
import org.glowroot.transaction.model.Transaction.OverrideSource;
import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;
class TransactionServiceImpl implements TransactionService, ConfigListener {
private static final Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class);
private final TransactionRegistry transactionRegistry;
private final TransactionCollector transactionCollector;
private final ConfigService configService;
private final TimerNameCache timerNameCache;
private final @Nullable ThreadAllocatedBytes threadAllocatedBytes;
private final UserProfileScheduler userProfileScheduler;
private final Clock clock;
private final Ticker ticker;
private final TransactionCompletionCallback transactionCompletionCallback =
new TransactionCompletionCallback();
// cache for fast read access
// visibility is provided by memoryBarrier below
private boolean captureThreadInfo;
private boolean captureGcInfo;
private int maxAggregateQueriesPerQueryType;
private int maxTraceEntriesPerTransaction;
private @MonotonicNonNull PluginConfig pluginConfig;
static TransactionServiceImpl create(TransactionRegistry transactionRegistry,
TransactionCollector transactionCollector, ConfigService configService,
TimerNameCache timerNameCache, @Nullable ThreadAllocatedBytes threadAllocatedBytes,
UserProfileScheduler userProfileScheduler, Ticker ticker, Clock clock) {
TransactionServiceImpl transactionServiceImpl =
new TransactionServiceImpl(transactionRegistry, transactionCollector, configService,
timerNameCache, threadAllocatedBytes, userProfileScheduler, ticker, clock);
configService.addConfigListener(transactionServiceImpl);
return transactionServiceImpl;
}
private TransactionServiceImpl(TransactionRegistry transactionRegistry,
TransactionCollector transactionCollector, ConfigService configService,
TimerNameCache timerNameCache, @Nullable ThreadAllocatedBytes threadAllocatedBytes,
UserProfileScheduler userProfileScheduler, Ticker ticker, Clock clock) {
this.transactionRegistry = transactionRegistry;
this.transactionCollector = transactionCollector;
this.configService = configService;
this.timerNameCache = timerNameCache;
this.threadAllocatedBytes = threadAllocatedBytes;
this.userProfileScheduler = userProfileScheduler;
this.clock = clock;
this.ticker = ticker;
}
@Override
public TimerName getTimerName(Class> adviceClass) {
return timerNameCache.getName(adviceClass);
}
@Override
public TraceEntry startTransaction(String transactionType, String transactionName,
MessageSupplier messageSupplier, TimerName timerName) {
if (transactionType == null) {
logger.error("startTransaction(): argument 'transactionType' must be non-null");
return NopTraceEntry.INSTANCE;
}
if (transactionName == null) {
logger.error("startTransaction(): argument 'transactionName' must be non-null");
return NopTraceEntry.INSTANCE;
}
if (messageSupplier == null) {
logger.error("startTransaction(): argument 'messageSupplier' must be non-null");
return NopTraceEntry.INSTANCE;
}
if (timerName == null) {
logger.error("startTransaction(): argument 'timerName' must be non-null");
return NopTraceEntry.INSTANCE;
}
// ensure visibility of recent configuration updates
configService.readMemoryBarrier();
return startTransactionInternal(transactionType, transactionName, messageSupplier,
timerName);
}
@Override
public TraceEntry startTraceEntry(MessageSupplier messageSupplier, TimerName timerName) {
if (messageSupplier == null) {
logger.error("startTraceEntry(): argument 'messageSupplier' must be non-null");
return NopTraceEntry.INSTANCE;
}
if (timerName == null) {
logger.error("startTraceEntry(): argument 'timerName' must be non-null");
return NopTraceEntry.INSTANCE;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction == null) {
return NopTraceEntry.INSTANCE;
}
return startTraceEntryInternal(transaction, messageSupplier, null, null, 0, timerName);
}
@Override
public QueryEntry startQueryEntry(String queryType, String queryText,
MessageSupplier messageSupplier, TimerName timerName) {
return startQueryEntry(queryType, queryText, 1, messageSupplier, timerName);
}
@Override
public QueryEntry startQueryEntry(String queryType, String queryText, long queryExecutionCount,
MessageSupplier messageSupplier, TimerName timerName) {
if (queryType == null) {
logger.error("startQuery(): argument 'queryType' must be non-null");
return NopQueryEntry.INSTANCE;
}
if (queryText == null) {
logger.error("startQuery(): argument 'queryText' must be non-null");
return NopQueryEntry.INSTANCE;
}
if (messageSupplier == null) {
logger.error("startQuery(): argument 'messageSupplier' must be non-null");
return NopQueryEntry.INSTANCE;
}
if (timerName == null) {
logger.error("startQuery(): argument 'timerName' must be non-null");
return NopQueryEntry.INSTANCE;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction == null) {
return NopQueryEntry.INSTANCE;
}
return startTraceEntryInternal(transaction, messageSupplier, queryType, queryText,
queryExecutionCount, timerName);
}
@Override
public Timer startTimer(TimerName timerName) {
if (timerName == null) {
logger.error("startTimer(): argument 'timerName' must be non-null");
return NopTimer.INSTANCE;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction == null) {
return NopTimer.INSTANCE;
}
TimerImpl currentTimer = transaction.getCurrentTimer();
if (currentTimer == null) {
return NopTimer.INSTANCE;
}
return currentTimer.startNestedTimer(timerName);
}
@Override
public void addErrorEntry(Throwable t) {
addErrorEntryInternal(ErrorMessageBase.from(t));
}
@Override
public void addErrorEntry(@Nullable String message) {
addErrorEntryInternal(ErrorMessageBase.from(message));
}
@Override
public void addErrorEntry(@Nullable String message, Throwable t) {
addErrorEntryInternal(ErrorMessageBase.from(message, t));
}
private void addErrorEntryInternal(ErrorMessage errorMessage) {
Transaction transaction = transactionRegistry.getCurrentTransaction();
// use higher entry limit when adding errors, but still need some kind of cap
if (transaction != null
&& transaction.getEntryCount() < 2 * maxTraceEntriesPerTransaction) {
long currTick = ticker.read();
org.glowroot.transaction.model.TraceEntryImpl entry =
transaction.addEntry(currTick, currTick, null, errorMessage, true);
if (errorMessage.throwable() == null) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// need to strip back a few stack calls:
// skip i=0 which is "java.lang.Thread.getStackTrace()"
// skip i=1 which is "...TransactionServiceImpl.addErrorEntryInternal()"
// skip i=2 which is "...TransactionServiceImpl.addErrorEntry()"
// skip i=3 which is the plugin advice
entry.setStackTrace(ImmutableList.copyOf(stackTrace).subList(4, stackTrace.length));
}
}
}
@Override
public void setTransactionType(@Nullable String transactionType) {
if (Strings.isNullOrEmpty(transactionType)) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setTransactionType(transactionType, OverrideSource.PLUGIN_API);
}
}
@Override
public void setTransactionName(@Nullable String transactionName) {
if (Strings.isNullOrEmpty(transactionName)) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setTransactionName(transactionName, OverrideSource.PLUGIN_API);
}
}
@Override
public void setTransactionError(@Nullable Throwable t) {
if (t == null) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setError(ErrorMessageBase.from(t), OverrideSource.PLUGIN_API);
}
}
@Override
public void setTransactionError(@Nullable String message) {
if (Strings.isNullOrEmpty(message)) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setError(ErrorMessageBase.from(message), OverrideSource.PLUGIN_API);
}
}
@Override
public void setTransactionError(@Nullable String message, @Nullable Throwable t) {
if (Strings.isNullOrEmpty(message) && t == null) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setError(ErrorMessageBase.from(message, t), OverrideSource.PLUGIN_API);
}
}
@Override
public void setTransactionUser(@Nullable String user) {
if (Strings.isNullOrEmpty(user)) {
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.setUser(user, OverrideSource.PLUGIN_API);
if (transaction.getUserProfileRunnable() == null) {
userProfileScheduler.maybeScheduleUserProfiling(transaction, user);
}
}
}
@Override
public void addTransactionCustomAttribute(String name, @Nullable String value) {
if (name == null) {
logger.error("addTransactionCustomAttribute(): argument 'name' must be non-null");
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
transaction.addCustomAttribute(name, value);
}
}
@Override
public void setTransactionSlowThreshold(long threshold, TimeUnit unit) {
if (threshold < 0) {
logger.error(
"setTransactionSlowThreshold(): argument 'threshold' must be non-negative");
return;
}
if (unit == null) {
logger.error("setTransactionSlowThreshold(): argument 'unit' must be non-null");
return;
}
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction != null) {
int thresholdMillis = Ints.saturatedCast(unit.toMillis(threshold));
transaction.setSlowThresholdMillis(thresholdMillis, OverrideSource.PLUGIN_API);
}
}
@Override
public boolean isInTransaction() {
return transactionRegistry.getCurrentTransaction() != null;
}
private TraceEntry startTransactionInternal(String transactionType, String transactionName,
MessageSupplier messageSupplier, TimerName timerName) {
Transaction transaction = transactionRegistry.getCurrentTransaction();
if (transaction == null) {
long startTick = ticker.read();
transaction = new Transaction(clock.currentTimeMillis(), transactionType,
transactionName, messageSupplier, timerName, startTick, captureThreadInfo,
captureGcInfo, maxAggregateQueriesPerQueryType, threadAllocatedBytes,
transactionCompletionCallback, ticker);
transactionRegistry.addTransaction(transaction);
return transaction.getRootEntry();
} else {
return startTraceEntryInternal(transaction, messageSupplier, null, null, 0, timerName);
}
}
private QueryEntry startTraceEntryInternal(Transaction transaction,
MessageSupplier messageSupplier, @Nullable String queryType, @Nullable String queryText,
long queryExecutionCount, TimerName timerName) {
long startTick = ticker.read();
if (transaction.getEntryCount() < maxTraceEntriesPerTransaction) {
TimerImpl timer = startTimer(timerName, startTick, transaction);
return transaction.pushEntry(startTick, messageSupplier, queryType, queryText,
queryExecutionCount, timer);
}
// split out to separate method so as not to affect inlining budget of common path
return startDummyTraceEntry(transaction, timerName, messageSupplier, queryType, queryText,
queryExecutionCount, startTick);
}
private QueryEntry startDummyTraceEntry(Transaction transaction, TimerName timerName,
MessageSupplier messageSupplier, @Nullable String queryType, @Nullable String queryText,
long queryExecutionCount, long startTick) {
// the entry limit has been exceeded for this trace
QueryData queryData = null;
if (queryType != null && queryText != null) {
queryData = transaction.getOrCreateQueryDataIfPossible(queryType, queryText);
}
transaction.addEntryLimitExceededMarkerIfNeeded();
TimerImpl timer = startTimer(timerName, startTick, transaction);
return new DummyTraceEntryOrQuery(timer, startTick, transaction, messageSupplier, queryData,
queryExecutionCount);
}
private TimerImpl startTimer(TimerName timerName, long startTick, Transaction transaction) {
TimerImpl currentTimer = transaction.getCurrentTimer();
if (currentTimer == null) {
// this really shouldn't happen as current timer should be non-null unless transaction
// has completed
return TimerImpl.createRootTimer(transaction, (TimerNameImpl) timerName);
}
return currentTimer.startNestedTimer(timerName, startTick);
}
@Override
public void onChange() {
AdvancedConfig advancedConfig = configService.getAdvancedConfig();
maxAggregateQueriesPerQueryType = advancedConfig.maxAggregateQueriesPerQueryType();
maxTraceEntriesPerTransaction = advancedConfig.maxTraceEntriesPerTransaction();
captureThreadInfo = advancedConfig.captureThreadInfo();
captureGcInfo = advancedConfig.captureGcInfo();
}
private class TransactionCompletionCallback implements CompletionCallback {
@Override
public void completed(Transaction transaction) {
// send to trace collector before removing from trace registry so that trace
// collector can cover the gap
// (via TransactionCollectorImpl.getPendingCompleteTraces())
// between removing the trace from the registry and storing it
transactionCollector.onCompletedTransaction(transaction);
transactionRegistry.removeTransaction(transaction);
}
}
private class DummyTraceEntryOrQuery implements QueryEntry, Timer {
private final TimerImpl timer;
private final long startTick;
private final Transaction transaction;
private final MessageSupplier messageSupplier;
// not volatile, so depends on memory barrier in Transaction for visibility
private int selfNestingLevel;
// only used by transaction thread
private @MonotonicNonNull TimerImpl extendedTimer;
// queryData, currRow and maxRow are only used by query entries
private final @Nullable QueryData queryData;
// row numbers start at 1
private long currRow = -1;
private long maxRow;
public DummyTraceEntryOrQuery(TimerImpl timer, long startTick, Transaction transaction,
MessageSupplier messageSupplier, @Nullable QueryData queryData,
long queryExecutionCount) {
this.timer = timer;
this.startTick = startTick;
this.transaction = transaction;
this.messageSupplier = messageSupplier;
this.queryData = queryData;
if (queryData != null) {
queryData.start(startTick, queryExecutionCount);
}
}
@Override
public void end() {
endInternal(ticker.read());
}
@Override
public void endWithStackTrace(long threshold, TimeUnit unit) {
if (threshold < 0) {
logger.error("endWithStackTrace(): argument 'threshold' must be non-negative");
end();
return;
}
endInternal(ticker.read());
}
@Override
public void endWithError(Throwable t) {
endWithErrorInternal(ErrorMessage.from(t));
}
@Override
public void endWithError(@Nullable String message) {
endWithErrorInternal(ErrorMessage.from(message));
}
@Override
public void endWithError(@Nullable String message, Throwable t) {
endWithErrorInternal(ErrorMessage.from(message, t));
}
private void endWithErrorInternal(ErrorMessage errorMessage) {
long endTick = ticker.read();
endInternal(endTick);
// use higher entry limit when adding errors, but still need some kind of cap
if (transaction.getEntryCount() < 2 * maxTraceEntriesPerTransaction) {
// entry won't be nested properly, but at least the error will get captured
org.glowroot.transaction.model.TraceEntryImpl entry = transaction
.addEntry(startTick, endTick, messageSupplier, errorMessage, true);
if (errorMessage.throwable() == null) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// need to strip back a few stack calls:
// skip i=0 which is "java.lang.Thread.getStackTrace()"
// skip i=1 which is "...DummyTraceEntryOrQuery.endWithErrorInternal()"
// skip i=2 which is "...DummyTraceEntryOrQuery.endWithError()"
// skip i=3 which is the plugin advice
entry.setStackTrace(
ImmutableList.copyOf(stackTrace).subList(4, stackTrace.length));
}
}
}
private void endInternal(long endTick) {
timer.end(endTick);
if (queryData != null) {
queryData.end(endTick);
}
}
@Override
public Timer extend() {
if (selfNestingLevel++ == 0) {
long currTick = ticker.read();
extendedTimer = timer.extend(currTick);
if (queryData != null) {
queryData.extend(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) {
long stopTick = ticker.read();
checkNotNull(extendedTimer);
extendedTimer.end(stopTick);
if (queryData != null) {
queryData.end(stopTick);
}
}
}
@Override
public MessageSupplier getMessageSupplier() {
return messageSupplier;
}
@Override
public void incrementCurrRow() {
if (currRow == -1) {
currRow = 1;
maxRow = 1;
if (queryData != null) {
// queryData can be null here if the aggregated query limit is exceeded
// (though typically query limit is larger than trace entry limit)
queryData.incrementRowCount(1);
}
} else if (currRow == maxRow) {
currRow++;
maxRow = currRow;
if (queryData != null) {
// queryData can be null here if the aggregated query limit is exceeded
// (though typically query limit is larger than trace entry limit)
queryData.incrementRowCount(1);
}
} else {
currRow++;
}
}
@Override
public void setCurrRow(long row) {
if (row > maxRow) {
if (queryData != null) {
// queryData can be null here if the query limit is exceeded
// (though typically query limit is larger than trace entry limit)
queryData.incrementRowCount(row - maxRow);
}
maxRow = row;
}
currRow = row;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy