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

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

package io.split.client.impressions;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.split.client.dtos.UniqueKeys;
import io.split.client.impressions.filters.BloomFilterImp;
import io.split.client.impressions.filters.Filter;
import io.split.client.impressions.filters.FilterAdapter;
import io.split.client.impressions.filters.FilterAdapterImpl;
import io.split.telemetry.synchronizer.TelemetrySynchronizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class UniqueKeysTrackerImp implements UniqueKeysTracker{
    private static final Logger _log = LoggerFactory.getLogger(UniqueKeysTrackerImp.class);
    private static final double MARGIN_ERROR = 0.01;
    private static final int MAX_AMOUNT_OF_TRACKED_UNIQUE_KEYS = 30000;
    private static final int MAX_AMOUNT_OF_KEYS = 10000000;
    private FilterAdapter filterAdapter;
    private final TelemetrySynchronizer _telemetrySynchronizer;
    private final ScheduledExecutorService _uniqueKeysSyncScheduledExecutorService;
    private final ScheduledExecutorService _cleanFilterScheduledExecutorService;
    private final ConcurrentHashMap> uniqueKeysTracker;
    private final int _uniqueKeysRefreshRate;
    private final int _filterRefreshRate;
    private static final Logger _logger = LoggerFactory.getLogger(UniqueKeysTrackerImp.class);

    public UniqueKeysTrackerImp(TelemetrySynchronizer telemetrySynchronizer, int uniqueKeysRefreshRate, int filterRefreshRate) {
        Filter bloomFilter = new BloomFilterImp(MAX_AMOUNT_OF_KEYS, MARGIN_ERROR);
        this.filterAdapter = new FilterAdapterImpl(bloomFilter);
        uniqueKeysTracker = new ConcurrentHashMap<>();
        _telemetrySynchronizer = telemetrySynchronizer;
        _uniqueKeysRefreshRate = uniqueKeysRefreshRate;
        _filterRefreshRate = filterRefreshRate;

        ThreadFactory uniqueKeysSyncThreadFactory = new ThreadFactoryBuilder()
                .setDaemon(true)
                .setNameFormat("UniqueKeys-sync-%d")
                .build();
        ThreadFactory filterThreadFactory = new ThreadFactoryBuilder()
                .setDaemon(true)
                .setNameFormat("Filter-%d")
                .build();
        _uniqueKeysSyncScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(uniqueKeysSyncThreadFactory);
        _cleanFilterScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(filterThreadFactory);
    }

    @Override
    public synchronized boolean track(String featureName, String key) {
        if (!filterAdapter.add(featureName, key)) {
            _logger.debug("The feature " + featureName + " and key " + key + " exist in the UniqueKeysTracker");
            return false;
        }
        HashSet value = new HashSet<>();
        if(uniqueKeysTracker.containsKey(featureName)){
            value = uniqueKeysTracker.get(featureName);
        }
        value.add(key);
        uniqueKeysTracker.put(featureName, value);
        _logger.debug("The feature " + featureName + " and key " + key + " was added");
        if (uniqueKeysTracker.size() == MAX_AMOUNT_OF_TRACKED_UNIQUE_KEYS){
            _logger.warn("The UniqueKeysTracker size reached the maximum limit");
            try {
                sendUniqueKeys();
            } catch (Exception e) {
                _log.error("Error sending unique keys.", e);
            }
        }
        return true;
    }

    @Override
    public void start() {
        scheduleWithFixedDelay(_uniqueKeysSyncScheduledExecutorService, _uniqueKeysRefreshRate, new ExecuteSendUniqueKeys());
        scheduleWithFixedDelay(_cleanFilterScheduledExecutorService, _filterRefreshRate, new ExecuteCleanFilter());
    }

    private void scheduleWithFixedDelay(ScheduledExecutorService scheduledExecutorService, int refreshRate, ExecuteUniqueKeysAction executeUniqueKeysAction) {
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                executeUniqueKeysAction.execute();
            } catch (Exception e) {
                _log.error("Error executing an Unique Key Action.", e);
            }
        }, refreshRate, refreshRate, TimeUnit.SECONDS);
    }

    @Override
    public void stop() {
        try {
            sendUniqueKeys();
        } catch (Exception e) {
            _log.error("Error sending unique keys.");
        }
        _uniqueKeysSyncScheduledExecutorService.shutdown();
        _cleanFilterScheduledExecutorService.shutdown();
    }

    public HashMap> popAll(){
        HashMap> toReturn = new HashMap<>();
        for (String key : uniqueKeysTracker.keySet()) {
            HashSet value = uniqueKeysTracker.remove(key);
            toReturn.put(key, value);
        }
        return toReturn;
    }

    private void sendUniqueKeys(){
        if (uniqueKeysTracker.size() == 0) {
           _log.warn("The Unique Keys Tracker is empty");
           return;
        }
        HashMap> uniqueKeysHashMap = popAll();
        List uniqueKeysFromPopAll = new ArrayList<>();
        for (String feature : uniqueKeysHashMap.keySet()) {
            UniqueKeys.UniqueKey uniqueKey = new UniqueKeys.UniqueKey(feature, new ArrayList<>(uniqueKeysHashMap.get(feature)));
            uniqueKeysFromPopAll.add(uniqueKey);
        }
        _telemetrySynchronizer.synchronizeUniqueKeys(new UniqueKeys(uniqueKeysFromPopAll));
    }

    private interface ExecuteUniqueKeysAction{
        void execute();
    }
    private class ExecuteCleanFilter implements ExecuteUniqueKeysAction {

        @Override
        public void execute() {
            filterAdapter.clear();
        }
    }

    private class ExecuteSendUniqueKeys implements ExecuteUniqueKeysAction {

        @Override
        public void execute() {
            sendUniqueKeys();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy