package net.iponweb.disthene.reader.service.stats; import com.google.common.util.concurrent.AtomicDouble; import net.iponweb.disthene.reader.config.StatsConfiguration; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.io.DataOutputStream; import java.net.Socket; import java.util.HashMap; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; /** * @author Andrei Ivanov */ public class StatsService { private Logger logger = Logger.getLogger(StatsService.class); private StatsConfiguration statsConfiguration; private ConcurrentMap<String, StatsRecord> stats = new ConcurrentHashMap<>(); private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); public StatsService(StatsConfiguration statsConfiguration) { this.statsConfiguration = statsConfiguration; scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { flush(); } }, 60 - ((System.currentTimeMillis() / 1000L) % 60), statsConfiguration.getInterval(), TimeUnit.SECONDS); } private StatsRecord getStatsRecord(String tenant) { StatsRecord statsRecord = stats.get(tenant); if (statsRecord == null) { StatsRecord newStatsRecord = new StatsRecord(); statsRecord = stats.putIfAbsent(tenant, newStatsRecord); if (statsRecord == null) { statsRecord = newStatsRecord; } } return statsRecord; } public void incRenderRequests(String tenant) { getStatsRecord(tenant).incRenderRequests(); } public void incRenderPointsRead(String tenant, int inc) { getStatsRecord(tenant).incRenderPointsRead(inc); } public void incRenderPathsRead(String tenant, int inc) { getStatsRecord(tenant).incRenderPathsRead(inc); } public void incPathsRequests(String tenant) { getStatsRecord(tenant).incPathsRequests(); } public void incThrottleTime(String tenant, double value) { getStatsRecord(tenant).incThrottled(value); } public void incTimedOutRequests(String tenant) { getStatsRecord(tenant).incTimedOutRequests(); } private synchronized void flush() { Map<String, StatsRecord> statsToFlush = new HashMap<>(); for (ConcurrentMap.Entry<String, StatsRecord> entry : stats.entrySet()) { statsToFlush.put(entry.getKey(), entry.getValue().reset()); } long timestamp = DateTime.now(DateTimeZone.UTC).withSecondOfMinute(0).withMillisOfSecond(0).getMillis() / 1000L; try { Socket connection = new Socket(statsConfiguration.getCarbonHost(), statsConfiguration.getCarbonPort()); DataOutputStream dos = new DataOutputStream(connection.getOutputStream()); long totalRenderRequests = 0; long totalRenderPathsRead = 0; long totalRenderPointsRead = 0; long totalPathsRequests = 0; double totalThrottled = 0; long totalTimedOutRequests = 0; for (Map.Entry<String, StatsRecord> entry : statsToFlush.entrySet()) { String tenant = entry.getKey(); StatsRecord statsRecord = entry.getValue(); totalRenderRequests += statsRecord.getRenderRequests(); totalRenderPathsRead += statsRecord.getRenderPathsRead(); totalRenderPointsRead += statsRecord.getRenderPointsRead(); totalPathsRequests += statsRecord.getPathsRequests(); totalThrottled += statsRecord.getThrottled(); totalTimedOutRequests += statsRecord.getTimedOutRequests(); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".render_requests " + statsRecord.getRenderRequests() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".render_paths_read " + statsRecord.getRenderPathsRead() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".render_points_read " + statsRecord.getRenderPointsRead() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".paths_requests " + statsRecord.getPathsRequests() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".throttled " + statsRecord.getThrottled() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.tenants." + tenant + ".timed_out_requests " + statsRecord.getTimedOutRequests() + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); } dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.render_requests " + totalRenderRequests + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.render_paths_read " + totalRenderPathsRead + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.render_points_read " + totalRenderPointsRead + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.paths_requests " + totalPathsRequests + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.throttled " + totalThrottled + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.writeBytes(statsConfiguration.getHostname() + ".disthene-reader.timed_out_requests " + totalTimedOutRequests + " " + timestamp + " " + statsConfiguration.getTenant() + "\n"); dos.flush(); connection.close(); } catch (Exception e) { logger.error("Failed to send stats", e); } } public synchronized void shutdown() { scheduler.shutdown(); } private class StatsRecord { private AtomicLong renderRequests = new AtomicLong(0); private AtomicLong renderPathsRead = new AtomicLong(0); private AtomicLong renderPointsRead = new AtomicLong(0); private AtomicLong pathsRequests = new AtomicLong(0); private AtomicDouble throttled = new AtomicDouble(0); private AtomicLong timedOutRequests = new AtomicLong(0); public StatsRecord() { } public StatsRecord(long renderRequests, long renderPathsRead, long renderPointsRead, long pathsRequests, double throttled, long timedOut) { this.renderRequests = new AtomicLong(renderRequests); this.renderPathsRead = new AtomicLong(renderPathsRead); this.renderPointsRead = new AtomicLong(renderPointsRead); this.pathsRequests = new AtomicLong(pathsRequests); this.throttled = new AtomicDouble(throttled); this.timedOutRequests = new AtomicLong(timedOut); } /** * Resets the stats to zeroes and returns a snapshot of the record * @return snapshot of the record */ public StatsRecord reset() { return new StatsRecord(renderRequests.getAndSet(0), renderPathsRead.getAndSet(0), renderPointsRead.getAndSet(0), pathsRequests.getAndSet(0), throttled.getAndSet(0), timedOutRequests.getAndSet(0)); } public void incRenderRequests() { renderRequests.addAndGet(1); } public void incRenderPathsRead(int inc) { renderPathsRead.addAndGet(inc); } public void incRenderPointsRead(int inc) { renderPointsRead.addAndGet(inc); } public void incPathsRequests() { pathsRequests.addAndGet(1); } public void incThrottled(double value) { throttled.addAndGet(value); } public void incTimedOutRequests() { timedOutRequests.addAndGet(1); } public long getRenderRequests() { return renderRequests.get(); } public long getRenderPathsRead() { return renderPathsRead.get(); } public long getRenderPointsRead() { return renderPointsRead.get(); } public long getPathsRequests() { return pathsRequests.get(); } public double getThrottled() { return throttled.get(); } public long getTimedOutRequests() { return timedOutRequests.get(); } } }