diff --git a/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHdrHistogram.java b/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHdrHistogram.java index 28c284dc..86af751f 100644 --- a/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHdrHistogram.java +++ b/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHdrHistogram.java @@ -1,211 +1,224 @@ /** * Copyright (c) 2010-2016 Yahoo! Inc., 2017 YCSB contributors 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.yahoo.ycsb.measurements; import com.yahoo.ycsb.measurements.exporter.MeasurementsExporter; import org.HdrHistogram.Histogram; import org.HdrHistogram.HistogramIterationValue; import org.HdrHistogram.HistogramLogWriter; import org.HdrHistogram.Recorder; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Properties; /** * Take measurements and maintain a HdrHistogram of a given metric, such as READ LATENCY. * */ public class OneMeasurementHdrHistogram extends OneMeasurement { // we need one log per measurement histogram private final PrintStream log; private final HistogramLogWriter histogramLogWriter; private final Recorder histogram; private Histogram totalHistogram; /** * The name of the property for deciding what percentile values to output. */ public static final String PERCENTILES_PROPERTY = "hdrhistogram.percentiles"; /** * The default value for the hdrhistogram.percentiles property. */ public static final String PERCENTILES_PROPERTY_DEFAULT = "95,99"; + + /** + * The name of the property for determining if we should print out the buckets. + */ + public static final String VERBOSE_PROPERTY = "measurement.histogram.verbose"; + /** + * Whether or not to emit the histogram buckets. + */ + private final boolean verbose; + private final List percentiles; public OneMeasurementHdrHistogram(String name, Properties props) { super(name); percentiles = getPercentileValues(props.getProperty(PERCENTILES_PROPERTY, PERCENTILES_PROPERTY_DEFAULT)); + verbose = Boolean.valueOf(props.getProperty(VERBOSE_PROPERTY, String.valueOf(false))); boolean shouldLog = Boolean.parseBoolean(props.getProperty("hdrhistogram.fileoutput", "false")); if (!shouldLog) { log = null; histogramLogWriter = null; } else { try { final String hdrOutputFilename = props.getProperty("hdrhistogram.output.path", "") + name + ".hdr"; log = new PrintStream(new FileOutputStream(hdrOutputFilename), false); } catch (FileNotFoundException e) { throw new RuntimeException("Failed to open hdr histogram output file", e); } histogramLogWriter = new HistogramLogWriter(log); histogramLogWriter.outputComment("[Logging for: " + name + "]"); histogramLogWriter.outputLogFormatVersion(); long now = System.currentTimeMillis(); histogramLogWriter.outputStartTime(now); histogramLogWriter.setBaseTime(now); histogramLogWriter.outputLegend(); } histogram = new Recorder(3); } /** * It appears latency is reported in micros. * Using {@link Recorder} to support concurrent updates to histogram. */ public void measure(int latencyInMicros) { histogram.recordValue(latencyInMicros); } /** * This is called from a main thread, on orderly termination. */ @Override public void exportMeasurements(MeasurementsExporter exporter) throws IOException { // accumulate the last interval which was not caught by status thread Histogram intervalHistogram = getIntervalHistogramAndAccumulate(); if (histogramLogWriter != null) { histogramLogWriter.outputIntervalHistogram(intervalHistogram); // we can close now log.close(); } exporter.write(getName(), "Operations", totalHistogram.getTotalCount()); exporter.write(getName(), "AverageLatency(us)", totalHistogram.getMean()); exporter.write(getName(), "MinLatency(us)", totalHistogram.getMinValue()); exporter.write(getName(), "MaxLatency(us)", totalHistogram.getMaxValue()); for (Double percentile : percentiles) { exporter.write(getName(), ordinal(percentile) + "PercentileLatency(us)", totalHistogram.getValueAtPercentile(percentile)); } exportStatusCounts(exporter); // also export totalHistogram - for (HistogramIterationValue v : totalHistogram.recordedValues()) { - int value; - if (v.getValueIteratedTo() > (long)Integer.MAX_VALUE) { - value = Integer.MAX_VALUE; - } else { - value = (int)v.getValueIteratedTo(); + if (verbose) { + for (HistogramIterationValue v : totalHistogram.recordedValues()) { + int value; + if (v.getValueIteratedTo() > (long)Integer.MAX_VALUE) { + value = Integer.MAX_VALUE; + } else { + value = (int)v.getValueIteratedTo(); + } + + exporter.write(getName(), Integer.toString(value), (double)v.getCountAtValueIteratedTo()); } - - exporter.write(getName(), Integer.toString(value), (double)v.getCountAtValueIteratedTo()); } } /** * This is called periodically from the StatusThread. There's a single * StatusThread per Client process. We optionally serialize the interval to * log on this opportunity. * * @see com.yahoo.ycsb.measurements.OneMeasurement#getSummary() */ @Override public String getSummary() { Histogram intervalHistogram = getIntervalHistogramAndAccumulate(); // we use the summary interval as the histogram file interval. if (histogramLogWriter != null) { histogramLogWriter.outputIntervalHistogram(intervalHistogram); } DecimalFormat d = new DecimalFormat("#.##"); return "[" + getName() + ": Count=" + intervalHistogram.getTotalCount() + ", Max=" + intervalHistogram.getMaxValue() + ", Min=" + intervalHistogram.getMinValue() + ", Avg=" + d.format(intervalHistogram.getMean()) + ", 90=" + d.format(intervalHistogram.getValueAtPercentile(90)) + ", 99=" + d.format(intervalHistogram.getValueAtPercentile(99)) + ", 99.9=" + d.format(intervalHistogram.getValueAtPercentile(99.9)) + ", 99.99=" + d.format(intervalHistogram.getValueAtPercentile(99.99)) + "]"; } private Histogram getIntervalHistogramAndAccumulate() { Histogram intervalHistogram = histogram.getIntervalHistogram(); // add this to the total time histogram. if (totalHistogram == null) { totalHistogram = intervalHistogram; } else { totalHistogram.add(intervalHistogram); } return intervalHistogram; } /** * Helper method to parse the given percentile value string. * * @param percentileString - comma delimited string of Integer values * @return An Integer List of percentile values */ private List getPercentileValues(String percentileString) { List percentileValues = new ArrayList<>(); try { for (String rawPercentile : percentileString.split(",")) { percentileValues.add(Double.parseDouble(rawPercentile)); } } catch (Exception e) { // If the given hdrhistogram.percentiles value is unreadable for whatever reason, // then calculate and return the default set. System.err.println("[WARN] Couldn't read " + PERCENTILES_PROPERTY + " value: '" + percentileString + "', the default of '" + PERCENTILES_PROPERTY_DEFAULT + "' will be used."); e.printStackTrace(); return getPercentileValues(PERCENTILES_PROPERTY_DEFAULT); } return percentileValues; } /** * Helper method to find the ordinal of any number. eg 1 -> 1st * @param i number * @return ordinal string */ private String ordinal(Double i) { String[] suffixes = new String[]{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}; Integer j = i.intValue(); if (i % 1 == 0) { switch (j % 100) { case 11: case 12: case 13: return j + "th"; default: return j + suffixes[j % 10]; } } else { return i.toString(); } } } diff --git a/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHistogram.java b/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHistogram.java index de550d12..4a7f6f8c 100644 --- a/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHistogram.java +++ b/core/src/main/java/com/yahoo/ycsb/measurements/OneMeasurementHistogram.java @@ -1,156 +1,166 @@ /** * Copyright (c) 2010-2016 Yahoo! Inc., 2017 YCSB contributors 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.yahoo.ycsb.measurements; import com.yahoo.ycsb.measurements.exporter.MeasurementsExporter; import java.io.IOException; import java.text.DecimalFormat; import java.util.Properties; /** * Take measurements and maintain a histogram of a given metric, such as READ LATENCY. * */ public class OneMeasurementHistogram extends OneMeasurement { public static final String BUCKETS = "histogram.buckets"; public static final String BUCKETS_DEFAULT = "1000"; + public static final String VERBOSE_PROPERTY = "measurement.histogram.verbose"; /** * Specify the range of latencies to track in the histogram. */ private final int buckets; /** * Groups operations in discrete blocks of 1ms width. */ private long[] histogram; /** * Counts all operations outside the histogram's range. */ private long histogramoverflow; /** * The total number of reported operations. */ private long operations; /** * The sum of each latency measurement over all operations. * Calculated in ms. */ private long totallatency; /** * The sum of each latency measurement squared over all operations. * Used to calculate variance of latency. * Calculated in ms. */ private double totalsquaredlatency; + /** + * Whether or not to emit the histogram buckets. + */ + private final boolean verbose; + //keep a windowed version of these stats for printing status private long windowoperations; private long windowtotallatency; private int min; private int max; public OneMeasurementHistogram(String name, Properties props) { super(name); buckets = Integer.parseInt(props.getProperty(BUCKETS, BUCKETS_DEFAULT)); + verbose = Boolean.valueOf(props.getProperty(VERBOSE_PROPERTY, String.valueOf(false))); histogram = new long[buckets]; histogramoverflow = 0; operations = 0; totallatency = 0; totalsquaredlatency = 0; windowoperations = 0; windowtotallatency = 0; min = -1; max = -1; } /* (non-Javadoc) * @see com.yahoo.ycsb.OneMeasurement#measure(int) */ public synchronized void measure(int latency) { //latency reported in us and collected in bucket by ms. if (latency / 1000 >= buckets) { histogramoverflow++; } else { histogram[latency / 1000]++; } operations++; totallatency += latency; totalsquaredlatency += ((double) latency) * ((double) latency); windowoperations++; windowtotallatency += 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 = totallatency / ((double) operations); double variance = totalsquaredlatency / ((double) operations) - (mean * mean); exporter.write(getName(), "Operations", operations); exporter.write(getName(), "AverageLatency(us)", mean); exporter.write(getName(), "LatencyVariance(us)", variance); exporter.write(getName(), "MinLatency(us)", min); exporter.write(getName(), "MaxLatency(us)", max); long opcounter=0; boolean done95th = false; for (int i = 0; i < buckets; i++) { opcounter += histogram[i]; if ((!done95th) && (((double) opcounter) / ((double) operations) >= 0.95)) { exporter.write(getName(), "95thPercentileLatency(us)", i * 1000); done95th = true; } if (((double) opcounter) / ((double) operations) >= 0.99) { exporter.write(getName(), "99thPercentileLatency(us)", i * 1000); break; } } exportStatusCounts(exporter); - for (int i = 0; i < buckets; i++) { - exporter.write(getName(), Integer.toString(i), histogram[i]); + if (verbose) { + for (int i = 0; i < buckets; i++) { + exporter.write(getName(), Integer.toString(i), histogram[i]); + } + + exporter.write(getName(), ">" + buckets, histogramoverflow); } - exporter.write(getName(), ">" + buckets, histogramoverflow); } @Override public String getSummary() { if (windowoperations == 0) { return ""; } DecimalFormat d = new DecimalFormat("#.##"); double report = ((double) windowtotallatency) / ((double) windowoperations); windowtotallatency = 0; windowoperations = 0; return "[" + getName() + " AverageLatency(us)=" + d.format(report) + "]"; } } diff --git a/core/src/test/java/com/yahoo/ycsb/measurements/exporter/TestMeasurementsExporter.java b/core/src/test/java/com/yahoo/ycsb/measurements/exporter/TestMeasurementsExporter.java index 4aff0941..8479365c 100644 --- a/core/src/test/java/com/yahoo/ycsb/measurements/exporter/TestMeasurementsExporter.java +++ b/core/src/test/java/com/yahoo/ycsb/measurements/exporter/TestMeasurementsExporter.java @@ -1,58 +1,61 @@ /** * Copyright (c) 2015 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.yahoo.ycsb.measurements.exporter; import com.yahoo.ycsb.generator.ZipfianGenerator; import com.yahoo.ycsb.measurements.Measurements; +import com.yahoo.ycsb.measurements.OneMeasurementHistogram; + import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.testng.annotations.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Properties; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; public class TestMeasurementsExporter { @Test public void testJSONArrayMeasurementsExporter() throws IOException { Properties props = new Properties(); props.put(Measurements.MEASUREMENT_TYPE_PROPERTY, "histogram"); + props.put(OneMeasurementHistogram.VERBOSE_PROPERTY, "true"); Measurements mm = new Measurements(props); ByteArrayOutputStream out = new ByteArrayOutputStream(); JSONArrayMeasurementsExporter export = new JSONArrayMeasurementsExporter(out); long min = 5000; long max = 100000; ZipfianGenerator zipfian = new ZipfianGenerator(min, max); for (int i = 0; i < 1000; i++) { int rnd = zipfian.nextValue().intValue(); mm.measure("UPDATE", rnd); } mm.exportMeasurements(export); export.close(); ObjectMapper mapper = new ObjectMapper(); JsonNode json = mapper.readTree(out.toString("UTF-8")); assertTrue(json.isArray()); assertEquals(json.get(0).get("measurement").asText(), "Operations"); assertEquals(json.get(4).get("measurement").asText(), "MaxLatency(us)"); assertEquals(json.get(11).get("measurement").asText(), "4"); } } diff --git a/workloads/workload_template b/workloads/workload_template index b66d3b6e..af1c3250 100644 --- a/workloads/workload_template +++ b/workloads/workload_template @@ -1,203 +1,207 @@ # Copyright (c) 2012-2016 YCSB contributors. 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. # Yahoo! Cloud System Benchmark # Workload Template: Default Values # # File contains all properties that can be set to define a # YCSB session. All properties are set to their default # value if one exists. If not, the property is commented # out. When a property has a finite number of settings, # the default is enabled and the alternates are shown in # comments below it. # # Use of most explained through comments in Client.java or # CoreWorkload.java or on the YCSB wiki page: # https://github.com/brianfrankcooper/YCSB/wiki/Core-Properties # The name of the workload class to use workload=com.yahoo.ycsb.workloads.CoreWorkload # There is no default setting for recordcount but it is # required to be set. # The number of records in the table to be inserted in # the load phase or the number of records already in the # table before the run phase. recordcount=1000000 # There is no default setting for operationcount but it is # required to be set. # The number of operations to use during the run phase. operationcount=3000000 # The number of insertions to do, if different from recordcount. # Used with insertstart to grow an existing table. #insertcount= # The offset of the first insertion insertstart=0 # The number of fields in a record fieldcount=10 # The size of each field (in bytes) fieldlength=100 # Should read all fields readallfields=true # Should write all fields on update writeallfields=false # The distribution used to choose the length of a field fieldlengthdistribution=constant #fieldlengthdistribution=uniform #fieldlengthdistribution=zipfian # What proportion of operations are reads readproportion=0.95 # What proportion of operations are updates updateproportion=0.05 # What proportion of operations are inserts insertproportion=0 # What proportion of operations read then modify a record readmodifywriteproportion=0 # What proportion of operations are scans scanproportion=0 # On a single scan, the maximum number of records to access maxscanlength=1000 # The distribution used to choose the number of records to access on a scan scanlengthdistribution=uniform #scanlengthdistribution=zipfian # Should records be inserted in order or pseudo-randomly insertorder=hashed #insertorder=ordered # The distribution of requests across the keyspace requestdistribution=zipfian #requestdistribution=uniform #requestdistribution=latest # Percentage of data items that constitute the hot set hotspotdatafraction=0.2 # Percentage of operations that access the hot set hotspotopnfraction=0.8 # Maximum execution time in seconds #maxexecutiontime= # The name of the database table to run queries against table=usertable # The column family of fields (required by some databases) #columnfamily= # How the latency measurements are presented measurementtype=histogram #measurementtype=timeseries #measurementtype=raw # When measurementtype is set to raw, measurements will be output # as RAW datapoints in the following csv format: # "operation, timestamp of the measurement, latency in us" # # Raw datapoints are collected in-memory while the test is running. Each # data point consumes about 50 bytes (including java object overhead). # For a typical run of 1 million to 10 million operations, this should # fit into memory most of the time. If you plan to do 100s of millions of # operations per run, consider provisioning a machine with larger RAM when using # the RAW measurement type, or split the run into multiple runs. # # Optionally, you can specify an output file to save raw datapoints. # Otherwise, raw datapoints will be written to stdout. # The output file will be appended to if it already exists, otherwise # a new output file will be created. #measurement.raw.output_file = /tmp/your_output_file_for_this_run +# Whether or not to emit individual histogram buckets when measuring +# using histograms. +# measurement.histogram.verbose = false + # JVM Reporting. # # Measure JVM information over time including GC counts, max and min memory # used, max and min thread counts, max and min system load and others. This # setting must be enabled in conjunction with the "-s" flag to run the status # thread. Every "status.interval", the status thread will capture JVM # statistics and record the results. At the end of the run, max and mins will # be recorded. # measurement.trackjvm = false # The range of latencies to track in the histogram (milliseconds) histogram.buckets=1000 # Granularity for time series (in milliseconds) timeseries.granularity=1000 # Latency reporting. # # YCSB records latency of failed operations separately from successful ones. # Latency of all OK operations will be reported under their operation name, # such as [READ], [UPDATE], etc. # # For failed operations: # By default we don't track latency numbers of specific error status. # We just report latency of all failed operation under one measurement name # such as [READ-FAILED]. But optionally, user can configure to have either: # 1. Record and report latency for each and every error status code by # setting reportLatencyForEachError to true, or # 2. Record and report latency for a select set of error status codes by # providing a CSV list of Status codes via the "latencytrackederrors" # property. # reportlatencyforeacherror=false # latencytrackederrors="" # Insertion error retry for the core workload. # # By default, the YCSB core workload does not retry any operations. # However, during the load process, if any insertion fails, the entire # load process is terminated. # If a user desires to have more robust behavior during this phase, they can # enable retry for insertion by setting the following property to a positive # number. # core_workload_insertion_retry_limit = 0 # # the following number controls the interval between retries (in seconds): # core_workload_insertion_retry_interval = 3 # Distributed Tracing via Apache HTrace (http://htrace.incubator.apache.org/) # # Defaults to blank / no tracing # Below sends to a local file, sampling at 0.1% # # htrace.sampler.classes=ProbabilitySampler # htrace.sampler.fraction=0.001 # htrace.span.receiver.classes=org.apache.htrace.core.LocalFileSpanReceiver # htrace.local.file.span.receiver.path=/some/path/to/local/file # # To capture all spans, use the AlwaysSampler # # htrace.sampler.classes=AlwaysSampler # # To send spans to an HTraced receiver, use the below and ensure # your classpath contains the htrace-htraced jar (i.e. when invoking the ycsb # command add -cp /path/to/htrace-htraced.jar) # # htrace.span.receiver.classes=org.apache.htrace.impl.HTracedSpanReceiver # htrace.htraced.receiver.address=example.com:9075 # htrace.htraced.error.log.period.ms=10000