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

com.microsoft.applicationinsights.internal.channel.sampling.AdaptiveTelemetrySampler Maven / Gradle / Ivy

/*
 * ApplicationInsights-Java
 * Copyright (c) Microsoft Corporation
 * All rights reserved.
 *
 * MIT License
 * 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.microsoft.applicationinsights.internal.channel.sampling;

import java.util.Date;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import com.microsoft.applicationinsights.channel.TelemetrySampler;
import com.microsoft.applicationinsights.internal.logger.InternalLogger;
import com.microsoft.applicationinsights.internal.shutdown.SDKShutdownActivity;
import com.microsoft.applicationinsights.internal.shutdown.Stoppable;
import com.microsoft.applicationinsights.internal.util.ThreadPoolUtils;
import com.microsoft.applicationinsights.telemetry.Telemetry;

/**
 * This sampler will keep the outgoing telemetries within the limit that was decided by the user.
 *
 * This sampler will change the sampling rate as needed to keep up in the pace, as opposed to the 'FixedRateTelemetrySampler'
 * This sampler employs the {@link FixedRateTelemetrySampler} for doing the actual sampling and a timer for re-evaluating the sampling percentage.
 *
 * Created by gupele on 11/9/2016.
 */
public final class AdaptiveTelemetrySampler implements Stoppable, TelemetrySampler {
    private final static int DEFAULT_MAX_TELEMETRIES_PER_SECOND = 100;
    private final static int DEFAULT_EVALUATION_INTERVAL_IN_SECONDS = 900;
    private final static int DEFAULT_SAMPLING_PERCENTAGE_DECREASE_TIMEOUT_IN_SECONDS = 120;
    private final static int DEFAULT_SAMPLING_PERCENTAGE_INCREASE_TIMEOUT_IN_SECONDS = 900;
    private final static int DEFAULT_MIN_SAMPLING_PERCENTAGE = 1;
    private final static int DEFAULT_MAX_SAMPLING_PERCENTAGE = 100;
    private final static int DEFAULT_INITIAL_SAMPLING_PERCENTAGE = 100;
    private final static double DEFAULT_MOVING_AVERAGE_RATIO = 0.25;

    private enum ChangeDirection {
        Up,
        Down,
        None
    };

    private class SamplingRangeEvaluator implements Runnable {
        private boolean first = true;
        private double average = 0.0;

        @Override
        public void run() {
            double telemetriesPerSecond = (double)counter.get() / (double)evaluationIntervalInSec;
            counter.set(0);
            if (!first) {
                average = average * (1 - movingAverageRatio) + telemetriesPerSecond * movingAverageRatio;
            } else {
                first = false;
                average = telemetriesPerSecond;
            }

            InternalLogger.INSTANCE.trace("Average for sampling is %s", average);

            double suggestedSamplingPercentage;
            if (average > maxTelemetriesPerSecond) {
                suggestedSamplingPercentage = 100.0 - (average - maxTelemetriesPerSecond) * 100.0 / maxTelemetriesPerSecond;
            } else {
                suggestedSamplingPercentage = 100;
            }
            if (suggestedSamplingPercentage > maxSamplingPercentage) {
                suggestedSamplingPercentage = maxSamplingPercentage;
            }
            if (suggestedSamplingPercentage < minSamplingPercentage) {
                suggestedSamplingPercentage = minSamplingPercentage;
            }

            boolean samplingPercentageChangeNeeded = suggestedSamplingPercentage != currentSamplingPercentage;
            if (samplingPercentageChangeNeeded) {
                Date currentDate = new Date();
                long duration = currentDate.getTime() - lastChangedDate.getTime();

                long diffInSeconds = TimeUnit.MILLISECONDS.toSeconds(duration);
                if (suggestedSamplingPercentage > currentSamplingPercentage) {
                    if (lastChangeDirection != ChangeDirection.Up || diffInSeconds >= samplingPercentageIncreaseTimeoutInSec) {
                        updateSamplingData(suggestedSamplingPercentage, ChangeDirection.Up, currentDate);
                    }
                } else {
                    if (lastChangeDirection != ChangeDirection.Down || diffInSeconds >= samplingPercentageDecreaseTimeoutInSec) {
                        updateSamplingData(suggestedSamplingPercentage, ChangeDirection.Down, currentDate);
                    }
                }
            }
        }

        private void updateSamplingData(double suggestedSamplingPercentage, ChangeDirection direction, Date currentDate) {
            InternalLogger.INSTANCE.trace("Updating sampling percentage from %s to %s", currentSamplingPercentage, suggestedSamplingPercentage);
            currentSamplingPercentage = suggestedSamplingPercentage;
            lastChangeDirection = direction;
            lastChangedDate = currentDate;
            sampler.setSamplingPercentage((double) suggestedSamplingPercentage);
        }
    }

    // Max telemetries per second, the instance will
    // try to keep up by adjusting the sampling percentage
    private double maxTelemetriesPerSecond;

    // How much time to wait between evaluations of the sampling percentage
    private int evaluationIntervalInSec;

    // How much time to wait between successive increases of the sampling percentage
    private int samplingPercentageDecreaseTimeoutInSec;

    // How much time to wait between successive decreases of the sampling percentage
    private int samplingPercentageIncreaseTimeoutInSec;

    private int minSamplingPercentage;
    private int maxSamplingPercentage;

    // The weigh given to the last telemetries count within 'evaluationIntervalInSeconds'
    private double movingAverageRatio = 0.25;

    private double currentSamplingPercentage;
    private Date lastChangedDate;

    // This is for working with 'samplingPercentageDecreaseTimeoutInSeconds' and ...Increase....
    private ChangeDirection lastChangeDirection = ChangeDirection.None;

    private final AtomicLong counter = new AtomicLong(0);

    private ScheduledThreadPoolExecutor threads;

    // We use the 'FixedRateTelemetrySampler' to do the actual sampling
    private final FixedRateTelemetrySampler sampler = new FixedRateTelemetrySampler();

    @Override
    public synchronized void stop(long timeout, TimeUnit timeUnit) {
        ThreadPoolUtils.stop(threads, timeout, timeUnit);
    }

    /**
     * This method must be called prior to any use of the instance
     *
      * @param maxTelemetriesPerSecond  maxTelemetriesPerSecond
     * @param evaluationIntervalInSeconds evaluationIntervalInSeconds
     * @param samplingPercentageDecreaseTimeoutInSeconds samplingPercentageDecreaseTimeoutInSeconds
     * @param samplingPercentageIncreaseTimeoutInSeconds samplingPercentageIncreaseTimeoutInSeconds
     * @param minSamplingPercentage minSamplingPercentage
     * @param maxSamplingPercentage maxSamplingPercentage
     * @param initialSamplingPercentage initialSamplingPercentage
     * @param movingAverageRatio movingAverageRatio
     */
    public void initialize(String maxTelemetriesPerSecond,
                           String evaluationIntervalInSeconds,
                           String samplingPercentageDecreaseTimeoutInSeconds,
                           String samplingPercentageIncreaseTimeoutInSeconds,
                           String minSamplingPercentage,
                           String maxSamplingPercentage,
                           String initialSamplingPercentage,
                           String movingAverageRatio) {
        this.maxTelemetriesPerSecond = getIntValueOrDefault("maxTelemetriesPerSecond", maxTelemetriesPerSecond, DEFAULT_MAX_TELEMETRIES_PER_SECOND, 0, Integer.MAX_VALUE);
        this.evaluationIntervalInSec = getIntValueOrDefault("evaluationIntervalInSec", evaluationIntervalInSeconds, DEFAULT_EVALUATION_INTERVAL_IN_SECONDS, 0, Integer.MAX_VALUE);
        this.samplingPercentageDecreaseTimeoutInSec = getIntValueOrDefault("samplingPercentageDecreaseTimeoutInSec", samplingPercentageDecreaseTimeoutInSeconds, DEFAULT_SAMPLING_PERCENTAGE_DECREASE_TIMEOUT_IN_SECONDS, 0, Integer.MAX_VALUE);
        this.samplingPercentageIncreaseTimeoutInSec = getIntValueOrDefault("samplingPercentageIncreaseTimeoutInSec", samplingPercentageIncreaseTimeoutInSeconds, DEFAULT_SAMPLING_PERCENTAGE_INCREASE_TIMEOUT_IN_SECONDS, 0, Integer.MAX_VALUE);
        this.minSamplingPercentage = getIntValueOrDefault("minSamplingPercentage", minSamplingPercentage, DEFAULT_MIN_SAMPLING_PERCENTAGE, 0, 100);
        this.maxSamplingPercentage = getIntValueOrDefault("maxSamplingPercentage", maxSamplingPercentage, DEFAULT_MAX_SAMPLING_PERCENTAGE, 0, 100);
        this.currentSamplingPercentage = getDoubleValueOrDefault("initialSamplingPercentage", initialSamplingPercentage, DEFAULT_INITIAL_SAMPLING_PERCENTAGE, 0.0, 100.0);
        this.movingAverageRatio = getDoubleValueOrDefault("movingAverageRatio", movingAverageRatio, DEFAULT_MOVING_AVERAGE_RATIO, 0.0, 100.0);

        createTimerThread();

        lastChangedDate = new Date();
        sampler.setSamplingPercentage(this.currentSamplingPercentage);
        threads.scheduleAtFixedRate(new SamplingRangeEvaluator(), this.evaluationIntervalInSec, this.evaluationIntervalInSec, TimeUnit.SECONDS);
        SDKShutdownActivity.INSTANCE.register(this);
    }

    @Override
    public Set getExcludeTypes() {
        return sampler.getExcludeTypes();
    }

    @Override
    public void setExcludeTypes(String types) {
        sampler.setExcludeTypes(types);
    }

    @Override
    public Set getIncludeTypes() {
        return sampler.getIncludeTypes();
    }

    @Override
    public void setIncludeTypes(String types) {
        sampler.setIncludeTypes(types);
    }

    @Override
    public Double getSamplingPercentage() {
        return sampler.getSamplingPercentage();
    }

    @Override
    public void setSamplingPercentage(Double samplingPercentage) {
        sampler.setSamplingPercentage(samplingPercentage);
    }

    @Override
    public boolean isSampledIn(Telemetry telemetry) {
        if (sampler.isSampledIn(telemetry)) {
            counter.incrementAndGet();
            return true;
        }

        return false;
    }

    private void createTimerThread() {
        threads = new ScheduledThreadPoolExecutor(1);
        threads.setThreadFactory(ThreadPoolUtils.createDaemonThreadFactory(AdaptiveTelemetrySampler.class));
    }

    private int getIntValueOrDefault(String name, String valueAsString, int defaultValue, int minValue, int maxValue) {
        int result = defaultValue;
        try {
            int value = Integer.valueOf(valueAsString);
            if (value > 0) {
                result = value;
            }
        } catch (Exception e) {
        }

        if (result > maxValue) {
            result = maxValue;
        }
        if (result < minValue) {
            result = minValue;
        }

        InternalLogger.INSTANCE.trace("%s is set to %s", name, defaultValue);
        return result;
    }

    private double getDoubleValueOrDefault(String name, String valueAsString, double defaultValue, double minValue, double maxValue) {
        double result = defaultValue;
        try {
            double value = Double.valueOf(valueAsString);
            if (value > 0) {
                result = value;
            }
        } catch (Exception e) {
        }

        if (result > maxValue) {
            result = maxValue;
        }
        if (result < minValue) {
            result = minValue;
        }

        InternalLogger.INSTANCE.trace("%s is set to %s", name, defaultValue);
        return result;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy