/*
* Copyright 2013-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.embedded.repo;
import java.io.ByteArrayInputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicLongArray;
import javax.annotation.Nullable;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Parser;
import org.checkerframework.checker.tainting.qual.Untainted;
import org.immutables.value.Value;
import org.glowroot.agent.collector.Collector.AggregateReader;
import org.glowroot.agent.collector.Collector.AggregateVisitor;
import org.glowroot.agent.embedded.repo.model.Stored;
import org.glowroot.agent.embedded.util.CappedDatabase;
import org.glowroot.agent.embedded.util.DataSource;
import org.glowroot.agent.embedded.util.DataSource.JdbcQuery;
import org.glowroot.agent.embedded.util.DataSource.JdbcRowQuery;
import org.glowroot.agent.embedded.util.ImmutableColumn;
import org.glowroot.agent.embedded.util.ImmutableIndex;
import org.glowroot.agent.embedded.util.RowMappers;
import org.glowroot.agent.embedded.util.Schemas.Column;
import org.glowroot.agent.embedded.util.Schemas.ColumnType;
import org.glowroot.agent.embedded.util.Schemas.Index;
import org.glowroot.common.config.ConfigDefaults;
import org.glowroot.common.config.StorageConfig;
import org.glowroot.common.live.ImmutableOverviewAggregate;
import org.glowroot.common.live.ImmutablePercentileAggregate;
import org.glowroot.common.live.ImmutableThroughputAggregate;
import org.glowroot.common.live.LiveAggregateRepository.OverallQuery;
import org.glowroot.common.live.LiveAggregateRepository.OverviewAggregate;
import org.glowroot.common.live.LiveAggregateRepository.PercentileAggregate;
import org.glowroot.common.live.LiveAggregateRepository.ThroughputAggregate;
import org.glowroot.common.live.LiveAggregateRepository.TransactionQuery;
import org.glowroot.common.model.LazyHistogram.ScratchBuffer;
import org.glowroot.common.model.OverallErrorSummaryCollector;
import org.glowroot.common.model.OverallSummaryCollector;
import org.glowroot.common.model.ProfileCollector;
import org.glowroot.common.model.QueryCollector;
import org.glowroot.common.model.ServiceCallCollector;
import org.glowroot.common.model.TransactionErrorSummaryCollector;
import org.glowroot.common.model.TransactionErrorSummaryCollector.ErrorSummarySortOrder;
import org.glowroot.common.model.TransactionSummaryCollector;
import org.glowroot.common.model.TransactionSummaryCollector.SummarySortOrder;
import org.glowroot.common.repo.AggregateRepository;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.ConfigRepository.RollupConfig;
import org.glowroot.common.repo.MutableAggregate;
import org.glowroot.common.repo.util.RollupLevelService;
import org.glowroot.common.repo.util.ThreadStatsCreator;
import org.glowroot.common.util.Styles;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AdvancedConfig;
import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate;
import org.glowroot.wire.api.model.ProfileOuterClass.Profile;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.agent.util.Checkers.castUntainted;
public class AggregateDao implements AggregateRepository {
private static final String AGENT_ID = "";
private static final ImmutableList<Column> overallAggregatePointColumns =
ImmutableList.<Column>of(
ImmutableColumn.of("transaction_type", ColumnType.VARCHAR),
ImmutableColumn.of("capture_time", ColumnType.BIGINT),
ImmutableColumn.of("total_duration_nanos", ColumnType.DOUBLE),
ImmutableColumn.of("transaction_count", ColumnType.BIGINT),
ImmutableColumn.of("error_count", ColumnType.BIGINT),
ImmutableColumn.of("async_transactions", ColumnType.BOOLEAN),
ImmutableColumn.of("queries_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("service_calls_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("main_thread_profile_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("aux_thread_profile_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("main_thread_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("aux_thread_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("async_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("main_thread_total_cpu_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_blocked_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_waited_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_allocated_bytes", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_cpu_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_blocked_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_waited_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_allocated_bytes", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("duration_nanos_histogram", ColumnType.VARBINARY)); // protobuf
private static final ImmutableList<Column> transactionAggregateColumns =
ImmutableList.<Column>of(
ImmutableColumn.of("transaction_type", ColumnType.VARCHAR),
ImmutableColumn.of("transaction_name", ColumnType.VARCHAR),
ImmutableColumn.of("capture_time", ColumnType.BIGINT),
ImmutableColumn.of("total_duration_nanos", ColumnType.DOUBLE),
ImmutableColumn.of("transaction_count", ColumnType.BIGINT),
ImmutableColumn.of("error_count", ColumnType.BIGINT),
ImmutableColumn.of("async_transactions", ColumnType.BOOLEAN),
ImmutableColumn.of("queries_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("service_calls_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("main_thread_profile_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("aux_thread_profile_capped_id", ColumnType.BIGINT),
ImmutableColumn.of("main_thread_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("aux_thread_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("async_root_timers", ColumnType.VARBINARY), // protobuf
ImmutableColumn.of("main_thread_total_cpu_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_blocked_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_waited_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("main_thread_total_allocated_bytes", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_cpu_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_blocked_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_waited_nanos", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("aux_thread_total_allocated_bytes", ColumnType.DOUBLE), // nullable
ImmutableColumn.of("duration_nanos_histogram", ColumnType.VARBINARY)); // protobuf
// this index includes all columns needed for the overall aggregate query so h2 can return
// the result set directly from the index without having to reference the table for each row
private static final ImmutableList<String> overallAggregateIndexColumns =
ImmutableList.of("capture_time", "transaction_type", "total_duration_nanos",
"transaction_count", "error_count");
// this index includes all columns needed for the transaction aggregate query so h2 can return
// the result set directly from the index without having to reference the table for each row
//
// capture_time is first so this can also be used for readTransactionErrorCounts()
private static final ImmutableList<String> transactionAggregateIndexColumns =
ImmutableList.of("capture_time", "transaction_type", "transaction_name",
"total_duration_nanos", "transaction_count", "error_count");
private final DataSource dataSource;
private final List<CappedDatabase> rollupCappedDatabases;
private final ConfigRepository configRepository;
private final TransactionTypeDao transactionTypeDao;
private final FullQueryTextDao fullQueryTextDao;
private final AtomicLongArray lastRollupTimes;
private final Object rollupLock = new Object();
AggregateDao(DataSource dataSource, List<CappedDatabase> rollupCappedDatabases,
ConfigRepository configRepository, TransactionTypeDao transactionTypeDao,
FullQueryTextDao fullQueryTextDao) throws Exception {
this.dataSource = dataSource;
this.rollupCappedDatabases = rollupCappedDatabases;
this.configRepository = configRepository;
this.transactionTypeDao = transactionTypeDao;
this.fullQueryTextDao = fullQueryTextDao;
List<RollupConfig> rollupConfigs = configRepository.getRollupConfigs();
for (int i = 0; i < rollupConfigs.size(); i++) {
String overallTableName = "aggregate_tt_rollup_" + castUntainted(i);
dataSource.syncTable(overallTableName, overallAggregatePointColumns);
dataSource.syncIndexes(overallTableName, ImmutableList.<Index>of(
ImmutableIndex.of(overallTableName + "_idx", overallAggregateIndexColumns)));
String transactionTableName = "aggregate_tn_rollup_" + castUntainted(i);
dataSource.syncTable(transactionTableName, transactionAggregateColumns);
dataSource.syncIndexes(transactionTableName, ImmutableList.<Index>of(ImmutableIndex
.of(transactionTableName + "_idx", transactionAggregateIndexColumns)));
}
// don't need last_rollup_times table like in GaugeValueDao since there is already index
// on capture_time so these queries are relatively fast
long[] lastRollupTimes = new long[rollupConfigs.size()];
lastRollupTimes[0] = 0;
for (int i = 1; i < lastRollupTimes.length; i++) {
lastRollupTimes[i] = dataSource.queryForLong("select ifnull(max(capture_time), 0)"
+ " from aggregate_tt_rollup_" + castUntainted(i));
}
this.lastRollupTimes = new AtomicLongArray(lastRollupTimes);
// TODO initial rollup in case store is not called in a reasonable time
}
public void store(AggregateReader aggregateReader) throws Exception {
final long captureTime = aggregateReader.captureTime();
// intentionally not using batch update as that could cause memory spike while preparing a
// large batch
final CappedDatabase cappedDatabase = rollupCappedDatabases.get(0);
final List<TruncatedQueryText> truncatedQueryTexts = Lists.newArrayList();
aggregateReader.accept(new AggregateVisitor() {
@Override
public void visitOverallAggregate(String transactionType, List<String> sharedQueryTexts,
Aggregate overallAggregate) throws Exception {
addToTruncatedQueryTexts(sharedQueryTexts);
dataSource.update(new AggregateInsert(transactionType, null, captureTime,
overallAggregate, truncatedQueryTexts, 0, cappedDatabase));
transactionTypeDao.updateLastCaptureTime(transactionType, captureTime);
}
@Override
public void visitTransactionAggregate(String transactionType, String transactionName,
List<String> sharedQueryTexts, Aggregate transactionAggregate)
throws Exception {
addToTruncatedQueryTexts(sharedQueryTexts);
dataSource.update(new AggregateInsert(transactionType, transactionName, captureTime,
transactionAggregate, truncatedQueryTexts, 0, cappedDatabase));
}
private void addToTruncatedQueryTexts(List<String> sharedQueryTexts)
throws SQLException {
for (String sharedQueryText : sharedQueryTexts) {
String truncatedText;
String fullTextSha1;
if (sharedQueryText.length() > StorageConfig.AGGREGATE_QUERY_TEXT_TRUNCATE) {
truncatedText = sharedQueryText.substring(0,
StorageConfig.AGGREGATE_QUERY_TEXT_TRUNCATE);
fullTextSha1 = fullQueryTextDao.updateLastCaptureTime(sharedQueryText,
captureTime);
} else {
truncatedText = sharedQueryText;
fullTextSha1 = null;
}
truncatedQueryTexts
.add(ImmutableTruncatedQueryText.of(truncatedText, fullTextSha1));
}
}
});
synchronized (rollupLock) {
List<RollupConfig> rollupConfigs = configRepository.getRollupConfigs();
for (int i = 1; i < rollupConfigs.size(); i++) {
RollupConfig rollupConfig = rollupConfigs.get(i);
long safeRollupTime = RollupLevelService.getSafeRollupTime(captureTime,
rollupConfig.intervalMillis());
long lastRollupTime = lastRollupTimes.get(i);
if (safeRollupTime > lastRollupTime) {
rollup(lastRollupTime, safeRollupTime, rollupConfig.intervalMillis(), i, i - 1);
lastRollupTimes.set(i, safeRollupTime);
}
}
}
}
// query.from() is non-inclusive
@Override
public void mergeOverallSummaryInto(String agentRollupId, OverallQuery query,
OverallSummaryCollector collector) throws Exception {
dataSource.query(new OverallSummaryQuery(collector, query));
}
// query.from() is non-inclusive
@Override
public void mergeTransactionSummariesInto(String agentRollupId, OverallQuery query,
SummarySortOrder sortOrder, int limit, TransactionSummaryCollector collector)
throws Exception {
dataSource.query(
new TransactionSummaryQuery(query, sortOrder, limit, collector));
}
// query.from() is non-inclusive
@Override
public void mergeOverallErrorSummaryInto(String agentRollupId, OverallQuery query,
OverallErrorSummaryCollector collector) throws Exception {
dataSource.query(new OverallErrorSummaryQuery(collector, query));
}
// query.from() is non-inclusive
@Override
public void mergeTransactionErrorSummariesInto(String agentRollupId, OverallQuery query,
ErrorSummarySortOrder sortOrder, int limit, TransactionErrorSummaryCollector collector)
throws Exception {
dataSource.query(new TransactionErrorSummaryQuery(query, sortOrder, limit,
collector));
}
// query.from() is INCLUSIVE
@Override
public List<OverviewAggregate> readOverviewAggregates(String agentRollupId,
TransactionQuery query) throws Exception {
return dataSource.query(new OverviewAggregateQuery(query));
}
// query.from() is INCLUSIVE
@Override
public List<PercentileAggregate> readPercentileAggregates(String agentRollupId,
TransactionQuery query) throws Exception {
return dataSource.query(new PercentileAggregateQuery(query));
}
// query.from() is INCLUSIVE
@Override
public List<ThroughputAggregate> readThroughputAggregates(String agentRollupId,
TransactionQuery query) throws Exception {
return dataSource.query(new ThroughputAggregateQuery(query));
}
@Override
public @Nullable String readFullQueryText(String agentRollupId, String fullQueryTextSha1)
throws Exception {
return fullQueryTextDao.getFullText(fullQueryTextSha1);
}
// query.from() is non-inclusive
@Override
public void mergeQueriesInto(String agentRollupId, TransactionQuery query,
QueryCollector collector) throws Exception {
// get list of capped ids first since that is done under the data source lock
// then do the expensive part of reading and constructing the protobuf messages outside of
// the data source lock
List<CappedId> cappedIds = dataSource.query(new CappedIdQuery("queries_capped_id", query));
long captureTime = Long.MIN_VALUE;
for (CappedId cappedId : cappedIds) {
captureTime = Math.max(captureTime, cappedId.captureTime());
List<Stored.QueriesByType> queries = rollupCappedDatabases.get(query.rollupLevel())
.readMessages(cappedId.cappedId(), Stored.QueriesByType.parser());
for (Stored.QueriesByType toBeMergedQueries : queries) {
for (Stored.Query toBeMergedQuery : toBeMergedQueries.getQueryList()) {
collector.mergeQuery(toBeMergedQueries.getType(),
toBeMergedQuery.getTruncatedText(),
Strings.emptyToNull(toBeMergedQuery.getFullTextSha1()),
toBeMergedQuery.getTotalDurationNanos(),
toBeMergedQuery.getExecutionCount(), toBeMergedQuery.hasTotalRows(),
toBeMergedQuery.getTotalRows().getValue());
}
}
collector.updateLastCaptureTime(captureTime);
}
}
// query.from() is non-inclusive
@Override
public void mergeServiceCallsInto(String agentRollupId, TransactionQuery query,
ServiceCallCollector collector) throws Exception {
// get list of capped ids first since that is done under the data source lock
// then do the expensive part of reading and constructing the protobuf messages outside of
// the data source lock
List<CappedId> cappedIds =
dataSource.query(new CappedIdQuery("service_calls_capped_id", query));
long captureTime = Long.MIN_VALUE;
for (CappedId cappedId : cappedIds) {
captureTime = Math.max(captureTime, cappedId.captureTime());
List<Aggregate.ServiceCallsByType> queries =
rollupCappedDatabases.get(query.rollupLevel()).readMessages(cappedId.cappedId(),
Aggregate.ServiceCallsByType.parser());
if (queries != null) {
collector.mergeServiceCalls(queries);
collector.updateLastCaptureTime(captureTime);
}
}
}
// query.from() is non-inclusive
@Override
public void mergeMainThreadProfilesInto(String agentRollupId, TransactionQuery query,
ProfileCollector collector) throws Exception {
mergeProfilesInto(collector, query, "main_thread_profile_capped_id");
}
// query.from() is non-inclusive
@Override
public void mergeAuxThreadProfilesInto(String agentRollupId, TransactionQuery query,
ProfileCollector collector) throws Exception {
mergeProfilesInto(collector, query, "aux_thread_profile_capped_id");
}
// query.from() is non-inclusive
@Override
public boolean hasMainThreadProfile(String agentRollupId, TransactionQuery query)
throws Exception {
return !dataSource.query(new CappedIdQuery("main_thread_profile_capped_id", query))
.isEmpty();
}
// query.from() is non-inclusive
@Override
public boolean hasAuxThreadProfile(String agentRollupId, TransactionQuery query)
throws Exception {
return !dataSource.query(new CappedIdQuery("aux_thread_profile_capped_id", query))
.isEmpty();
}
// query.from() is non-inclusive
@Override
public boolean shouldHaveMainThreadProfile(String agentRollupId, TransactionQuery query)
throws Exception {
return dataSource
.query(new ShouldHaveSomethingQuery(query, "main_thread_profile_capped_id"));
}
// query.from() is non-inclusive
@Override
public boolean shouldHaveAuxThreadProfile(String agentRollupId, TransactionQuery query)
throws Exception {
return dataSource
.query(new ShouldHaveSomethingQuery(query, "aux_thread_profile_capped_id"));
}
// query.from() is non-inclusive
@Override
public boolean shouldHaveQueries(String agentRollupId, TransactionQuery query)
throws Exception {
return dataSource.query(new ShouldHaveSomethingQuery(query, "queries_capped_id"));
}
// query.from() is non-inclusive
@Override
public boolean shouldHaveServiceCalls(String agentRollupId, TransactionQuery query)
throws Exception {
return dataSource.query(new ShouldHaveSomethingQuery(query, "service_calls_capped_id"));
}
void deleteBefore(long captureTime, int rollupLevel) throws Exception {
dataSource.deleteBefore("aggregate_tt_rollup_" + castUntainted(rollupLevel), captureTime);
dataSource.deleteBefore("aggregate_tn_rollup_" + castUntainted(rollupLevel), captureTime);
}
private void rollup(long lastRollupTime, long curentRollupTime, long fixedIntervalMillis,
int toRollupLevel, int fromRollupLevel) throws Exception {
List<Long> rollupTimes = dataSource.query(new RollupTimeRowMapper(fromRollupLevel,
fixedIntervalMillis, lastRollupTime, curentRollupTime));
for (Long rollupTime : rollupTimes) {
dataSource.query(new RollupOverallAggregates(rollupTime, fixedIntervalMillis,
fromRollupLevel, toRollupLevel));
dataSource.query(new RollupTransactionAggregates(rollupTime, fixedIntervalMillis,
fromRollupLevel, toRollupLevel));
}
}
private void mergeProfilesInto(ProfileCollector collector, TransactionQuery query,
@Untainted String cappedIdColumnName) throws Exception {
// get list of capped ids first since that is done under the data source lock
// then do the expensive part of reading and constructing the protobuf messages outside of
// the data source lock
List<CappedId> cappedIds = dataSource.query(new CappedIdQuery(cappedIdColumnName, query));
long captureTime = Long.MIN_VALUE;
for (CappedId cappedId : cappedIds) {
captureTime = Math.max(captureTime, cappedId.captureTime());
Profile profile = rollupCappedDatabases.get(query.rollupLevel())
.readMessage(cappedId.cappedId(), Profile.parser());
if (profile != null) {
collector.mergeProfile(profile);
collector.updateLastCaptureTime(captureTime);
}
}
}
private void merge(MutableAggregate mergedAggregate, ResultSet resultSet, int startColumnIndex,
int fromRollupLevel) throws Exception {
int i = startColumnIndex;
double totalDurationNanos = resultSet.getDouble(i++);
long transactionCount = resultSet.getLong(i++);
long errorCount = resultSet.getLong(i++);
boolean asyncTransactions = resultSet.getBoolean(i++);
Long queriesCappedId = RowMappers.getLong(resultSet, i++);
Long serviceCallsCappedId = RowMappers.getLong(resultSet, i++);
Long mainThreadProfileCappedId = RowMappers.getLong(resultSet, i++);
Long auxThreadProfileCappedId = RowMappers.getLong(resultSet, i++);
byte[] mainThreadRootTimers = resultSet.getBytes(i++);
byte[] auxThreadRootTimers = resultSet.getBytes(i++);
byte[] asyncTimers = resultSet.getBytes(i++);
Double mainThreadTotalCpuNanos = RowMappers.getDouble(resultSet, i++);
Double mainThreadTotalBlockedNanos = RowMappers.getDouble(resultSet, i++);
Double mainThreadTotalWaitedNanos = RowMappers.getDouble(resultSet, i++);
Double mainThreadTotalAllocatedBytes = RowMappers.getDouble(resultSet, i++);
Double auxThreadTotalCpuNanos = RowMappers.getDouble(resultSet, i++);
Double auxThreadTotalBlockedNanos = RowMappers.getDouble(resultSet, i++);
Double auxThreadTotalWaitedNanos = RowMappers.getDouble(resultSet, i++);
Double auxThreadTotalAllocatedBytes = RowMappers.getDouble(resultSet, i++);
byte[] durationNanosHistogram = checkNotNull(resultSet.getBytes(i++));
mergedAggregate.addTotalDurationNanos(totalDurationNanos);
mergedAggregate.addTransactionCount(transactionCount);
mergedAggregate.addErrorCount(errorCount);
mergedAggregate.addAsyncTransactions(asyncTransactions);
if (mainThreadRootTimers != null) {
mergedAggregate.mergeMainThreadRootTimers(
readMessages(mainThreadRootTimers, Aggregate.Timer.parser()));
}
if (auxThreadRootTimers != null) {
mergedAggregate.mergeAuxThreadRootTimers(
readMessages(auxThreadRootTimers, Aggregate.Timer.parser()));
}
if (asyncTimers != null) {
mergedAggregate.mergeAsyncTimers(readMessages(asyncTimers, Aggregate.Timer.parser()));
}
mergedAggregate.addMainThreadTotalCpuNanos(mainThreadTotalCpuNanos);
mergedAggregate.addMainThreadTotalBlockedNanos(mainThreadTotalBlockedNanos);
mergedAggregate.addMainThreadTotalWaitedNanos(mainThreadTotalWaitedNanos);
mergedAggregate.addMainThreadTotalAllocatedBytes(mainThreadTotalAllocatedBytes);
mergedAggregate.addAuxThreadTotalCpuNanos(auxThreadTotalCpuNanos);
mergedAggregate.addAuxThreadTotalBlockedNanos(auxThreadTotalBlockedNanos);
mergedAggregate.addAuxThreadTotalWaitedNanos(auxThreadTotalWaitedNanos);
mergedAggregate.addAuxThreadTotalAllocatedBytes(auxThreadTotalAllocatedBytes);
mergedAggregate
.mergeDurationNanosHistogram(Aggregate.Histogram.parseFrom(durationNanosHistogram));
if (queriesCappedId != null) {
List<Stored.QueriesByType> queries = rollupCappedDatabases.get(fromRollupLevel)
.readMessages(queriesCappedId, Stored.QueriesByType.parser());
if (queries != null) {
for (Stored.QueriesByType queriesByType : queries) {
for (Stored.Query query : queriesByType.getQueryList()) {
mergedAggregate.mergeQuery(queriesByType.getType(),
query.getTruncatedText(),
Strings.emptyToNull(query.getFullTextSha1()),
query.getTotalDurationNanos(), query.getExecutionCount(),
query.hasTotalRows(), query.getTotalRows().getValue());
}
}
}
}
if (serviceCallsCappedId != null) {
List<Aggregate.ServiceCallsByType> serviceCalls =
rollupCappedDatabases.get(fromRollupLevel).readMessages(serviceCallsCappedId,
Aggregate.ServiceCallsByType.parser());
if (serviceCalls != null) {
mergedAggregate.mergeServiceCalls(serviceCalls);
}
}
if (mainThreadProfileCappedId != null) {
Profile mainThreadProfile = rollupCappedDatabases.get(fromRollupLevel)
.readMessage(mainThreadProfileCappedId, Profile.parser());
if (mainThreadProfile != null) {
mergedAggregate.mergeMainThreadProfile(mainThreadProfile);
}
}
if (auxThreadProfileCappedId != null) {
Profile auxThreadProfile = rollupCappedDatabases.get(fromRollupLevel)
.readMessage(auxThreadProfileCappedId, Profile.parser());
if (auxThreadProfile != null) {
mergedAggregate.mergeAuxThreadProfile(auxThreadProfile);
}
}
}
private int getMaxAggregateQueriesPerType() throws Exception {
AdvancedConfig advancedConfig = configRepository.getAdvancedConfig(AGENT_ID);
if (advancedConfig != null && advancedConfig.hasMaxAggregateQueriesPerType()) {
return advancedConfig.getMaxAggregateQueriesPerType().getValue();
} else {
return ConfigDefaults.MAX_AGGREGATE_QUERIES_PER_TYPE;
}
}
private int getMaxAggregateServiceCallsPerType() throws Exception {
AdvancedConfig advancedConfig = configRepository.getAdvancedConfig(AGENT_ID);
if (advancedConfig != null && advancedConfig.hasMaxAggregateServiceCallsPerType()) {
return advancedConfig.getMaxAggregateServiceCallsPerType().getValue();
} else {
return ConfigDefaults.MAX_AGGREGATE_SERVICE_CALLS_PER_TYPE;
}
}
private static @Untainted String getTableName(TransactionQuery query) {
if (query.transactionName() == null) {
return "aggregate_tt_rollup_" + castUntainted(query.rollupLevel());
} else {
return "aggregate_tn_rollup_" + castUntainted(query.rollupLevel());
}
}
private static @Untainted String getTransactionNameCriteria(TransactionQuery query) {
if (query.transactionName() == null) {
return "";
} else {
return " and transaction_name = ?";
}
}
private static int bindQuery(PreparedStatement preparedStatement, TransactionQuery query)
throws SQLException {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
String transactionName = query.transactionName();
if (transactionName != null) {
preparedStatement.setString(i++, transactionName);
}
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
return i;
}
private static <T extends /*@NonNull*/ Object> List<T> readMessages(byte[] bytes,
Parser<T> parser) throws InvalidProtocolBufferException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
List<T> messages = Lists.newArrayList();
T message;
while ((message = parser.parseDelimitedFrom(bais)) != null) {
messages.add(message);
}
return messages;
}
private static class OverallSummaryQuery implements JdbcQuery</*@Nullable*/ Void> {
private final OverallSummaryCollector collector;
private final OverallQuery query;
private OverallSummaryQuery(OverallSummaryCollector collector, OverallQuery query) {
this.collector = collector;
this.query = query;
}
@Override
public @Untainted String getSql() {
// it's important that all these columns are in a single index so h2 can return the
// result set directly from the index without having to reference the table for each row
return "select sum(total_duration_nanos), sum(transaction_count), max(capture_time)"
+ " from aggregate_tt_rollup_" + castUntainted(query.rollupLevel())
+ " where transaction_type = ? and capture_time > ? and capture_time <= ?";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
if (!resultSet.next()) {
// this is an aggregate query so this should be impossible
throw new SQLException("Aggregate query did not return any results");
}
int i = 1;
double totalDurationNanos = resultSet.getDouble(i++);
long transactionCount = resultSet.getLong(i++);
long captureTime = resultSet.getLong(i++);
collector.mergeSummary(totalDurationNanos, transactionCount, captureTime);
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
}
private class TransactionSummaryQuery implements JdbcQuery</*@Nullable*/ Void> {
private final OverallQuery query;
private final SummarySortOrder sortOrder;
private final int limit;
private final TransactionSummaryCollector collector;
private TransactionSummaryQuery(OverallQuery query, SummarySortOrder sortOrder, int limit,
TransactionSummaryCollector collector) {
this.query = query;
this.sortOrder = sortOrder;
this.limit = limit;
this.collector = collector;
}
@Override
public @Untainted String getSql() {
// it's important that all these columns are in a single index so h2 can return the
// result set directly from the index without having to reference the table for each row
StringBuilder sb = new StringBuilder();
sb.append("select transaction_name, sum(total_duration_nanos), sum(transaction_count),"
+ " max(capture_time) from aggregate_tn_rollup_");
sb.append(query.rollupLevel());
sb.append(" where transaction_type = ? and capture_time > ? and capture_time <= ?"
+ " group by transaction_name order by ");
sb.append(getSortClause(sortOrder));
sb.append(", transaction_name limit ?");
return castUntainted(sb.toString());
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
// limit + 100 since this result still needs to be merged with other results
preparedStatement.setInt(i++, limit + 100);
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
while (resultSet.next()) {
int i = 1;
String transactionName = checkNotNull(resultSet.getString(i++));
double totalDurationNanos = resultSet.getDouble(i++);
long transactionCount = resultSet.getLong(i++);
long maxCaptureTime = resultSet.getLong(i++);
collector.collect(transactionName, totalDurationNanos, transactionCount,
maxCaptureTime);
}
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
private @Untainted String getSortClause(SummarySortOrder sortOrder) {
switch (sortOrder) {
case TOTAL_TIME:
return "sum(total_duration_nanos) desc";
case AVERAGE_TIME:
return "sum(total_duration_nanos) / sum(transaction_count) desc";
case THROUGHPUT:
return "sum(transaction_count) desc";
default:
throw new AssertionError("Unexpected sort order: " + sortOrder);
}
}
}
private static class OverallErrorSummaryQuery implements JdbcQuery</*@Nullable*/ Void> {
private final OverallErrorSummaryCollector collector;
private final OverallQuery query;
private OverallErrorSummaryQuery(OverallErrorSummaryCollector collector,
OverallQuery query) {
this.collector = collector;
this.query = query;
}
@Override
public @Untainted String getSql() {
return "select sum(error_count), sum(transaction_count), max(capture_time)"
+ " from aggregate_tt_rollup_" + castUntainted(query.rollupLevel())
+ " where transaction_type = ? and capture_time > ? and capture_time <= ?";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
if (!resultSet.next()) {
// this is an aggregate query so this should be impossible
throw new SQLException("Aggregate query did not return any results");
}
int i = 1;
long errorCount = resultSet.getLong(i++);
long transactionCount = resultSet.getLong(i++);
long captureTime = resultSet.getLong(i++);
collector.mergeErrorSummary(errorCount, transactionCount, captureTime);
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
}
private class TransactionErrorSummaryQuery implements JdbcQuery</*@Nullable*/ Void> {
private final OverallQuery query;
private final ErrorSummarySortOrder sortOrder;
private final int limit;
private final TransactionErrorSummaryCollector collector;
private TransactionErrorSummaryQuery(OverallQuery query, ErrorSummarySortOrder sortOrder,
int limit, TransactionErrorSummaryCollector collector) {
this.query = query;
this.sortOrder = sortOrder;
this.limit = limit;
this.collector = collector;
}
@Override
public @Untainted String getSql() {
// it's important that all these columns are in a single index so h2 can return the
// result set directly from the index without having to reference the table for each row
StringBuilder sb = new StringBuilder();
sb.append("select transaction_name, sum(error_count), sum(transaction_count),");
sb.append(" max(capture_time) from aggregate_tn_rollup_");
sb.append(castUntainted(query.rollupLevel()));
sb.append(" where transaction_type = ? and capture_time > ? and capture_time <= ?"
+ " group by transaction_name having sum(error_count) > 0 order by ");
sb.append(getSortClause(sortOrder));
sb.append(", transaction_name limit ?");
return castUntainted(sb.toString());
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
// limit + 100 since this result still needs to be merged with other results
preparedStatement.setInt(i++, limit + 100);
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
while (resultSet.next()) {
int i = 1;
String transactionName = checkNotNull(resultSet.getString(i++));
long errorCount = resultSet.getLong(i++);
long transactionCount = resultSet.getLong(i++);
long maxCaptureTime = resultSet.getLong(i++);
collector.collect(transactionName, errorCount, transactionCount, maxCaptureTime);
}
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
private @Untainted String getSortClause(ErrorSummarySortOrder sortOrder) {
switch (sortOrder) {
case ERROR_COUNT:
return "sum(error_count) desc";
case ERROR_RATE:
return "sum(error_count) / sum(transaction_count) desc";
default:
throw new AssertionError("Unexpected sort order: " + sortOrder);
}
}
}
private static class OverviewAggregateQuery implements JdbcRowQuery<OverviewAggregate> {
private final TransactionQuery query;
private OverviewAggregateQuery(TransactionQuery query) {
this.query = query;
}
@Override
public @Untainted String getSql() {
String tableName = getTableName(query);
String transactionNameCriteria = getTransactionNameCriteria(query);
return "select capture_time, total_duration_nanos, transaction_count,"
+ " async_transactions, main_thread_root_timers, aux_thread_root_timers,"
+ " async_root_timers, main_thread_total_cpu_nanos,"
+ " main_thread_total_blocked_nanos, main_thread_total_waited_nanos,"
+ " main_thread_total_allocated_bytes, aux_thread_total_cpu_nanos,"
+ " aux_thread_total_blocked_nanos, aux_thread_total_waited_nanos,"
+ " aux_thread_total_allocated_bytes from " + tableName
+ " where transaction_type = ?" + transactionNameCriteria
+ " and capture_time >= ? and capture_time <= ? order by capture_time";
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
bindQuery(preparedStatement, query);
}
@Override
public OverviewAggregate mapRow(ResultSet resultSet) throws Exception {
int i = 1;
ImmutableOverviewAggregate.Builder builder = ImmutableOverviewAggregate.builder()
.captureTime(resultSet.getLong(i++))
.totalDurationNanos(resultSet.getDouble(i++))
.transactionCount(resultSet.getLong(i++))
.asyncTransactions(resultSet.getBoolean(i++));
byte[] mainThreadRootTimers = resultSet.getBytes(i++);
if (mainThreadRootTimers != null) {
builder.mainThreadRootTimers(
readMessages(mainThreadRootTimers, Aggregate.Timer.parser()));
}
byte[] auxThreadRootTimers = resultSet.getBytes(i++);
if (auxThreadRootTimers != null) {
builder.auxThreadRootTimers(
readMessages(auxThreadRootTimers, Aggregate.Timer.parser()));
}
byte[] asyncTimers = resultSet.getBytes(i++);
if (asyncTimers != null) {
builder.asyncTimers(readMessages(asyncTimers, Aggregate.Timer.parser()));
}
Aggregate.ThreadStats mainThreadStats =
ThreadStatsCreator.create(RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++));
if (mainThreadStats != null) {
builder.mainThreadStats(mainThreadStats);
}
Aggregate.ThreadStats auxThreadStats =
ThreadStatsCreator.create(RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++),
RowMappers.getDouble(resultSet, i++));
if (auxThreadStats != null) {
builder.auxThreadStats(auxThreadStats);
}
return builder.build();
}
}
private static class PercentileAggregateQuery implements JdbcRowQuery<PercentileAggregate> {
private final TransactionQuery query;
private PercentileAggregateQuery(TransactionQuery query) {
this.query = query;
}
@Override
public @Untainted String getSql() {
String tableName = getTableName(query);
String transactionNameCriteria = getTransactionNameCriteria(query);
return "select capture_time, total_duration_nanos, transaction_count,"
+ " duration_nanos_histogram from " + tableName + " where transaction_type = ?"
+ transactionNameCriteria + " and capture_time >= ? and capture_time <= ?"
+ " order by capture_time";
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
bindQuery(preparedStatement, query);
}
@Override
public PercentileAggregate mapRow(ResultSet resultSet) throws Exception {
int i = 1;
ImmutablePercentileAggregate.Builder builder = ImmutablePercentileAggregate.builder()
.captureTime(resultSet.getLong(i++))
.totalDurationNanos(resultSet.getLong(i++))
.transactionCount(resultSet.getLong(i++));
byte[] durationNanosHistogram = checkNotNull(resultSet.getBytes(i++));
builder.durationNanosHistogram(
Aggregate.Histogram.parser().parseFrom(durationNanosHistogram));
return builder.build();
}
}
private static class ThroughputAggregateQuery implements JdbcRowQuery<ThroughputAggregate> {
private final TransactionQuery query;
private ThroughputAggregateQuery(TransactionQuery query) {
this.query = query;
}
@Override
public @Untainted String getSql() {
String tableName = getTableName(query);
String transactionNameCriteria = getTransactionNameCriteria(query);
return "select capture_time, transaction_count from " + tableName
+ " where transaction_type = ?" + transactionNameCriteria
+ " and capture_time >= ? and capture_time <= ? order by capture_time";
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
bindQuery(preparedStatement, query);
}
@Override
public ThroughputAggregate mapRow(ResultSet resultSet) throws Exception {
int i = 1;
return ImmutableThroughputAggregate.builder()
.captureTime(resultSet.getLong(i++))
.transactionCount(resultSet.getLong(i++))
.build();
}
}
private class RollupOverallAggregates implements JdbcQuery</*@Nullable*/ Void> {
private final long rollupCaptureTime;
private final long fixedIntervalMillis;
private final int fromRollupLevel;
private final int toRollupLevel;
private final ScratchBuffer scratchBuffer = new ScratchBuffer();
private RollupOverallAggregates(long rollupCaptureTime, long fixedIntervalMillis,
int fromRollupLevel, int toRollupLevel) {
this.rollupCaptureTime = rollupCaptureTime;
this.fixedIntervalMillis = fixedIntervalMillis;
this.fromRollupLevel = fromRollupLevel;
this.toRollupLevel = toRollupLevel;
}
@Override
public @Untainted String getSql() {
return "select transaction_type, total_duration_nanos, transaction_count, error_count,"
+ " async_transactions, queries_capped_id, service_calls_capped_id,"
+ " main_thread_profile_capped_id, aux_thread_profile_capped_id,"
+ " main_thread_root_timers, aux_thread_root_timers, async_root_timers,"
+ " main_thread_total_cpu_nanos, main_thread_total_blocked_nanos,"
+ " main_thread_total_waited_nanos, main_thread_total_allocated_bytes,"
+ " aux_thread_total_cpu_nanos, aux_thread_total_blocked_nanos,"
+ " aux_thread_total_waited_nanos, aux_thread_total_allocated_bytes,"
+ " duration_nanos_histogram from aggregate_tt_rollup_"
+ castUntainted(fromRollupLevel) + " where capture_time > ?"
+ " and capture_time <= ? order by transaction_type";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
preparedStatement.setLong(1, rollupCaptureTime - fixedIntervalMillis);
preparedStatement.setLong(2, rollupCaptureTime);
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
int maxAggregateQueriesPerType = getMaxAggregateQueriesPerType();
int maxAggregateServiceCallsPerType = getMaxAggregateServiceCallsPerType();
CappedDatabase cappedDatabase = rollupCappedDatabases.get(toRollupLevel);
MutableOverallAggregate curr = null;
while (resultSet.next()) {
String transactionType = checkNotNull(resultSet.getString(1));
if (curr == null || !transactionType.equals(curr.transactionType())) {
if (curr != null) {
dataSource.update(new AggregateInsert(curr.transactionType(), null,
rollupCaptureTime, curr.aggregate(), toRollupLevel,
cappedDatabase, scratchBuffer));
}
curr = ImmutableMutableOverallAggregate.of(transactionType,
new MutableAggregate(maxAggregateQueriesPerType,
maxAggregateServiceCallsPerType));
}
merge(curr.aggregate(), resultSet, 2, fromRollupLevel);
}
if (curr != null) {
dataSource.update(new AggregateInsert(curr.transactionType(), null,
rollupCaptureTime, curr.aggregate(), toRollupLevel, cappedDatabase,
scratchBuffer));
}
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
}
private class RollupTransactionAggregates implements JdbcQuery</*@Nullable*/ Void> {
private final long rollupCaptureTime;
private final long fixedIntervalMillis;
private final int fromRollupLevel;
private final int toRollupLevel;
private RollupTransactionAggregates(long rollupCaptureTime, long fixedIntervalMillis,
int fromRollupLevel, int toRollupLevel) {
this.rollupCaptureTime = rollupCaptureTime;
this.fixedIntervalMillis = fixedIntervalMillis;
this.fromRollupLevel = fromRollupLevel;
this.toRollupLevel = toRollupLevel;
}
@Override
public @Untainted String getSql() {
return "select transaction_type, transaction_name, total_duration_nanos,"
+ " transaction_count, error_count, async_transactions, queries_capped_id,"
+ " service_calls_capped_id, main_thread_profile_capped_id,"
+ " aux_thread_profile_capped_id, main_thread_root_timers,"
+ " aux_thread_root_timers, async_root_timers, main_thread_total_cpu_nanos,"
+ " main_thread_total_blocked_nanos, main_thread_total_waited_nanos,"
+ " main_thread_total_allocated_bytes, aux_thread_total_cpu_nanos,"
+ " aux_thread_total_blocked_nanos, aux_thread_total_waited_nanos,"
+ " aux_thread_total_allocated_bytes, duration_nanos_histogram"
+ " from aggregate_tn_rollup_" + castUntainted(fromRollupLevel)
+ " where capture_time > ? and capture_time <= ? order by transaction_type,"
+ " transaction_name";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
preparedStatement.setLong(1, rollupCaptureTime - fixedIntervalMillis);
preparedStatement.setLong(2, rollupCaptureTime);
}
@Override
public @Nullable Void processResultSet(ResultSet resultSet) throws Exception {
int maxAggregateQueriesPerType = getMaxAggregateQueriesPerType();
int maxAggregateServiceCallsPerType = getMaxAggregateServiceCallsPerType();
CappedDatabase cappedDatabase = rollupCappedDatabases.get(toRollupLevel);
ScratchBuffer scratchBuffer = new ScratchBuffer();
MutableTransactionAggregate curr = null;
while (resultSet.next()) {
int i = 1;
String transactionType = checkNotNull(resultSet.getString(i++));
String transactionName = checkNotNull(resultSet.getString(i++));
if (curr == null || !transactionType.equals(curr.transactionType())
|| !transactionName.equals(curr.transactionName())) {
if (curr != null) {
dataSource.update(new AggregateInsert(curr.transactionType(),
curr.transactionName(), rollupCaptureTime, curr.aggregate(),
toRollupLevel, cappedDatabase, scratchBuffer));
}
curr = ImmutableMutableTransactionAggregate.of(transactionType, transactionName,
new MutableAggregate(maxAggregateQueriesPerType,
maxAggregateServiceCallsPerType));
}
merge(curr.aggregate(), resultSet, i++, fromRollupLevel);
}
if (curr != null) {
dataSource.update(new AggregateInsert(curr.transactionType(),
curr.transactionName(), rollupCaptureTime, curr.aggregate(), toRollupLevel,
cappedDatabase, scratchBuffer));
}
return null;
}
@Override
public @Nullable Void valueIfDataSourceClosed() {
return null;
}
}
private class CappedIdQuery implements JdbcQuery<List<CappedId>> {
private final @Untainted String cappedIdColumnName;
private final TransactionQuery query;
private final long smallestNonExpiredCappedId;
private CappedIdQuery(@Untainted String cappedIdColumnName, TransactionQuery query) {
this.cappedIdColumnName = cappedIdColumnName;
this.query = query;
smallestNonExpiredCappedId =
rollupCappedDatabases.get(query.rollupLevel()).getSmallestNonExpiredId();
}
@Override
public @Untainted String getSql() {
String tableName = getTableName(query);
String transactionNameCriteria = getTransactionNameCriteria(query);
return "select capture_time, " + cappedIdColumnName + " from " + tableName
+ " where transaction_type = ?" + transactionNameCriteria
+ " and capture_time > ? and capture_time <= ? and " + cappedIdColumnName
+ " >= ?";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
int i = bindQuery(preparedStatement, query);
preparedStatement.setLong(i++, smallestNonExpiredCappedId);
}
@Override
public List<CappedId> processResultSet(ResultSet resultSet) throws Exception {
List<CappedId> cappedIds = Lists.newArrayList();
while (resultSet.next()) {
cappedIds.add(ImmutableCappedId.of(resultSet.getLong(1), resultSet.getLong(2)));
}
return cappedIds;
}
@Override
public List<CappedId> valueIfDataSourceClosed() {
return ImmutableList.of();
}
}
private static class ShouldHaveSomethingQuery implements JdbcQuery<Boolean> {
private final TransactionQuery query;
private final @Untainted String cappedIdColumnName;
private ShouldHaveSomethingQuery(TransactionQuery query,
@Untainted String cappedIdColumnName) {
this.query = query;
this.cappedIdColumnName = cappedIdColumnName;
}
@Override
public @Untainted String getSql() {
String tableName = getTableName(query);
String transactionNameCriteria = getTransactionNameCriteria(query);
return "select 1 from " + tableName + " where transaction_type = ?"
+ transactionNameCriteria + " and capture_time > ? and capture_time <= ?"
+ " and " + cappedIdColumnName + " is not null limit 1";
}
@Override
public void bind(PreparedStatement preparedStatement) throws Exception {
int i = 1;
preparedStatement.setString(i++, query.transactionType());
String transactionName = query.transactionName();
if (transactionName != null) {
preparedStatement.setString(i++, transactionName);
}
preparedStatement.setLong(i++, query.from());
preparedStatement.setLong(i++, query.to());
}
@Override
public Boolean processResultSet(ResultSet resultSet) throws Exception {
return resultSet.next();
}
@Override
public Boolean valueIfDataSourceClosed() {
return false;
}
}
private static class RollupTimeRowMapper implements JdbcRowQuery<Long> {
private final int rollupLevel;
private final long fixedIntervalMillis;
private final long lastRollupTime;
private final long curentRollupTime;
private RollupTimeRowMapper(int rollupLevel, long fixedIntervalMillis, long lastRollupTime,
long curentRollupTime) {
this.rollupLevel = rollupLevel;
this.fixedIntervalMillis = fixedIntervalMillis;
this.lastRollupTime = lastRollupTime;
this.curentRollupTime = curentRollupTime;
}
@Override
public @Untainted String getSql() {
// need ".0" to force double result
String captureTimeSql = castUntainted(
"ceil(capture_time / " + fixedIntervalMillis + ".0) * " + fixedIntervalMillis);
return "select distinct " + captureTimeSql + " from aggregate_tt_rollup_"
+ castUntainted(rollupLevel) + " where capture_time > ? and capture_time <= ?";
}
@Override
public void bind(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setLong(1, lastRollupTime);
preparedStatement.setLong(2, curentRollupTime);
}
@Override
public Long mapRow(ResultSet resultSet) throws SQLException {
return resultSet.getLong(1);
}
}
@Value.Immutable
@Styles.AllParameters
interface CappedId {
long captureTime();
long cappedId();
}
@Value.Immutable
@Styles.AllParameters
interface MutableOverallAggregate {
String transactionType();
MutableAggregate aggregate();
}
@Value.Immutable
@Styles.AllParameters
interface MutableTransactionAggregate {
String transactionType();
String transactionName();
MutableAggregate aggregate();
}
@Value.Immutable
@Styles.AllParameters
interface TruncatedQueryText {
String truncatedText();
@Nullable
String fullTextSha1();
}
}