/* 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;
import java.util.ArrayList;
import java.util.Iterator;
import org.voltcore.logging.VoltLogger;
import org.voltcore.utils.CoreUtils;
import org.voltdb.VoltTable.ColumnInfo;
/**
* Collects global cache use stats
*/
public class PlannerStatsCollector extends StatsSource {
private static final VoltLogger log = new VoltLogger("HOST");
/**
* Whether to return results in intervals since polling or since the beginning
*/
private boolean m_interval = false;
/**
* Record timings every N invocations
*/
final long m_collectionFrequency = 20;
/**
* Flag indicating cache disposition of a planned statement.
*/
public enum CacheUse {
/// Plan came from cache #1.
HIT1,
/// Plan came from cache #2.
HIT2,
/// Plan not found in either cache.
MISS,
/// An unexpected failure interrupted cache lookup or planning.
FAIL
}
/**
* Site ID
*/
final long m_siteId;
/**
* Partition ID
*/
long m_partitionId;
/**
* Cache 1 level
*/
long m_cache1Level = 0;
long m_lastCache1Level = 0;
/**
* Cache 2 level
*/
long m_cache2Level = 0;
long m_lastCache2Level = 0;
/**
* Cache 1 hits
*/
long m_cache1Hits = 0;
long m_lastCache1Hits = 0;
/**
* Cache 2 hits
*/
long m_cache2Hits = 0;
long m_lastCache2Hits = 0;
/**
* Cache misses
*/
long m_cacheMisses = 0;
long m_lastCacheMisses = 0;
/**
* Time of last planning start
*/
Long m_currentStartTime = null;
/**
* Total amount of planning time
*/
long m_totalPlanningTime = 0;
long m_lastTimedPlanningTime = 0;
/**
* Shortest amount of time used for planning
*/
long m_minPlanningTime = Long.MAX_VALUE;
long m_lastMinPlanningTime = Long.MAX_VALUE;
/**
* Longest amount of time used for planning
*/
long m_maxPlanningTime = Long.MIN_VALUE;
long m_lastMaxPlanningTime = Long.MIN_VALUE;
/**
* Count of the number of errors that occured during procedure execution
*/
long m_failures = 0;
long m_lastFailures = 0;
/**
* Count of the number of invocations = m_cache1Hits + m_cache2Hits + m_cacheMisses + m_failures;
*/
long m_invocations = 0;
long m_lastInvocations = 0;
/**
* Calculate the invocation count based on the cache hit/miss counts.
* @return invocation count
*/
long getInvocations() {
return m_invocations;
}
/**
* Calculate the last invocation count based on the last cache hit/miss counts.
* @return last invocation count
*/
long getLastInvocations() {
return m_lastInvocations;
}
/**
* Calculate the sample count based on the invocation count and the collection frequency.
* @return sample count
*/
long getSampleCount() {
return getInvocations() / m_collectionFrequency;
}
/**
* Constructor
*
* @param siteId site id
*/
public PlannerStatsCollector(long siteId) {
super(false);
m_siteId = siteId;
}
/**
* Used to update EE cache stats without changing tracked time
*/
public void updateEECacheStats(long eeCacheSize, long hits, long misses, int partitionId) {
m_cache1Level = eeCacheSize;
m_cache1Hits += hits;
m_cacheMisses += misses;
m_invocations += hits + misses;
m_partitionId = partitionId;
}
/**
* Called before doing planning. Starts timer.
*/
public void startStatsCollection() {
if (getInvocations() % m_collectionFrequency == 0) {
m_currentStartTime = System.nanoTime();
}
}
/**
* Called after planning or failing to plan. Records timer and cache stats.
*
* @param cache1Size number of entries in level 1 cache
* @param cache2Size number of entries in level 2 cache
* @param cacheUse where the planned statement came from
* @param partitionId partition id
*/
public void endStatsCollection(long cache1Size, long cache2Size, CacheUse cacheUse, long partitionId) {
if (m_currentStartTime != null) {
long delta = System.nanoTime() - m_currentStartTime;
if (delta < 0) {
if (Math.abs(delta) > 1000000000) {
log.info("Planner statistics recorded a negative planning time larger than one second: " +
delta);
}
}
else {
m_totalPlanningTime += delta;
m_minPlanningTime = Math.min(delta, m_minPlanningTime);
m_maxPlanningTime = Math.max(delta, m_maxPlanningTime);
m_lastMinPlanningTime = Math.min(delta, m_lastMinPlanningTime);
m_lastMaxPlanningTime = Math.max(delta, m_lastMaxPlanningTime);
}
m_currentStartTime = null;
}
m_cache1Level = cache1Size;
m_cache2Level = cache2Size;
switch(cacheUse) {
case HIT1:
m_cache1Hits++;
break;
case HIT2:
m_cache2Hits++;
break;
case MISS:
m_cacheMisses++;
break;
case FAIL:
m_failures++;
break;
}
m_invocations++;
m_partitionId = partitionId;
}
/**
* Update the rowValues array with the latest statistical information.
* This method is overrides the super class version
* which must also be called so that it can update its columns.
* @param values Values of each column of the row of stats. Used as output.
*/
@Override
protected void updateStatsRow(Object rowKey, Object rowValues[]) {
super.updateStatsRow(rowKey, rowValues);
rowValues[columnNameToIndex.get("PARTITION_ID")] = m_partitionId;
long totalTimedExecutionTime = m_totalPlanningTime;
long minExecutionTime = m_minPlanningTime;
long maxExecutionTime = m_maxPlanningTime;
long cache1Level = m_cache1Level;
long cache2Level = m_cache2Level;
long cache1Hits = m_cache1Hits;
long cache2Hits = m_cache2Hits;
long cacheMisses = m_cacheMisses;
long failureCount = m_failures;
if (m_interval) {
totalTimedExecutionTime = m_totalPlanningTime - m_lastTimedPlanningTime;
m_lastTimedPlanningTime = m_totalPlanningTime;
minExecutionTime = m_lastMinPlanningTime;
maxExecutionTime = m_lastMaxPlanningTime;
m_lastMinPlanningTime = Long.MAX_VALUE;
m_lastMaxPlanningTime = Long.MIN_VALUE;
cache1Level = m_cache1Level - m_lastCache1Level;
m_lastCache1Level = m_cache1Level;
cache2Level = m_cache2Level - m_lastCache2Level;
m_lastCache2Level = m_cache2Level;
cache1Hits = m_cache1Hits - m_lastCache1Hits;
m_lastCache1Hits = m_cache1Hits;
cache2Hits = m_cache2Hits - m_lastCache2Hits;
m_lastCache2Hits = m_cache2Hits;
cacheMisses = m_cacheMisses - m_lastCacheMisses;
m_lastCacheMisses = m_cacheMisses;
failureCount = m_failures - m_lastFailures;
m_lastFailures = m_failures;
m_lastInvocations = m_invocations;
}
rowValues[columnNameToIndex.get(VoltSystemProcedure.CNAME_SITE_ID)] = CoreUtils.getSiteIdFromHSId(m_siteId);
rowValues[columnNameToIndex.get("PARTITION_ID")] = m_partitionId;
rowValues[columnNameToIndex.get("CACHE1_LEVEL")] = cache1Level;
rowValues[columnNameToIndex.get("CACHE2_LEVEL")] = cache2Level;
rowValues[columnNameToIndex.get("CACHE1_HITS" )] = cache1Hits;
rowValues[columnNameToIndex.get("CACHE2_HITS" )] = cache2Hits;
rowValues[columnNameToIndex.get("CACHE_MISSES")] = cacheMisses;
rowValues[columnNameToIndex.get("PLAN_TIME_MIN")] = minExecutionTime;
rowValues[columnNameToIndex.get("PLAN_TIME_MAX")] = maxExecutionTime;
if (getSampleCount() != 0) {
rowValues[columnNameToIndex.get("PLAN_TIME_AVG")] =
(totalTimedExecutionTime / getSampleCount());
} else {
rowValues[columnNameToIndex.get("PLAN_TIME_AVG")] = 0L;
}
rowValues[columnNameToIndex.get("FAILURES")] = failureCount;
}
/**
* Specifies the columns of statistics that are added by this class to the schema of a statistical results.
* @param columns List of columns that are in a stats row.
*/
@Override
protected void populateColumnSchema(ArrayList<VoltTable.ColumnInfo> columns) {
super.populateColumnSchema(columns);
columns.add(new ColumnInfo(VoltSystemProcedure.CNAME_SITE_ID, VoltSystemProcedure.CTYPE_ID));
columns.add(new ColumnInfo("PARTITION_ID", VoltType.INTEGER));
columns.add(new ColumnInfo("CACHE1_LEVEL", VoltType.INTEGER));
columns.add(new ColumnInfo("CACHE2_LEVEL", VoltType.INTEGER));
columns.add(new ColumnInfo("CACHE1_HITS", VoltType.BIGINT));
columns.add(new ColumnInfo("CACHE2_HITS", VoltType.BIGINT));
columns.add(new ColumnInfo("CACHE_MISSES", VoltType.BIGINT));
columns.add(new ColumnInfo("PLAN_TIME_MIN", VoltType.BIGINT));
columns.add(new ColumnInfo("PLAN_TIME_MAX", VoltType.BIGINT));
columns.add(new ColumnInfo("PLAN_TIME_AVG", VoltType.BIGINT));
columns.add(new ColumnInfo("FAILURES", VoltType.BIGINT));
}
@Override
protected Iterator<Object> getStatsRowKeyIterator(boolean interval) {
m_interval = interval;
return new Iterator<Object>() {
boolean givenNext = false;
@Override
public boolean hasNext() {
if (!isInterval()) {
if (getInvocations() == 0) {
return false;
}
}
else if (getInvocations() - getLastInvocations() == 0) {
return false;
}
return !givenNext;
}
@Override
public Object next() {
if (!givenNext) {
givenNext = true;
return new Object();
}
return null;
}
@Override
public void remove() {}
};
}
/**
* @return the m_interval
*/
public boolean isInterval() {
return m_interval;
}
/**
* @param m_interval the m_interval to set
*/
public void setInterval(boolean m_interval) {
this.m_interval = m_interval;
}
}