/* 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.List; /** * Record statistics for each statement in the stored procedure. */ public final class StatementStats { /** * The name of the statement. * If it's for the statistics of the whole procedure, the name will be <ALL>. */ String m_stmtName; StatsData m_coordinatorTask = null; StatsData m_workerTask; public StatementStats(String stmtName, boolean hasCoordinatorTask) { m_stmtName = stmtName; m_workerTask = new StatsData(); if (hasCoordinatorTask) { m_coordinatorTask = new StatsData(); } } // Maybe the worker task got executed and timed multiple times, but all failed. // The coordinator task never got executed. // So we do not only need to check if m_coordinatorTask != null, but also need to check // if any coordinator task is executed at all. private boolean isCoordinatorStatsUsable(boolean incremental) { if (m_coordinatorTask == null) { return false; } if (incremental) { return m_coordinatorTask.m_timedInvocations - m_coordinatorTask.m_lastTimedInvocations > 0; } return m_coordinatorTask.m_timedInvocations > 0; } // Below is a bunch of access functions to help merging the numbers from the coordinator task and the worker task. // =============================================================================================================== // m_coordinatorTask may have fewer m_invocations than m_workerTask because m_workerTask // failures can prevent m_coordinatorTask from further execution. // So m_workerTask.m_invocations is accurate for the m_invocations number of the whole statement. // Same for the m_lastInvocations. // However, this does not mean the invocation counts for the coordinator task is useless. // We need to use them when calculating the min/max times and sizes. See below. public long getInvocations() { return m_workerTask.m_invocations; } public long getLastInvocationsAndReset() { long retval = m_workerTask.m_lastInvocations; m_workerTask.m_lastInvocations = m_workerTask.m_invocations; if (m_coordinatorTask != null) { m_coordinatorTask.m_lastInvocations = m_coordinatorTask.m_invocations; } return retval; } public long getTimedInvocations() { return m_workerTask.m_timedInvocations; } public long getLastTimedInvocations() { return m_workerTask.m_lastTimedInvocations; } public long getLastTimedInvocationsAndReset() { long retval = m_workerTask.m_lastTimedInvocations; m_workerTask.m_lastTimedInvocations = m_workerTask.m_timedInvocations; if (m_coordinatorTask != null) { m_coordinatorTask.m_lastTimedInvocations = m_coordinatorTask.m_timedInvocations; } return retval; } public long getTotalTimedExecutionTime() { return m_workerTask.m_totalTimedExecutionTime + (m_coordinatorTask == null ? 0 : m_coordinatorTask.m_totalTimedExecutionTime); } public long getLastTotalTimedExecutionTimeAndReset() { long retval = m_workerTask.m_lastTotalTimedExecutionTime; m_workerTask.m_lastTotalTimedExecutionTime = m_workerTask.m_totalTimedExecutionTime; if (m_coordinatorTask != null) { retval += m_coordinatorTask.m_lastTotalTimedExecutionTime; m_coordinatorTask.m_lastTotalTimedExecutionTime = m_coordinatorTask.m_totalTimedExecutionTime; } return retval; } // Notice that does min(worker + coord) == min(worker) + min(coord)? // The answer is NO. This is an approximation. public long getMinExecutionTime() { if (isCoordinatorStatsUsable(false)) { return m_workerTask.m_minExecutionTime + m_coordinatorTask.m_minExecutionTime; } return m_workerTask.m_minExecutionTime; } public long getIncrementalMinExecutionTimeAndReset() { long retval = m_workerTask.m_incrMinExecutionTime; if (isCoordinatorStatsUsable(true)) { retval += m_coordinatorTask.m_incrMinExecutionTime; m_coordinatorTask.m_incrMinExecutionTime = Long.MAX_VALUE; } m_workerTask.m_incrMinExecutionTime = Long.MAX_VALUE; return retval; } public long getMaxExecutionTime() { if (isCoordinatorStatsUsable(false)) { return m_workerTask.m_maxExecutionTime + m_coordinatorTask.m_maxExecutionTime; } return m_workerTask.m_maxExecutionTime; } public long getIncrementalMaxExecutionTimeAndReset() { long retval = m_workerTask.m_incrMaxExecutionTime; if (isCoordinatorStatsUsable(true)) { retval += m_coordinatorTask.m_incrMaxExecutionTime; m_coordinatorTask.m_incrMaxExecutionTime = Long.MIN_VALUE; } m_workerTask.m_incrMaxExecutionTime = Long.MIN_VALUE; return retval; } public long getAbortCount() { return m_workerTask.m_abortCount; } public long getLastAbortCountAndReset() { // Only the whole procedure can abort and the procedure stats does not have a coordinator task. long retval = m_workerTask.m_lastAbortCount; m_workerTask.m_lastAbortCount = m_workerTask.m_abortCount; return retval; } public long getFailureCount() { return m_workerTask.m_failureCount + (m_coordinatorTask == null ? 0 : m_coordinatorTask.m_failureCount); } public long getLastFailureCountAndReset() { long retval = m_workerTask.m_lastFailureCount; m_workerTask.m_lastFailureCount = m_workerTask.m_failureCount; if (m_coordinatorTask != null) { retval += m_coordinatorTask.m_lastFailureCount; m_coordinatorTask.m_lastFailureCount = m_coordinatorTask.m_failureCount; } return retval; } public int getMinResultSize() { return m_workerTask.m_minResultSize; } // The result size should be taken from the final output, coming from the coordinator task. public int getIncrementalMinResultSizeAndReset() { int retval = m_workerTask.m_incrMinResultSize; m_workerTask.m_incrMinResultSize = Integer.MAX_VALUE; if (isCoordinatorStatsUsable(true)) { m_coordinatorTask.m_incrMinResultSize = Integer.MAX_VALUE; } return retval; } public int getMaxResultSize() { return m_workerTask.m_maxResultSize; } public int getIncrementalMaxResultSizeAndReset() { int retval = m_workerTask.m_incrMaxResultSize; m_workerTask.m_incrMaxResultSize = Integer.MIN_VALUE; if (isCoordinatorStatsUsable(true)) { m_coordinatorTask.m_incrMaxResultSize = Integer.MIN_VALUE; } return retval; } public long getTotalResultSize() { return m_workerTask.m_totalResultSize; } public long getLastTotalResultSizeAndReset() { long retval = m_workerTask.m_lastTotalResultSize; m_workerTask.m_lastTotalResultSize = m_workerTask.m_totalResultSize; if (isCoordinatorStatsUsable(true)) { m_coordinatorTask.m_lastTotalResultSize = m_coordinatorTask.m_totalResultSize; } return retval; } public int getMinParameterSetSize() { return m_workerTask.m_minParameterSetSize; } public int getIncrementalMinParameterSetSizeAndReset() { int retval = m_workerTask.m_incrMinParameterSetSize; m_workerTask.m_incrMinResultSize = Integer.MAX_VALUE; if (m_coordinatorTask != null) { m_coordinatorTask.m_incrMinResultSize = Integer.MAX_VALUE; } return retval; } public int getMaxParameterSetSize() { return m_workerTask.m_maxParameterSetSize; } public int getIncrementalMaxParameterSetSizeAndReset() { int retval = m_workerTask.m_incrMaxParameterSetSize; m_workerTask.m_incrMaxResultSize = Integer.MIN_VALUE; if (m_coordinatorTask != null) { m_coordinatorTask.m_incrMaxResultSize = Integer.MIN_VALUE; } return retval; } public long getTotalParameterSetSize() { return m_workerTask.m_totalParameterSetSize; } public long getLastTotalParameterSetSizeAndReset() { long retval = m_workerTask.m_lastTotalParameterSetSize; m_workerTask.m_lastTotalParameterSetSize = m_workerTask.m_totalParameterSetSize; if (m_coordinatorTask != null) { m_coordinatorTask.m_lastTotalParameterSetSize = m_coordinatorTask.m_totalParameterSetSize; } return retval; } /** * This is a token the ProcedureRunner holds onto while it's running. * It collects stats information during the procedure run without needing * to touch the actual stats source. * When the procedure is done (commit/abort/whatever), this token is given * to the ProcedureStatsCollector in a single (thread-safe) call. * */ public static final class SingleCallStatsToken { class PerStmtStats { final String stmtName; final boolean isCoordinatorTask; final boolean stmtFailed; final MeasuredStmtStats measurements; PerStmtStats(String stmtName, boolean isCoordinatorTask, boolean failed, MeasuredStmtStats measurements) { this.stmtName = stmtName; this.isCoordinatorTask = isCoordinatorTask; this.stmtFailed = failed; this.measurements = measurements; } } /** * Per-statment stats */ class MeasuredStmtStats { final long stmtDuration; final int stmtResultSize; final int stmtParameterSetSize; MeasuredStmtStats(long duration, int resultSize, int paramSetSize) { this.stmtDuration = duration; this.stmtResultSize = resultSize; this.stmtParameterSetSize = paramSetSize; } } final long startTimeNanos; final boolean samplingStatements; // stays null until used List<PerStmtStats> stmtStats = null; int parameterSetSize = 0; int resultSize = 0; public SingleCallStatsToken(long startTimeNanos, boolean samplingStatements) { this.startTimeNanos = startTimeNanos; this.samplingStatements = samplingStatements; } public boolean samplingProcedure() { return startTimeNanos != 0; } public boolean samplingStmts() { return samplingStatements; } public void setParameterSize(int size) { parameterSetSize = size; } public void setResultSize(VoltTable[] results) { resultSize = 0; if (results != null) { for (VoltTable result : results ) { resultSize += result.getSerializedSize(); } } } /** * Called when a statement completes. * Adds a record to the parent stats token. */ public void recordStatementStats(String stmtName, boolean isCoordinatorTask, boolean failed, long duration, VoltTable result, ParameterSet parameterSet) { if (stmtStats == null) { stmtStats = new ArrayList<>(); } MeasuredStmtStats measuredStmtStats = null; if (samplingStatements) { int stmtResultSize = 0; if (result != null) { stmtResultSize = result.getSerializedSize(); } int stmtParamSize = 0; if (parameterSet != null) { stmtParamSize += parameterSet.getSerializedSize(); } measuredStmtStats = new MeasuredStmtStats(duration, stmtResultSize, stmtParamSize); } stmtStats.add(new PerStmtStats(stmtName, isCoordinatorTask, failed, measuredStmtStats)); } } static final class StatsData { /** * Number of times this procedure has been invoked. */ long m_invocations = 0; long m_lastInvocations = 0; /** * Number of timed invocations */ long m_timedInvocations = 0; long m_lastTimedInvocations = 0; /** * Total amount of timed execution time */ long m_totalTimedExecutionTime = 0; long m_lastTotalTimedExecutionTime = 0; /** * Shortest amount of time this procedure has executed in */ long m_minExecutionTime = Long.MAX_VALUE; long m_incrMinExecutionTime = Long.MAX_VALUE; /** * Longest amount of time this procedure has executed in */ long m_maxExecutionTime = Long.MIN_VALUE; long m_incrMaxExecutionTime = Long.MIN_VALUE; /** * Count of the number of aborts (user initiated or DB initiated) */ long m_abortCount = 0; long m_lastAbortCount = 0; /** * Count of the number of errors that occurred during procedure execution */ long m_failureCount = 0; long m_lastFailureCount = 0; /** * Smallest result size */ int m_minResultSize = Integer.MAX_VALUE; int m_incrMinResultSize = Integer.MAX_VALUE; /** * Largest result size */ int m_maxResultSize = Integer.MIN_VALUE; int m_incrMaxResultSize = Integer.MIN_VALUE; /** * Total result size for calculating averages */ long m_totalResultSize = 0; long m_lastTotalResultSize = 0; /** * Smallest parameter set size */ int m_minParameterSetSize = Integer.MAX_VALUE; int m_incrMinParameterSetSize = Integer.MAX_VALUE; /** * Largest parameter set size */ int m_maxParameterSetSize = Integer.MIN_VALUE; int m_incrMaxParameterSetSize = Integer.MIN_VALUE; /** * Total parameter set size for calculating averages */ long m_totalParameterSetSize = 0; long m_lastTotalParameterSetSize = 0; } }