/* * Copyright 2016 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.ByteArrayOutputStream; import java.io.IOException; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; import java.util.Map.Entry; 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.AbstractMessage; import org.checkerframework.checker.tainting.qual.Untainted; import org.glowroot.agent.embedded.repo.AggregateDao.TruncatedQueryText; import org.glowroot.agent.embedded.repo.model.Stored; import org.glowroot.agent.embedded.repo.model.Stored.QueriesByType; import org.glowroot.agent.embedded.util.CappedDatabase; import org.glowroot.agent.embedded.util.DataSource.JdbcUpdate; import org.glowroot.agent.embedded.util.RowMappers; import org.glowroot.common.model.LazyHistogram.ScratchBuffer; import org.glowroot.common.model.MutableProfile; import org.glowroot.common.model.MutableQuery; import org.glowroot.common.model.QueryCollector; import org.glowroot.common.repo.MutableAggregate; import org.glowroot.common.repo.MutableThreadStats; import org.glowroot.common.util.NotAvailableAware; import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate; import org.glowroot.wire.api.model.ProfileOuterClass.Profile; import static org.glowroot.agent.util.Checkers.castUntainted; class AggregateInsert implements JdbcUpdate { private final String transactionType; private final @Nullable String transactionName; private final long captureTime; private final double totalDurationNanos; private final long transactionCount; private final long errorCount; private final boolean asyncTransactions; private final @Nullable Long queriesCappedId; private final @Nullable Long serviceCallsCappedId; private final @Nullable Long mainThreadProfileCappedId; private final @Nullable Long auxThreadProfileCappedId; private final byte /*@Nullable*/[] mainThreadRootTimers; private final byte /*@Nullable*/[] auxThreadRootTimers; private final byte /*@Nullable*/[] asyncTimers; private final @Nullable Double mainThreadTotalCpuNanos; private final @Nullable Double mainThreadTotalBlockedNanos; private final @Nullable Double mainThreadTotalWaitedNanos; private final @Nullable Double mainThreadTotalAllocatedBytes; private final @Nullable Double auxThreadTotalCpuNanos; private final @Nullable Double auxThreadTotalBlockedNanos; private final @Nullable Double auxThreadTotalWaitedNanos; private final @Nullable Double auxThreadTotalAllocatedBytes; private final byte[] durationNanosHistogramBytes; private final int rollupLevel; AggregateInsert(String transactionType, @Nullable String transactionName, long captureTime, Aggregate aggregate, List<TruncatedQueryText> truncatedQueryTexts, int rollupLevel, CappedDatabase cappedDatabase) throws IOException { this.transactionType = transactionType; this.transactionName = transactionName; this.captureTime = captureTime; this.rollupLevel = rollupLevel; totalDurationNanos = aggregate.getTotalDurationNanos(); transactionCount = aggregate.getTransactionCount(); errorCount = aggregate.getErrorCount(); asyncTransactions = aggregate.getAsyncTransactions(); queriesCappedId = writeQueries(cappedDatabase, convertToStored(aggregate.getQueriesByTypeList(), truncatedQueryTexts)); serviceCallsCappedId = writeServiceCalls(cappedDatabase, aggregate.getServiceCallsByTypeList()); if (aggregate.hasMainThreadProfile()) { mainThreadProfileCappedId = writeProfile(cappedDatabase, aggregate.getMainThreadProfile()); } else { mainThreadProfileCappedId = null; } if (aggregate.hasAuxThreadProfile()) { auxThreadProfileCappedId = writeProfile(cappedDatabase, aggregate.getAuxThreadProfile()); } else { auxThreadProfileCappedId = null; } mainThreadRootTimers = toByteArray(aggregate.getMainThreadRootTimerList()); auxThreadRootTimers = toByteArray(aggregate.getAuxThreadRootTimerList()); asyncTimers = toByteArray(aggregate.getAsyncTimerList()); Aggregate.ThreadStats mainThreadStats = aggregate.getMainThreadStats(); mainThreadTotalCpuNanos = mainThreadStats.hasTotalCpuNanos() ? mainThreadStats.getTotalCpuNanos().getValue() : null; mainThreadTotalBlockedNanos = mainThreadStats.hasTotalBlockedNanos() ? mainThreadStats.getTotalBlockedNanos().getValue() : null; mainThreadTotalWaitedNanos = mainThreadStats.hasTotalWaitedNanos() ? mainThreadStats.getTotalWaitedNanos().getValue() : null; mainThreadTotalAllocatedBytes = mainThreadStats.hasTotalAllocatedBytes() ? mainThreadStats.getTotalAllocatedBytes().getValue() : null; Aggregate.ThreadStats auxThreadStats = aggregate.getAuxThreadStats(); auxThreadTotalCpuNanos = auxThreadStats.hasTotalCpuNanos() ? auxThreadStats.getTotalCpuNanos().getValue() : null; auxThreadTotalBlockedNanos = auxThreadStats.hasTotalBlockedNanos() ? auxThreadStats.getTotalBlockedNanos().getValue() : null; auxThreadTotalWaitedNanos = auxThreadStats.hasTotalWaitedNanos() ? auxThreadStats.getTotalWaitedNanos().getValue() : null; auxThreadTotalAllocatedBytes = auxThreadStats.hasTotalAllocatedBytes() ? auxThreadStats.getTotalAllocatedBytes().getValue() : null; durationNanosHistogramBytes = aggregate.getDurationNanosHistogram().toByteArray(); } AggregateInsert(String transactionType, @Nullable String transactionName, long captureTime, MutableAggregate aggregate, int rollupLevel, CappedDatabase cappedDatabase, ScratchBuffer scratchBuffer) throws IOException { this.transactionType = transactionType; this.transactionName = transactionName; this.captureTime = captureTime; this.rollupLevel = rollupLevel; totalDurationNanos = aggregate.getTotalDurationNanos(); transactionCount = aggregate.getTransactionCount(); errorCount = aggregate.getErrorCount(); asyncTransactions = aggregate.isAsyncTransactions(); queriesCappedId = writeQueries(cappedDatabase, convertToStored(aggregate.getQueries())); serviceCallsCappedId = writeServiceCalls(cappedDatabase, aggregate.getServiceCallsProto()); mainThreadProfileCappedId = writeProfile(cappedDatabase, aggregate.getMainThreadProfile()); auxThreadProfileCappedId = writeProfile(cappedDatabase, aggregate.getAuxThreadProfile()); mainThreadRootTimers = toByteArray(aggregate.getMainThreadRootTimersProto()); auxThreadRootTimers = toByteArray(aggregate.getAuxThreadRootTimersProto()); asyncTimers = toByteArray(aggregate.getAsyncTimersProto()); MutableThreadStats mainThreadStats = aggregate.getMainThreadStats(); mainThreadTotalCpuNanos = NotAvailableAware.orNull(mainThreadStats.getTotalCpuNanos()); mainThreadTotalBlockedNanos = NotAvailableAware.orNull(mainThreadStats.getTotalBlockedNanos()); mainThreadTotalWaitedNanos = NotAvailableAware.orNull(mainThreadStats.getTotalWaitedNanos()); mainThreadTotalAllocatedBytes = NotAvailableAware.orNull(mainThreadStats.getTotalAllocatedBytes()); MutableThreadStats auxThreadStats = aggregate.getAuxThreadStats(); auxThreadTotalCpuNanos = NotAvailableAware.orNull(auxThreadStats.getTotalCpuNanos()); auxThreadTotalBlockedNanos = NotAvailableAware.orNull(auxThreadStats.getTotalBlockedNanos()); auxThreadTotalWaitedNanos = NotAvailableAware.orNull(auxThreadStats.getTotalWaitedNanos()); auxThreadTotalAllocatedBytes = NotAvailableAware.orNull(auxThreadStats.getTotalAllocatedBytes()); durationNanosHistogramBytes = aggregate.getDurationNanosHistogram().toProto(scratchBuffer).toByteArray(); } @Override public @Untainted String getSql() { StringBuilder sb = new StringBuilder(); sb.append("merge into aggregate_"); if (transactionName != null) { sb.append("tn_"); } else { sb.append("tt_"); } sb.append("rollup_"); sb.append(castUntainted(rollupLevel)); sb.append(" (transaction_type,"); if (transactionName != null) { sb.append(" transaction_name,"); } sb.append(" capture_time, 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) key (transaction_type"); if (transactionName != null) { sb.append(", transaction_name"); } sb.append(", capture_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?," + " ?, ?, ?"); if (transactionName != null) { sb.append(", ?"); } sb.append(")"); return castUntainted(sb.toString()); } // minimal work inside this method as it is called with active connection @Override public void bind(PreparedStatement preparedStatement) throws SQLException { int i = 1; preparedStatement.setString(i++, transactionType); if (transactionName != null) { preparedStatement.setString(i++, transactionName); } preparedStatement.setLong(i++, captureTime); preparedStatement.setDouble(i++, totalDurationNanos); preparedStatement.setLong(i++, transactionCount); preparedStatement.setLong(i++, errorCount); preparedStatement.setBoolean(i++, asyncTransactions); RowMappers.setLong(preparedStatement, i++, queriesCappedId); RowMappers.setLong(preparedStatement, i++, serviceCallsCappedId); RowMappers.setLong(preparedStatement, i++, mainThreadProfileCappedId); RowMappers.setLong(preparedStatement, i++, auxThreadProfileCappedId); RowMappers.setBytes(preparedStatement, i++, mainThreadRootTimers); RowMappers.setBytes(preparedStatement, i++, auxThreadRootTimers); RowMappers.setBytes(preparedStatement, i++, asyncTimers); RowMappers.setDouble(preparedStatement, i++, mainThreadTotalCpuNanos); RowMappers.setDouble(preparedStatement, i++, mainThreadTotalBlockedNanos); RowMappers.setDouble(preparedStatement, i++, mainThreadTotalWaitedNanos); RowMappers.setDouble(preparedStatement, i++, mainThreadTotalAllocatedBytes); RowMappers.setDouble(preparedStatement, i++, auxThreadTotalCpuNanos); RowMappers.setDouble(preparedStatement, i++, auxThreadTotalBlockedNanos); RowMappers.setDouble(preparedStatement, i++, auxThreadTotalWaitedNanos); RowMappers.setDouble(preparedStatement, i++, auxThreadTotalAllocatedBytes); preparedStatement.setBytes(i++, durationNanosHistogramBytes); } private static List<Stored.QueriesByType> convertToStored(List<Aggregate.QueriesByType> queries, List<TruncatedQueryText> truncatedQueryTexts) { List<Stored.QueriesByType> storedQueries = Lists.newArrayList(); for (Aggregate.QueriesByType loopQueriesByType : queries) { Stored.QueriesByType.Builder storedQueryByType = Stored.QueriesByType.newBuilder() .setType(loopQueriesByType.getType()); for (Aggregate.Query loopQuery : loopQueriesByType.getQueryList()) { TruncatedQueryText truncatedQueryText = truncatedQueryTexts.get(loopQuery.getSharedQueryTextIndex()); Stored.Query.Builder storedQuery = Stored.Query.newBuilder() .setTruncatedText(truncatedQueryText.truncatedText()) .setFullTextSha1(Strings.nullToEmpty(truncatedQueryText.fullTextSha1())) .setTotalDurationNanos(loopQuery.getTotalDurationNanos()) .setExecutionCount(loopQuery.getExecutionCount()); if (loopQuery.hasTotalRows()) { storedQuery.setTotalRows(Stored.OptionalInt64.newBuilder() .setValue(loopQuery.getTotalRows().getValue()) .build()); } storedQueryByType.addQuery(storedQuery.build()); } storedQueries.add(storedQueryByType.build()); } return storedQueries; } private static List<QueriesByType> convertToStored(@Nullable QueryCollector queries) { if (queries == null) { return ImmutableList.of(); } List<Stored.QueriesByType> storedQueries = Lists.newArrayList(); for (Entry<String, List<MutableQuery>> entry : queries.getSortedQueries().entrySet()) { Stored.QueriesByType.Builder storedQueryByType = Stored.QueriesByType.newBuilder() .setType(entry.getKey()); for (MutableQuery query : entry.getValue()) { Stored.Query.Builder storedQuery = Stored.Query.newBuilder() .setTruncatedText(query.getTruncatedText()) .setFullTextSha1(Strings.nullToEmpty(query.getFullTextSha1())) .setTotalDurationNanos(query.getTotalDurationNanos()) .setExecutionCount(query.getExecutionCount()); if (query.hasTotalRows()) { storedQuery.setTotalRows(Stored.OptionalInt64.newBuilder() .setValue(query.getTotalRows()) .build()); } storedQueryByType.addQuery(storedQuery.build()); } storedQueries.add(storedQueryByType.build()); } return storedQueries; } private static @Nullable Long writeQueries(CappedDatabase cappedDatabase, List<Stored.QueriesByType> queries) throws IOException { if (queries.isEmpty()) { return null; } return cappedDatabase.writeMessages(queries, RollupCappedDatabaseStats.AGGREGATE_QUERIES); } private static @Nullable Long writeServiceCalls(CappedDatabase cappedDatabase, List<Aggregate.ServiceCallsByType> serviceCalls) throws IOException { if (serviceCalls.isEmpty()) { return null; } return cappedDatabase.writeMessages(serviceCalls, RollupCappedDatabaseStats.AGGREGATE_SERVICE_CALLS); } private static @Nullable Long writeProfile(CappedDatabase cappedDatabase, @Nullable MutableProfile profile) throws IOException { if (profile == null) { return null; } return cappedDatabase.writeMessage(profile.toProto(), RollupCappedDatabaseStats.AGGREGATE_PROFILES); } private static @Nullable Long writeProfile(CappedDatabase cappedDatabase, Profile profile) throws IOException { return cappedDatabase.writeMessage(profile, RollupCappedDatabaseStats.AGGREGATE_PROFILES); } private static byte /*@Nullable*/ [] toByteArray(List<? extends AbstractMessage> messages) throws IOException { if (messages.isEmpty()) { return null; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (AbstractMessage message : messages) { message.writeDelimitedTo(baos); } return baos.toByteArray(); } }