/* 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 org.voltdb; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.LinkedBlockingQueue; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.voltcore.network.*; import org.voltdb.client.ClientResponse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class TestStatsAgent { private MockVoltDB m_mvoltdb; private final LinkedBlockingQueue<ClientResponseImpl> responses = new LinkedBlockingQueue<ClientResponseImpl>(); private final Connection m_mockConnection = new MockConnection() { @Override public WriteStream writeStream() { return new MockWriteStream() { @Override public void enqueue(ByteBuffer buf) { ClientResponseImpl cri = new ClientResponseImpl(); buf.clear(); buf.position(4); try { cri.initFromBuffer(buf); } catch (IOException e) { throw new RuntimeException(e); } responses.offer(cri); } }; } }; @Before public void setUp() throws Exception { m_mvoltdb = new MockVoltDB(); VoltDB.replaceVoltDBInstanceForTest(m_mvoltdb); } @After public void tearDown() throws Exception { MockStatsSource.delay = 0; StatsAgent.OPS_COLLECTION_TIMEOUT = 60 * 1000; m_mvoltdb.shutdown(null); VoltDB.replaceVoltDBInstanceForTest(null); } private void createAndRegisterStats() throws Exception { List<VoltTable.ColumnInfo> partitionColumns = Arrays.asList(new VoltTable.ColumnInfo[] { new VoltTable.ColumnInfo( "p1", VoltType.INTEGER), new VoltTable.ColumnInfo( "p2", VoltType.STRING) }); MockStatsSource.columns = partitionColumns; MockStatsSource partitionSource = new MockStatsSource(new Object[][] { { 42, "42" }, { 43, "43" } }); m_mvoltdb.getStatsAgent().registerStatsSource(StatsSelector.DRPRODUCERPARTITION, 0, partitionSource); List<VoltTable.ColumnInfo> nodeColumns = Arrays.asList(new VoltTable.ColumnInfo[] { new VoltTable.ColumnInfo( "c1", VoltType.STRING), new VoltTable.ColumnInfo( "c2", VoltType.INTEGER) }); MockStatsSource.columns = nodeColumns; MockStatsSource nodeSource = new MockStatsSource(new Object[][] { { "43", 43 }, { "42", 43 } }); m_mvoltdb.getStatsAgent().registerStatsSource(StatsSelector.DRPRODUCERNODE, 0, nodeSource); List<VoltTable.ColumnInfo> snapshotStatusColumns = Arrays.asList(new VoltTable.ColumnInfo[] { new VoltTable.ColumnInfo("c1", VoltType.STRING), new VoltTable.ColumnInfo("c2", VoltType.STRING) }); MockStatsSource.columns = snapshotStatusColumns; MockStatsSource snapshotSource = new MockStatsSource(new Object[][] { {"RYANLOVES", "THEYANKEES"}, {"NOREALLY", "ASKHIM"} }); m_mvoltdb.getStatsAgent().registerStatsSource(StatsSelector.SNAPSHOTSTATUS, 0, snapshotSource); } private ParameterSet subselect(String subselector, int interval) { Object[] blah = new Object[2]; blah[0] = subselector; blah[1] = interval; return ParameterSet.fromArrayWithCopy(blah); } @Test public void testInvalidStatisticsSubselector() throws Exception { createAndRegisterStats(); m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect("CRAZY", 0)); ClientResponseImpl response = responses.take(); assertEquals(ClientResponse.GRACEFUL_FAILURE, response.getStatus()); assertEquals("First argument to @Statistics must be a valid STRING selector, instead was CRAZY", response.getStatusString()); System.out.println(response.toJSONString()); } @Test public void testCollectDRStats() throws Exception { createAndRegisterStats(); m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect("DR", 0)); ClientResponseImpl response = responses.take(); assertEquals(ClientResponse.SUCCESS, response.getStatus()); VoltTable results[] = response.getResults(); System.out.println(results[0]); System.out.println(results[1]); verifyResults(response); } @Test public void testCollectSnapshotStatusStats() throws Exception { createAndRegisterStats(); m_mvoltdb.getStatsAgent().performOpsAction( m_mockConnection, 32, OpsSelector.STATISTICS, subselect("SNAPSHOTSTATUS", 0)); ClientResponseImpl response = responses.take(); assertEquals(ClientResponse.SUCCESS, response.getStatus()); VoltTable results[] = response.getResults(); System.out.println(results[0]); while (results[0].advanceRow()) { String c1 = results[0].getString("c1"); String c2 = results[0].getString("c2"); if (c1.equalsIgnoreCase("RYANLOVES")) { assertEquals("THEYANKEES", c2); } else if (c1.equalsIgnoreCase("NOREALLY")) { assertEquals("ASKHIM", c2); } else { fail("Unexpected row in results: c1: " + c1 + ", c2: " + c2); } } } @Test public void testCollectUnavailableStats() throws Exception { for (StatsSelector selector : StatsSelector.values()) { m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect(selector.name(), 0)); ClientResponseImpl response = responses.take(); assertEquals(ClientResponse.GRACEFUL_FAILURE, response.getStatus()); VoltTable results[] = response.getResults(); assertEquals(0, results.length); assertEquals( "Requested info \"" + selector.name() + "\" is not yet available or not " + "supported in the current configuration.", response.getStatusString()); } } @Test public void testCollectionTimeout() throws Exception { createAndRegisterStats(); StatsAgent.OPS_COLLECTION_TIMEOUT = 300; MockStatsSource.delay = 200; m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect("DR", 0)); ClientResponseImpl response = responses.take(); assertEquals(ClientResponse.GRACEFUL_FAILURE, response.getStatus()); VoltTable results[] = response.getResults(); assertEquals(0, results.length); System.out.println(response.getStatusString()); assertEquals("OPS request hit sixty second timeout before all responses were received", response.getStatusString()); } @Test public void testBackpressure() throws Exception { createAndRegisterStats(); MockStatsSource.delay = 20; /* * Generate a bunch of requests, should get backpressure on some of them */ for (int ii = 0; ii < 30; ii++) { m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect("DR", 0)); } boolean hadBackpressure = false; for (int ii = 0; ii < 30; ii++) { ClientResponseImpl response = responses.take(); if (response.getStatus() == ClientResponse.GRACEFUL_FAILURE) { assertTrue( "Too many pending stat requests".equals( response.getStatusString())); hadBackpressure = true; } } assertTrue(hadBackpressure); /* * Now having recieved all responses, it should be possible to collect the stats */ m_mvoltdb.getStatsAgent().performOpsAction(m_mockConnection, 32, OpsSelector.STATISTICS, subselect("DR", 0)); ClientResponseImpl response = responses.take(); verifyResults(response); } private void verifyResults(ClientResponseImpl response) { VoltTable results[] = response.getResults(); assertEquals(2, results.length); Set<Integer> pValues = new HashSet<Integer>(); pValues.add(45); pValues.add(44); pValues.add(43); pValues.add(42); while (results[0].advanceRow()) { assertTrue(pValues.contains((int)results[0].getLong(0))); assertTrue(pValues.contains(Integer.valueOf(results[0].getString(1)))); } Set<Integer> c1Values = pValues; Set<Integer> c2Values = new HashSet<Integer>(); c2Values.add(43); c2Values.add(44); while (results[1].advanceRow()) { assertTrue(c1Values.contains(Integer.valueOf(results[1].getString(0)))); assertTrue(c2Values.contains((int)results[1].getLong(1))); } } }