/* * Copyright 2013-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.impl; import java.io.IOException; import java.util.List; import javax.annotation.Nullable; import com.google.common.collect.Lists; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.glowroot.agent.config.AdvancedConfig; import org.glowroot.agent.impl.Transaction.RootTimerCollector; import org.glowroot.agent.impl.Transaction.ThreadStatsCollector; import org.glowroot.agent.model.CommonTimerImpl; import org.glowroot.agent.model.MutableAggregateTimer; import org.glowroot.agent.model.Profile; import org.glowroot.agent.model.QueryCollector; import org.glowroot.agent.model.QueryCollector.SharedQueryTextCollector; import org.glowroot.agent.model.ThreadStats; import org.glowroot.common.live.ImmutableOverviewAggregate; import org.glowroot.common.live.ImmutablePercentileAggregate; import org.glowroot.common.live.ImmutableThroughputAggregate; 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.model.LazyHistogram; import org.glowroot.common.model.LazyHistogram.ScratchBuffer; import org.glowroot.common.model.MutableProfile; import org.glowroot.common.model.OverallErrorSummaryCollector; import org.glowroot.common.model.OverallSummaryCollector; import org.glowroot.common.model.ProfileCollector; import org.glowroot.common.model.ServiceCallCollector; import org.glowroot.common.model.TransactionErrorSummaryCollector; import org.glowroot.common.model.TransactionSummaryCollector; import org.glowroot.common.util.NotAvailableAware; import org.glowroot.common.util.Styles; import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate; import org.glowroot.wire.api.model.Proto.OptionalDouble; import static com.google.common.base.Preconditions.checkNotNull; // must be used under an appropriate lock @Styles.Private class AggregateCollector { private static final double NANOSECONDS_PER_MILLISECOND = 1000000.0; private final @Nullable String transactionName; // aggregates use double instead of long to avoid (unlikely) 292 year nanosecond rollover private double totalDurationNanos; private long transactionCount; private long errorCount; private boolean asyncTransactions; private final RootTimerCollectorImpl mainThreadRootTimers = new RootTimerCollectorImpl(); private final RootTimerCollectorImpl auxThreadRootTimers = new RootTimerCollectorImpl(); private final RootTimerCollectorImpl asyncTimers = new RootTimerCollectorImpl(); private final ThreadStatsCollectorImpl mainThreadStats = new ThreadStatsCollectorImpl(); private final ThreadStatsCollectorImpl auxThreadStats = new ThreadStatsCollectorImpl(); // histogram values are in nanoseconds, but with microsecond precision to reduce the number of // buckets (and memory) required private final LazyHistogram durationNanosHistogram = new LazyHistogram(); // lazy instantiated to reduce memory footprint private @MonotonicNonNull QueryCollector queries; private @MonotonicNonNull ServiceCallCollector serviceCalls; private @MonotonicNonNull MutableProfile mainThreadProfile; private @MonotonicNonNull MutableProfile auxThreadProfile; private final int maxAggregateQueriesPerType; private final int maxAggregateServiceCallsPerType; AggregateCollector(@Nullable String transactionName, int maxAggregateQueriesPerType, int maxAggregateServiceCallsPerType) { this.transactionName = transactionName; this.maxAggregateQueriesPerType = maxAggregateQueriesPerType; this.maxAggregateServiceCallsPerType = maxAggregateServiceCallsPerType; } void add(Transaction transaction) { long totalDurationNanos = transaction.getDurationNanos(); this.totalDurationNanos += totalDurationNanos; transactionCount++; if (transaction.getErrorMessage() != null) { errorCount++; } if (transaction.isAsync()) { asyncTransactions = true; } mainThreadStats.mergeThreadStats(transaction.getMainThreadStats()); transaction.mergeAuxThreadStatsInto(auxThreadStats); durationNanosHistogram.add(totalDurationNanos); } RootTimerCollector getMainThreadRootTimers() { return mainThreadRootTimers; } RootTimerCollector getAuxThreadRootTimers() { return auxThreadRootTimers; } RootTimerCollector getAsyncTimers() { return asyncTimers; } void mergeMainThreadProfile(Profile toBeMergedProfile) { if (mainThreadProfile == null) { mainThreadProfile = new MutableProfile(); } toBeMergedProfile.mergeInto(mainThreadProfile); } void mergeAuxThreadProfile(Profile toBeMergedProfile) { if (auxThreadProfile == null) { auxThreadProfile = new MutableProfile(); } toBeMergedProfile.mergeInto(auxThreadProfile); } QueryCollector getQueryCollector() { if (queries == null) { int queriesHardLimitMultiplierWhileBuilding = transactionName == null ? AdvancedConfig.OVERALL_AGGREGATE_QUERIES_HARD_LIMIT_MULTIPLIER : AdvancedConfig.TRANSACTION_AGGREGATE_QUERIES_HARD_LIMIT_MULTIPLIER; queries = new QueryCollector(maxAggregateQueriesPerType, queriesHardLimitMultiplierWhileBuilding, false); } return queries; } ServiceCallCollector getServiceCallCollector() { if (serviceCalls == null) { int serviceCallsHardLimitMultiplierWhileBuilding = transactionName == null ? AdvancedConfig.OVERALL_AGGREGATE_SERVICE_CALLS_HARD_LIMIT_MULTIPLIER : AdvancedConfig.TRANSACTION_AGGREGATE_SERVICE_CALLS_HARD_LIMIT_MULTIPLIER; serviceCalls = new ServiceCallCollector(maxAggregateServiceCallsPerType, serviceCallsHardLimitMultiplierWhileBuilding); } return serviceCalls; } Aggregate build(SharedQueryTextCollector sharedQueryTextCollector, ScratchBuffer scratchBuffer) { Aggregate.Builder builder = Aggregate.newBuilder() .setTotalDurationNanos(totalDurationNanos) .setTransactionCount(transactionCount) .setErrorCount(errorCount) .setAsyncTransactions(asyncTransactions) .addAllMainThreadRootTimer(mainThreadRootTimers.toProto()) .addAllAuxThreadRootTimer(auxThreadRootTimers.toProto()) .addAllAsyncTimer(asyncTimers.toProto()) .setDurationNanosHistogram(durationNanosHistogram.toProto(scratchBuffer)); if (!mainThreadStats.isNA()) { builder.setMainThreadStats(mainThreadStats.toProto()); } if (!auxThreadStats.isNA()) { builder.setAuxThreadStats(auxThreadStats.toProto()); } if (queries != null) { builder.addAllQueriesByType(queries.toAggregateProto(sharedQueryTextCollector)); } if (serviceCalls != null) { builder.addAllServiceCallsByType(serviceCalls.toProto()); } if (mainThreadProfile != null) { builder.setMainThreadProfile(mainThreadProfile.toProto()); } if (auxThreadProfile != null) { builder.setAuxThreadProfile(auxThreadProfile.toProto()); } return builder.build(); } void mergeOverallSummaryInto(OverallSummaryCollector collector) { collector.mergeSummary(totalDurationNanos, transactionCount, 0); } void mergeTransactionSummariesInto(TransactionSummaryCollector collector) { checkNotNull(transactionName); collector.collect(transactionName, totalDurationNanos, transactionCount, 0); } void mergeOverallErrorSummaryInto(OverallErrorSummaryCollector collector) { collector.mergeErrorSummary(errorCount, transactionCount, 0); } void mergeTransactionErrorSummariesInto(TransactionErrorSummaryCollector collector) { checkNotNull(transactionName); if (errorCount != 0) { collector.collect(transactionName, errorCount, transactionCount, 0); } } OverviewAggregate getOverviewAggregate(long captureTime) { ImmutableOverviewAggregate.Builder builder = ImmutableOverviewAggregate.builder() .captureTime(captureTime) .totalDurationNanos(totalDurationNanos) .transactionCount(transactionCount) .asyncTransactions(asyncTransactions) .mainThreadRootTimers(mainThreadRootTimers.toProto()) .auxThreadRootTimers(auxThreadRootTimers.toProto()) .asyncTimers(asyncTimers.toProto()); if (!mainThreadStats.isNA()) { builder.mainThreadStats(mainThreadStats.toProto()); } if (!auxThreadStats.isNA()) { builder.auxThreadStats(auxThreadStats.toProto()); } return builder.build(); } PercentileAggregate getPercentileAggregate(long captureTime) { return ImmutablePercentileAggregate.builder() .captureTime(captureTime) .totalDurationNanos(totalDurationNanos) .transactionCount(transactionCount) .durationNanosHistogram(durationNanosHistogram.toProto(new ScratchBuffer())) .build(); } ThroughputAggregate getThroughputAggregate(long captureTime) { return ImmutableThroughputAggregate.of(captureTime, transactionCount); } @Nullable String getFullQueryText(String fullQueryTextSha1) { if (queries == null) { return null; } return queries.getFullQueryText(fullQueryTextSha1); } void mergeQueriesInto(org.glowroot.common.model.QueryCollector collector) throws IOException { if (queries != null) { queries.mergeQueriesInto(collector); } } void mergeServiceCallsInto(ServiceCallCollector collector) throws IOException { if (serviceCalls != null) { collector.mergeServiceCalls(serviceCalls.toProto()); } } void mergeMainThreadProfilesInto(ProfileCollector collector) { if (mainThreadProfile != null) { collector.mergeProfile(mainThreadProfile.toProto()); } } void mergeAuxThreadProfilesInto(ProfileCollector collector) { if (auxThreadProfile != null) { collector.mergeProfile(auxThreadProfile.toProto()); } } private static class RootTimerCollectorImpl implements RootTimerCollector { List<MutableAggregateTimer> rootMutableTimers = Lists.newArrayList(); @Override public void mergeRootTimer(CommonTimerImpl rootTimer) { mergeRootTimer(rootTimer, rootMutableTimers); } private List<Aggregate.Timer> toProto() { List<Aggregate.Timer> rootTimers = Lists.newArrayList(); for (MutableAggregateTimer rootMutableTimer : rootMutableTimers) { rootTimers.add(rootMutableTimer.toProto()); } return rootTimers; } private static void mergeRootTimer(CommonTimerImpl toBeMergedRootTimer, List<MutableAggregateTimer> rootTimers) { for (MutableAggregateTimer rootTimer : rootTimers) { if (toBeMergedRootTimer.getName().equals(rootTimer.getName()) && toBeMergedRootTimer.isExtended() == rootTimer.isExtended()) { rootTimer.merge(toBeMergedRootTimer); return; } } MutableAggregateTimer rootTimer = MutableAggregateTimer.createRootTimer( toBeMergedRootTimer.getName(), toBeMergedRootTimer.isExtended()); rootTimer.merge(toBeMergedRootTimer); rootTimers.add(rootTimer); } } private static class ThreadStatsCollectorImpl implements ThreadStatsCollector { // aggregates use double instead of long to avoid (unlikely) 292 year nanosecond rollover private double totalCpuNanos; private double totalBlockedMillis; private double totalWaitedMillis; private double totalAllocatedBytes; private boolean empty = true; @Override public void mergeThreadStats(ThreadStats threadStats) { totalCpuNanos = NotAvailableAware.add(totalCpuNanos, threadStats.getTotalCpuNanos()); totalBlockedMillis = NotAvailableAware.add(totalBlockedMillis, threadStats.getTotalBlockedMillis()); totalWaitedMillis = NotAvailableAware.add(totalWaitedMillis, threadStats.getTotalWaitedMillis()); totalAllocatedBytes = NotAvailableAware.add(totalAllocatedBytes, threadStats.getTotalAllocatedBytes()); empty = false; } private boolean isNA() { if (empty) { return true; } return NotAvailableAware.isNA(totalCpuNanos) && NotAvailableAware.isNA(totalBlockedMillis) && NotAvailableAware.isNA(totalWaitedMillis) && NotAvailableAware.isNA(totalAllocatedBytes); } public Aggregate.ThreadStats toProto() { Aggregate.ThreadStats.Builder builder = Aggregate.ThreadStats.newBuilder(); if (!NotAvailableAware.isNA(totalCpuNanos)) { builder.setTotalCpuNanos(toProto(totalCpuNanos)); } if (!NotAvailableAware.isNA(totalBlockedMillis)) { builder.setTotalBlockedNanos( toProto(totalBlockedMillis * NANOSECONDS_PER_MILLISECOND)); } if (!NotAvailableAware.isNA(totalWaitedMillis)) { builder.setTotalWaitedNanos( toProto(totalWaitedMillis * NANOSECONDS_PER_MILLISECOND)); } if (!NotAvailableAware.isNA(totalAllocatedBytes)) { builder.setTotalAllocatedBytes(toProto(totalAllocatedBytes)); } return builder.build(); } private static OptionalDouble toProto(double value) { return OptionalDouble.newBuilder().setValue(value).build(); } } }