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

com.uber.m3.tally.ScopeImpl Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2021 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package com.uber.m3.tally;

import com.uber.m3.util.ImmutableMap;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;

/**
 * Default {@link Scope} implementation.
 */
class ScopeImpl implements Scope, TestScope {
    private StatsReporter reporter;
    private String prefix;
    private String separator;
    private ImmutableMap tags;
    private Buckets defaultBuckets;

    private ScheduledExecutorService scheduler;
    private Registry registry;

    private final ConcurrentHashMap counters = new ConcurrentHashMap<>();
    private final ConcurrentHashMap gauges = new ConcurrentHashMap<>();
    private final ConcurrentHashMap histograms = new ConcurrentHashMap<>();

    private final CopyOnWriteArrayList reportingList = new CopyOnWriteArrayList<>();

    private final ConcurrentHashMap timers = new ConcurrentHashMap<>();

    // Private ScopeImpl constructor. Root scopes should be built using the RootScopeBuilder class
    ScopeImpl(ScheduledExecutorService scheduler, Registry registry, ScopeBuilder builder) {
        this.scheduler = scheduler;
        this.registry = registry;

        this.reporter = builder.reporter;
        this.prefix = builder.prefix;
        this.separator = builder.separator;
        this.tags = builder.tags;
        this.defaultBuckets = builder.defaultBuckets;
    }

    @Override
    public Counter counter(String name) {
        return counters.computeIfAbsent(name, ignored ->
                // NOTE: This will called at most once
                new CounterImpl(this, fullyQualifiedName(name))
        );
    }

    @Override
    public Gauge gauge(String name) {
        return gauges.computeIfAbsent(name, ignored ->
                // NOTE: This will called at most once
                new GaugeImpl(this, fullyQualifiedName(name)));
    }

    @Override
    public Timer timer(String name) {
        // Timers report directly to the {@code StatsReporter}, and therefore not added to reporting queue
        // i.e. they are not buffered
        return timers.computeIfAbsent(name, ignored -> new TimerImpl(fullyQualifiedName(name), tags, reporter));
    }

    @Override
    public Histogram histogram(String name, @Nullable Buckets buckets) {
        return histograms.computeIfAbsent(name, ignored ->
                // NOTE: This will be called at most once
                new HistogramImpl(
                        this,
                        fullyQualifiedName(name),
                        tags,
                        Optional.ofNullable(buckets)
                                .orElse(defaultBuckets)
                )
        );
    }

    @Override
    public Scope tagged(Map tags) {
        return subScopeHelper(prefix, tags);
    }

    @Override
    public Scope subScope(String name) {
        return subScopeHelper(fullyQualifiedName(name), null);
    }

    @Override
    public Capabilities capabilities() {
        if (reporter != null) {
            return reporter.capabilities();
        }

        return CapableOf.NONE;
    }

    @Override
    public void close() {
        // First, stop periodic reporting of this scope
        scheduler.shutdown();

        // More metrics may have come in between the time of the last report by the
        // scheduler and the call to close this scope, so report once more to flush
        // all metrics.
        reportLoopIteration();

        if (reporter != null) {
            // Now that all metrics should be known to the reporter, close the reporter
            reporter.close();
        }
    }

     void addToReportingQueue(T metric) {
        reportingList.add(metric);
    }

    /**
     * Reports using the specified reporter.
     * @param reporter the reporter to report
     */
    void report(StatsReporter reporter) {
        for (Reportable metric : reportingList) {
            metric.report(tags, reporter);
        }
    }

    // Serializes a map to generate a key for a prefix/map combination
    // Non-generic EMPTY ImmutableMap will never contain any elements
    static ScopeKey keyForPrefixedStringMap(String prefix, ImmutableMap stringMap) {
        return new ScopeKey(prefix, stringMap);
    }

    String fullyQualifiedName(String name) {
        if (prefix == null || prefix.length() == 0) {
            return name;
        }

        return String.format("%s%s%s", prefix, separator, name);
    }

    /**
     * Snapshot returns a copy of all values since the last report execution
     * This is an expensive operation and should only be used for testing purposes.
     *
     * @return a {@link Snapshot} of this {@link Scope}
     */
    @Override
    public Snapshot snapshot() {
        Snapshot snap = new SnapshotImpl();

        ArrayList scopes = new ArrayList<>();
        scopes.add(this);
        scopes.addAll(registry.subscopes.values());

        for (ScopeImpl subscope : scopes) {
            ImmutableMap tags = new ImmutableMap.Builder()
                    .putAll(this.tags)
                    .putAll(subscope.tags)
                    .build();

            for (Map.Entry counter : subscope.counters.entrySet()) {
                String name = subscope.fullyQualifiedName(counter.getKey());

                ScopeKey scopeKey = keyForPrefixedStringMap(name, tags);

                snap.counters().put(
                        scopeKey,
                        new CounterSnapshotImpl(
                                name,
                                tags,
                                counter.getValue().snapshot()
                        )
                );
            }

            for (Map.Entry gauge : subscope.gauges.entrySet()) {
                String name = subscope.fullyQualifiedName(gauge.getKey());

                ScopeKey scopeKey = keyForPrefixedStringMap(name, tags);

                snap.gauges().put(
                        scopeKey,
                        new GaugeSnapshotImpl(
                                name,
                                tags,
                                gauge.getValue().snapshot()
                        )
                );
            }

            for (Map.Entry timer : subscope.timers.entrySet()) {
                String name = subscope.fullyQualifiedName(timer.getKey());

                ScopeKey scopeKey = keyForPrefixedStringMap(name, tags);

                snap.timers().put(
                        scopeKey,
                        new TimerSnapshotImpl(
                                name,
                                tags,
                                timer.getValue().snapshot()
                        )
                );
            }

            for (Map.Entry histogram : subscope.histograms.entrySet()) {
                String name = subscope.fullyQualifiedName(histogram.getKey());

                ScopeKey scopeKey = keyForPrefixedStringMap(name, tags);

                snap.histograms().put(
                        scopeKey,
                        new HistogramSnapshotImpl(
                                name,
                                tags,
                                histogram.getValue().snapshotValues(),
                                histogram.getValue().snapshotDurations()
                        )
                );
            }
        }

        return snap;
    }

    // Helper function used to create subscopes
    private Scope subScopeHelper(String prefix, Map tags) {
        ImmutableMap.Builder mapBuilder = new ImmutableMap.Builder<>();

        if (this.tags != null) {
            mapBuilder.putAll(this.tags);
        }
        if (tags != null) {
            // New tags override old tag buckets
            mapBuilder.putAll(tags);
        }

        ImmutableMap mergedTags = mapBuilder.build();

        ScopeKey key = keyForPrefixedStringMap(prefix, mergedTags);

        return computeSubscopeIfAbsent(prefix, key, mergedTags);
    }

    // This method must only be called on unit tests or benchmarks
    protected Scope computeSubscopeIfAbsent(String prefix, ScopeKey key, ImmutableMap mergedTags) {
        Scope scope = registry.subscopes.get(key);
        if (scope != null) {
            return scope;
        }

        return registry.subscopes.computeIfAbsent(
            key,
            (k) -> new ScopeBuilder(scheduler, registry)
                .reporter(reporter)
                .prefix(prefix)
                .separator(separator)
                .tags(mergedTags)
                .defaultBuckets(defaultBuckets)
                .build()
        );
    }

    // One iteration of reporting this scope and all its subscopes
    void reportLoopIteration() {
        Collection subscopes = registry.subscopes.values();

        if (reporter != null) {
            for (ScopeImpl subscope : subscopes) {
                subscope.report(reporter);
            }

            reporter.flush();
        }
    }

    class ReportLoop implements Runnable {
        private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;

        ReportLoop(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
            this.uncaughtExceptionHandler = uncaughtExceptionHandler;
        }

        public void run() {
            try {
                reportLoopIteration();
            } catch (Exception uncaughtException) {
                if (uncaughtExceptionHandler != null) {
                    reportUncaughtException(uncaughtException);
                }
            }
        }

        private void reportUncaughtException(Exception uncaughtException) {
            try {
                uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), uncaughtException);
            } catch (Exception ignored) {
                // ignore exception
            }
        }
    }

    static class Registry {
        Map subscopes = new ConcurrentHashMap<>();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy