/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.dtxn;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.HdrHistogram_voltpatches.AbstractHistogram;
import org.HdrHistogram_voltpatches.Histogram;
import org.voltdb.ClientInterface;
import org.voltdb.StatsSource;
import org.voltdb.VoltDB;
import org.voltdb.VoltType;
import org.voltdb.types.TimestampType;
import org.voltdb.VoltTable.ColumnInfo;
/** Source of @Statistics LATENCY, which provides key latency metrics from the past 5 seconds of transactions.
* This is intended to be used as part of a manual or automatic monitoring solution,
* where plotting latency fluctuations over time with minimal post-processing is desired.
*
* Each call returns latency percentiles from the most recent complete window,
* along with the timestamp associated with that window.
* Samples are calculated by using a differential histogram,
* subtracting the previous window's histogram from the current one.
* Tables with the same HOST_ID and TIMESTAMP represent the same data.
*
* Statistics are returned with one row for each node.
*
* To get a complete latency curve which includes all data since VoltDB started,
* get the whole histogram via @Statistics LATENCY_COMPRESSED or @Statistics LATENCY_HISTOGRAM (both undocumented).
*/
public class LatencyStats extends StatsSource {
public static final int INTERVAL_MS = Integer.getInteger("LATENCY_STATS_WINDOW_MS", (int) TimeUnit.SECONDS.toMillis(5));
private AtomicReference<AbstractHistogram> m_diffHistProvider = new AtomicReference<AbstractHistogram>();
private ScheduledExecutorService m_updater = Executors.newScheduledThreadPool(1);
private class UpdaterJob implements Runnable {
private AbstractHistogram m_previousHist = LatencyHistogramStats.constructHistogram(false);
@Override
public void run() {
AbstractHistogram currentHist = LatencyHistogramStats.constructHistogram(false);
ClientInterface clientInterface = VoltDB.instance().getClientInterface();
if (clientInterface != null) {
List<AbstractHistogram> statsHists = clientInterface.getLatencyStats();
for (AbstractHistogram hist : statsHists) {
currentHist.add(hist);
}
}
AbstractHistogram diffHist = currentHist.copy();
diffHist.subtract(m_previousHist);
diffHist.setEndTimeStamp(System.currentTimeMillis());
m_previousHist = currentHist;
m_diffHistProvider.set(diffHist);
}
}
/** A dummy iterator that lets getStatsRowKeyIterator() access a single row. */
private static class SingleRowIterator implements Iterator<Object> {
boolean rowProvided = false;
@Override
public boolean hasNext() {
if (!rowProvided) {
rowProvided = true;
return true;
}
return false;
}
@Override
public Object next() {
return null;
}
@Override
public void remove() {
}
}
public LatencyStats() {
super(false);
m_diffHistProvider.set(LatencyHistogramStats.constructHistogram(false));
final int initialDelay = 0;
m_updater.scheduleAtFixedRate(new UpdaterJob(), initialDelay, INTERVAL_MS, TimeUnit.MILLISECONDS);
}
@Override
protected Iterator<Object> getStatsRowKeyIterator(boolean interval) {
return new SingleRowIterator();
}
@Override
protected void populateColumnSchema(ArrayList<ColumnInfo> columns) {
super.populateColumnSchema(columns); // timestamp is milliseconds
columns.add(new ColumnInfo("INTERVAL", VoltType.INTEGER)); // milliseconds
columns.add(new ColumnInfo("COUNT", VoltType.INTEGER)); // samples
columns.add(new ColumnInfo("TPS", VoltType.INTEGER)); // samples per second
columns.add(new ColumnInfo("P50", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("P95", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("P99", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("P99.9", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("P99.99", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("P99.999", VoltType.BIGINT)); // microseconds
columns.add(new ColumnInfo("MAX", VoltType.BIGINT)); // microseconds
}
@Override
protected void updateStatsRow(Object rowKey, Object[] rowValues) {
super.updateStatsRow(rowKey, rowValues);
AbstractHistogram diffHist = m_diffHistProvider.get();
// Override timestamp from the procedure call with the one from when the data was fetched.
rowValues[columnNameToIndex.get("TIMESTAMP")] = diffHist.getEndTimeStamp();
rowValues[columnNameToIndex.get("INTERVAL")] = INTERVAL_MS;
rowValues[columnNameToIndex.get("COUNT")] = diffHist.getTotalCount();
rowValues[columnNameToIndex.get("TPS")] = (int) (TimeUnit.SECONDS.toMillis(diffHist.getTotalCount()) / INTERVAL_MS);
rowValues[columnNameToIndex.get("P50")] = diffHist.getValueAtPercentile(50D);
rowValues[columnNameToIndex.get("P95")] = diffHist.getValueAtPercentile(95D);
rowValues[columnNameToIndex.get("P99")] = diffHist.getValueAtPercentile(99D);
rowValues[columnNameToIndex.get("P99.9")] = diffHist.getValueAtPercentile(99.9D);
rowValues[columnNameToIndex.get("P99.99")] = diffHist.getValueAtPercentile(99.99D);
rowValues[columnNameToIndex.get("P99.999")] = diffHist.getValueAtPercentile(99.999D);
rowValues[columnNameToIndex.get("MAX")] = diffHist.getMaxValue();
}
}