org.apache.aurora.common.stats.TimeSeriesRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.aurora.common.stats.TimeSeriesRepositoryImpl.java

Source

/**
 * 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.apache.aurora.common.stats;

import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.AbstractScheduledService;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.google.inject.name.Named;

import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.common.util.BuildInfo;
import org.apache.aurora.common.util.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A simple in-memory repository for exported variables.
 *
 * @author John Sirois
 */
public class TimeSeriesRepositoryImpl extends AbstractScheduledService implements TimeSeriesRepository {

    private static final Logger LOG = LoggerFactory.getLogger(TimeSeriesRepositoryImpl.class);

    /**
     * {@literal @Named} binding key for the sampling period.
     */
    public static final String SAMPLE_PERIOD = "com.twitter.common.stats.TimeSeriesRepositoryImpl.SAMPLE_PERIOD";

    /**
     * {@literal @Named} binding key for the maximum number of retained samples.
     */
    public static final String SAMPLE_RETENTION_PERIOD = "com.twitter.common.stats.TimeSeriesRepositoryImpl.SAMPLE_RETENTION_PERIOD";

    private final SlidingStats scrapeDuration = new SlidingStats("variable_scrape", "micros");

    // We store TimeSeriesImpl, which allows us to add samples.
    private final LoadingCache<String, TimeSeriesImpl> timeSeries;
    private final EvictingQueue<Number> timestamps;

    private final StatRegistry statRegistry;
    private final Amount<Long, Time> samplePeriod;
    private final int retainedSampleLimit;
    private final BuildInfo buildInfo;

    @Inject
    public TimeSeriesRepositoryImpl(StatRegistry statRegistry,
            @Named(SAMPLE_PERIOD) Amount<Long, Time> samplePeriod,
            @Named(SAMPLE_RETENTION_PERIOD) final Amount<Long, Time> retentionPeriod, BuildInfo buildInfo) {
        this.statRegistry = checkNotNull(statRegistry);
        this.samplePeriod = checkNotNull(samplePeriod);
        this.buildInfo = checkNotNull(buildInfo);
        Preconditions.checkArgument(samplePeriod.getValue() > 0, "Sample period must be positive.");
        checkNotNull(retentionPeriod);
        Preconditions.checkArgument(retentionPeriod.getValue() > 0, "Sample retention period must be positive.");

        retainedSampleLimit = (int) (retentionPeriod.as(Time.SECONDS) / samplePeriod.as(Time.SECONDS));
        Preconditions.checkArgument(retainedSampleLimit > 0,
                "Sample retention period must be greater than sample period.");

        timeSeries = CacheBuilder.newBuilder().build(new CacheLoader<String, TimeSeriesImpl>() {
            @Override
            public TimeSeriesImpl load(final String name) {
                TimeSeriesImpl timeSeries = new TimeSeriesImpl(name);

                // Backfill so we have data for pre-accumulated timestamps.
                int numTimestamps = timestamps.size();
                if (numTimestamps != 0) {
                    for (int i = 1; i < numTimestamps; i++) {
                        timeSeries.addSample(0L);
                    }
                }

                return timeSeries;
            }
        });

        timestamps = EvictingQueue.create(retainedSampleLimit);
    }

    private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1 /* One thread. */,
            new ThreadFactoryBuilder().setNameFormat("VariableSampler-%d").setDaemon(true).build());

    @Override
    protected void startUp() throws Exception {
        JvmStats.export();

        for (String name : buildInfo.getProperties().keySet()) {
            final String stringValue = buildInfo.getProperties().get(name);
            LOG.info("Build Info key: " + name + " has value " + stringValue);
            if (stringValue == null) {
                continue;
            }
            final Long longValue = Longs.tryParse(stringValue);
            if (longValue != null) {
                Stats.exportStatic(new StatImpl<Long>(Stats.normalizeName("build." + name)) {
                    @Override
                    public Long read() {
                        return longValue;
                    }
                });
            } else {
                Stats.exportString(new StatImpl<String>(Stats.normalizeName("build." + name)) {
                    @Override
                    public String read() {
                        return stringValue;
                    }
                });
            }
        }
    }

    @Override
    protected Scheduler scheduler() {
        return Scheduler.newFixedRateSchedule(samplePeriod.getValue(), samplePeriod.getValue(),
                samplePeriod.getUnit().getTimeUnit());
    }

    @Override
    protected ScheduledExecutorService executor() {
        return executor;
    }

    @Override
    protected void runOneIteration() throws Exception {
        runSampler(Clock.SYSTEM_CLOCK);
    }

    @Override
    protected void shutDown() throws Exception {
        executor.shutdown();
        LOG.info("Variable sampler shut down");
    }

    @VisibleForTesting
    synchronized void runSampler(Clock clock) {
        timestamps.add(clock.nowMillis());

        long startNanos = clock.nowNanos();
        for (RecordingStat<? extends Number> var : statRegistry.getStats()) {
            timeSeries.getUnchecked(var.getName()).addSample(var.sample());
        }
        scrapeDuration.accumulate(Amount.of(clock.nowNanos() - startNanos, Time.NANOSECONDS).as(Time.MICROSECONDS));
    }

    @Override
    public synchronized Set<String> getAvailableSeries() {
        return ImmutableSet.copyOf(timeSeries.asMap().keySet());
    }

    @Override
    public synchronized TimeSeries get(String name) {
        if (!timeSeries.asMap().containsKey(name))
            return null;
        return timeSeries.getUnchecked(name);
    }

    @Override
    public synchronized Iterable<Number> getTimestamps() {
        return Iterables.unmodifiableIterable(timestamps);
    }

    private class TimeSeriesImpl implements TimeSeries {
        private final String name;
        private final EvictingQueue<Number> samples;

        TimeSeriesImpl(String name) {
            this.name = name;
            samples = EvictingQueue.create(retainedSampleLimit);
        }

        @Override
        public String getName() {
            return name;
        }

        void addSample(Number value) {
            samples.add(value);
        }

        @Override
        public Iterable<Number> getSamples() {
            return Iterables.unmodifiableIterable(samples);
        }
    }
}