/* 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.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; import org.voltdb.ClientInterface; import org.voltdb.SiteStatsSource; import org.voltdb.StatsSelector; import org.voltdb.VoltDB; import org.voltdb.VoltTable.ColumnInfo; import org.voltdb.VoltType; import org.voltdb.client.ClientResponse; /** * Class that provides storage for statistical information generated by an Initiator */ // NOTE: Initiator stats aren't per-site (which is the point of the SiteStatsSource) // but changing this now affects a public API, so, we'll just fill it in with the host ID, // print the HOST ID twice in the result table, and move on. public class InitiatorStats extends SiteStatsSource { /** * * @param name * @param siteId */ public InitiatorStats(long hostId) { super(hostId, false); VoltDB.instance().getStatsAgent().registerStatsSource(StatsSelector.INITIATOR, 0, this); } public static class InvocationInfo { /** * Hostname of the host this connection is with */ private final String connectionHostname; /** * Number of time procedure has been invoked */ private long invocationCount = 0; private long lastInvocationCount = 0; /** * Shortest amount of time this procedure has executed in */ private int minExecutionTime = Integer.MAX_VALUE; private int lastMinExecutionTime = Integer.MAX_VALUE; /** * Longest amount of time this procedure has executed in */ private int maxExecutionTime = Integer.MIN_VALUE; private int lastMaxExecutionTime = Integer.MIN_VALUE; /** * Total amount of time spent executing procedure */ private long totalExecutionTime = 0; private long lastTotalExecutionTime = 0; private long abortCount = 0; private long lastAbortCount = 0; private long failureCount = 0; private long lastFailureCount = 0; public InvocationInfo (String hostname) { connectionHostname = hostname; } public void processInvocation(int delta, byte status) { totalExecutionTime += delta; minExecutionTime = Math.min( delta, minExecutionTime); maxExecutionTime = Math.max( delta, maxExecutionTime); lastMinExecutionTime = Math.min( delta, lastMinExecutionTime); lastMaxExecutionTime = Math.max( delta, lastMaxExecutionTime); invocationCount++; if (status != ClientResponse.SUCCESS) { if (status == ClientResponse.GRACEFUL_FAILURE || status == ClientResponse.USER_ABORT) { abortCount++; } else { failureCount++; } } } } @Override protected void populateColumnSchema(ArrayList<ColumnInfo> columns) { super.populateColumnSchema(columns); columns.add(new ColumnInfo("CONNECTION_ID", VoltType.BIGINT)); columns.add(new ColumnInfo("CONNECTION_HOSTNAME", VoltType.STRING)); columns.add(new ColumnInfo("PROCEDURE_NAME", VoltType.STRING)); columns.add(new ColumnInfo("INVOCATIONS", VoltType.BIGINT)); columns.add(new ColumnInfo("AVG_EXECUTION_TIME", VoltType.INTEGER)); columns.add(new ColumnInfo("MIN_EXECUTION_TIME", VoltType.INTEGER)); columns.add(new ColumnInfo("MAX_EXECUTION_TIME", VoltType.INTEGER)); columns.add(new ColumnInfo("ABORTS", VoltType.BIGINT)); columns.add(new ColumnInfo("FAILURES", VoltType.BIGINT)); } @Override protected void updateStatsRow(final Object rowKey, Object rowValues[]) { DummyIterator iterator = (DummyIterator)rowKey; Map.Entry<String, InvocationInfo> entry = iterator.innerNext; iterator.innerNext = null; final InvocationInfo info = entry.getValue(); final String procName = entry.getKey(); final Long connectionId = iterator.outerNext.getKey(); long invocationCount = info.invocationCount; long totalExecutionTime = info.totalExecutionTime; int minExecutionTime = info.minExecutionTime; int maxExecutionTime = info.maxExecutionTime; long abortCount = info.abortCount; long failureCount = info.failureCount; if (iterator.interval) { invocationCount = info.invocationCount - info.lastInvocationCount; info.lastInvocationCount = info.invocationCount; totalExecutionTime = info.totalExecutionTime - info.lastTotalExecutionTime; info.lastTotalExecutionTime = info.totalExecutionTime; minExecutionTime = info.lastMinExecutionTime; maxExecutionTime = info.lastMaxExecutionTime; info.lastMinExecutionTime = Integer.MAX_VALUE; info.lastMaxExecutionTime = Integer.MIN_VALUE; abortCount = info.abortCount - info.lastAbortCount; info.lastAbortCount = info.abortCount; failureCount = info.failureCount - info.lastFailureCount; info.lastFailureCount = info.failureCount; } rowValues[columnNameToIndex.get("CONNECTION_ID")] = connectionId; rowValues[columnNameToIndex.get("CONNECTION_HOSTNAME")] = info.connectionHostname; rowValues[columnNameToIndex.get("PROCEDURE_NAME")] = procName; rowValues[columnNameToIndex.get("INVOCATIONS")] = invocationCount; rowValues[columnNameToIndex.get("AVG_EXECUTION_TIME")] = (int)(totalExecutionTime / invocationCount); rowValues[columnNameToIndex.get("MIN_EXECUTION_TIME")] = minExecutionTime; rowValues[columnNameToIndex.get("MAX_EXECUTION_TIME")] = maxExecutionTime; rowValues[columnNameToIndex.get("ABORTS")] = abortCount; rowValues[columnNameToIndex.get("FAILURES")] = failureCount; super.updateStatsRow(rowKey, rowValues); } private class DummyIterator implements Iterator<Object> { private final Iterator<Map.Entry<Long, Map<String, InvocationInfo>>> outerItr; private Iterator<Map.Entry<String, InvocationInfo>> innerItr = null; private Map.Entry<Long, Map<String, InvocationInfo>> outerNext = null; private Map.Entry<String, InvocationInfo> innerNext = null; private final boolean interval; private DummyIterator(Iterator<Map.Entry<Long, Map<String, InvocationInfo>>> i, boolean interval) { this.outerItr = i; this.interval = interval; } private boolean advanceOuter() { if(outerItr.hasNext()) { outerNext = outerItr.next(); // reset innerItr innerItr = outerNext.getValue().entrySet().iterator(); return true; } outerNext = null; return false; } private boolean advanceInner() { if(innerItr.hasNext()) { innerNext = innerItr.next(); return true; } innerNext = null; return false; } @Override public boolean hasNext() { if (!interval) { if(innerItr == null) { if(advanceOuter()) { return advanceInner(); } return false; } else { if(advanceInner()) { return true; } if(advanceOuter()) { return advanceInner(); } return false; } } if (innerItr == null) { advanceOuter(); } // innerItr can be null if connection created but not doing any procedures if (innerItr == null || !outerItr.hasNext() && !innerItr.hasNext()) { return false; } else { while (innerNext == null && (outerItr.hasNext() || innerItr.hasNext())) { InvocationInfo info = null; // first, look up at lower level map advanceInner(); // not found, advance upper level map itr, and look up in next lower level map if(innerNext == null) { advanceOuter(); advanceInner(); } info = innerNext.getValue(); if(info.invocationCount - info.lastInvocationCount == 0) { innerNext = null; continue; } } if(innerNext == null) { return false; } } return true; } @Override public Object next() { return this; } @Override public void remove() { throw new UnsupportedOperationException(); } } private class AggregatingIterator implements Iterator<Map.Entry<Long, Map<String, InvocationInfo>>> { private final Queue<Iterator<Map.Entry<Long, Map<String, InvocationInfo>>>> m_sources; private AggregatingIterator(Queue<Iterator<Map.Entry<Long, Map<String, InvocationInfo>>>> sources) { m_sources = sources; } @Override public boolean hasNext() { Iterator<Map.Entry<Long, Map<String, InvocationInfo>>> i = null; while ((i = m_sources.peek()) != null) { if (i.hasNext()) return true; m_sources.remove(); } return false; } @Override public Map.Entry<Long, Map<String, InvocationInfo>> next() { final Iterator<Map.Entry<Long, Map<String, InvocationInfo>>> i = m_sources.peek(); if (i == null || !i.hasNext()) { throw new NoSuchElementException(); } return i.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } @Override protected Iterator<Object> getStatsRowKeyIterator(boolean interval) { ArrayDeque<Iterator<Map.Entry<Long, Map<String, InvocationInfo>>>> d = new ArrayDeque<Iterator<Map.Entry<Long, Map<String, InvocationInfo>>>>(); ClientInterface ci = VoltDB.instance().getClientInterface(); if (ci != null) { d.addAll(ci.getIV2InitiatorStats()); } return new DummyIterator( new AggregatingIterator(d), interval); } }