All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.local.ui.TransactionCommonService Maven / Gradle / Ivy

/*
 * Copyright 2014-2015 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.local.ui;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Nullable;

import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Maps;

import org.glowroot.collector.Aggregate;
import org.glowroot.collector.AggregateCollector;
import org.glowroot.collector.AggregateIntervalCollector;
import org.glowroot.collector.ProfileAggregate;
import org.glowroot.collector.QueryAggregate;
import org.glowroot.collector.QueryComponent.AggregateQuery;
import org.glowroot.collector.TransactionSummary;
import org.glowroot.common.ScratchBuffer;
import org.glowroot.common.Traverser;
import org.glowroot.config.ConfigService;
import org.glowroot.local.store.AggregateDao;
import org.glowroot.local.store.AggregateDao.MergedAggregate;
import org.glowroot.local.store.AggregateDao.TransactionSummarySortOrder;
import org.glowroot.local.store.QueryResult;
import org.glowroot.local.store.TransactionSummaryQuery;
import org.glowroot.transaction.model.ProfileNode;

import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;

class TransactionCommonService {

    private final AggregateDao aggregateDao;
    private final @Nullable AggregateCollector aggregateCollector;
    private final ConfigService configService;

    private final long fixedRollup1Millis;
    private final long fixedRollup2Millis;

    TransactionCommonService(AggregateDao aggregateDao,
            @Nullable AggregateCollector aggregateCollector, ConfigService configService,
            long fixedRollup1Seconds, long fixedRollup2Seconds) {
        this.aggregateDao = aggregateDao;
        this.aggregateCollector = aggregateCollector;
        this.configService = configService;
        this.fixedRollup1Millis = fixedRollup1Seconds * 1000;
        this.fixedRollup2Millis = fixedRollup2Seconds * 1000;
    }

    TransactionSummary readOverallSummary(String transactionType, long from, long to)
            throws SQLException {
        List orderedIntervalCollectors =
                getOrderedIntervalCollectorsInRange(from, to);
        if (orderedIntervalCollectors.isEmpty()) {
            return aggregateDao.readOverallTransactionSummary(transactionType, from, to);
        }
        long revisedTo = getRevisedTo(to, orderedIntervalCollectors);
        TransactionSummary overallSummary =
                aggregateDao.readOverallTransactionSummary(transactionType, from, revisedTo);
        for (AggregateIntervalCollector intervalCollector : orderedIntervalCollectors) {
            TransactionSummary liveOverallSummary =
                    intervalCollector.getLiveOverallSummary(transactionType);
            if (liveOverallSummary != null) {
                overallSummary = combineTransactionSummaries(null, overallSummary,
                        liveOverallSummary);
            }
        }
        return overallSummary;
    }

    QueryResult readTransactionSummaries(TransactionSummaryQuery query)
            throws SQLException {
        List orderedIntervalCollectors =
                getOrderedIntervalCollectorsInRange(query.from(), query.to());
        if (orderedIntervalCollectors.isEmpty()) {
            return aggregateDao.readTransactionSummaries(query);
        }
        long revisedTo = getRevisedTo(query.to(), orderedIntervalCollectors);
        TransactionSummaryQuery revisedQuery = query.withTo(revisedTo);
        QueryResult queryResult =
                aggregateDao.readTransactionSummaries(revisedQuery);
        if (orderedIntervalCollectors.isEmpty()) {
            return queryResult;
        }
        return mergeInLiveTransactionSummaries(revisedQuery, queryResult,
                orderedIntervalCollectors);
    }

    boolean shouldHaveQueries(String transactionType, @Nullable String transactionName, long from,
            long to) throws SQLException {
        if (transactionName == null) {
            return aggregateDao.shouldHaveOverallQueries(transactionType, from, to);
        } else {
            return aggregateDao.shouldHaveTransactionQueries(transactionType, transactionName,
                    from, to);
        }
    }

    boolean shouldHaveProfile(String transactionType, @Nullable String transactionName, long from,
            long to) throws SQLException {
        if (transactionName == null) {
            return aggregateDao.shouldHaveOverallProfile(transactionType, from, to);
        } else {
            return aggregateDao.shouldHaveTransactionProfile(transactionType, transactionName,
                    from, to);
        }
    }

    List getAggregates(String transactionType, @Nullable String transactionName,
            long from, long to, long liveCaptureTime) throws Exception {
        int rollupLevel = aggregateDao.getRollupLevelForView(from, to);
        List orderedIntervalCollectors =
                getOrderedIntervalCollectorsInRange(from, to);
        long revisedTo = getRevisedTo(to, orderedIntervalCollectors);
        List aggregates = getAggregatesFromDao(transactionType, transactionName, from,
                revisedTo, rollupLevel);
        if (rollupLevel == 0) {
            aggregates = Lists.newArrayList(aggregates);
            aggregates.addAll(getLiveAggregates(transactionType, transactionName,
                    orderedIntervalCollectors, liveCaptureTime));
            return aggregates;
        }
        long nonRolledUpFrom = from;
        if (!aggregates.isEmpty()) {
            long lastRolledUpTime = aggregates.get(aggregates.size() - 1).captureTime();
            nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1);
        }
        List orderedNonRolledUpAggregates = Lists.newArrayList();
        orderedNonRolledUpAggregates.addAll(getAggregatesFromDao(transactionType, transactionName,
                nonRolledUpFrom, revisedTo, 0));
        orderedNonRolledUpAggregates.addAll(getLiveAggregates(transactionType, transactionName,
                orderedIntervalCollectors, liveCaptureTime));
        aggregates = Lists.newArrayList(aggregates);
        aggregates.addAll(rollUp(transactionType, transactionName, orderedNonRolledUpAggregates,
                liveCaptureTime, rollupLevel));
        return aggregates;
    }

    Map> getQueries(String transactionType,
            @Nullable String transactionName, long from, long to) throws Exception {
        List queryAggregates =
                getQueryAggregates(transactionType, transactionName, from, to);
        return AggregateMerging.getOrderedAndTruncatedQueries(queryAggregates,
                configService.getAdvancedConfig().maxAggregateQueriesPerQueryType());
    }

    ProfileNode getProfile(String transactionType, @Nullable String transactionName, long from,
            long to, @Nullable String filterText, double truncateLeafPercentage) throws Exception {
        List profileAggregate =
                getProfileAggregates(transactionType, transactionName, from, to);
        ProfileNode syntheticRootNode = AggregateMerging.getMergedProfile(profileAggregate);
        long syntheticRootNodeSampleCount = syntheticRootNode.getSampleCount();
        if (filterText != null) {
            filter(syntheticRootNode, filterText);
        }
        if (truncateLeafPercentage != 0) {
            int minSamples = (int) (syntheticRootNode.getSampleCount() * truncateLeafPercentage);
            // don't truncate any root nodes
            truncateLeafs(syntheticRootNode.getChildNodes(), minSamples);
        }
        // retain original sample count for synthetic root node in case of filtered profile
        syntheticRootNode.setSampleCount(syntheticRootNodeSampleCount);
        return syntheticRootNode;
    }

    private List getOrderedIntervalCollectorsInRange(long from,
            long to) {
        if (aggregateCollector == null) {
            return ImmutableList.of();
        }
        return aggregateCollector.getOrderedIntervalCollectorsInRange(from, to);
    }

    private List getAggregatesFromDao(String transactionType,
            @Nullable String transactionName, long from, long to, int rollupLevel)
                    throws SQLException {
        if (transactionName == null) {
            return aggregateDao.readOverallAggregates(transactionType, from, to, rollupLevel);
        } else {
            return aggregateDao.readTransactionAggregates(transactionType, transactionName, from,
                    to, rollupLevel);
        }
    }

    // this method may return some rolled up query aggregates and some non-rolled up
    // they are all distinct though
    // this is ok since the results of this method are currently just aggregated into single
    // result as opposed to charted over time period
    private List getQueryAggregates(String transactionType,
            @Nullable String transactionName, long from, long to) throws Exception {
        int rollupLevel = aggregateDao.getRollupLevelForView(from, to);
        List orderedIntervalCollectors =
                getOrderedIntervalCollectorsInRange(from, to);
        long revisedTo = getRevisedTo(to, orderedIntervalCollectors);
        List queryAggregates = getQueryAggregatesFromDao(transactionType,
                transactionName, from, revisedTo, rollupLevel);
        if (rollupLevel == 0) {
            queryAggregates = Lists.newArrayList(queryAggregates);
            queryAggregates.addAll(getLiveQueryAggregates(transactionType, transactionName,
                    orderedIntervalCollectors));
            return queryAggregates;
        }
        long nonRolledUpFrom = from;
        if (!queryAggregates.isEmpty()) {
            long lastRolledUpTime = queryAggregates.get(queryAggregates.size() - 1).captureTime();
            nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1);
        }
        List orderedNonRolledUpQueryAggregates = Lists.newArrayList();
        orderedNonRolledUpQueryAggregates.addAll(getQueryAggregatesFromDao(transactionType,
                transactionName, nonRolledUpFrom, revisedTo, 0));
        orderedNonRolledUpQueryAggregates.addAll(getLiveQueryAggregates(transactionType,
                transactionName, orderedIntervalCollectors));
        queryAggregates = Lists.newArrayList(queryAggregates);
        queryAggregates.addAll(orderedNonRolledUpQueryAggregates);
        return queryAggregates;
    }

    private List getQueryAggregatesFromDao(String transactionType,
            @Nullable String transactionName, long from, long to, int rollupLevel)
                    throws SQLException {
        if (transactionName == null) {
            return aggregateDao.readOverallQueryAggregates(transactionType, from, to, rollupLevel);
        } else {
            return aggregateDao.readTransactionQueryAggregates(transactionType, transactionName,
                    from, to, rollupLevel);
        }
    }

    // this method may return some rolled up profile aggregates and some non-rolled up
    // they are all distinct though
    // this is ok since the results of this method are currently just aggregated into single
    // result as opposed to charted over time period
    private List getProfileAggregates(String transactionType,
            @Nullable String transactionName, long from, long to) throws Exception {
        int rollupLevel = aggregateDao.getRollupLevelForView(from, to);
        List orderedIntervalCollectors =
                getOrderedIntervalCollectorsInRange(from, to);
        long revisedTo = getRevisedTo(to, orderedIntervalCollectors);
        List profileAggregates = getProfileAggregatesFromDao(transactionType,
                transactionName, from, revisedTo, rollupLevel);
        if (rollupLevel == 0) {
            profileAggregates = Lists.newArrayList(profileAggregates);
            profileAggregates.addAll(getLiveProfileAggregates(transactionType, transactionName,
                    orderedIntervalCollectors));
            return profileAggregates;
        }
        long nonRolledUpFrom = from;
        if (!profileAggregates.isEmpty()) {
            long lastRolledUpTime =
                    profileAggregates.get(profileAggregates.size() - 1).captureTime();
            nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1);
        }
        List orderedNonRolledUpProfileAggregates = Lists.newArrayList();
        orderedNonRolledUpProfileAggregates.addAll(getProfileAggregatesFromDao(transactionType,
                transactionName, nonRolledUpFrom, revisedTo, 0));
        orderedNonRolledUpProfileAggregates.addAll(getLiveProfileAggregates(transactionType,
                transactionName, orderedIntervalCollectors));
        profileAggregates = Lists.newArrayList(profileAggregates);
        profileAggregates.addAll(orderedNonRolledUpProfileAggregates);
        return profileAggregates;
    }

    private List getProfileAggregatesFromDao(String transactionType,
            @Nullable String transactionName, long from, long to, int rollupLevel)
                    throws SQLException {
        if (transactionName == null) {
            return aggregateDao.readOverallProfileAggregates(transactionType, from, to,
                    rollupLevel);
        } else {
            return aggregateDao.readTransactionProfileAggregates(transactionType, transactionName,
                    from, to, rollupLevel);
        }
    }

    private List rollUp(String transactionType, @Nullable String transactionName,
            List orderedNonRolledUpAggregates, long liveCaptureTime, int rollupLevel)
                    throws Exception {
        long fixedRollupMillis;
        if (rollupLevel == 1) {
            fixedRollupMillis = fixedRollup1Millis;
        } else {
            fixedRollupMillis = fixedRollup2Millis;
        }
        List rolledUpAggregates = Lists.newArrayList();
        ScratchBuffer scratchBuffer = new ScratchBuffer();
        MergedAggregate currMergedAggregate = null;
        long currRollupTime = Long.MIN_VALUE;
        for (Aggregate nonRolledUpAggregate : orderedNonRolledUpAggregates) {
            long rollupTime = (long) Math.ceil(nonRolledUpAggregate.captureTime()
                    / (double) fixedRollupMillis) * fixedRollupMillis;
            if (rollupTime != currRollupTime && currMergedAggregate != null) {
                rolledUpAggregates.add(currMergedAggregate.toAggregate(scratchBuffer));
                currMergedAggregate = new MergedAggregate(Math.min(rollupTime, liveCaptureTime),
                        transactionType, transactionName, configService.getAdvancedConfig()
                                .maxAggregateQueriesPerQueryType());
            }
            if (currMergedAggregate == null) {
                currMergedAggregate = new MergedAggregate(Math.min(rollupTime, liveCaptureTime),
                        transactionType, transactionName, configService.getAdvancedConfig()
                                .maxAggregateQueriesPerQueryType());
            }
            currRollupTime = rollupTime;
            currMergedAggregate.addTotalMicros(nonRolledUpAggregate.totalMicros());
            currMergedAggregate.addErrorCount(nonRolledUpAggregate.errorCount());
            currMergedAggregate.addTransactionCount(nonRolledUpAggregate.transactionCount());
            currMergedAggregate.addTotalCpuMicros(nonRolledUpAggregate.totalCpuMicros());
            currMergedAggregate.addTotalBlockedMicros(nonRolledUpAggregate.totalBlockedMicros());
            currMergedAggregate.addTotalWaitedMicros(nonRolledUpAggregate.totalWaitedMicros());
            currMergedAggregate.addTotalAllocatedKBytes(
                    nonRolledUpAggregate.totalAllocatedKBytes());
            currMergedAggregate.addTimers(nonRolledUpAggregate.timers());
            currMergedAggregate.addHistogram(nonRolledUpAggregate.histogram());
        }
        if (currMergedAggregate != null) {
            // roll up final one
            rolledUpAggregates.add(currMergedAggregate.toAggregate(scratchBuffer));
        }
        return rolledUpAggregates;
    }

    private static long getRevisedTo(long to,
            List orderedIntervalCollectors) {
        if (orderedIntervalCollectors.isEmpty()) {
            return to;
        } else {
            // -1 since query 'to' is inclusive
            // this way don't need to worry about de-dupping between live and stored aggregates
            return orderedIntervalCollectors.get(0).getEndTime() - 1;
        }
    }

    private static QueryResult mergeInLiveTransactionSummaries(
            TransactionSummaryQuery query, QueryResult queryResult,
            List intervalCollectors) {
        List transactionSummaries = queryResult.records();
        Map transactionSummaryMap = Maps.newHashMap();
        for (TransactionSummary transactionSummary : transactionSummaries) {
            String transactionName = transactionSummary.transactionName();
            // transaction name is only null for overall summary
            checkNotNull(transactionName);
            transactionSummaryMap.put(transactionName, transactionSummary);
        }
        for (AggregateIntervalCollector intervalCollector : intervalCollectors) {
            List liveTransactionSummaries =
                    intervalCollector.getLiveTransactionSummaries(query.transactionType());
            for (TransactionSummary liveTransactionSummary : liveTransactionSummaries) {
                String transactionName = liveTransactionSummary.transactionName();
                // transaction name is only null for overall summary
                checkNotNull(transactionName);
                TransactionSummary transactionSummary = transactionSummaryMap.get(transactionName);
                if (transactionSummary == null) {
                    transactionSummaryMap.put(transactionName, liveTransactionSummary);
                } else {
                    transactionSummaryMap.put(transactionName,
                            combineTransactionSummaries(transactionName, transactionSummary,
                                    liveTransactionSummary));
                }
            }
        }
        transactionSummaries =
                sortTransactionSummaries(transactionSummaryMap.values(), query.sortOrder());
        boolean moreAvailable = queryResult.moreAvailable();
        if (transactionSummaries.size() > query.limit()) {
            moreAvailable = true;
            transactionSummaries = transactionSummaries.subList(0, query.limit());
        }
        return new QueryResult(transactionSummaries, moreAvailable);
    }

    private static List getLiveAggregates(String transactionType,
            @Nullable String transactionName, List intervalCollectors,
            long liveCaptureTime) throws IOException {
        List aggregates = Lists.newArrayList();
        for (AggregateIntervalCollector intervalCollector : intervalCollectors) {
            Aggregate liveAggregate = intervalCollector.getLiveAggregate(transactionType,
                    transactionName, liveCaptureTime);
            if (liveAggregate != null) {
                aggregates.add(liveAggregate);
            }
        }
        return aggregates;
    }

    private static List getLiveQueryAggregates(String transactionType,
            @Nullable String transactionName, List intervalCollectors)
                    throws IOException {
        List queryAggregates = Lists.newArrayList();
        for (AggregateIntervalCollector intervalCollector : intervalCollectors) {
            QueryAggregate liveQueryAggregate =
                    intervalCollector.getLiveQueryAggregate(transactionType, transactionName);
            if (liveQueryAggregate != null) {
                queryAggregates.add(liveQueryAggregate);
            }
        }
        return queryAggregates;
    }

    private static List getLiveProfileAggregates(String transactionType,
            @Nullable String transactionName, List intervalCollectors)
                    throws IOException {
        List profileAggregates = Lists.newArrayList();
        for (AggregateIntervalCollector intervalCollector : intervalCollectors) {
            ProfileAggregate liveProfileAggregate =
                    intervalCollector.getLiveProfileAggregate(transactionType, transactionName);
            if (liveProfileAggregate != null) {
                profileAggregates.add(liveProfileAggregate);
            }
        }
        return profileAggregates;
    }

    private static TransactionSummary combineTransactionSummaries(@Nullable String transactionName,
            TransactionSummary summary1, TransactionSummary summary2) {
        return TransactionSummary.builder()
                .transactionName(transactionName)
                .totalMicros(summary1.totalMicros() + summary2.totalMicros())
                .transactionCount(summary1.transactionCount() + summary2.transactionCount())
                .build();
    }

    // using non-recursive algorithm to avoid stack overflow error on deep profiles
    private static void filter(ProfileNode syntheticRootNode, String filterText) {
        new ProfileFilterer(syntheticRootNode, filterText).traverse();
    }

    // using non-recursive algorithm to avoid stack overflow error on deep profiles
    private static void truncateLeafs(Iterable rootNodes, int minSamples) {
        Deque toBeVisited = new ArrayDeque();
        for (ProfileNode rootNode : rootNodes) {
            toBeVisited.add(rootNode);
        }
        ProfileNode node;
        while ((node = toBeVisited.poll()) != null) {
            for (Iterator i = node.getChildNodes().iterator(); i.hasNext();) {
                ProfileNode childNode = i.next();
                if (childNode.getSampleCount() < minSamples) {
                    i.remove();
                    // TODO capture sampleCount per timerName of non-ellipsed structure
                    // and use this in UI dropdown filter of timer names
                    // (currently sampleCount per timerName of ellipsed structure is used)
                    node.incrementEllipsedSampleCount((int) childNode.getSampleCount());
                } else {
                    toBeVisited.add(childNode);
                }
            }
        }
    }

    private static List sortTransactionSummaries(
            Iterable transactionSummaries,
            TransactionSummarySortOrder sortOrder) {
        switch (sortOrder) {
            case TOTAL_TIME:
                return TransactionSummary.orderingByTotalTimeDesc.immutableSortedCopy(
                        transactionSummaries);
            case AVERAGE_TIME:
                return TransactionSummary.orderingByAverageTimeDesc.immutableSortedCopy(
                        transactionSummaries);
            case THROUGHPUT:
                return TransactionSummary.orderingByTransactionCountDesc.immutableSortedCopy(
                        transactionSummaries);
            default:
                throw new AssertionError("Unexpected sort order: " + sortOrder);
        }
    }

    private static class ProfileFilterer extends Traverser {

        private final String filterTextUpper;

        private ProfileFilterer(ProfileNode rootNode, String filterText) {
            super(rootNode);
            this.filterTextUpper = filterText.toUpperCase(Locale.ENGLISH);
        }

        @Override
        public List visit(ProfileNode node) {
            if (isMatch(node)) {
                node.setMatched();
                // no need to visit children
                return ImmutableList.of();
            }
            return ImmutableList.copyOf(node.getChildNodes());
        }

        @Override
        public void revisitAfterChildren(ProfileNode node) {
            if (node.isMatched()) {
                // keep node and all children
                return;
            }
            if (!hasMatchedChild(node) && !node.isSyntheticRootNode()) {
                // node is unmatched and will be removed by parent
                return;
            }
            node.setMatched();
            long filteredSampleCount = 0;
            for (Iterator i = node.iterator(); i.hasNext();) {
                ProfileNode childNode = i.next();
                if (childNode.isMatched()) {
                    filteredSampleCount += childNode.getSampleCount();
                } else {
                    i.remove();
                }
            }
            node.setSampleCount(filteredSampleCount);
        }

        private boolean isMatch(ProfileNode node) {
            String stackTraceElementUpper =
                    node.getStackTraceElementStr().toUpperCase(Locale.ENGLISH);
            if (stackTraceElementUpper.contains(filterTextUpper)) {
                return true;
            }
            String leafThreadState = node.getLeafThreadState();
            if (leafThreadState != null) {
                String leafThreadStateUpper = leafThreadState.toUpperCase(Locale.ENGLISH);
                if (leafThreadStateUpper.contains(filterTextUpper)) {
                    return true;
                }
            }
            return false;
        }

        private static boolean hasMatchedChild(ProfileNode node) {
            for (ProfileNode childNode : node) {
                if (childNode.isMatched()) {
                    return true;
                }
            }
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy