/* * Copyright (C) 2012-2015 DataStax Inc. * * 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.datastax.driver.core.utils; import com.datastax.driver.core.NettyOptions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.SocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * Utility that observes {@link SocketChannel}s. Helpful for ensuring that Sockets are actually closed * when they should be. Utilizes {@link NettyOptions} to monitor created {@link SocketChannel}s. */ public class SocketChannelMonitor implements Runnable, Closeable { private static final Logger logger = LoggerFactory.getLogger(SocketChannelMonitor.class); private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("SocketMonitor-%d").build()); // use a weak set so channels may be garbage collected. private final Collection<SocketChannel> channels = Collections.newSetFromMap( new MapMaker().weakKeys().<SocketChannel, Boolean>makeMap()); private final AtomicLong channelsCreated = new AtomicLong(0); private final NettyOptions nettyOptions = new NettyOptions() { @Override public void afterChannelInitialized(SocketChannel channel) throws Exception { channels.add(channel); channelsCreated.incrementAndGet(); } @Override public void onClusterClose(EventLoopGroup eventLoopGroup) { eventLoopGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS).syncUninterruptibly(); } }; @Override public void run() { try { report(); } catch (Exception e) { logger.error("Error countered.", e); } } @Override public void close() throws IOException { stop(); } public void stop() { executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { // ok } } /** * @return A custom {@link NettyOptions} instance that hooks into afterChannelInitialized added channels may be * monitored. */ public NettyOptions nettyOptions() { return nettyOptions; } public static Predicate<SocketChannel> openChannels = new Predicate<SocketChannel>() { @Override public boolean apply(SocketChannel input) { return input.isOpen(); } }; /** * Schedules a {@link #report()} to be called every configured interval. * * @param interval how often to report. * @param timeUnit at what time precision to report at. */ public void reportAtFixedInterval(int interval, TimeUnit timeUnit) { executor.scheduleAtFixedRate(this, interval, interval, timeUnit); } /** * Reports for all sockets. */ public void report() { report(Predicates.<SocketChannel>alwaysTrue()); } /** * <p/> * Report for all sockets matching the given predicate. The report format reflects the number of open, closed, * live and total sockets created. This is logged at DEBUG if enabled. * <p/> * <p/> * If TRACE is enabled, each individual socket will be logged as well. * * @param channelFilter used to determine which sockets to report on. */ public void report(Predicate<SocketChannel> channelFilter) { if (logger.isDebugEnabled()) { Iterable<SocketChannel> channels = matchingChannels(channelFilter); Iterable<SocketChannel> open = Iterables.filter(channels, openChannels); Iterable<SocketChannel> closed = Iterables.filter(channels, Predicates.not(openChannels)); logger.debug("Channel states: {} open, {} closed, live {}, total sockets created " + "(including those that don't match filter) {}.", Iterables.size(open), Iterables.size(closed), Iterables.size(channels), channelsCreated.get()); if (logger.isTraceEnabled()) { logger.trace("Open channels {}.", open); logger.trace("Closed channels {}.", closed); } } } private static Comparator<SocketChannel> BY_REMOTE_ADDRESS = new Comparator<SocketChannel>() { @Override public int compare(SocketChannel t0, SocketChannel t1) { // Should not be null as these are filtered previously in matchingChannels. assert t0 != null && t0.remoteAddress() != null; assert t1 != null && t1.remoteAddress() != null; return t0.remoteAddress().toString().compareTo(t1.remoteAddress().toString()); } }; public Collection<SocketChannel> openChannels(InetSocketAddress... addresses) { return openChannels(Arrays.asList(addresses)); } /** * @param addresses The addresses to include. * @return Open channels matching the given socket addresses. */ public Collection<SocketChannel> openChannels(final Collection<InetSocketAddress> addresses) { List<SocketChannel> channels = Lists.newArrayList(matchingChannels(new Predicate<SocketChannel>() { @Override public boolean apply(SocketChannel input) { return input.isOpen() && input.remoteAddress() != null && addresses.contains(input.remoteAddress()); } })); Collections.sort(channels, BY_REMOTE_ADDRESS); return channels; } /** * @param channelFilter {@link Predicate} to use to determine whether or not a socket shall be considered. * @return Channels matching the given {@link Predicate}. */ public Iterable<SocketChannel> matchingChannels(final Predicate<SocketChannel> channelFilter) { return Iterables.filter(Lists.newArrayList(channels), Predicates.and(Predicates.notNull(), channelFilter)); } }