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

org.glowroot.agent.impl.TransactionCollector Maven / Gradle / Ivy

There is a newer version: 0.14.0-beta.3
Show newest version
/*
 * Copyright 2011-2018 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.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Ticker;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableMap;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Maps;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Sets;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.immutables.value.Value;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;

import org.glowroot.agent.collector.Collector;
import org.glowroot.agent.collector.Collector.TraceReader;
import org.glowroot.agent.config.ConfigService;
import org.glowroot.agent.config.TransactionConfig;
import org.glowroot.agent.config.TransactionConfig.SlowThreshold;
import org.glowroot.agent.plugin.api.config.ConfigListener;
import org.glowroot.agent.util.RateLimitedLogger;
import org.glowroot.agent.util.ThreadFactories;
import org.glowroot.agent.shaded.org.glowroot.common.util.Clock;
import org.glowroot.agent.shaded.org.glowroot.common.util.OnlyUsedByTests;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

public class TransactionCollector {

    private static final Logger logger = LoggerFactory.getLogger(TransactionCollector.class);

    // back pressure on trace collection
    private static final int PENDING_LIMIT = 100;

    private final ExecutorService dedicatedExecutor;
    private final Collector collector;
    private final Aggregator aggregator;
    private final Clock clock;
    private final Ticker ticker;
    private final Set pendingTransactions = Sets.newCopyOnWriteArraySet();

    private final RateLimitedLogger backPressureLogger =
            new RateLimitedLogger(TransactionCollector.class);

    // visibility is provided by memoryBarrier in org.glowroot.config.ConfigService
    private Map slowThresholds = ImmutableMap.of();
    // visibility is provided by memoryBarrier in org.glowroot.config.ConfigService
    private long defaultSlowThresholdNanos;

    public TransactionCollector(final ConfigService configService, Collector collector,
            Aggregator aggregator, Clock clock, Ticker ticker) {
        this.collector = collector;
        this.aggregator = aggregator;
        this.clock = clock;
        this.ticker = ticker;
        dedicatedExecutor = Executors
                .newSingleThreadExecutor(ThreadFactories.create("Glowroot-Trace-Collector"));
        configService.addConfigListener(new UpdateSlowThresholds(configService));
    }

    public boolean shouldStoreSlow(Transaction transaction) {
        if (transaction.isPartiallyStored()) {
            return true;
        }
        long durationNanos = transaction.getDurationNanos();
        // check if trace-specific store threshold was set
        long slowThresholdMillis = transaction.getSlowThresholdMillisOverride();
        if (slowThresholdMillis != Transaction.USE_GENERAL_STORE_THRESHOLD) {
            return durationNanos >= MILLISECONDS.toNanos(slowThresholdMillis);
        }
        // check if there is a matching transaction type / transaction name specific slow threshold
        if (!slowThresholds.isEmpty()) {
            SlowThresholdsForType slowThresholdForType =
                    slowThresholds.get(transaction.getTransactionType());
            if (slowThresholdForType != null) {
                Long slowThresholdNanos =
                        slowThresholdForType.thresholdNanos().get(transaction.getTransactionName());
                if (slowThresholdNanos != null) {
                    return durationNanos >= slowThresholdNanos;
                }
                slowThresholdNanos = slowThresholdForType.defaultThresholdNanos();
                if (slowThresholdNanos != null) {
                    return durationNanos >= slowThresholdNanos;
                }
            }
        }
        // fall back to default slow trace threshold
        return durationNanos >= defaultSlowThresholdNanos;
    }

    public boolean shouldStoreError(Transaction transaction) {
        return transaction.getErrorMessage() != null;
    }

    public Collection getPendingTransactions() {
        return pendingTransactions;
    }

    @OnlyUsedByTests
    public void close() throws InterruptedException {
        dedicatedExecutor.shutdown();
        if (!dedicatedExecutor.awaitTermination(10, SECONDS)) {
            throw new IllegalStateException("Could not terminate executor");
        }
    }

    void onCompletedTransaction(final Transaction transaction) {
        // capture time is calculated by the aggregator because it depends on monotonically
        // increasing capture times so it can flush aggregates without concern for new data
        // arriving with a prior capture time
        long captureTime = aggregator.add(transaction);
        final boolean slow = shouldStoreSlow(transaction);
        if (!slow && !shouldStoreError(transaction)) {
            return;
        }
        // limit doesn't apply to transactions that were already (partially) stored to make sure
        // they don't get left out in case they cause an avalanche of slowness
        if (pendingTransactions.size() >= PENDING_LIMIT && !transaction.isPartiallyStored()) {
            backPressureLogger.warn("not storing a trace because of an excessive backlog of {}"
                    + " traces already waiting to be stored", PENDING_LIMIT);
            return;
        }
        pendingTransactions.add(transaction);

        // this need to be called inside the transaction thread
        transaction.onCompleteWillStoreTrace(captureTime);

        // transaction is ended, so Executor Plugin won't tie this async work to the transaction
        // (which is good)
        dedicatedExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    TraceReader traceReader =
                            TraceCreator.createTraceReaderForCompleted(transaction, slow);
                    collector.collectTrace(traceReader);
                } catch (Throwable t) {
                    logger.error(t.getMessage(), t);
                } finally {
                    pendingTransactions.remove(transaction);
                }
            }
        });
    }

    // no need to throttle partial trace storage since throttling is handled upstream by using a
    // single thread executor in PartialTraceStorageWatcher
    public void storePartialTrace(Transaction transaction) {
        try {
            TraceReader traceReader = TraceCreator.createTraceReaderForPartial(transaction,
                    clock.currentTimeMillis(), ticker.read());
            // one last check if transaction has completed
            if (!transaction.isCompleted()) {
                transaction.setPartiallyStored();
                collector.collectTrace(traceReader);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private class UpdateSlowThresholds implements ConfigListener {

        private final ConfigService configService;

        private UpdateSlowThresholds(ConfigService configService) {
            this.configService = configService;
        }

        @Override
        public void onChange() {
            TransactionConfig transactionConfig = configService.getTransactionConfig();
            Map slowThresholds = Maps.newHashMap();
            for (SlowThreshold slowThreshold : transactionConfig.slowThresholds()) {
                String transactionType = slowThreshold.transactionType();
                SlowThresholdsForTypeBuilder slowThresholdForType =
                        slowThresholds.get(transactionType);
                if (slowThresholdForType == null) {
                    slowThresholdForType = new SlowThresholdsForTypeBuilder();
                    slowThresholds.put(transactionType, slowThresholdForType);
                }
                String transactionName = slowThreshold.transactionName();
                long thresholdNanos = MILLISECONDS.toNanos(slowThreshold.thresholdMillis());
                if (transactionName.isEmpty()) {
                    slowThresholdForType.defaultThresholdNanos = thresholdNanos;
                } else {
                    slowThresholdForType.thresholdNanos.put(transactionName, thresholdNanos);
                }
            }
            Map builder = Maps.newHashMap();
            for (Map.Entry entry : slowThresholds
                    .entrySet()) {
                builder.put(entry.getKey(), entry.getValue().toImmutable());
            }
            TransactionCollector.this.slowThresholds = ImmutableMap.copyOf(builder);
            defaultSlowThresholdNanos =
                    MILLISECONDS.toNanos(transactionConfig.slowThresholdMillis());
        }
    }

    @Value.Immutable
    interface SlowThresholdsForType {
        @Nullable
        Long defaultThresholdNanos();
        Map thresholdNanos(); // key is transaction name
    }

    // need separate builder type to avoid exception in case of duplicate
    // transactionType/transactionName pair
    private static class SlowThresholdsForTypeBuilder {

        private @Nullable Long defaultThresholdNanos;
        private Map thresholdNanos = Maps.newHashMap();

        private SlowThresholdsForType toImmutable() {
            return ImmutableSlowThresholdsForType.builder()
                    .defaultThresholdNanos(defaultThresholdNanos)
                    .putAllThresholdNanos(thresholdNanos)
                    .build();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy