/* * Copyright 2015-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.impl; import java.io.IOException; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glowroot.agent.collector.Collector; import org.glowroot.agent.collector.Collector.AggregateReader; import org.glowroot.agent.collector.Collector.AggregateVisitor; import org.glowroot.agent.model.Profile; import org.glowroot.agent.model.QueryCollector.SharedQueryTextCollector; 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.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.TransactionSummaryCollector; import org.glowroot.common.repo.Utils; import org.glowroot.common.util.Clock; import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate; public class AggregateIntervalCollector { private static final Logger logger = LoggerFactory.getLogger(AggregateIntervalCollector.class); private static final AtomicBoolean maxAggregateTransactionsWarnLogged = new AtomicBoolean(); private final long captureTime; private final int maxAggregateTransactionsPerTransactionType; private final int maxAggregateQueriesPerType; private final int maxAggregateServiceCallsPerType; private final Clock clock; @GuardedBy("lock") private final Map<String, IntervalTypeCollector> typeCollectors = Maps.newHashMap(); private final Object lock = new Object(); AggregateIntervalCollector(long currentTime, long aggregateIntervalMillis, int maxAggregateTransactionsPerTransactionType, int maxAggregateQueriesPerType, int maxAggregateServiceCallsPerType, Clock clock) { captureTime = Utils.getRollupCaptureTime(currentTime, aggregateIntervalMillis); this.maxAggregateTransactionsPerTransactionType = maxAggregateTransactionsPerTransactionType; this.maxAggregateQueriesPerType = maxAggregateQueriesPerType; this.maxAggregateServiceCallsPerType = maxAggregateServiceCallsPerType; this.clock = clock; } public long getCaptureTime() { return captureTime; } public void add(Transaction transaction) { synchronized (lock) { IntervalTypeCollector typeCollector = getTypeCollector(transaction.getTransactionType()); typeCollector.add(transaction); } } public void mergeOverallSummaryInto(OverallSummaryCollector collector, String transactionType) { synchronized (lock) { IntervalTypeCollector typeCollector = typeCollectors.get(transactionType); if (typeCollector == null) { return; } typeCollector.overallAggregateCollector.mergeOverallSummaryInto(collector); } } public void mergeTransactionSummariesInto(TransactionSummaryCollector collector, String transactionType) { synchronized (lock) { IntervalTypeCollector typeCollector = typeCollectors.get(transactionType); if (typeCollector == null) { return; } for (AggregateCollector aggregateCollector : typeCollector.transactionAggregateCollectors .values()) { aggregateCollector.mergeTransactionSummariesInto(collector); } } } public void mergeOverallErrorSummaryInto(OverallErrorSummaryCollector collector, String transactionType) { synchronized (lock) { IntervalTypeCollector typeCollector = typeCollectors.get(transactionType); if (typeCollector == null) { return; } typeCollector.overallAggregateCollector.mergeOverallErrorSummaryInto(collector); } } public void mergeTransactionErrorSummariesInto(TransactionErrorSummaryCollector collector, String transactionType) { synchronized (lock) { IntervalTypeCollector typeCollector = typeCollectors.get(transactionType); if (typeCollector == null) { return; } for (AggregateCollector aggregateCollector : typeCollector.transactionAggregateCollectors .values()) { aggregateCollector.mergeTransactionErrorSummariesInto(collector); } } } public @Nullable OverviewAggregate getOverviewAggregate(String transactionType, @Nullable String transactionName) { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return null; } long liveCaptureTime = Math.min(captureTime, clock.currentTimeMillis()); return aggregateCollector.getOverviewAggregate(liveCaptureTime); } } public @Nullable PercentileAggregate getPercentileAggregate(String transactionType, @Nullable String transactionName) { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return null; } long liveCaptureTime = Math.min(captureTime, clock.currentTimeMillis()); return aggregateCollector.getPercentileAggregate(liveCaptureTime); } } public @Nullable ThroughputAggregate getThroughputAggregate(String transactionType, @Nullable String transactionName) { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return null; } long liveCaptureTime = Math.min(captureTime, clock.currentTimeMillis()); return aggregateCollector.getThroughputAggregate(liveCaptureTime); } } public @Nullable String getFullQueryText(String fullQueryTextSha1) { synchronized (lock) { for (IntervalTypeCollector typeCollector : typeCollectors.values()) { String fullQueryText = typeCollector.getFullQueryText(fullQueryTextSha1); if (fullQueryText != null) { return fullQueryText; } } return null; } } public void mergeQueriesInto(QueryCollector collector, String transactionType, @Nullable String transactionName) throws IOException { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return; } aggregateCollector.mergeQueriesInto(collector); } } public void mergeServiceCallsInto(ServiceCallCollector collector, String transactionType, @Nullable String transactionName) throws IOException { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return; } aggregateCollector.mergeServiceCallsInto(collector); } } public void mergeMainThreadProfilesInto(ProfileCollector collector, String transactionType, @Nullable String transactionName) { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return; } aggregateCollector.mergeMainThreadProfilesInto(collector); } } public void mergeAuxThreadProfilesInto(ProfileCollector collector, String transactionType, @Nullable String transactionName) { synchronized (lock) { AggregateCollector aggregateCollector = getAggregateCollector(transactionType, transactionName); if (aggregateCollector == null) { return; } aggregateCollector.mergeAuxThreadProfilesInto(collector); } } void flush(Collector collector) throws Exception { collector.collectAggregates(new AggregatesImpl(captureTime)); } void clear() { synchronized (lock) { typeCollectors.clear(); } } private IntervalTypeCollector getTypeCollector(String transactionType) { IntervalTypeCollector typeCollector; typeCollector = typeCollectors.get(transactionType); if (typeCollector == null) { typeCollector = new IntervalTypeCollector(); typeCollectors.put(transactionType, typeCollector); } return typeCollector; } private @Nullable AggregateCollector getAggregateCollector(String transactionType, @Nullable String transactionName) { IntervalTypeCollector intervalTypeCollector = typeCollectors.get(transactionType); if (intervalTypeCollector == null) { return null; } if (transactionName == null) { return intervalTypeCollector.overallAggregateCollector; } else { return intervalTypeCollector.transactionAggregateCollectors.get(transactionName); } } private class IntervalTypeCollector { private final AggregateCollector overallAggregateCollector; private final Map<String, AggregateCollector> transactionAggregateCollectors = Maps.newConcurrentMap(); private IntervalTypeCollector() { overallAggregateCollector = new AggregateCollector(null, maxAggregateQueriesPerType, maxAggregateServiceCallsPerType); } private void add(Transaction transaction) { merge(transaction, overallAggregateCollector); AggregateCollector transactionAggregateCollector = transactionAggregateCollectors.get(transaction.getTransactionName()); if (transactionAggregateCollector == null && transactionAggregateCollectors .size() < maxAggregateTransactionsPerTransactionType) { transactionAggregateCollector = new AggregateCollector(transaction.getTransactionName(), maxAggregateQueriesPerType, maxAggregateServiceCallsPerType); transactionAggregateCollectors.put(transaction.getTransactionName(), transactionAggregateCollector); } if (transactionAggregateCollector == null) { if (!maxAggregateTransactionsWarnLogged.getAndSet(true)) { logger.warn("the max transaction names per transaction type was exceeded" + " during the current interval. consider increasing the limit under" + " Configuration > Advanced, or reducing the number of transaction" + " names by configuring instrumentation points under Configuration" + " > Instrumentation that override the transaction name."); } return; } merge(transaction, transactionAggregateCollector); } private void merge(Transaction transaction, AggregateCollector aggregateCollector) { aggregateCollector.add(transaction); aggregateCollector.getMainThreadRootTimers() .mergeRootTimer(transaction.getMainThreadRootTimer()); transaction.mergeAuxThreadTimersInto(aggregateCollector.getAuxThreadRootTimers()); transaction.mergeAsyncTimersInto(aggregateCollector.getAsyncTimers()); transaction.mergeQueriesInto(aggregateCollector.getQueryCollector()); transaction.mergeServiceCallsInto(aggregateCollector.getServiceCallCollector()); Profile mainThreadProfile = transaction.getMainThreadProfile(); if (mainThreadProfile != null) { aggregateCollector.mergeMainThreadProfile(mainThreadProfile); } Profile auxThreadProfile = transaction.getAuxThreadProfile(); if (auxThreadProfile != null) { aggregateCollector.mergeAuxThreadProfile(auxThreadProfile); } } private @Nullable String getFullQueryText(String fullQueryTextSha1) { String fullQueryText = overallAggregateCollector.getFullQueryText(fullQueryTextSha1); if (fullQueryText != null) { return fullQueryText; } for (AggregateCollector aggregateCollector : transactionAggregateCollectors.values()) { fullQueryText = aggregateCollector.getFullQueryText(fullQueryTextSha1); if (fullQueryText != null) { return fullQueryText; } } return null; } } private class AggregatesImpl implements AggregateReader { private final long captureTime; private AggregatesImpl(long captureTime) { this.captureTime = captureTime; } @Override public long captureTime() { return captureTime; } @Override public void accept(AggregateVisitor aggregateVisitor) throws Exception { synchronized (lock) { SharedQueryTextCollector sharedQueryTextCollector = new SharedQueryTextCollector(); ScratchBuffer scratchBuffer = new ScratchBuffer(); for (Entry<String, IntervalTypeCollector> e : typeCollectors.entrySet()) { String transactionType = e.getKey(); IntervalTypeCollector intervalTypeCollector = e.getValue(); Aggregate overallAggregate = intervalTypeCollector.overallAggregateCollector .build(sharedQueryTextCollector, scratchBuffer); aggregateVisitor.visitOverallAggregate(transactionType, sharedQueryTextCollector.getAndClearLastestSharedQueryTexts(), overallAggregate); for (Entry<String, AggregateCollector> f : intervalTypeCollector.transactionAggregateCollectors .entrySet()) { Aggregate transactionAggregate = f.getValue().build(sharedQueryTextCollector, scratchBuffer); aggregateVisitor.visitTransactionAggregate(transactionType, f.getKey(), sharedQueryTextCollector.getAndClearLastestSharedQueryTexts(), transactionAggregate); } } } } } }