/** * Copyright (c) 2010 Yahoo! Inc. All rights reserved. * * 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. See accompanying * LICENSE file. */ package com.couchbase.loadgen.measurements; import java.io.IOException; import java.util.HashMap; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import com.couchbase.loadgen.JsonStringBuilder; import com.couchbase.loadgen.measurements.exporter.MeasurementsExporter; /** * Take measurements and maintain a histogram of a given metric, such as READ * LATENCY. * * @author cooperb * */ public class OneMeasurementHistogram extends OneMeasurement { private static final long serialVersionUID = 8771477575164658300L; private Object lock = new Object(); private static final int microhistogramlength = 5; private static final int millihistogramlength = 10; int[] microhistogram; int[] millihistogram; int histogramoverflow; int operations; long totallatency; int exp_offset; int min; int max; HashMap<Integer, int[]> returncodes; public OneMeasurementHistogram(String name) { super(name); microhistogram = new int[microhistogramlength]; millihistogram = new int[millihistogramlength]; histogramoverflow = 0; operations = 0; totallatency = 0; exp_offset = 8; min = -1; max = -1; returncodes = new HashMap<Integer, int[]>(); } /* * (non-Javadoc) * * @see com.yahoo.ycsb.OneMeasurement#reportReturnCode(int) */ public synchronized void reportReturnCode(int code) { Integer Icode = code; if (!returncodes.containsKey(Icode)) { int[] val = new int[1]; val[0] = 0; returncodes.put(Icode, val); } returncodes.get(Icode)[0]++; } /* * (non-Javadoc) * * @see com.yahoo.ycsb.OneMeasurement#measure(int) */ public void measure(int latency) { synchronized(lock) { if (((int)Math.pow(2.0, (double)millihistogramlength) * 1000) < latency) histogramoverflow++; for (int i = 0; i < microhistogramlength; i++) { if (latency >= (i * 200) && latency < ((i + 1) * 200)) { microhistogram[i]++; break; } } for (int i = 0; i < millihistogramlength; i++) { int intervalmin = (int)Math.pow(2.0, (double)(i)) * 1000; int intervalmax = (int)Math.pow(2.0, (double)(i + 1)) * 1000; if (latency >= intervalmin && latency < intervalmax) { millihistogram[i]++; break; } } operations++; totallatency += latency; if ((min < 0) || (latency < min)) { min = latency; } if ((max < 0) || (latency > max)) { max = latency; } } } @Override public void exportMeasurements(MeasurementsExporter exporter) throws IOException { double mean = ((double)totallatency / (double)operations); exporter.write(getName(), "Operations", operations); exporter.write(getName(), "AverageLatency", computeTime(mean)); exporter.write(getName(), "MinLatency", computeTime((double)min)); exporter.write(getName(), "MaxLatency", computeTime((double)max)); exporter.write(getName(), "95thPercentileLatency", getPercentile(.95)); exporter.write(getName(), "99thPercentileLatency", getPercentile(.99)); exporter.write(getName(), "99.9thPercentileLatency", getPercentile(.999)); for (Integer I : returncodes.keySet()) { int[] val = returncodes.get(I); exporter.write(getName(), "Return=" + I, val[0]); } for (int i = 0; i < microhistogram.length; i++) { String key = computeTime(i * 200) + " - " + computeTime((i + 1) * 200); exporter.write(getName(), key, microhistogram[i]); } for (int i = 0; i < millihistogram.length; i++) { int intervalmin = (int)Math.pow(2.0, (double)(i)) * 1000; int intervalmax = (int)Math.pow(2.0, (double)(i + 1)) * 1000; String key = computeTime(intervalmin) + " - " + computeTime(intervalmax); exporter.write(getName(), key, millihistogram[i]); } String overflowtime = computeTime((int)Math.pow(2.0, (double)(millihistogramlength)) * 1000); exporter.write(getName(), ">" + overflowtime, histogramoverflow); } @Override public String getSummary() { if (operations == 0) { return ""; } String avg = computeTime((int)(((double) totallatency) / ((double) operations))); String p99 = getPercentile(.99); return "[" + getName() + " total=" + operations + " avg=" + avg + " 99th=" + p99 + "]"; } public String getPercentile(double percentile) { int i; int opcounter = 0; for (i = 0; i < microhistogramlength; i++) { opcounter += microhistogram[i]; if (((double) opcounter) / ((double) operations) >= percentile) { return computeTime((int)(i * 200)); } } for (i = 0; i < millihistogramlength; i++) { opcounter += millihistogram[i]; if (((double) opcounter) / ((double) operations) >= percentile) { return computeTime((int)(Math.pow(2, i) * 1000)); } } return computeTime((int)(Math.pow(2, millihistogramlength) * 1000)); } public void encodeJson(JsonStringBuilder builder) { try { builder.addElement("totallatency", new Double((double) totallatency)); builder.openSubelement("stats"); for (int i = 0; i < microhistogram.length; i++) { String key = computeTime(i * 200) + " - " + computeTime((i + 1) * 200); builder.addElement(key, new Integer(microhistogram[i])); } for (int i = 0; i < millihistogram.length; i++) { int intervalmin = (int)Math.pow(2.0, (double)(i)) * 1000; int intervalmax = (int)Math.pow(2.0, (double)(i + 1)) * 1000; String key = computeTime(intervalmin) + " - " + computeTime(intervalmax); builder.addElement(key, new Integer(millihistogram[i])); } builder.closeSubelement(); builder.openSubelement("returncodes"); for (Integer I : returncodes.keySet()) { int[] val = returncodes.get(I); builder.addElement(I.toString(), new Integer(val[0])); } builder.closeSubelement(); } catch (JsonGenerationException e) { } catch (IOException e) { } } public void decodeJson(JsonParser p) { synchronized(lock) { try { p.nextToken(); p.nextToken(); if (p.getCurrentName().equals("totallatency")) { p.nextToken(); totallatency += p.getLongValue(); p.nextToken(); } if (p.getCurrentName().equals("stats")) { p.nextToken(); int statnum = 0; while(p.nextToken() != JsonToken.END_OBJECT) { JsonToken token = p.nextToken(); if (token == JsonToken.VALUE_NUMBER_INT) { if (statnum < microhistogramlength) microhistogram[statnum] += p.getIntValue(); else millihistogram[statnum - microhistogramlength] += p.getIntValue(); operations += p.getIntValue(); statnum++; } else { //LOG.error("Field for " + key + " did not match type"); } } } p.nextToken(); if (p.getCurrentName().equals("returncodes")) { p.nextToken(); while (p.nextToken() != JsonToken.END_OBJECT) { Integer code = new Integer(p.getCurrentName()); int[] list = new int[1]; JsonToken token = p.nextToken(); list[0] = p.getIntValue(); if (token == JsonToken.VALUE_NUMBER_INT) { if (returncodes.containsKey(code)) list[0] += returncodes.get(code)[0]; returncodes.put(code, list); } else { //LOG.error("Field for " + key + " did not match type"); } } } } catch (JsonParseException e) { } catch (IOException e) { } } } }