/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.presto.connector.jmx;
import com.facebook.presto.client.NodeVersion;
import com.facebook.presto.metadata.PrestoNode;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.ConnectorSplit;
import com.facebook.presto.spi.ConnectorSplitSource;
import com.facebook.presto.spi.ConnectorTableLayoutHandle;
import com.facebook.presto.spi.HostAddress;
import com.facebook.presto.spi.Node;
import com.facebook.presto.spi.NodeManager;
import com.facebook.presto.spi.RecordCursor;
import com.facebook.presto.spi.RecordSet;
import com.facebook.presto.spi.SchemaTableName;
import com.facebook.presto.spi.connector.ConnectorContext;
import com.facebook.presto.spi.connector.ConnectorTransactionHandle;
import com.facebook.presto.spi.predicate.NullableValue;
import com.facebook.presto.spi.predicate.TupleDomain;
import com.facebook.presto.spi.type.TimestampType;
import com.facebook.presto.testing.TestingNodeManager;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.airlift.units.Duration;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static com.facebook.presto.connector.jmx.JmxMetadata.HISTORY_SCHEMA_NAME;
import static com.facebook.presto.connector.jmx.JmxMetadata.JMX_SCHEMA_NAME;
import static com.facebook.presto.spi.type.VarcharType.createUnboundedVarcharType;
import static com.facebook.presto.testing.TestingConnectorSession.SESSION;
import static io.airlift.slice.Slices.utf8Slice;
import static java.lang.String.format;
import static java.lang.management.ManagementFactory.getPlatformMBeanServer;
import static java.util.stream.Collectors.toSet;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class TestJmxSplitManager
{
private static final Duration JMX_STATS_DUMP = new Duration(100, TimeUnit.MILLISECONDS);
private static final long SLEEP_TIME = JMX_STATS_DUMP.toMillis() / 5;
private static final long TIMEOUT_TIME = JMX_STATS_DUMP.toMillis() * 40;
private static final String TEST_BEANS = "java.lang:type=Runtime";
private static final String CONNECTOR_ID = "test-id";
private final Node localNode = createTestingNode("host1");
private final Set<Node> nodes = ImmutableSet.of(localNode, createTestingNode("host2"), createTestingNode("host3"));
private final NodeManager nodeManager = new TestingNodeManager(localNode, nodes);
private final JmxConnector jmxConnector =
(JmxConnector) new JmxConnectorFactory(getPlatformMBeanServer())
.create(CONNECTOR_ID, ImmutableMap.of(
"jmx.dump-tables", TEST_BEANS,
"jmx.dump-period", format("%dms", JMX_STATS_DUMP.toMillis()),
"jmx.max-entries", "1000"),
new ConnectorContext()
{
@Override
public NodeManager getNodeManager()
{
return nodeManager;
}
});
private final JmxColumnHandle columnHandle = new JmxColumnHandle("node", createUnboundedVarcharType());
private final JmxTableHandle tableHandle = new JmxTableHandle("objectName", ImmutableList.of(columnHandle), true);
private final JmxSplitManager splitManager = jmxConnector.getSplitManager();
private final JmxMetadata metadata = jmxConnector.getMetadata(new ConnectorTransactionHandle() {});
private final JmxRecordSetProvider recordSetProvider = jmxConnector.getRecordSetProvider();
@AfterClass
public void tearDown()
{
jmxConnector.shutdown();
}
@Test
public void testPredicatePushdown()
throws Exception
{
for (Node node : nodes) {
String nodeIdentifier = node.getNodeIdentifier();
TupleDomain<ColumnHandle> nodeTupleDomain = TupleDomain.fromFixedValues(ImmutableMap.of(columnHandle, NullableValue.of(createUnboundedVarcharType(), utf8Slice(nodeIdentifier))));
ConnectorTableLayoutHandle layout = new JmxTableLayoutHandle(tableHandle, nodeTupleDomain);
ConnectorSplitSource splitSource = splitManager.getSplits(JmxTransactionHandle.INSTANCE, SESSION, layout);
List<ConnectorSplit> allSplits = getAllSplits(splitSource);
assertEquals(allSplits.size(), 1);
assertEquals(allSplits.get(0).getAddresses().size(), 1);
assertEquals(allSplits.get(0).getAddresses().get(0).getHostText(), nodeIdentifier);
}
}
@Test
public void testNoPredicate()
throws Exception
{
ConnectorTableLayoutHandle layout = new JmxTableLayoutHandle(tableHandle, TupleDomain.all());
ConnectorSplitSource splitSource = splitManager.getSplits(JmxTransactionHandle.INSTANCE, SESSION, layout);
List<ConnectorSplit> allSplits = getAllSplits(splitSource);
assertEquals(allSplits.size(), nodes.size());
Set<String> actualNodes = nodes.stream().map(Node::getNodeIdentifier).collect(toSet());
Set<String> expectedNodes = new HashSet<>();
for (ConnectorSplit split : allSplits) {
List<HostAddress> addresses = split.getAddresses();
assertEquals(addresses.size(), 1);
expectedNodes.add(addresses.get(0).getHostText());
}
assertEquals(actualNodes, expectedNodes);
}
@Test
public void testRecordSetProvider()
throws Exception
{
for (SchemaTableName schemaTableName : metadata.listTables(SESSION, JMX_SCHEMA_NAME)) {
RecordSet recordSet = getRecordSet(schemaTableName);
try (RecordCursor cursor = recordSet.cursor()) {
while (cursor.advanceNextPosition()) {
for (int i = 0; i < recordSet.getColumnTypes().size(); i++) {
cursor.isNull(i);
}
}
}
}
}
@Test
public void testHistoryRecordSetProvider()
throws Exception
{
for (SchemaTableName schemaTableName : metadata.listTables(SESSION, HISTORY_SCHEMA_NAME)) {
// wait for at least two samples
List<Long> timeStamps = ImmutableList.of();
for (int waited = 0; waited < TIMEOUT_TIME; waited += SLEEP_TIME) {
RecordSet recordSet = getRecordSet(schemaTableName);
timeStamps = readTimeStampsFrom(recordSet);
if (timeStamps.size() >= 2) {
break;
}
Thread.sleep(SLEEP_TIME);
}
assertTrue(timeStamps.size() >= 2);
// we don't have equality check here because JmxHistoryDumper scheduling can lag
assertTrue(timeStamps.get(1) - timeStamps.get(0) >= JMX_STATS_DUMP.toMillis());
}
}
private List<Long> readTimeStampsFrom(RecordSet recordSet)
{
ImmutableList.Builder<Long> result = ImmutableList.builder();
try (RecordCursor cursor = recordSet.cursor()) {
while (cursor.advanceNextPosition()) {
for (int i = 0; i < recordSet.getColumnTypes().size(); i++) {
cursor.isNull(i);
}
if (cursor.isNull(0)) {
return result.build();
}
assertTrue(recordSet.getColumnTypes().get(0) instanceof TimestampType);
result.add(cursor.getLong(0));
}
}
return result.build();
}
private RecordSet getRecordSet(SchemaTableName schemaTableName)
throws Exception
{
JmxTableHandle tableHandle = metadata.getTableHandle(SESSION, schemaTableName);
List<ColumnHandle> columnHandles = ImmutableList.copyOf(metadata.getColumnHandles(SESSION, tableHandle).values());
ConnectorTableLayoutHandle layout = new JmxTableLayoutHandle(tableHandle, TupleDomain.all());
ConnectorSplitSource splitSource = splitManager.getSplits(JmxTransactionHandle.INSTANCE, SESSION, layout);
List<ConnectorSplit> allSplits = getAllSplits(splitSource);
assertEquals(allSplits.size(), nodes.size());
ConnectorSplit split = allSplits.get(0);
return recordSetProvider.getRecordSet(JmxTransactionHandle.INSTANCE, SESSION, split, columnHandles);
}
private static List<ConnectorSplit> getAllSplits(ConnectorSplitSource splitSource)
throws InterruptedException, ExecutionException
{
ImmutableList.Builder<ConnectorSplit> splits = ImmutableList.builder();
while (!splitSource.isFinished()) {
List<ConnectorSplit> batch = splitSource.getNextBatch(1000).get();
splits.addAll(batch);
}
return splits.build();
}
private static Node createTestingNode(String hostname)
{
return new PrestoNode(hostname, URI.create(format("http://%s:8080", hostname)), NodeVersion.UNKNOWN, false);
}
}