/*
* 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.plugin.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import javax.annotation.Nullable;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.agent.plugin.api.Agent;
import org.glowroot.agent.plugin.api.Logger;
import org.glowroot.agent.plugin.api.Message;
import org.glowroot.agent.plugin.api.MessageSupplier;
import org.glowroot.agent.plugin.api.ThreadContext;
import org.glowroot.agent.plugin.api.Timer;
import org.glowroot.agent.plugin.api.TimerName;
import org.glowroot.agent.plugin.api.TraceEntry;
import org.glowroot.agent.plugin.api.config.BooleanProperty;
import org.glowroot.agent.plugin.api.config.ConfigService;
import org.glowroot.agent.plugin.api.weaving.BindReturn;
import org.glowroot.agent.plugin.api.weaving.BindThrowable;
import org.glowroot.agent.plugin.api.weaving.BindTraveler;
import org.glowroot.agent.plugin.api.weaving.IsEnabled;
import org.glowroot.agent.plugin.api.weaving.OnBefore;
import org.glowroot.agent.plugin.api.weaving.OnReturn;
import org.glowroot.agent.plugin.api.weaving.OnThrow;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
// DataSource.getConnection() can be interesting in case the data source is improperly sized and is
// slow while expanding
public class DataSourceAspect {
private static final Logger logger = Agent.getLogger(DataSourceAspect.class);
private static final ConfigService configService = Agent.getConfigService("jdbc");
private static final BooleanProperty captureGetConnection =
configService.getBooleanProperty("captureGetConnection");
private static final BooleanProperty captureConnectionLifecycleTraceEntries =
configService.getBooleanProperty("captureConnectionLifecycleTraceEntries");
private static final BooleanProperty captureTransactionLifecycleTraceEntries =
configService.getBooleanProperty("captureTransactionLifecycleTraceEntries");
@Pointcut(className = "javax.sql.DataSource", methodName = "getConnection",
methodParameterTypes = {".."}, nestingGroup = "jdbc", timerName = "jdbc get connection")
public static class GetConnectionAdvice {
private static final TimerName timerName = Agent.getTimerName(GetConnectionAdvice.class);
@IsEnabled
public static boolean isEnabled() {
return captureGetConnection.value() || captureConnectionLifecycleTraceEntries.value();
}
@OnBefore
public static Object onBefore(ThreadContext context) {
if (captureConnectionLifecycleTraceEntries.value()) {
return context.startTraceEntry(new GetConnectionMessageSupplier(), timerName);
} else {
return context.startTimer(timerName);
}
}
@OnReturn
public static void onReturn(@BindReturn @Nullable Connection connection,
@BindTraveler Object entryOrTimer) {
if (entryOrTimer instanceof TraceEntry) {
onReturnTraceEntry(connection, (TraceEntry) entryOrTimer);
} else {
((Timer) entryOrTimer).stop();
}
}
@OnThrow
public static void onThrow(@BindThrowable Throwable t, @BindTraveler Object entryOrTimer) {
if (entryOrTimer instanceof TraceEntry) {
((TraceEntry) entryOrTimer).endWithError(t);
} else {
((Timer) entryOrTimer).stop();
}
}
// split out to separate method so it doesn't affect inlining budget of common case
private static void onReturnTraceEntry(@Nullable Connection connection,
TraceEntry traceEntry) {
if (captureTransactionLifecycleTraceEntries.value() && connection != null) {
GetConnectionMessageSupplier messageSupplier =
(GetConnectionMessageSupplier) traceEntry.getMessageSupplier();
if (messageSupplier != null) {
// messageSupplier can be null if max trace entries was exceeded
String autoCommit;
try {
autoCommit = Boolean.toString(connection.getAutoCommit());
} catch (SQLException e) {
logger.warn(e.getMessage(), e);
// using toString() instead of getMessage() in order to capture exception
// class name
autoCommit = "<error occurred: " + e.toString() + ">";
}
messageSupplier.setAutoCommit(autoCommit);
}
}
traceEntry.endWithStackTrace(JdbcPluginProperties.stackTraceThresholdMillis(),
MILLISECONDS);
}
}
private static class GetConnectionMessageSupplier extends MessageSupplier {
private volatile @MonotonicNonNull String autoCommit;
@Override
public Message get() {
if (autoCommit == null) {
return Message.create("jdbc get connection");
} else {
return Message.create("jdbc get connection (autocommit: {})", autoCommit);
}
}
private void setAutoCommit(String autoCommit) {
this.autoCommit = autoCommit;
}
}
}