/** * Copyright 2014 LinkedIn Corp. 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. */ package com.linkedin.multitenant.profiler; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.linkedin.multitenant.db.Database.DatabaseResult; import org.apache.log4j.Logger; public class EpochResult { @SuppressWarnings("unused") private static final Logger _LOG = Logger.getLogger(EpochResult.class); private static long M = 1000 * 1000; //mapping that holds how many operations ended with what latency private Map<Integer, Integer> _map; //maximum latency to keep track of private int _histogramMax; //total latency in nanoseconds for this epoch private long _totalLat; //number of successful operations private int _succOpt; //number of failed operations private int _failedOpt; //starting time in seconds for this epoch (inclusive) private int _start; //ending time in seconds for this epoch (exclusive) private int _end; public EpochResult(int histogramSize, int start, int end) { _map = new HashMap<Integer, Integer>(); if(histogramSize <= 0) _histogramMax = 100; else _histogramMax = histogramSize; _totalLat = 0; _succOpt = 0; _failedOpt = 0; _start = start; _end = end; } public EpochResult(byte arr[]) throws Exception { ByteArrayInputStream bs = new ByteArrayInputStream(arr); DataInputStream in = new DataInputStream(bs); //read start time _start = in.readInt(); //read end time _end = in.readInt(); //read histogramMax _histogramMax = in.readInt(); //read totalLat _totalLat = in.readLong(); //read opt _succOpt = in.readInt(); //read failedOpt _failedOpt = in.readInt(); //read number of elements in map int size = in.readInt(); _map = new HashMap<Integer, Integer>(); for(int a = 0; a<size; a++) { int currentKey = in.readInt(); int currentVal = in.readInt(); _map.put(currentKey, currentVal); } in.close(); } public EpochResult copy() throws Exception { byte[] tempArr = toByteArray(); return new EpochResult(tempArr); } public byte[] toByteArray() throws Exception { ByteArrayOutputStream bs = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(bs); //write start time out.writeInt(_start); //write end time out.writeInt(_end); //write histogramMax out.writeInt(_histogramMax); //write totalLat out.writeLong(_totalLat); //write opt out.writeInt(_succOpt); //write failedOpt out.writeInt(_failedOpt); //number of elements in map out.writeInt(_map.size()); //write each element in the map Iterator<Integer> itr = _map.keySet().iterator(); while(itr.hasNext()) { int currentKey = itr.next().intValue(); int currentVal = _map.get(currentKey).intValue(); out.writeInt(currentKey); out.writeInt(currentVal); } out.close(); return bs.toByteArray(); } /** * Returns total latency in nanoseconds * @return */ public long getTotalLat() { return _totalLat; } public int getStartTime() { return _start; } public int getEndTime() { return _end; } public int getSuccOpt() { return _succOpt; } public int getFailedOpt() { return _failedOpt; } public Map<Integer, Integer> getMap() { return _map; } /** * Add a measured latency value to the epoch result * @param latency Latency in nanoseconds */ public void add(long latency, DatabaseResult optResult) { if(optResult == DatabaseResult.FAIL) { _failedOpt++; return; } //calculate the latency bucket. int actualKey = (int) (latency / M); if(actualKey >= _histogramMax) actualKey = _histogramMax; else if(actualKey < 0) actualKey = 0; int val = 1; if(_map.containsKey(actualKey)) { val += _map.get(actualKey).intValue(); } _map.put(actualKey, val); _totalLat += latency; _succOpt++; } /** * Add given instance to this instance. Result is written in this instance. * @param rhs */ public void add(EpochResult rhs) { _totalLat += rhs.getTotalLat(); _succOpt += rhs.getSuccOpt(); _failedOpt += rhs.getFailedOpt(); Map<Integer, Integer> rhsMap = rhs.getMap(); Iterator<Integer> itr = rhsMap.keySet().iterator(); while(itr.hasNext()) { Integer currentKey = itr.next(); int newVal = rhsMap.get(currentKey); if(_map.containsKey(currentKey)) newVal += _map.get(currentKey).intValue(); _map.put(currentKey, newVal); } } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("["); sb.append(_start); sb.append(", "); sb.append(_end); sb.append(")"); sb.append(" SuccOpt="); sb.append(_succOpt); sb.append(" TotalLatInNanoSec="); sb.append(_totalLat); sb.append(" FailedOpt="); sb.append(_failedOpt); sb.append(" Lat-Opt\t" ); for(int a = 0; a <= _histogramMax; a++) { Integer cnt = _map.get(a); if(cnt == null) { sb.append(" "); sb.append(a); sb.append(":"); sb.append("0"); } else { sb.append(" "); sb.append(a); sb.append(":"); sb.append(cnt); } } return sb.toString(); } /** * Summarizes data in this epoch. * @return List of Objects.<br> * Index-0: (Integer) End of this epoch in seconds<br> * Index-1: (Integer) Number of successful operations<br> * Index-2: (Double) Average Latency in milliseconds<br> * Index-3: (Integer) 95% latency in milliseconds<br> * Index-4: (Integer) 99% latency in milliseconds */ public List<Object> summarize() { List<Object> rtnList = new ArrayList<Object>(); //add end time rtnList.add(new Integer(_end)); //add number of operations rtnList.add(new Integer(_succOpt)); //add average latency long avgLatInNs; if(_succOpt == 0) avgLatInNs = 0; else avgLatInNs = _totalLat/_succOpt; double avgLatInMS = ((double) avgLatInNs) / 1000000; rtnList.add(new Double(avgLatInMS)); //calculate 95% and 99% latencies long swappedOpt = 0; int lat95 = -1; int lat99 = -1; int currentLat = 0; long limit95 = _succOpt * 95L / 100L; long limit99 = _succOpt * 99L / 100L; while(lat99 == -1 && currentLat <= _histogramMax) { if(_map.containsKey(currentLat)) { swappedOpt += _map.get(currentLat).longValue(); if(lat95 == -1 && swappedOpt >= limit95) { lat95 = currentLat; } if(lat99 == -1 && swappedOpt >= limit99) { lat99 = currentLat; } } currentLat++; } //add 95% latency rtnList.add(new Integer(lat95)); //add 99% latency rtnList.add(new Integer(lat99)); return rtnList; } }