/* * Copyright 2014-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.ui; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.collect.Lists; import org.glowroot.common.config.ConfigDefaults; import org.glowroot.common.live.ImmutableOverallQuery; import org.glowroot.common.live.ImmutableThroughputAggregate; import org.glowroot.common.live.ImmutableTransactionQuery; import org.glowroot.common.live.LiveAggregateRepository; import org.glowroot.common.live.LiveAggregateRepository.LiveResult; 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.MutableProfile; import org.glowroot.common.model.MutableQuery; import org.glowroot.common.model.OverallSummaryCollector; import org.glowroot.common.model.OverallSummaryCollector.OverallSummary; import org.glowroot.common.model.ProfileCollector; import org.glowroot.common.model.QueryCollector; import org.glowroot.common.model.Result; import org.glowroot.common.model.ServiceCallCollector; import org.glowroot.common.model.TransactionSummaryCollector; import org.glowroot.common.model.TransactionSummaryCollector.SummarySortOrder; import org.glowroot.common.model.TransactionSummaryCollector.TransactionSummary; import org.glowroot.common.repo.AggregateRepository; import org.glowroot.common.repo.ConfigRepository; import org.glowroot.common.repo.MutableAggregate; import org.glowroot.common.repo.Utils; import org.glowroot.common.util.Clock; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AdvancedConfig; import org.glowroot.wire.api.model.AggregateOuterClass.Aggregate; class TransactionCommonService { private final AggregateRepository aggregateRepository; private final LiveAggregateRepository liveAggregateRepository; private final ConfigRepository configRepository; private final Clock clock; TransactionCommonService(AggregateRepository aggregateRepository, LiveAggregateRepository liveAggregateRepository, ConfigRepository configRepository, Clock clock) { this.aggregateRepository = aggregateRepository; this.liveAggregateRepository = liveAggregateRepository; this.configRepository = configRepository; this.clock = clock; } // query.from() is non-inclusive OverallSummary readOverallSummary(String agentRollupId, OverallQuery query, boolean autoRefresh) throws Exception { OverallSummaryCollector collector = new OverallSummaryCollector(); long revisedFrom = query.from(); long revisedTo; if (autoRefresh) { revisedTo = query.to(); } else { revisedTo = liveAggregateRepository.mergeInOverallSummary(agentRollupId, query, collector); } for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { OverallQuery revisedQuery = ImmutableOverallQuery.builder() .copyFrom(query) .from(revisedFrom) .to(revisedTo) .rollupLevel(rollupLevel) .build(); aggregateRepository.mergeOverallSummaryInto(agentRollupId, revisedQuery, collector); long lastRolledUpTime = collector.getLastCaptureTime(); revisedFrom = Math.max(revisedFrom, lastRolledUpTime + 1); if (revisedFrom > revisedTo) { break; } } return collector.getOverallSummary(); } // query.from() is non-inclusive Result<TransactionSummary> readTransactionSummaries(String agentRollupId, OverallQuery query, SummarySortOrder sortOrder, int limit, boolean autoRefresh) throws Exception { TransactionSummaryCollector collector = new TransactionSummaryCollector(); long revisedFrom = query.from(); long revisedTo; if (autoRefresh) { revisedTo = query.to(); } else { revisedTo = liveAggregateRepository.mergeInTransactionSummaries(agentRollupId, query, collector); } for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { OverallQuery revisedQuery = ImmutableOverallQuery.builder() .copyFrom(query) .from(revisedFrom) .to(revisedTo) .rollupLevel(rollupLevel) .build(); aggregateRepository.mergeTransactionSummariesInto(agentRollupId, revisedQuery, sortOrder, limit, collector); long lastRolledUpTime = collector.getLastCaptureTime(); revisedFrom = Math.max(revisedFrom, lastRolledUpTime + 1); if (revisedFrom > revisedTo) { break; } } return collector.getResult(sortOrder, limit); } // query.from() is INCLUSIVE List<OverviewAggregate> getOverviewAggregates(String agentRollupId, TransactionQuery query, boolean autoRefresh) throws Exception { LiveResult<OverviewAggregate> liveResult; long revisedTo; if (autoRefresh) { liveResult = null; revisedTo = query.to(); } else { liveResult = liveAggregateRepository.getOverviewAggregates(agentRollupId, query); revisedTo = liveResult == null ? query.to() : liveResult.revisedTo(); } TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .to(revisedTo) .build(); List<OverviewAggregate> aggregates = aggregateRepository.readOverviewAggregates(agentRollupId, revisedQuery); if (revisedQuery.rollupLevel() == 0) { if (liveResult != null) { aggregates = Lists.newArrayList(aggregates); aggregates.addAll(liveResult.get()); } return aggregates; } long nonRolledUpFrom = revisedQuery.from(); if (!aggregates.isEmpty()) { long lastRolledUpTime = aggregates.get(aggregates.size() - 1).captureTime(); nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1); } List<OverviewAggregate> orderedNonRolledUpAggregates = Lists.newArrayList(); if (nonRolledUpFrom <= revisedTo) { orderedNonRolledUpAggregates.addAll( aggregateRepository.readOverviewAggregates(agentRollupId, ImmutableTransactionQuery.builder() .copyFrom(revisedQuery) .from(nonRolledUpFrom) .rollupLevel(0) .build())); } if (liveResult != null) { orderedNonRolledUpAggregates.addAll(liveResult.get()); } aggregates = Lists.newArrayList(aggregates); long fixedIntervalMillis = configRepository.getRollupConfigs() .get(revisedQuery.rollupLevel()).intervalMillis(); aggregates.addAll(rollUpOverviewAggregates(orderedNonRolledUpAggregates, new RollupCaptureTimeFn(fixedIntervalMillis))); if (aggregates.size() >= 2) { long currentTime = clock.currentTimeMillis(); OverviewAggregate nextToLastAggregate = aggregates.get(aggregates.size() - 2); if (currentTime - nextToLastAggregate.captureTime() < 60000) { aggregates.remove(aggregates.size() - 1); } } return aggregates; } // query.from() is INCLUSIVE List<PercentileAggregate> getPercentileAggregates(String agentRollupId, TransactionQuery query, boolean autoRefresh) throws Exception { LiveResult<PercentileAggregate> liveResult; long revisedTo; if (autoRefresh) { liveResult = null; revisedTo = query.to(); } else { liveResult = liveAggregateRepository.getPercentileAggregates(agentRollupId, query); revisedTo = liveResult == null ? query.to() : liveResult.revisedTo(); } TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .to(revisedTo) .build(); List<PercentileAggregate> aggregates = aggregateRepository.readPercentileAggregates(agentRollupId, revisedQuery); if (revisedQuery.rollupLevel() == 0) { if (liveResult != null) { aggregates = Lists.newArrayList(aggregates); aggregates.addAll(liveResult.get()); } return aggregates; } long nonRolledUpFrom = revisedQuery.from(); if (!aggregates.isEmpty()) { long lastRolledUpTime = aggregates.get(aggregates.size() - 1).captureTime(); nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1); } List<PercentileAggregate> orderedNonRolledUpAggregates = Lists.newArrayList(); if (nonRolledUpFrom <= revisedTo) { orderedNonRolledUpAggregates.addAll( aggregateRepository.readPercentileAggregates(agentRollupId, ImmutableTransactionQuery.builder() .copyFrom(revisedQuery) .from(nonRolledUpFrom) .rollupLevel(0) .build())); } if (liveResult != null) { orderedNonRolledUpAggregates.addAll(liveResult.get()); } aggregates = Lists.newArrayList(aggregates); long fixedIntervalMillis = configRepository.getRollupConfigs() .get(revisedQuery.rollupLevel()).intervalMillis(); aggregates.addAll(rollUpPercentileAggregates(orderedNonRolledUpAggregates, new RollupCaptureTimeFn(fixedIntervalMillis))); if (aggregates.size() >= 2) { long currentTime = clock.currentTimeMillis(); PercentileAggregate nextToLastAggregate = aggregates.get(aggregates.size() - 2); if (currentTime - nextToLastAggregate.captureTime() < 60000) { aggregates.remove(aggregates.size() - 1); } } return aggregates; } // query.from() is INCLUSIVE List<ThroughputAggregate> getThroughputAggregates(String agentRollupId, TransactionQuery query, boolean autoRefresh) throws Exception { LiveResult<ThroughputAggregate> liveResult; long revisedTo; if (autoRefresh) { liveResult = null; revisedTo = query.to(); } else { liveResult = liveAggregateRepository.getThroughputAggregates(agentRollupId, query); revisedTo = liveResult == null ? query.to() : liveResult.revisedTo(); } TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .to(revisedTo) .build(); List<ThroughputAggregate> aggregates = aggregateRepository.readThroughputAggregates(agentRollupId, revisedQuery); if (revisedQuery.rollupLevel() == 0) { if (liveResult != null) { aggregates = Lists.newArrayList(aggregates); aggregates.addAll(liveResult.get()); } return aggregates; } long nonRolledUpFrom = revisedQuery.from(); if (!aggregates.isEmpty()) { long lastRolledUpTime = aggregates.get(aggregates.size() - 1).captureTime(); nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1); } List<ThroughputAggregate> orderedNonRolledUpAggregates = Lists.newArrayList(); if (nonRolledUpFrom <= revisedTo) { orderedNonRolledUpAggregates.addAll(aggregateRepository .readThroughputAggregates(agentRollupId, ImmutableTransactionQuery.builder() .copyFrom(revisedQuery) .from(nonRolledUpFrom) .rollupLevel(0) .build())); } if (liveResult != null) { orderedNonRolledUpAggregates.addAll(liveResult.get()); } aggregates = Lists.newArrayList(aggregates); long fixedIntervalMillis = configRepository.getRollupConfigs() .get(revisedQuery.rollupLevel()).intervalMillis(); aggregates.addAll(rollUpThroughputAggregates(orderedNonRolledUpAggregates, new RollupCaptureTimeFn(fixedIntervalMillis))); if (aggregates.size() >= 2) { long currentTime = clock.currentTimeMillis(); ThroughputAggregate nextToLastAggregate = aggregates.get(aggregates.size() - 2); if (currentTime - nextToLastAggregate.captureTime() < 60000) { aggregates.remove(aggregates.size() - 1); } } return aggregates; } // query.from() is non-inclusive Map<String, List<MutableQuery>> getMergedQueries(String agentRollupId, TransactionQuery query) throws Exception { int maxAggregateQueriesPerType = getMaxAggregateQueriesPerType(agentRollupId); QueryCollector queryCollector = new QueryCollector(maxAggregateQueriesPerType); long revisedFrom = query.from(); long revisedTo = liveAggregateRepository.mergeInQueries(agentRollupId, query, queryCollector); for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .from(revisedFrom) .to(revisedTo) .rollupLevel(rollupLevel) .build(); aggregateRepository.mergeQueriesInto(agentRollupId, revisedQuery, queryCollector); long lastRolledUpTime = queryCollector.getLastCaptureTime(); revisedFrom = Math.max(revisedFrom, lastRolledUpTime + 1); if (revisedFrom > revisedTo) { break; } } return queryCollector.getSortedQueries(); } @Nullable String readFullQueryText(String agentRollupId, String fullQueryTextSha1) throws Exception { // checking live data is not efficient since must perform many sha1 hashes // so check repository first String fullQueryText = aggregateRepository.readFullQueryText(agentRollupId, fullQueryTextSha1); if (fullQueryText != null) { return fullQueryText; } return liveAggregateRepository.getFullQueryText(agentRollupId, fullQueryTextSha1); } // query.from() is non-inclusive List<Aggregate.ServiceCallsByType> getMergedServiceCalls(String agentRollupId, TransactionQuery query) throws Exception { int maxAggregateServiceCallsPerType = getMaxAggregateServiceCallsPerType(agentRollupId); ServiceCallCollector serviceCallCollector = new ServiceCallCollector(maxAggregateServiceCallsPerType, 0); long revisedFrom = query.from(); long revisedTo = liveAggregateRepository.mergeInServiceCalls(agentRollupId, query, serviceCallCollector); for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .from(revisedFrom) .to(revisedTo) .rollupLevel(rollupLevel) .build(); aggregateRepository.mergeServiceCallsInto(agentRollupId, revisedQuery, serviceCallCollector); long lastRolledUpTime = serviceCallCollector.getLastCaptureTime(); revisedFrom = Math.max(revisedFrom, lastRolledUpTime + 1); if (revisedFrom > revisedTo) { break; } } return serviceCallCollector.toProto(); } // query.from() is non-inclusive MutableProfile getMergedProfile(String agentRollupId, TransactionQuery query, boolean auxiliary, List<String> includes, List<String> excludes, double truncateBranchPercentage) throws Exception { MutableProfile profile = getMergedProfile(agentRollupId, query, auxiliary); if (!includes.isEmpty() || !excludes.isEmpty()) { profile.filter(includes, excludes); } if (truncateBranchPercentage != 0) { int minSamples = (int) Math.ceil(profile.getSampleCount() * truncateBranchPercentage / 100); // don't truncate any root nodes profile.truncateBranches(minSamples); } return profile; } boolean hasMainThreadProfile(String agentRollupId, TransactionQuery query) throws Exception { for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .rollupLevel(rollupLevel) .build(); if (aggregateRepository.hasMainThreadProfile(agentRollupId, revisedQuery)) { return true; } } return false; } boolean hasAuxThreadProfile(String agentRollupId, TransactionQuery query) throws Exception { for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .rollupLevel(rollupLevel) .build(); if (aggregateRepository.hasAuxThreadProfile(agentRollupId, revisedQuery)) { return true; } } return false; } static List<OverviewAggregate> rollUpOverviewAggregates( List<OverviewAggregate> orderedNonRolledUpOverviewAggregates, Function<Long, Long> rollupCaptureTimeFn) throws Exception { List<OverviewAggregate> rolledUpOverviewAggregates = Lists.newArrayList(); MutableAggregate currMergedAggregate = new MutableAggregate(0, 0); long currRollupCaptureTime = Long.MIN_VALUE; long maxCaptureTime = Long.MIN_VALUE; for (OverviewAggregate nonRolledUpOverviewAggregate : orderedNonRolledUpOverviewAggregates) { maxCaptureTime = nonRolledUpOverviewAggregate.captureTime(); long rollupCaptureTime = rollupCaptureTimeFn.apply(maxCaptureTime); if (rollupCaptureTime != currRollupCaptureTime && !currMergedAggregate.isEmpty()) { rolledUpOverviewAggregates .add(currMergedAggregate.toOverviewAggregate(currRollupCaptureTime)); currMergedAggregate = new MutableAggregate(0, 0); } currRollupCaptureTime = rollupCaptureTime; currMergedAggregate .addTotalDurationNanos(nonRolledUpOverviewAggregate.totalDurationNanos()); currMergedAggregate .addTransactionCount(nonRolledUpOverviewAggregate.transactionCount()); currMergedAggregate .mergeMainThreadRootTimers(nonRolledUpOverviewAggregate.mainThreadRootTimers()); currMergedAggregate .mergeAuxThreadRootTimers(nonRolledUpOverviewAggregate.auxThreadRootTimers()); currMergedAggregate .mergeAsyncTimers(nonRolledUpOverviewAggregate.asyncTimers()); currMergedAggregate .mergeMainThreadStats(nonRolledUpOverviewAggregate.mainThreadStats()); currMergedAggregate.mergeAuxThreadStats(nonRolledUpOverviewAggregate.auxThreadStats()); } if (!currMergedAggregate.isEmpty()) { // roll up final one rolledUpOverviewAggregates.add(currMergedAggregate.toOverviewAggregate(maxCaptureTime)); } return rolledUpOverviewAggregates; } static List<PercentileAggregate> rollUpPercentileAggregates( List<PercentileAggregate> orderedNonRolledUpPercentileAggregates, Function<Long, Long> rollupCaptureTimeFn) throws Exception { List<PercentileAggregate> rolledUpPercentileAggregates = Lists.newArrayList(); MutableAggregate currMergedAggregate = new MutableAggregate(0, 0); long currRollupCaptureTime = Long.MIN_VALUE; long maxCaptureTime = Long.MIN_VALUE; for (PercentileAggregate nonRolledUpPercentileAggregate : orderedNonRolledUpPercentileAggregates) { maxCaptureTime = nonRolledUpPercentileAggregate.captureTime(); long rollupCaptureTime = rollupCaptureTimeFn.apply(maxCaptureTime); if (rollupCaptureTime != currRollupCaptureTime && !currMergedAggregate.isEmpty()) { rolledUpPercentileAggregates .add(currMergedAggregate.toPercentileAggregate(currRollupCaptureTime)); currMergedAggregate = new MutableAggregate(0, 0); } currRollupCaptureTime = rollupCaptureTime; currMergedAggregate .addTotalDurationNanos(nonRolledUpPercentileAggregate.totalDurationNanos()); currMergedAggregate .addTransactionCount(nonRolledUpPercentileAggregate.transactionCount()); currMergedAggregate.mergeDurationNanosHistogram( nonRolledUpPercentileAggregate.durationNanosHistogram()); } if (!currMergedAggregate.isEmpty()) { // roll up final one rolledUpPercentileAggregates .add(currMergedAggregate.toPercentileAggregate(maxCaptureTime)); } return rolledUpPercentileAggregates; } static List<ThroughputAggregate> rollUpThroughputAggregates( List<ThroughputAggregate> orderedNonRolledUpThroughputAggregates, Function<Long, Long> rollupCaptureTimeFn) throws Exception { List<ThroughputAggregate> rolledUpThroughputAggregates = Lists.newArrayList(); long currTransactionCount = 0; long currRollupCaptureTime = Long.MIN_VALUE; long maxCaptureTime = Long.MIN_VALUE; for (ThroughputAggregate nonRolledUpThroughputAggregate : orderedNonRolledUpThroughputAggregates) { maxCaptureTime = nonRolledUpThroughputAggregate.captureTime(); long rollupCaptureTime = rollupCaptureTimeFn.apply(maxCaptureTime); if (rollupCaptureTime != currRollupCaptureTime && currTransactionCount > 0) { rolledUpThroughputAggregates .add(ImmutableThroughputAggregate.of(currRollupCaptureTime, currTransactionCount)); currTransactionCount = 0; } currRollupCaptureTime = rollupCaptureTime; currTransactionCount += nonRolledUpThroughputAggregate.transactionCount(); } if (currTransactionCount > 0) { // roll up final one rolledUpThroughputAggregates .add(ImmutableThroughputAggregate.of(maxCaptureTime, currTransactionCount)); } return rolledUpThroughputAggregates; } private MutableProfile getMergedProfile(String agentRollupId, TransactionQuery query, boolean auxiliary) throws Exception { ProfileCollector collector = new ProfileCollector(); long revisedFrom = query.from(); long revisedTo; if (auxiliary) { revisedTo = liveAggregateRepository.mergeInAuxThreadProfiles(agentRollupId, query, collector); } else { revisedTo = liveAggregateRepository.mergeInMainThreadProfiles(agentRollupId, query, collector); } for (int rollupLevel = query.rollupLevel(); rollupLevel >= 0; rollupLevel--) { TransactionQuery revisedQuery = ImmutableTransactionQuery.builder() .copyFrom(query) .from(revisedFrom) .to(revisedTo) .rollupLevel(rollupLevel) .build(); if (auxiliary) { aggregateRepository.mergeAuxThreadProfilesInto(agentRollupId, revisedQuery, collector); } else { aggregateRepository.mergeMainThreadProfilesInto(agentRollupId, revisedQuery, collector); } long lastRolledUpTime = collector.getLastCaptureTime(); revisedFrom = Math.max(revisedFrom, lastRolledUpTime + 1); if (revisedFrom > revisedTo) { break; } } return collector.getProfile(); } private int getMaxAggregateQueriesPerType(String agentRollupId) throws Exception { AdvancedConfig advancedConfig = configRepository.getAdvancedConfig(agentRollupId); if (advancedConfig != null && advancedConfig.hasMaxAggregateQueriesPerType()) { return advancedConfig.getMaxAggregateQueriesPerType().getValue(); } else { return ConfigDefaults.MAX_AGGREGATE_QUERIES_PER_TYPE; } } private int getMaxAggregateServiceCallsPerType(String agentRollupId) throws Exception { AdvancedConfig advancedConfig = configRepository.getAdvancedConfig(agentRollupId); if (advancedConfig != null && advancedConfig.hasMaxAggregateServiceCallsPerType()) { return advancedConfig.getMaxAggregateServiceCallsPerType().getValue(); } else { return ConfigDefaults.MAX_AGGREGATE_SERVICE_CALLS_PER_TYPE; } } private static class RollupCaptureTimeFn implements Function<Long, Long> { private final long fixedIntervalMillis; private RollupCaptureTimeFn(long fixedIntervalMillis) { this.fixedIntervalMillis = fixedIntervalMillis; } @Override public Long apply(Long captureTime) { return Utils.getRollupCaptureTime(captureTime, fixedIntervalMillis); } } }