/* 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.client; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; /** * <p>An object to store and manipulate statistics information from * the VoltDB Java client. Each instance has a set of timestamped * baseline statistics and a set of timestamped current statistics. * Given these two sets of data, this object can return statistics * covering the period between the baseline data and current data.</p> * * <p>An instance is created using {@link Client#createStatsContext()}. * Mutliple instances can coexist, each covering a different time * period. See the Voter example in /examples for an example of using * one context for long term stats and another for short term updates.</p> */ public class ClientStatsContext { final Distributer m_distributor; Map<Long, Map<String, ClientStats>> m_baseline; Map<Long, Map<String, ClientStats>> m_current; Map<Long, ClientIOStats> m_baselineIO; Map<Long, ClientIOStats> m_currentIO; Map<Integer, ClientAffinityStats> m_baselineAffinity; Map<Integer, ClientAffinityStats> m_currentAffinity; long m_baselineTS; long m_currentTS; ClientStatsContext(Distributer distributor, Map<Long, Map<String, ClientStats>> current, Map<Long, ClientIOStats> currentIO, Map<Integer, ClientAffinityStats> currentAffinity) { m_distributor = distributor; m_baseline = new TreeMap<Long, Map<String, ClientStats>>(); m_baselineIO = new TreeMap<Long, ClientIOStats>(); m_baselineAffinity = new HashMap<Integer, ClientAffinityStats>(); m_current = current; m_currentIO = currentIO; m_currentAffinity = currentAffinity; m_baselineTS = m_currentTS = System.currentTimeMillis(); } /** * Fetch current statistics from the client internals. Don't * update the baseline. This will increase the range covered * by any {@link ClientStats} instances returned from this * context. * * @return A <code>this</code> pointer for chaining calls. */ public ClientStatsContext fetch() { m_current = m_distributor.getStatsSnapshot(); m_currentIO = m_distributor.getIOStatsSnapshot(); m_currentTS = System.currentTimeMillis(); m_currentAffinity = m_distributor.getAffinityStatsSnapshot(); return this; } /** * Fetch current statistics from the client internals and set them to be the current baseline. * Subsequent calls to <code>getStats(..)</code> methods on this instance will return 0 values for * all statistics until either <code>fetch()</code> or <code>fetchAndResetBaseline()</code> are called. * * @return A new ClientStatsContext object that uses the newly fetched stats with the old baseline. */ public ClientStatsContext fetchAndResetBaseline() { fetch(); ClientStatsContext retval = new ClientStatsContext(m_distributor, m_current, m_currentIO, m_currentAffinity); retval.m_baseline = m_baseline; retval.m_baselineIO = m_baselineIO; retval.m_baselineTS = m_baselineTS; retval.m_baselineAffinity = m_baselineAffinity; retval.m_currentTS = m_currentTS; m_baseline = m_current; m_baselineIO = m_currentIO; m_baselineTS = m_currentTS; m_baselineAffinity = m_currentAffinity; return retval; } /** * Return a {@link ClientStats} that covers all procedures and * all connection ids. The {@link ClientStats} instance will * apply to the time period currently covered by the context. * * @return A {@link ClientStats} instance. */ public ClientStats getStats() { return ClientStats.merge(getStatsByConnection().values()); } /** * Return a map of {@link ClientStats} by procedure name. This will * roll up {@link ClientStats} instances by connection id. Each * {@link ClientStats} instance will apply to the time period * currently covered by the context. * * @return A map from procedure name to {@link ClientStats} instances. */ public Map<String, ClientStats> getStatsByProc() { Map<Long, Map<String, ClientStats>> complete = getCompleteStats(); Map<String, ClientStats> retval = new TreeMap<String, ClientStats>(); for (Entry<Long, Map<String, ClientStats>> e : complete.entrySet()) { for (Entry<String, ClientStats> e2 : e.getValue().entrySet()) { ClientStats current = e2.getValue(); ClientStats aggregate = retval.get(current.getProcedureName()); if (aggregate == null) { retval.put(current.getProcedureName(), (ClientStats) current.clone()); } else { aggregate.add(current); } } } return retval; } /** * Return a map of {@link ClientStats} by connection id. This will * roll up {@link ClientStats} instances by procedure name for each * connection. Note that connection id is unique, while hostname and * port may not be. Hostname and port will be included in the * {@link ClientStats} instance data. Each {@link ClientStats} * instance will apply to the time period currently covered by the * context. * * @return A map from connection id to {@link ClientStats} instances. */ public Map<Long, ClientStats> getStatsByConnection() { Map<Long, Map<String, ClientStats>> complete = getCompleteStats(); Map<Long, ClientIOStats> completeIO = diffIO(m_currentIO, m_baselineIO); Map<Long, ClientStats> retval = new TreeMap<Long, ClientStats>(); for (Entry<Long, Map<String, ClientStats>> e : complete.entrySet()) { ClientStats cs = ClientStats.merge(e.getValue().values()); ClientIOStats cios = completeIO.get(e.getKey()); if (cios != null) { cs.m_bytesReceived = cios.m_bytesReceived; cs.m_bytesSent = cios.m_bytesSent; } retval.put(e.getKey(), cs); } return retval; } /** * Return a map of maps by connection id. Each sub-map maps procedure * names to {@link ClientStats} instances. Note that connection id is * unique, while hostname and port may not be. Hostname and port will * be included in the {@link ClientStats} instance data. Each * {@link ClientStats} instance will apply to the time period currently * covered by the context. This is full set of data available from this * context instance. * * @return A map from connection id to {@link ClientStats} instances. */ public Map<Long, Map<String, ClientStats>> getCompleteStats() { Map<Long, Map<String, ClientStats>> retval = new TreeMap<Long, Map<String, ClientStats>>(); for (Entry<Long, Map<String, ClientStats>> e : m_current.entrySet()) { if (m_baseline.containsKey(e.getKey())) { retval.put(e.getKey(), diff(e.getValue(), m_baseline.get(e.getKey()))); } else { retval.put(e.getKey(), dup(e.getValue())); } } // reset the timestamp fields to reflect the difference for (Entry<Long, Map<String, ClientStats>> e : retval.entrySet()) { for (Entry<String, ClientStats> e2 : e.getValue().entrySet()) { ClientStats cs = e2.getValue(); cs.m_startTS = m_baselineTS; cs.m_endTS = m_currentTS; assert(cs.m_startTS != Long.MAX_VALUE); assert(cs.m_endTS != Long.MIN_VALUE); } } return retval; } /** * Get the client affinity stats. Will only be populated if client affinity is enabled. * * @return A map from an internal partition id to a {@link ClientAffinityStats} instance. */ public Map<Integer, ClientAffinityStats> getAffinityStats() { Map<Integer, ClientAffinityStats> retval = new TreeMap<Integer, ClientAffinityStats>(); for (Entry<Integer, ClientAffinityStats> e : m_currentAffinity.entrySet()) { if (m_baselineAffinity.containsKey(e.getKey())) { retval.put(e.getKey(), ClientAffinityStats.diff(e.getValue(), m_baselineAffinity.get(e.getKey()))); } else { retval.put(e.getKey(), (ClientAffinityStats) e.getValue().clone()); } } return retval; } /** * Roll up the per-partition affinity stats and return the totals for each of the four * categories. Will only be populated if client affinity is enabled. * * @return A {@link ClientAffinityStats} instance covering all partitions. */ public ClientAffinityStats getAggregateAffinityStats() { long afWrites = 0; long afReads = 0; long rrWrites = 0; long rrReads = 0; Map<Integer, ClientAffinityStats> affinityStats = getAffinityStats(); for (Entry<Integer, ClientAffinityStats> e : affinityStats.entrySet()) { afWrites += e.getValue().getAffinityWrites(); afReads += e.getValue().getAffinityReads(); rrWrites += e.getValue().getRrWrites(); rrReads += e.getValue().getRrReads(); } ClientAffinityStats retval = new ClientAffinityStats(Integer.MAX_VALUE, afWrites, rrWrites, afReads, rrReads); return retval; } /** * Return a {@link ClientStats} instance for a specific procedure * name. This will be rolled up across all connections. The * {@link ClientStats} instance will apply to the time period * currently covered by the context. * * @param procedureName Name of the procedure. * @return A {@link ClientStats} instance. */ public ClientStats getStatsForProcedure(String procedureName) { Map<Long, Map<String, ClientStats>> complete = getCompleteStats(); List<ClientStats> statsForProc = new ArrayList<ClientStats>(); for (Entry<Long, Map<String, ClientStats>> e : complete.entrySet()) { ClientStats procStats = e.getValue().get(procedureName); if (procStats != null) { statsForProc.add(procStats); } } if (statsForProc.size() == 0) { return null; } return ClientStats.merge(statsForProc); } Map<Long, ClientIOStats> diffIO(Map<Long, ClientIOStats> newer, Map<Long, ClientIOStats> older) { Map<Long, ClientIOStats> retval = new TreeMap<Long, ClientIOStats>(); if (newer == null) { return retval; } if (older == null) { return newer; } for (Entry<Long, ClientIOStats> e : newer.entrySet()) { if (older.containsKey(e.getKey())) { retval.put(e.getKey(), ClientIOStats.diff(e.getValue(), older.get(e.getKey()))); } else { retval.put(e.getKey(), (ClientIOStats) e.getValue().clone()); } } return retval; } Map<String, ClientStats> diff(Map<String, ClientStats> newer, Map<String, ClientStats> older) { Map<String, ClientStats> retval = new TreeMap<String, ClientStats>(); for (Entry<String, ClientStats> e : newer.entrySet()) { if (older.containsKey(e.getKey())) { retval.put(e.getKey(), ClientStats.diff(e.getValue(), older.get(e.getKey()))); } else { retval.put(e.getKey(), (ClientStats) e.getValue().clone()); } } return retval; } Map<String, ClientStats> dup(Map<String, ClientStats> x) { Map<String, ClientStats> retval = new TreeMap<String, ClientStats>(); for (Entry<String, ClientStats> e : x.entrySet()) { retval.put(e.getKey(), (ClientStats) e.getValue().clone()); } return retval; } }