package com.bagri.support.stats; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bagri.support.stats.StatisticsEvent.EventType; import com.bagri.support.util.JMXUtils; /** * Represents a container for statistics series. Keeps statistic series in internal Map. Also listens for Statistics events and * updates corresponding statistics accordingly. * * @author Denis Sukhoroslov * * @param <S> the type of Statistics to collect */ public class StatisticsCollector<S extends Statistics> implements Runnable, StatisticsProvider { protected final Logger logger; private final Class<S> cls; protected String name; private BlockingQueue<StatisticsEvent> queue = null; protected Map<String, Statistics> series = new HashMap<>(); /** * * @param cls statistics class * @param name statistics name */ public StatisticsCollector(Class<S> cls, String name) { this.cls = cls; this.name = name; logger = LoggerFactory.getLogger(getClass().getName() + "[" + name + "]"); } /** * * @return managed statistic series names */ public Collection<String> getStatisticsNames() { return series.keySet(); } /** * * @param name statistics series name * @return statistics for the series provided */ public Map<String, Object> getNamedStatistics(String name) { return series.get(name).toMap(); } /** * {@inheritDoc} */ @Override public CompositeData getStatisticTotals() { // TODO: implement it? we need some aggregator for this! return null; } /** * {@inheritDoc} */ @Override public TabularData getStatisticSeries() { TabularData result = null; for (Map.Entry<String, Statistics> entry: series.entrySet()) { Statistics stats = entry.getValue(); if (reportStatistics(stats)) { try { String desc = stats.getDescription(); String name = stats.getName(); String header = stats.getHeader(); Map<String, Object> sts = stats.toMap(); sts.put(header, entry.getKey()); CompositeData data = JMXUtils.mapToComposite(name, desc, sts); result = JMXUtils.compositeToTabular(name, desc, header, result, data); } catch (Exception ex) { logger.error("getStatisticSeries; error", ex); } } } return result; } /** * * @param name the name of statistics series * @return the created statistics */ protected S createStatistics(String name) { try { return cls.getConstructor(String.class).newInstance(name); } catch (Exception ex) { logger.error("createStatistics.error", ex); } return null; } /** * Removes statistics from internal statistics series map * * @param name the name of statistics series */ public void deleteStatistics(String name) { series.remove(name); } /** * * @param name the name of statistics series * @return created and initialized statistics */ public Statistics initStatistics(String name) { Statistics sts = createStatistics(name); series.put(name, sts); return sts; } /** * Check do we need to report the concrete statistics series or not * * @param stats the statistics series to check * @return true if it has to be reported (default value), false otherwise */ protected boolean reportStatistics(Statistics stats) { return true; } /** * {@inheritDoc} */ @Override public void resetStatistics() { // renew all registered stats.. why just not call clear()? for (String name: series.keySet()) { initStatistics(name); } } /** * * @param event received event to update statistics */ protected void updateStatistics(StatisticsEvent event) { Statistics sts = series.get(event.getName()); if (sts == null) { sts = initStatistics(event.getName()); } sts.update(event); } /** * {@inheritDoc} */ @Override public void run() { logger.info("run.enter; starting [{}] stats collection", name); boolean running = true; while (running) { try { StatisticsEvent event = queue.take(); if (EventType.delete == event.getType()) { deleteStatistics(event.getName()); } else { updateStatistics(event); } } catch (InterruptedException ex) { logger.warn("run; interrupted"); running = false; } } logger.info("run.exit; finished [{}] stats collection", name); } /** * * @param queue the queue to consume events from */ public void setStatsQueue(BlockingQueue<StatisticsEvent> queue) { if (this.queue == null) { this.queue = queue; Thread listener = new Thread(this, name); listener.start(); } else { logger.warn("setStatsQueue; stats queue is already assigned: {}", queue); } } }