/******************************************************************************* * gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/ * Copyright (C) 2014 SVS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package userGeneratedContent.testbedPlugIns.staticFunctionPlugIns.layer5application.statisticsRecorder_v0_001; import java.util.Vector; import java.util.concurrent.TimeUnit; import staticContent.framework.AnonNode; import staticContent.framework.config.Settings; import staticContent.framework.message.Request; import staticContent.framework.userDatabase.User; import staticContent.framework.userDatabase.UserAttachment; import staticContent.framework.userDatabase.UserDatabase; import staticContent.framework.util.Util; public class StatisticsRecorder { public static boolean DISPLAY_THROUGHPUT; public static boolean DISPLAY_STATS_PER_USER; public static boolean DISPLAY_QUEUE_STATUS; public static boolean DISPLAY_MESSAGE_DWELL_TIMES; private static int DISPLAY_STAT_PERIOD; private static UserDatabase userDatabase; private static Settings settings; private static boolean initDone = false; private static Object synchronizer = new Object(); private static long sumOfRequestDataReceivedInPriod = 0; private static long sumOfRequestDataReceivedInTotal = 0; private static long sumOfReplyDataReceivedInPriod = 0; private static long sumOfReplyDataReceivedInTotal = 0; private static long startTime; private static Vector<AnonNode> mixes = new Vector<AnonNode>(); private static Vector<AnonNode> clients = new Vector<AnonNode>(); private static int[] statDelays = new int[1001]; private static long excessiveDelayCounter = 0; private static long statTotalMessages = 0; private static long startStat = 0; // TODO: change to "register(AnonNode)" and allow recording statistics of multiple anonNodes (add parameter "AnonNode" to addRecord()-methods) public static void init(AnonNode owner) { if (owner.IS_CLIENT) clients.add(owner); if (owner.IS_MIX) mixes.add(owner); if (initDone) return; initDone = true; userDatabase = owner.getUserDatabase(); settings = owner.getSettings(); DISPLAY_STAT_PERIOD = settings.getPropertyAsInt("DISPLAY_STAT_PERIOD"); DISPLAY_THROUGHPUT = settings.getPropertyAsBoolean("DISPLAY_THROUGHPUT"); DISPLAY_STATS_PER_USER = settings.getPropertyAsBoolean("DISPLAY_STATS_PER_USER"); DISPLAY_QUEUE_STATUS = settings.getPropertyAsBoolean("DISPLAY_QUEUE_STATUS"); DISPLAY_MESSAGE_DWELL_TIMES = settings.getPropertyAsBoolean("DISPLAY_MESSAGE_DWELL_TIMES"); new DisplayThread().start(); } public static void addRequestThroughputRecord(int ammountOfData) { if (DISPLAY_THROUGHPUT) { synchronized (synchronizer) { sumOfRequestDataReceivedInPriod += ammountOfData; } } } public static void addMessageDwellTimeRecord(Request request) { if (DISPLAY_MESSAGE_DWELL_TIMES) { synchronized (synchronizer) { long dwellTime = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - request.statisticsCreateTime); statTotalMessages++; if (dwellTime < 100000) statDelays[(int) (dwellTime/100l)]++; else excessiveDelayCounter++; } } } public static void addReplyThroughputRecord(int ammountOfData) { if (DISPLAY_THROUGHPUT) { synchronized (synchronizer) { sumOfReplyDataReceivedInPriod += ammountOfData; } } } public static void addRequestThroughputRecord(int ammountOfData, User user) { if (DISPLAY_THROUGHPUT) { synchronized (synchronizer) { sumOfRequestDataReceivedInPriod += ammountOfData; if (DISPLAY_STATS_PER_USER) { UserStatistics stats = user.getAttachment(synchronizer, UserStatistics.class); if (stats == null) stats = new UserStatistics(user); stats.sumOfRequestDataReceivedInPriod += ammountOfData; } } } } public static void addReplyThroughputRecord(int ammountOfData, User user) { if (DISPLAY_THROUGHPUT) { synchronized (synchronizer) { sumOfReplyDataReceivedInPriod += ammountOfData; if (DISPLAY_STATS_PER_USER) { UserStatistics stats = user.getAttachment(synchronizer, UserStatistics.class); if (stats == null) stats = new UserStatistics(user); stats.sumOfReplyDataReceivedInPriod += ammountOfData; } } } } static class UserStatistics extends UserAttachment { boolean ready = false; long sumOfRequestDataReceivedInPriod = 0; long sumOfRequestDataReceivedInTotal = 0; long sumOfReplyDataReceivedInPriod = 0; long sumOfReplyDataReceivedInTotal = 0; public UserStatistics(User owner) { super(owner, synchronizer); } } static class DisplayThread extends Thread { @Override public void run() { startTime = System.nanoTime(); while (true) { try { Thread.sleep(DISPLAY_STAT_PERIOD); } catch (InterruptedException e) { e.printStackTrace(); continue; } synchronized (synchronizer) { String output = "STATISTICS:"; if (DISPLAY_THROUGHPUT) output += getThroughputStatistics(); if (DISPLAY_QUEUE_STATUS && !mixes.isEmpty()) output += getQueueStatistics(); if (DISPLAY_MESSAGE_DWELL_TIMES) output += getMessageDwellTimeStatistics(); System.out.println(output); } } } private String getMessageDwellTimeStatistics() { String output = "\n DWELL_TIME stats:"; if (startStat == 0) { // ignore first round (init time etc) startStat = System.currentTimeMillis(); excessiveDelayCounter = 0; statTotalMessages = 0; statDelays = new int[1001]; } else { output += "\n " +statTotalMessages +" messages in total"; output += "\n " +excessiveDelayCounter + " messages with excessive delays (>=100ms)"; output += "\n stats for delays below 100ms: "; for (int j=1; j<=statDelays.length; j++) { if (statDelays[j-1] != 0) output += "\n " +statDelays[j-1] +"\tx between " +((j-1)*100) +"\tand " +(j*100) +"\tmicroseconds"; } // calculate 95 percentile // TODO long sum = 0; for (int j=1; j<=statDelays.length; j++) { sum += statDelays[j-1]; double perc = (double)sum / (double)statTotalMessages; if (perc >= 0.95d) { output += "\n " +(perc*100d) +"% of messages faster than " +(j*100) +" microseconds"; break; } } excessiveDelayCounter = 0; statTotalMessages = 0; statDelays = new int[1001]; startStat = System.currentTimeMillis(); } return output; } private String getQueueStatistics() { String output = "\n QUEUE_STATUS:"; for (AnonNode mix:mixes) { output += "\n " +mix +" status of RequestInputQueue: " +mix.getRequestInputQueue().size() +" of " +(mix.getRequestInputQueue().size()+mix.getRequestInputQueue().remainingCapacity()) +" occupied"; output += "\n " +mix +" status of RequestOutputQueue: " +mix.getRequestOutputQueue().size() +" of " +(mix.getRequestOutputQueue().size()+mix.getRequestOutputQueue().remainingCapacity()) +" occupied"; if (mix.IS_DUPLEX) { output += "\n " +mix +" status of ReplyInputQueue: " +mix.getReplyInputQueue().size() +" of " +(mix.getReplyInputQueue().size()+mix.getReplyInputQueue().remainingCapacity()) +" occupied"; output += "\n " +mix +" status of ReplyOutputQueue: " +mix.getReplyOutputQueue().size() +" of " +(mix.getReplyOutputQueue().size()+mix.getReplyOutputQueue().remainingCapacity()) +" occupied"; } } return output; } private String getThroughputStatistics() { long duration = TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); startTime = System.nanoTime(); sumOfRequestDataReceivedInTotal += sumOfRequestDataReceivedInPriod; String output = "\n total THROUGHPUT on last mix for request-channel(s): "; output += Util.humanReadableByteCount((sumOfRequestDataReceivedInPriod/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount((sumOfRequestDataReceivedInPriod/duration)*1000, true) +"/sec"; output += ", " +userDatabase.getNumberOfUsers() +" users" ; output += ", measurePeriod: "+duration +" ms"; output += ", transmitted so far: " +Util.humanReadableByteCount(sumOfRequestDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(sumOfRequestDataReceivedInTotal, true); output += ")"; if (sumOfReplyDataReceivedInPriod != 0) { sumOfReplyDataReceivedInTotal += sumOfReplyDataReceivedInPriod; output += "\n total THROUGHPUT on last mix for reply-channel(s): "; output += Util.humanReadableByteCount((sumOfReplyDataReceivedInPriod/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount((sumOfReplyDataReceivedInPriod/duration)*1000, true) +"/sec"; output += ", " +userDatabase.getNumberOfUsers() +" users" ; output += ", measurePeriod: "+duration +" ms"; output += ", transmitted so far: " +Util.humanReadableByteCount(sumOfReplyDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(sumOfReplyDataReceivedInTotal, true); output += ")"; output += "\n total THROUGHPUT on last mix (request- and reply-channel(s): "; output += Util.humanReadableByteCount(((sumOfRequestDataReceivedInPriod+sumOfReplyDataReceivedInPriod)/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount(((sumOfRequestDataReceivedInPriod+sumOfReplyDataReceivedInPriod)/duration)*1000, true) +"/sec"; output += ", " +userDatabase.getNumberOfUsers() +" users" ; output += ", measurePeriod: "+duration +" ms"; output += ", transmitted so far: " +Util.humanReadableByteCount(sumOfRequestDataReceivedInTotal+sumOfReplyDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(sumOfRequestDataReceivedInTotal+sumOfReplyDataReceivedInTotal, true); output += ")"; } sumOfRequestDataReceivedInPriod = 0; sumOfReplyDataReceivedInPriod = 0; if (DISPLAY_STATS_PER_USER) { // TODO: evaluate fairness output += "\n THROUGHPUT per user:"; for (User user:userDatabase.getAllUsers()) { UserStatistics stats = user.getAttachment(synchronizer, UserStatistics.class); if (stats != null) { if (stats.ready) { stats.sumOfRequestDataReceivedInTotal += stats.sumOfRequestDataReceivedInPriod; output += "\n " +user.toString().substring(28) +" (request-channel): "; output += Util.humanReadableByteCount((stats.sumOfRequestDataReceivedInPriod/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount((stats.sumOfRequestDataReceivedInPriod/duration)*1000, true) +"/sec"; output += ", transmitted so far: " +Util.humanReadableByteCount(stats.sumOfRequestDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(stats.sumOfRequestDataReceivedInTotal, true); output += ")"; if (sumOfReplyDataReceivedInTotal != 0) { stats.sumOfReplyDataReceivedInTotal += stats.sumOfReplyDataReceivedInPriod; output += "\n " +user.toString().substring(28) +" (reply-channel): "; output += Util.humanReadableByteCount((stats.sumOfReplyDataReceivedInPriod/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount((stats.sumOfReplyDataReceivedInPriod/duration)*1000, true) +"/sec"; output += ", transmitted so far: " +Util.humanReadableByteCount(stats.sumOfReplyDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(stats.sumOfReplyDataReceivedInTotal, true); output += ")"; output += "\n " +user.toString().substring(28) +" (request- and reply-channel): "; output += Util.humanReadableByteCount(((stats.sumOfRequestDataReceivedInPriod+stats.sumOfReplyDataReceivedInPriod)/duration)*1000, false) +"/sec"; output += " (" +Util.humanReadableByteCount(((stats.sumOfRequestDataReceivedInPriod+stats.sumOfReplyDataReceivedInPriod)/duration)*1000, true) +"/sec"; output += ", transmitted so far: " +Util.humanReadableByteCount(stats.sumOfRequestDataReceivedInTotal+stats.sumOfReplyDataReceivedInTotal, false) +" = " +Util.humanReadableByteCount(stats.sumOfRequestDataReceivedInTotal+stats.sumOfReplyDataReceivedInTotal, true); output += ")"; } stats.sumOfRequestDataReceivedInPriod = 0; stats.sumOfReplyDataReceivedInPriod = 0; } else { // only count intervals where user was already connected at the beginning stats.ready = true; } } } } return output; } } }