com.ebay.pulsar.metriccalculator.processor.MCSummingProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.ebay.pulsar.metriccalculator.processor.MCSummingProcessor.java

Source

/*
Pulsar
Copyright (C) 2013-2015 eBay Software Foundation
Licensed under the GPL v2 license.  See LICENSE for full terms.
*/
package com.ebay.pulsar.metriccalculator.processor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.context.ApplicationEvent;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

import com.ebay.jetstream.config.ContextBeanChangedEvent;
import com.ebay.jetstream.counter.LongCounter;
import com.ebay.jetstream.event.EventException;
import com.ebay.jetstream.event.JetstreamEvent;
import com.ebay.jetstream.event.support.AbstractEventProcessor;
import com.ebay.jetstream.management.Management;
import com.ebay.jetstream.spring.beans.factory.BeanChangeAware;
import com.ebay.jetstream.util.offheap.OffHeapMemoryManager;
import com.ebay.jetstream.xmlser.XSerializable;
import com.ebay.pulsar.metriccalculator.cache.CacheManager;
import com.ebay.pulsar.metriccalculator.cache.OffHeapCacheConfig;
import com.ebay.pulsar.metriccalculator.cache.OffHeapMemoryManagerRegistry;
import com.ebay.pulsar.metriccalculator.metric.MCMetricGroupDemension;
import com.ebay.pulsar.metriccalculator.metric.MetricFrequency;
import com.ebay.pulsar.metriccalculator.processor.configuration.MCSummingConfiguration;
import com.ebay.pulsar.metriccalculator.statistics.basic.AvgCounter;
import com.ebay.pulsar.metriccalculator.statistics.basic.Counter;
import com.ebay.pulsar.metriccalculator.util.MCConstant;
import com.ebay.pulsar.metriccalculator.util.MCCounterHelper;

@ManagedResource(objectName = "Event/Processor", description = "MC Counter  for reporting to Cassandra and EVPS")
public class MCSummingProcessor extends AbstractEventProcessor
        implements XSerializable, MCMetricsProvider, BeanChangeAware {
    private static final Logger LOGGER = LoggerFactory
            .getLogger("com.ebay.pulsar.metriccalculator.processor.MCSummingProcessor");

    private static final String CASSANDRA_SEND_COUNT = "cassandraMetricCount";
    private static final String EVPS_SEND_COUNT = "evpsMetricCount";
    private static final String MC_SUMMING_PROCESSOR_TIMER = "MCSummingProcessorTimer";

    private final Map<String, Counter> normalMetrics = new HashMap<String, Counter>();
    private final Map<String, Map<MCMetricGroupDemension, Counter>> groupbyWithTagsMetricMap = new HashMap<String, Map<MCMetricGroupDemension, Counter>>();
    private MCSummingConfiguration configuration;

    private long maxMetricCollectionTime;
    private long lastMetricCollectionTime;

    private Timer timer = new Timer(MC_SUMMING_PROCESSOR_TIMER);
    private TimerTask summingTimingTask;
    private final AtomicBoolean shutdownFlag = new AtomicBoolean(false);

    private final LongCounter mapClearCount = new LongCounter();
    private final LongCounter normalMetricCount = new LongCounter();
    private LongCounter groupByMetricCount = new LongCounter();

    private LongCounter rawEventCount = new LongCounter();
    private LongCounter totalMetricCollectionCount = new LongCounter();

    private long timerIterationCounter = 0;
    private long timerLoopSize = 0;

    private long maxDimensionSize = 0;
    private long latestDimensionSize = 0;

    private JetstreamEvent lastRawEvent;

    private Map<Long, Set<String>> frequencyMetrics = new ConcurrentHashMap<Long, Set<String>>();
    private Map<String, Long> metricFrequencies = new ConcurrentHashMap<String, Long>();
    private Map<String, String> metricTables = new ConcurrentHashMap<String, String>();
    private Map<String, LongCounter> metricCollectionCounts = new ConcurrentHashMap<String, LongCounter>();

    private static final MetricFrequency DEFAULT_FREQUENCY = MetricFrequency.ONE_MINUTE;
    private static final long TIMER_FREQUENCY;

    static {
        long timerFrequency = DEFAULT_FREQUENCY.getValue();
        for (MetricFrequency freq : MetricFrequency.values()) {
            timerFrequency = gcf(timerFrequency, freq.getValue());
        }
        TIMER_FREQUENCY = timerFrequency;
    }

    public MCSummingProcessor() {
        normalMetrics.put(EVPS_SEND_COUNT, new Counter());
        normalMetrics.put(CASSANDRA_SEND_COUNT, new Counter());
    }

    public MCSummingConfiguration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(MCSummingConfiguration configuration) {
        this.configuration = configuration;
    }

    public void setMetricFrequency(String metricName, MetricFrequency frequency) {
        if (metricName != null && frequency != null) {
            setMetricFrequency(metricName, frequency.getValue());
        }
    }

    public void setMetricFrequencyInMin(String metricName, int min) {
        if (metricName != null && min > 0) {
            long frequency = min * MetricFrequency.ONE_MINUTE.getValue();
            setMetricFrequency(metricName, frequency);
        }
    }

    public void setMetricFrequencyMap(Map<String, MetricFrequency> metricFreqencyMap) {
        if (metricFreqencyMap != null && !metricFreqencyMap.isEmpty()) {
            for (Map.Entry<String, MetricFrequency> entry : metricFreqencyMap.entrySet()) {
                setMetricFrequency(entry.getKey(), entry.getValue());
            }
        }
    }

    public void setMetricFrequencyInMin(Map<String, Integer> metricFreqencyMap) {
        if (metricFreqencyMap != null && !metricFreqencyMap.isEmpty()) {
            for (Map.Entry<String, Integer> entry : metricFreqencyMap.entrySet()) {
                setMetricFrequencyInMin(entry.getKey(), entry.getValue());
            }
        }
    }

    private void setMetricFrequency(String metricName, long seconds) {
        metricFrequencies.put(metricName, seconds);
        Set<String> metricNameSet = frequencyMetrics.get(seconds);
        if (metricNameSet == null) {
            metricNameSet = new HashSet<String>();
            frequencyMetrics.put(seconds, metricNameSet);
        }
        metricNameSet.add(metricName);
    }

    @Override
    public void processApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextBeanChangedEvent) {
            ContextBeanChangedEvent bcInfo = (ContextBeanChangedEvent) event;
            // Apply changes
            if (bcInfo.getBeanName().equals(getConfiguration().getBeanName())) {
                LOGGER.info("Received change bean:  - " + event.getClass().getName());
                LOGGER.info("Received new configuration for  - " + bcInfo.getBeanName());
                try {
                    setConfiguration(((MCSummingConfiguration) bcInfo.getChangedBean()));
                } catch (Exception e) {
                    LOGGER.error("Error while applying config to MCSummingProcessor - " + e.getMessage());
                }
            }
        }
    }

    private void evaluateRawEvent(JetstreamEvent event) {
        rawEventCount.increment();
        lastRawEvent = event;
    }

    @Override
    public void collectMetrics(Set<String> avaiableCounterName, boolean flushNormal) {
        try {
            totalMetricCollectionCount.increment();
            long start = System.currentTimeMillis();
            // Collect normal counters
            if (flushNormal) {
                List<JetstreamEvent> events = createJetStreamCountEvent();
                for (JetstreamEvent event : events) {
                    super.fireSendEvent(event);
                    incrementEventSentCounter();
                    Object metricNameInEvent = event.get(MCConstant.METRIC_NAME);
                    if ((!EVPS_SEND_COUNT.equals(metricNameInEvent))
                            && (!CASSANDRA_SEND_COUNT.equals(metricNameInEvent))) {
                        normalMetricCount.increment();
                        normalMetrics.get(EVPS_SEND_COUNT).inc();
                    }
                }
            }
            // Collect group by counters
            if (getConfiguration().isEnableGroupByCounter()) {
                for (String metricName : avaiableCounterName) {
                    List<JetstreamEvent> groupByEvents = createJetStreamGroupbyCountEventsWithTags(metricName);
                    for (JetstreamEvent event : groupByEvents) {
                        super.fireSendEvent(event);
                        incrementEventSentCounter();
                        groupByMetricCount.increment();
                        normalMetrics.get(CASSANDRA_SEND_COUNT).inc();
                    }
                }

                long end = System.currentTimeMillis();
                lastMetricCollectionTime = end - start;
                if (lastMetricCollectionTime > maxMetricCollectionTime) {
                    maxMetricCollectionTime = lastMetricCollectionTime;
                }
            }
        } catch (Exception ex) {
            LOGGER.warn("Error collection metrics in MCSummingProcessor:" + ex.getMessage());
            registerError(ex);
        }
    }

    private Counter getCounterByMetricName(String metricName, boolean isAvg) {
        Counter counter = normalMetrics.get(metricName);
        if (counter == null) {
            if (isAvg) {
                counter = new AvgCounter();
            } else {
                counter = new Counter();
            }
            normalMetrics.put(metricName, counter);
        }
        return counter;
    }

    private Counter getCounterByMetricDemensionAndInc(String metricName, String groupId, Map<String, String> tags,
            boolean isAvg, Long count, Long total) {
        Map<MCMetricGroupDemension, Counter> counters = groupbyWithTagsMetricMap.get(metricName);
        OffHeapCacheConfig conf = null;
        if (getConfiguration().getOffheapMetricConf() != null) {
            conf = getConfiguration().getOffheapMetricConf().get(metricName);
        }
        if (counters == null) {
            synchronized (this) {
                if (counters == null) {
                    if (conf != null) {
                        counters = CacheManager.getCounterOffHeapCache(metricName, conf);
                    } else {
                        counters = CacheManager.getCounterCache();
                    }
                    groupbyWithTagsMetricMap.put(metricName, counters);
                }
            }
        }

        MCMetricGroupDemension groupDemension = null;
        String tag_time = null;
        if (tags == null || tags.isEmpty()) {
            groupDemension = new MCMetricGroupDemension(metricName, groupId);
        } else {
            if (tags.containsKey(MCConstant.TAG_TIME_IGNORE)) {
                tag_time = tags.remove(MCConstant.TAG_TIME_IGNORE);
            }
            groupDemension = new MCMetricGroupDemension(metricName, groupId, tags);
        }

        Counter counter = counters.get(groupDemension);
        if (counter == null) {
            if (isAvg) {
                counter = new AvgCounter();
            } else {
                counter = new Counter();
            }
            if (conf == null) {
                // Store new counter to Map for heap map
                counters.put(groupDemension, counter);
            }
        }
        if (tag_time != null) {
            counter.setLastCounterTime(tag_time);
        }

        if (isAvg) {
            ((AvgCounter) counter).inc(count, total);
        } else {
            if (count != null) {
                counter.inc(count);
            } else {
                counter.inc();
            }
        }
        if (conf != null) {
            // Store counter back to off heap map
            counters.put(groupDemension, counter);
        }

        return counter;
    }

    private long getFrequencyByMetricName(String metricName) {
        Long frequency = metricFrequencies.get(metricName);
        if (frequency == null) {
            return -1;
        }
        return frequency.longValue();
    }

    private List<JetstreamEvent> createJetStreamCountEvent() {
        List<JetstreamEvent> result = new ArrayList<JetstreamEvent>(normalMetrics.size());
        for (Map.Entry<String, Counter> entry : normalMetrics.entrySet()) {
            String metricName = entry.getKey();
            Counter counter = entry.getValue();
            counter.mark();
            Map<String, Object> internalMap = new HashMap<String, Object>();
            if (counter instanceof AvgCounter) {
                internalMap.put(MCConstant.AGGREGATED_COUNT, ((AvgCounter) counter).getLatestAvgValue());
            } else {
                internalMap.put(MCConstant.AGGREGATED_COUNT, counter.getLastDeltaValue());
            }
            internalMap.put(MCConstant.METRIC_NAME, metricName);
            internalMap.put(MCConstant.METRIC_FREQUENCY, getFrequencyByMetricName(metricName));
            if (shutdownFlag.get()) {
                internalMap.put(MCConstant.SHUTDOWN_FLUSH, MCConstant.SHUTDOWN_FLUSH);
            }
            JetstreamEvent event = new JetstreamEvent(MCCounterHelper.COUNTEREVENT, null, internalMap);
            result.add(event);
        }
        return result;
    }

    private List<JetstreamEvent> createJetStreamGroupbyCountEventsWithTags(String metricName) {
        Map<MCMetricGroupDemension, Counter> counterMap = groupbyWithTagsMetricMap.get(metricName);
        if (counterMap == null)
            return Collections.emptyList();

        OffHeapCacheConfig conf = null;
        if (getConfiguration().getOffheapMetricConf() != null) {
            conf = getConfiguration().getOffheapMetricConf().get(metricName);
        }

        Integer threshold = getConfiguration().getMetricsThreshold().get(metricName);
        int _threshold = 0;
        if (threshold != null) {
            _threshold = threshold.intValue();
        }

        boolean mapClear = false;
        // Create new off heap map for each round of metric collection
        if (conf != null) {
            synchronized (this) {
                Map<MCMetricGroupDemension, Counter> newCounterMap = CacheManager.getCounterOffHeapCache(metricName,
                        conf);
                groupbyWithTagsMetricMap.put(metricName, newCounterMap);
            }
            mapClear = true;
        } else if (conf == null && counterMap.size() > getConfiguration().getGroupCounterMax()) {
            synchronized (this) {
                Map<MCMetricGroupDemension, Counter> newCounterMap = CacheManager.getCounterCache();
                groupbyWithTagsMetricMap.put(metricName, newCounterMap);
            }
            mapClear = true;
        }

        List<JetstreamEvent> result = new ArrayList<JetstreamEvent>(counterMap.size());
        long now = System.currentTimeMillis();
        for (Map.Entry<MCMetricGroupDemension, Counter> entry : counterMap.entrySet()) {
            MCMetricGroupDemension groupDemension = entry.getKey();
            Counter counter = entry.getValue();
            counter.mark();

            boolean timeBasedMetric = false;
            if (groupDemension.getDimensions() != null
                    && groupDemension.getDimensions().get(MCCounterHelper.TAG_METRICTIME) != null) {
                timeBasedMetric = true;
            }

            // Only publish events with deltaValue > _threshold
            if (counter.getLastDeltaValue() > _threshold) {
                Map<String, Object> internalMap = new LinkedHashMap<String, Object>();
                if (timeBasedMetric) {
                    internalMap.put(MCConstant.METRIC_TIME,
                            Long.valueOf(groupDemension.getDimensions().get(MCCounterHelper.TAG_METRICTIME)));
                } else {
                    internalMap.put(MCConstant.METRIC_TIME, now);
                }

                if (counter instanceof AvgCounter) {
                    internalMap.put(MCConstant.AGGREGATED_COUNT, ((AvgCounter) counter).getLatestAvgValue());
                } else {
                    internalMap.put(MCConstant.AGGREGATED_COUNT, counter.getLastDeltaValue());
                }
                internalMap.put(MCConstant.METRIC_NAME, metricName);
                internalMap.put(MCConstant.METRIC_FREQUENCY, getFrequencyByMetricName(metricName));

                internalMap.put(MCConstant.METRIC_DEMENSION, groupDemension);

                if (counter.getLastCounterTime() != null) {
                    internalMap.put(MCConstant.TAG_TIME_IGNORE, counter.getLastCounterTime());
                }

                if (shutdownFlag.get()) {
                    internalMap.put(MCConstant.SHUTDOWN_FLUSH, MCConstant.SHUTDOWN_FLUSH);
                }
                if ((metricCollectionCounts.get(metricName) != null)
                        && metricCollectionCounts.get(metricName).get() == 1) {
                    internalMap.put(MCConstant.FIRST_FLUSH, MCConstant.FIRST_FLUSH);
                }

                JetstreamEvent event = new JetstreamEvent(metricTables.get(metricName), null, internalMap);
                result.add(event);
            }
            if (timeBasedMetric && counter.getLastDeltaValue() == 0) {
                // remove the time based metric in memory
                counterMap.remove(groupDemension);
            }
        }
        if (result.size() > 0) {
            JetstreamEvent lastEvent = result.get(result.size() - 1);
            lastEvent.put("LastEventInBatch", "true");
        }
        if (mapClear) {
            counterMap.clear();
            counterMap = null;
            mapClearCount.increment();
        }
        return result;
    }

    @Override
    // Received event from Esper processor
    public void sendEvent(JetstreamEvent event) throws EventException {
        if (isPaused() || shutdownFlag.get()) {
            super.incrementEventDroppedCounter();
            return;
        }

        incrementEventRecievedCounter();

        if (MCCounterHelper.isMCCounterEvent(event)) {
            String metricName = (String) event.get(MCConstant.METRIC_NAME);
            Long total = null;
            if (MCCounterHelper.isAvgEvent(metricName)) {
                for (Map.Entry<String, Object> entry : event.entrySet()) {
                    if (entry.getKey().toLowerCase().contains("total")) {
                        total = (Long) entry.getValue();
                        break;
                    }
                }
            }

            if (event.get(MCConstant.METRIC_COUNT) != null) {
                Long count = (Long) event.get(MCConstant.METRIC_COUNT);
                if (total != null) {
                    ((AvgCounter) getCounterByMetricName(metricName, true)).inc(count, total);
                } else
                    getCounterByMetricName(metricName, false).inc(count);
            } else {
                getCounterByMetricName(metricName, false).inc();
            }
        } else if (MCCounterHelper.isMCMultiCounterEvent(event)) {
            // Does not support avg calculator

            for (Map.Entry<String, Object> entry : event.entrySet()) {
                if (entry.getKey().toLowerCase().contains("count")) {
                    String metricName = entry.getKey();
                    Long count = (Long) entry.getValue();
                    if (count != null) {
                        getCounterByMetricName(metricName, false).inc(count);
                    } else {
                        LOGGER.warn("Null Count returned by EPL, CountName:" + metricName);
                    }
                }
            }
        } else if (MCCounterHelper.isGroupByCounterEvent(event)) {
            if (getConfiguration().isEnableGroupByCounter()) {
                String metricName = (String) event.get(MCConstant.METRIC_NAME);
                String groupId = (String) event.get(MCConstant.GROUP_ID);
                if (groupId != null && groupId.trim().length() != 0) {
                    long registerdFreq = getFrequencyByMetricName(metricName);

                    // Unregistered, using default to register on the fly
                    if (event.get(MCConstant.FREQUENCY_IN_MIN) == null && registerdFreq <= 0) {
                        setMetricFrequency(metricName, MetricFrequency.ONE_MINUTE.getValue());
                    }
                    // Update registered frequency on the fly by EPL
                    else if (event.get(MCConstant.FREQUENCY_IN_MIN) != null) {
                        Integer frequencyInMin = (Integer) event.get(MCConstant.FREQUENCY_IN_MIN);
                        long frequency = frequencyInMin * MetricFrequency.ONE_MINUTE.getValue();
                        if (frequency > 0 && frequency != registerdFreq) {
                            setMetricFrequency(metricName, frequency);
                        }
                    }
                    metricTables.put(metricName, event.getEventType());

                    Map<String, String> tags = new HashMap<String, String>(5);
                    MCCounterHelper.isGroupByCounterEventWithTag(event, tags);

                    Long total = null;
                    if (MCCounterHelper.isAvgEvent(metricName)) {
                        for (Map.Entry<String, Object> entry : event.entrySet()) {
                            if (entry.getKey().toLowerCase().contains("total")) {
                                total = (Long) entry.getValue();
                                break;
                            }
                        }
                    }

                    if (event.get(MCConstant.METRIC_COUNT) != null) {
                        Long count = (Long) event.get(MCConstant.METRIC_COUNT);
                        if (total != null) {
                            getCounterByMetricDemensionAndInc(metricName, groupId, tags, true, count, total);
                        } else {
                            getCounterByMetricDemensionAndInc(metricName, groupId, tags, false, count, null);
                        }
                    } else {
                        getCounterByMetricDemensionAndInc(metricName, groupId, tags, false, null, null);
                    }
                } else {
                    LOGGER.warn("Null or empty groupId returned by EPL, metricName:" + metricName);
                }
            }
        } else {
            evaluateRawEvent(event);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        summingTimingTask = new MetricsSummingTimingTask(this);
        timer.scheduleAtFixedRate(summingTimingTask, 0, TIMER_FREQUENCY);
        Management.addBean(getBeanName(), this);
    }

    @Override
    public int getPendingEvents() {
        return 0;
    }

    @Override
    public void shutDown() {
        if (shutdownFlag.compareAndSet(false, true)) {
            LOGGER.warn("Start gracefully stop MCSummingProcessor!");
            try {
                collectMetrics(metricFrequencies.keySet(), true);
            } catch (Exception ex) {
                LOGGER.error("Error collecting metrics in MCSummingProcessor before shutdown:" + ex.getMessage());
                registerError(ex);
            }
            timer.cancel();
            Management.removeBeanOrFolder(getBeanName(), this);
            LOGGER.warn("Gracefully stopped MCSummingProcessor!");
        }
    }

    @ManagedOperation
    @Override
    public void pause() {
        if (isPaused()) {
            LOGGER.warn(getBeanName() + " could not be paused. It is already in paused state");
            return;
        }
        changeState(ProcessorOperationState.PAUSE);
    }

    @ManagedOperation
    @Override
    public void resume() {
        if (!isPaused()) {
            LOGGER.warn(getBeanName() + " could not be resumed. It is already in resume state");
            return;
        }
        changeState(ProcessorOperationState.RESUME);
    }

    public long getMapClearCount() {
        return mapClearCount.get();
    }

    public long getGroupByMetricCount() {
        return groupByMetricCount.get();
    }

    public long getNormalMetricCount() {
        return normalMetricCount.get();
    }

    public long getReceivedRawEventCount() {
        return rawEventCount.get();
    }

    public long getMaxMetricCollectionTime() {
        return maxMetricCollectionTime;
    }

    public long getLastMetricCollectionTime() {
        return lastMetricCollectionTime;
    }

    public long getTotalMetricCollectionCount() {
        return totalMetricCollectionCount.get();
    }

    public String getMetricCollectionCount() {
        return metricCollectionCounts.toString();
    }

    public long getLatestDimensionSize() {
        return latestDimensionSize;
    }

    public long getMaxDimensionSize() {
        return maxDimensionSize;
    }

    public String getInternalMapSize() {
        StringBuilder builder = new StringBuilder();
        builder.append("NormalMetricsMapSize:");
        builder.append(normalMetrics.size());
        builder.append(";");
        builder.append("GroupbyMetricsMapSize:");
        for (Map.Entry<String, Map<MCMetricGroupDemension, Counter>> entry : groupbyWithTagsMetricMap.entrySet()) {
            String metricName = entry.getKey();
            builder.append("Metric:");
            builder.append(metricName);
            builder.append("-Size:");
            Map<MCMetricGroupDemension, Counter> counterMap = entry.getValue();
            if (counterMap != null) {
                builder.append(counterMap.size());
            } else {
                builder.append("NULL");
            }
            builder.append(";");
        }
        return builder.toString();
    }

    public String getInternalOffHeapManagerInfo() {
        StringBuilder builder = new StringBuilder();
        builder.append("InternalOffHeapManagerInfo:");
        Map<String, OffHeapMemoryManager> memoryManagers = OffHeapMemoryManagerRegistry.getInstance()
                .getMemoryManagers();
        for (Map.Entry<String, OffHeapMemoryManager> entry : memoryManagers.entrySet()) {
            builder.append("MemoryManager-");
            builder.append(entry.getKey());
            builder.append(":");
            OffHeapMemoryManager manager = entry.getValue();
            if (manager != null) {
                builder.append("[");
                builder.append("FreeMemory:");
                builder.append(manager.getFreeMemory());
                builder.append(";");
                builder.append("MaxMemory:");
                builder.append(manager.getMaxMemory());
                builder.append(";");
                builder.append("ReservedMemory:");
                builder.append(manager.getReservedMemory());
                builder.append(";");
                builder.append("UsedMemory:");
                builder.append(manager.getUsedMemory());
                builder.append(";");
                builder.append("OutOfMeomoryErrorCount:");
                builder.append(manager.getOOMErrorCount());
                builder.append("]");
            }
        }
        return builder.toString();
    }

    public String getInternalTimerInfo() {
        StringBuilder builder = new StringBuilder();
        builder.append("TimeFrequency:");
        builder.append(TIMER_FREQUENCY);
        builder.append(";");
        builder.append("IterationCounter:");
        builder.append(timerIterationCounter);
        builder.append(";");
        builder.append("TimerLoopSize:");
        builder.append(timerLoopSize);
        builder.append(";");
        return builder.toString();
    }

    public String getMetricFrequencies() {
        return metricFrequencies.toString();
    }

    public String getMetricTableMapping() {
        return metricTables.toString();
    }

    public JetstreamEvent getLastRawEvent() {
        return lastRawEvent;
    }

    // least common multiple in frequencies
    private long findFreqLCM(long minFrequency) {
        Set<Long> values = frequencyMetrics.keySet();
        if (values == null || values.isEmpty()) {
            return 1;
        }
        Long valueArray[] = values.toArray(new Long[0]);
        long result = valueArray[0];
        for (int i = 1; i < valueArray.length; i++) {
            result = lcm(result, valueArray[i]);
        }
        return (result / minFrequency);
    }

    private long lcm(long a, long b) {
        return a * (b / gcf(a, b));
    }

    // greatest common factor
    private static final long gcf(long a, long b) {
        if (b == 0) {
            return a;
        }
        return gcf(b, a % b);
    }

    private void incrementMetricCollectionCounts(Set<String> value) {
        for (String metricName : value) {
            LongCounter metricCollectionCount = metricCollectionCounts.get(metricName);
            if (metricCollectionCount == null) {
                metricCollectionCount = new LongCounter();
                metricCollectionCounts.put(metricName, metricCollectionCount);
            }
            metricCollectionCount.increment();
        }
    }

    private class MetricsSummingTimingTask extends TimerTask {
        MCMetricsProvider metricProvider;

        public MetricsSummingTimingTask(MCMetricsProvider metricProvider) {
            this.metricProvider = metricProvider;
        }

        public void run() {
            try {
                timerIterationCounter++;

                evaluateDimensionSize();

                // find out a loop size for the timer
                long timer_loop_size = findFreqLCM(TIMER_FREQUENCY);
                timerLoopSize = timer_loop_size;

                Set<String> avaiableCounterName = new HashSet<String>();
                for (Entry<Long, Set<String>> entry : frequencyMetrics.entrySet()) {
                    Long freq = entry.getKey();
                    if ((timerIterationCounter * TIMER_FREQUENCY) % freq.longValue() > 0) {
                        continue;
                    }
                    avaiableCounterName.addAll(frequencyMetrics.get(freq));
                    incrementMetricCollectionCounts(entry.getValue());
                }

                metricProvider.collectMetrics(avaiableCounterName, true);
                long executionTime = System.currentTimeMillis() - scheduledExecutionTime();
                if (executionTime >= TIMER_FREQUENCY) {
                    LOGGER.warn("Metrics publishing took too long (" + executionTime + "ms).");
                }
                if (timerIterationCounter >= timer_loop_size) {
                    timerIterationCounter = 0;
                }
            } catch (Exception ex) {
                LOGGER.error("Error scheduing MCSummingProcessorTimer in MCSummingProcessor:" + ex.getMessage());
                registerError(ex);
            }
        }

        private void evaluateDimensionSize() {
            int size = normalMetrics.size();
            for (Map.Entry<String, Map<MCMetricGroupDemension, Counter>> entry : groupbyWithTagsMetricMap
                    .entrySet()) {
                Map<MCMetricGroupDemension, Counter> counterMap = entry.getValue();
                if (counterMap != null) {
                    size = size + counterMap.size();
                }
            }
            latestDimensionSize = size;
            if (latestDimensionSize > maxDimensionSize) {
                maxDimensionSize = latestDimensionSize;
            }
        }
    }
}