/*********************************************************************************************************************** * * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * **********************************************************************************************************************/ package eu.stratosphere.nephele.rpc; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import com.esotericsoftware.minlog.Log; /** * The RPCStatistics class collects statistics on the operation of the RPC service, e.g. packet loss, request retries, * etc. * <p> * This class is thread safe. */ public class RPCStatistics { /** * Map storing the collected retry statistics data by the number of packets the request consisted of. */ private final ConcurrentMap<Integer, RetryStatistics> statisticsData = new ConcurrentHashMap<Integer, RetryStatistics>(); /** * The RTT statistics. */ private final RTTStatistics rttStatistics = new RTTStatistics(); /** * Triggers the periodic processing the of the collected data. */ void processCollectedData() { final Iterator<RetryStatistics> it = this.statisticsData.values().iterator(); while (it.hasNext()) { final RetryStatistics data = it.next(); it.remove(); data.processCollectedData(); } this.rttStatistics.processCollectedData(); } void reportRTT(final String methodName, final int rtt) { this.rttStatistics.reportRTT(methodName, rtt); } /** * Reports the number of required retries for a successful transmission call to the statistics module. * * @param methodName * the name of the remote method that has been invoked as part of the transmission * @param numberOfPackets * the number of packets the transmission consisted of * @param requiredRetries * the number of required retries before the transmission was fully acknowledged */ void reportSuccessfulTransmission(final String methodName, final int numberOfPackets, final int requiredRetries) { final Integer key = Integer.valueOf(numberOfPackets); RetryStatistics data = this.statisticsData.get(key); if (data == null) { data = new RetryStatistics(numberOfPackets); final RetryStatistics oldValue = this.statisticsData.putIfAbsent(key, data); if (oldValue != null) data = oldValue; } data.reportNumberOfRequiredRetries(methodName, requiredRetries); } /** * Auxiliary class to hold the transmission retry statistics for a particular number of packets. * <p> * This class is thread-safe. */ private static final class RetryStatistics { /** * The number of packets this object has been created for. */ private final int numberOfPackets; /** * The minimum number of retries reported in the current collection interval. */ private final AtomicInteger minRetries = new AtomicInteger(Integer.MAX_VALUE); /** * The method name for which the minimum number of retries has been reported. */ private volatile String minMethodName = null; /** * The maximum number of retries reported in the current collection interval. */ private final AtomicInteger maxRetries = new AtomicInteger(Integer.MIN_VALUE); /** * The method name for which the maximum number of retries has been reported. */ private volatile String maxMethodName = null; /** * The number of reported requests in the current collection interval. */ private final AtomicInteger requestCounter = new AtomicInteger(0); /** * The sum of required retries in the current collection interval. */ private final AtomicInteger sumOfRetries = new AtomicInteger(0); /** * Creates a new retry statistics object. * * @param numberOfPackets * the number of packets this object is created for */ private RetryStatistics(final int numberOfPackets) { this.numberOfPackets = numberOfPackets; } /** * Processes and logs the collected statistics data. */ private void processCollectedData() { if (Log.DEBUG) { final int numberOfRequests = this.requestCounter.get(); if (numberOfRequests == 0) return; final float avg = (float) this.sumOfRetries.get() / (float) numberOfRequests; final StringBuilder sb = new StringBuilder(); sb.append(this.numberOfPackets); sb.append("\t: "); sb.append(avg); sb.append(" (min "); sb.append(this.minMethodName); sb.append(' '); sb.append(this.minRetries.get()); sb.append(", max "); sb.append(this.maxMethodName); sb.append(' '); sb.append(this.maxRetries.get()); sb.append(')'); Log.debug(sb.toString()); } } /** * Reports the number of required retries for an RPC call. * * @param methodName * the name of the remote method that has been called * @param requiredRetries * the number of required retries */ private final void reportNumberOfRequiredRetries(final String methodName, final int requiredRetries) { this.requestCounter.incrementAndGet(); this.sumOfRetries.addAndGet(requiredRetries); this.testAndSetMin(methodName, requiredRetries); this.testAndSetMax(methodName, requiredRetries); } /** * Sets the maximum number of required retries in a thread-safe way. * * @param methodName * the method name * @param requiredRetries * the number of required retries */ private void testAndSetMax(final String methodName, final int requiredRetries) { while (true) { final int val = this.maxRetries.get(); if (requiredRetries > val) { if (!this.maxRetries.compareAndSet(val, requiredRetries)) continue; this.maxMethodName = methodName; } break; } } /** * Sets the minimum number of required retries in a thread-safe way. * * @param methodName * the method name * @param requiredRetries * the number of required retries */ private void testAndSetMin(final String methodName, final int requiredRetries) { while (true) { final int val = this.minRetries.get(); if (requiredRetries < val) { if (!this.minRetries.compareAndSet(val, requiredRetries)) continue; this.minMethodName = methodName; } break; } } } /** * Auxiliary class to hold statistics on the round-trip times (RTTs) of RPC class. * <p> * This class is thread-safe. */ private static final class RTTStatistics { /** * The number of reported requests in the current collection interval. */ private final AtomicInteger requestCounter = new AtomicInteger(0); /** * The sum of all RTTs in the current collection interval in milliseconds. */ private final AtomicInteger sumOfRTTs = new AtomicInteger(0); /** * The name of the method with the minimum RTT in the current collection interval. */ private volatile String minMethodName = null; /** * The minimum RTT in the current collection interval in milliseconds. */ private final AtomicInteger minRTT = new AtomicInteger(Integer.MAX_VALUE); /** * The name of the method with the maximum RTT in the current collection interval. */ private volatile String maxMethodName = null; /** * The maximum RTT in the current collection interval in milliseconds. */ private final AtomicInteger maxRTT = new AtomicInteger(Integer.MIN_VALUE); /** * Processes and logs the collected RTT data. */ private void processCollectedData() { final int numberOfRequests = this.requestCounter.getAndSet(0); if (numberOfRequests == 0) return; final float avg = (float) this.sumOfRTTs.getAndSet(0) / (float) numberOfRequests; final int min = this.minRTT.getAndSet(Integer.MAX_VALUE); final String minMethodName = this.minMethodName; this.minMethodName = null; final int max = this.maxRTT.getAndSet(Integer.MIN_VALUE); final String maxMethodName = this.maxMethodName; this.maxMethodName = null; if (Log.DEBUG) { final StringBuilder sb = new StringBuilder("RTT stats: "); sb.append(avg); sb.append(" ms avg,\t: "); sb.append(min); sb.append(" ms min ("); sb.append(minMethodName); sb.append("),\t"); sb.append(max); sb.append(" ms max ("); sb.append(maxMethodName); sb.append(')'); Log.debug(sb.toString()); } } /** * Reports the RTT of an RPC call. * * @param methodName * the name of the remote method that has been called * @param rtt * the required RTT in milliseconds */ private final void reportRTT(final String methodName, final int rtt) { this.requestCounter.incrementAndGet(); this.sumOfRTTs.addAndGet(rtt); this.testAndSetLowestRTT(methodName, rtt); this.testAndSetHighestRTT(methodName, rtt); } /** * Sets the highest RTT in a thread-safe way. * * @param methodName * the method name * @param rtt * the RTT in milliseconds */ private void testAndSetHighestRTT(final String methodName, final int rtt) { while (true) { final int val = this.maxRTT.get(); if (rtt > val) { if (!this.maxRTT.compareAndSet(val, rtt)) continue; this.maxMethodName = methodName; } break; } } /** * Sets the lowest RTT in a thread-safe way. * * @param methodName * the method name * @param rtt * the RTT in milliseconds */ private void testAndSetLowestRTT(final String methodName, final int rtt) { while (true) { final int val = this.minRTT.get(); if (rtt < val) { if (!this.minRTT.compareAndSet(val, rtt)) continue; this.minMethodName = methodName; } break; } } } }