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

io.split.client.impressions.ImpressionsManagerImpl Maven / Gradle / Ivy

There is a newer version: 4.13.0
Show newest version
package io.split.client.impressions;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.split.client.SplitClientConfig;
import io.split.client.dtos.KeyImpression;
import io.split.client.dtos.TestImpressions;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

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

/**
 * Created by patricioe on 6/17/16.
 */
public class ImpressionsManagerImpl implements ImpressionsManager, Closeable {

    private static final Logger _log = LoggerFactory.getLogger(ImpressionsManagerImpl.class);

    private static final long BULK_INITIAL_DELAY_SECONDS = 10L;
    private static final long COUNT_INITIAL_DELAY_SECONDS = 100L;
    private static final long COUNT_REFRESH_RATE_SECONDS = 30 * 60;
    private static final long LAST_SEEN_CACHE_SIZE = 500000; // cache up to 500k impression hashes

    private final SplitClientConfig _config;
    private final ImpressionsStorage _storage;
    private final ScheduledExecutorService _scheduler;
    private final ImpressionsSender _impressionsSender;
    private final ImpressionObserver _impressionObserver;
    private final ImpressionCounter _counter;
    private final ImpressionListener _listener;
    private final ImpressionsManager.Mode _mode;

    public static ImpressionsManagerImpl instance(CloseableHttpClient client,
                                                  SplitClientConfig config,
                                                  List listeners) throws URISyntaxException {
        return new ImpressionsManagerImpl(client, config, null, listeners);
    }

    public static ImpressionsManagerImpl instanceForTest(CloseableHttpClient client,
                                                         SplitClientConfig config,
                                                         ImpressionsSender impressionsSender,
                                                         List listeners) throws URISyntaxException {
        return new ImpressionsManagerImpl(client, config, impressionsSender, listeners);
    }

    private ImpressionsManagerImpl(CloseableHttpClient client,
                                   SplitClientConfig config,
                                   ImpressionsSender impressionsSender,
                                   List listeners) throws URISyntaxException {


        _config = checkNotNull(config);
        _mode = checkNotNull(config.impressionsMode());
        _storage = new InMemoryImpressionsStorage(config.impressionsQueueSize());
        _impressionObserver = new ImpressionObserver(LAST_SEEN_CACHE_SIZE);
        _counter = new ImpressionCounter();
        _impressionsSender = (null != impressionsSender) ? impressionsSender
                : HttpImpressionsSender.create(client, URI.create(config.eventsEndpoint()), _mode);

        _scheduler = buildExecutor();
        _scheduler.scheduleAtFixedRate(this::sendImpressions, BULK_INITIAL_DELAY_SECONDS,config.impressionsRefreshRate(), TimeUnit.SECONDS);
        if (Mode.OPTIMIZED.equals(_mode)) {
            _scheduler.scheduleAtFixedRate(this::sendImpressionCounters, COUNT_INITIAL_DELAY_SECONDS, COUNT_REFRESH_RATE_SECONDS, TimeUnit.SECONDS);
        }

        _listener = (null != listeners && !listeners.isEmpty()) ? new ImpressionListener.FederatedImpressionListener(listeners)
                : new ImpressionListener.NoopImpressionListener();
    }

    private static boolean shouldQueueImpression(Impression i) {
        return Objects.isNull(i.pt()) ||
                ImpressionUtils.truncateTimeframe(i.pt()) != ImpressionUtils.truncateTimeframe(i.time());
    }

    @Override
    public void track(Impression impression) {
        if (null == impression) {
            return;
        }

        impression = impression.withPreviousTime(_impressionObserver.testAndSet(impression));
        _listener.log(impression);

        if (Mode.OPTIMIZED.equals(_mode)) {
            _counter.inc(impression.split(), impression.time(), 1);
        }

        if (Mode.DEBUG.equals(_mode) || shouldQueueImpression(impression)) {
            _storage.put(KeyImpression.fromImpression(impression));
        }
    }

    @Override
    public void close() {
        try {
            _listener.close();
            _log.info("Successful shutdown of ImpressionListener");
            _scheduler.shutdown();
            sendImpressions();
            _scheduler.awaitTermination(_config.waitBeforeShutdown(), TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            _log.warn("Unable to close ImpressionsManager properly", e);
        }

    }

    @VisibleForTesting
    /* package private */ void sendImpressions() {
        if (_storage.isFull()) {
            _log.warn("Split SDK impressions queue is full. Impressions may have been dropped. Consider increasing capacity.");
        }

        long start = System.currentTimeMillis();
        List impressions = _storage.pop();
        if (impressions.isEmpty()) {
            return; // Nothing to send
        }

        _impressionsSender.postImpressionsBulk(TestImpressions.fromKeyImpressions(impressions));
        if(_config.debugEnabled()) {
            _log.info(String.format("Posting %d Split impressions took %d millis",
                    impressions.size(), (System.currentTimeMillis() - start)));
        }
    }

    @VisibleForTesting
    /* package private */ void sendImpressionCounters() {
        if (!_counter.isEmpty()) {
            _impressionsSender.postCounters(_counter.popAll());
        }
    }

    private ScheduledExecutorService buildExecutor() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setDaemon(true)
                .setNameFormat("Split-ImpressionsManager-%d")
                .build();
        return Executors.newScheduledThreadPool(2, threadFactory);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy