/*
* Copyright 2012 LinkedIn, Inc
*
* 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 voldemort.store.stats;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import javax.management.ObjectName;
import org.apache.log4j.Logger;
import voldemort.utils.JmxUtils;
/**
* Some convenient statistics to track about the store
*
*
*/
public class StoreStats {
private final Map<Tracked, RequestCounter> counters;
private static final Logger logger = Logger.getLogger(StoreStats.class.getName());
private String storeName;
// RequestCounter config
private static final boolean useHistogram = true;
public static final long timeWindow = 60000;
public StoreStats(String storeName) {
this(storeName, null);
}
/**
* @param parent An optional parent stats object that will maintain
* aggregate data across many stores
*/
public StoreStats(String storeName, StoreStats parent) {
if (storeName == null) {
throw new IllegalArgumentException("StoreStats.storeName cannot be null !!");
}
this.storeName = storeName;
counters = new EnumMap<Tracked, RequestCounter>(Tracked.class);
for(Tracked tracked: Tracked.values()) {
String requestCounterName = "store-stats" + "." + storeName + "." + tracked.toString();
RequestCounter requestCounter;
if (parent == null) {
requestCounter = new RequestCounter(requestCounterName, timeWindow, useHistogram);
} else {
requestCounterName = parent.storeName + "." + requestCounterName;
requestCounter =
new RequestCounter(requestCounterName, timeWindow, useHistogram, parent.counters.get(tracked));
}
counters.put(tracked, requestCounter);
}
if(logger.isDebugEnabled()) {
logger.debug("Constructed StoreStats object (" + System.identityHashCode(this)
+ ") with parent object (" + System.identityHashCode(parent) + ")");
}
}
/**
* Record the duration of specified op. For PUT, GET and GET_ALL use
* specific methods for those ops.
*/
public void recordTime(Tracked op, long timeNS) {
recordTime(op, timeNS, 0, 0, 0, 0);
}
/**
* Record the duration of specified op. For PUT, GET and GET_ALL use
* specific methods for those ops.
*/
public void recordDeleteTime(long timeNS, long keySize) {
recordTime(Tracked.DELETE, timeNS, 0, 0, keySize, 0);
}
/**
* Record the duration of a put operation, along with the size of the values
* returned.
*/
public void recordPutTimeAndSize(long timeNS, long valueSize, long keySize) {
recordTime(Tracked.PUT, timeNS, 0, valueSize, keySize, 0);
}
/**
* Record the duration of a get operation, along with whether or not an
* empty response (ie no values matched) and the size of the values
* returned.
*/
public void recordGetTime(long timeNS, boolean emptyResponse, long totalBytes, long keyBytes) {
recordTime(Tracked.GET, timeNS, emptyResponse ? 1 : 0, totalBytes, keyBytes, 0);
}
/**
* Record the duration of a get versions operation, along with whether or
* not an empty response (ie no values matched) was returned.
*/
public void recordGetVersionsTime(long timeNS, boolean emptyResponse) {
recordTime(Tracked.GET_VERSIONS, timeNS, emptyResponse ? 1 : 0, 0, 0, 0);
}
/**
* Record the duration of a get_all operation, along with how many values
* were requested, how may were actually returned and the size of the values
* returned.
*/
public void recordGetAllTime(long timeNS,
int requested,
int returned,
long totalValueBytes,
long totalKeyBytes) {
recordTime(Tracked.GET_ALL,
timeNS,
requested - returned,
totalValueBytes,
totalKeyBytes,
requested);
}
/**
* Method to service public recording APIs
*
* @param op Operation being tracked
* @param timeNS Duration of operation
* @param numEmptyResponses Number of empty responses being sent back,
* i.e.: requested keys for which there were no values (GET and GET_ALL only)
* @param valueSize Size in bytes of the value
* @param keySize Size in bytes of the key
* @param getAllAggregateRequests Total of amount of keys requested in the operation (GET_ALL only)
*/
private void recordTime(Tracked op,
long timeNS,
long numEmptyResponses,
long valueSize,
long keySize,
long getAllAggregateRequests) {
counters.get(op).addRequest(timeNS,
numEmptyResponses,
valueSize,
keySize,
getAllAggregateRequests);
if (logger.isTraceEnabled() && !storeName.contains("aggregate") && !storeName.contains("voldsys$"))
logger.trace("Store '" + storeName + "' logged a " + op.toString() + " request taking " +
((double) timeNS / voldemort.utils.Time.NS_PER_MS) + " ms");
}
public long getCount(Tracked op) {
return counters.get(op).getCount();
}
public float getThroughput(Tracked op) {
return counters.get(op).getThroughput();
}
public float getThroughputInBytes(Tracked op) {
return counters.get(op).getThroughputInBytes();
}
public double getAvgTimeInMs(Tracked op) {
return counters.get(op).getAverageTimeInMs();
}
public long getNumEmptyResponses(Tracked op) {
return counters.get(op).getNumEmptyResponses();
}
public long getMaxLatencyInMs(Tracked op) {
return counters.get(op).getMaxLatencyInMs();
}
public double getQ95LatencyInMs(Tracked op) {
return counters.get(op).getQ95LatencyMs();
}
public double getQ99LatencyInMs(Tracked op) {
return counters.get(op).getQ99LatencyMs();
}
public Map<Tracked, RequestCounter> getCounters() {
return Collections.unmodifiableMap(counters);
}
public long getMaxValueSizeInBytes(Tracked op) {
return counters.get(op).getMaxValueSizeInBytes();
}
public long getMaxKeySizeInBytes(Tracked op) {
return counters.get(op).getMaxKeySizeInBytes();
}
public double getAvgValueSizeinBytes(Tracked op) {
return counters.get(op).getAverageValueSizeInBytes();
}
public double getAvgKeySizeinBytes(Tracked op) {
return counters.get(op).getAverageKeySizeInBytes();
}
public double getGetAllAverageCount() {
long total = getGetAllAggregatedCount();
return total == 0 ? 0.0d : total / counters.get(Tracked.GET_ALL).getCount();
}
public long getGetAllAggregatedCount() {
return counters.get(Tracked.GET_ALL).getGetAllAggregatedCount();
}
public long getGetAllMaxCount() {
return counters.get(Tracked.GET_ALL).getGetAllMaxCount();
}
/**
* @return The rate of keys per seconds that were queried through GetAll requests.
*/
public float getGetAllKeysThroughput() { return counters.get(Tracked.GET_ALL).getGetAllKeysThroughput(); }
private volatile boolean isJmxRegistered = false;
private volatile ObjectName jmxObjectName;
public synchronized void registerJmx(String identifierString) {
if(isJmxRegistered == false) {
StoreStatsJmx statsJmx = new StoreStatsJmx(this);
String domain = JmxUtils.getPackageName(this.getClass());
String type = this.storeName + identifierString;
this.jmxObjectName = JmxUtils.createObjectName(domain, type);
JmxUtils.registerMbean(statsJmx, jmxObjectName);
isJmxRegistered = true;
}
}
public synchronized void unregisterJmx() {
if(isJmxRegistered == true && jmxObjectName != null) {
JmxUtils.unregisterMbean(jmxObjectName);
isJmxRegistered = false;
}
}
}