/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package ddlwindowing; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.voltdb.VoltTable; import org.voltdb.client.ClientResponse; import org.voltdb.client.ProcCallException; import ddlwindowing.WindowingApp.PartitionInfo; /** * <p>Runnable-implementor that fetches some per-partition information * from a running VoltDB procedure using VoltDB system procedures. * First, it uses "@GetPartitionKeys" to get a value that will partition * to each partition (used for targeting single-partition procedure call). * Then it uses "@Statistics" with the "TABLE" selector to get the number * of tuples in the 'timedata' table at each partition.</p> * * <p>Every time that it's called, it updates a global data structure. This * structure is primarily used by Reporter to log per-partition statistics.</p> * * <p>This code is pretty adaptable to other applications without much * modification.</p> * */ public class PartitionDataTracker implements Runnable { // Global state final WindowingApp app; // track failures for reporting final AtomicLong failureCount = new AtomicLong(0); public PartitionDataTracker(WindowingApp app) { this.app = app; } @Override public void run() { Map<Long, PartitionInfo> partitionData = new HashMap<Long, PartitionInfo>(); VoltTable partitionKeys = null, tableStats = null; try { tableStats = app.client.callProcedure("@Statistics", "TABLE").getResults()[0]; partitionKeys = app.client.callProcedure("@GetPartitionKeys", "STRING").getResults()[0]; } catch (IOException | ProcCallException e) { // Track failures in a simplistic way. failureCount.incrementAndGet(); // No worries. Will be scheduled again soon. return; } while (tableStats.advanceRow()) { if (!tableStats.getString("TABLE_NAME").equalsIgnoreCase("timedata")) { continue; } PartitionInfo pinfo = new PartitionInfo(); long partitionId = tableStats.getLong("PARTITION_ID"); pinfo.tupleCount = tableStats.getLong("TUPLE_COUNT"); pinfo.partitionKey = null; // If redundancy (k-safety) is enabled, this will put k+1 times per partition, // but the tuple count will be the same so it will be ok. partitionData.put(partitionId, pinfo); } while (partitionKeys.advanceRow()) { long partitionId = partitionKeys.getLong("PARTITION_ID"); PartitionInfo pinfo = partitionData.get(partitionId); if (pinfo == null) { // The set of partitions from the two calls don't match. // Try again next time this is called... Maybe things // will have settled down. return; } pinfo.partitionKey = partitionKeys.getString("PARTITION_KEY"); try { // Find the age of the oldest and youngest tuples in this partition to // demonstrate that we're both accepting new tuples and aging out // old tuples at the appropriate time. ClientResponse cr = app.client.callProcedure("AgeOfOldest", pinfo.partitionKey); pinfo.oldestTupleAge = cr.getResults()[0].asScalarLong(); cr = app.client.callProcedure("AgeOfYoungest", pinfo.partitionKey); pinfo.youngestTupleAge = cr.getResults()[0].asScalarLong(); } catch (IOException | ProcCallException e) { failureCount.incrementAndGet(); return; } } // This is a sanity check to see that every partition has // a partition value boolean allMatched = true; for (PartitionInfo pinfo : partitionData.values()) { // a partition has a count, but no key if (pinfo.partitionKey == null) { allMatched = false; } } if (!allMatched) { // The set of partitions from the two calls don't match. // Try again next time this is called... Maybe things // will have settled down. return; } // atomically update the new map for the old one app.updatePartitionInfo(partitionData); } }