package org.opennms.newts.graphite; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.opennms.newts.api.Timestamp.fromEpochSeconds; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import org.opennms.newts.api.MetricType; import org.opennms.newts.api.Resource; import org.opennms.newts.api.Sample; import org.opennms.newts.api.SampleRepository; import org.opennms.newts.api.Timestamp; import org.opennms.newts.api.ValueType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class GraphiteHandler extends SimpleChannelInboundHandler<String> { private static final Logger LOG = LoggerFactory.getLogger(GraphiteHandler.class); private static final int DEFAULT_LINES_BUFFER = 50; private final ThreadPoolExecutor m_executor; private final SampleRepository m_repository; private final GraphiteInitializer m_parent; private List<String> m_lines; private AtomicInteger m_enQueued = new AtomicInteger(0); public GraphiteHandler(SampleRepository repository, GraphiteInitializer parent) { BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); int concurrency = Runtime.getRuntime().availableProcessors(); m_executor = new ThreadPoolExecutor(concurrency, concurrency, 0L, MILLISECONDS, queue); m_executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); m_repository = repository; m_parent = parent; m_lines = Lists.newArrayList(); LOG.debug("Using storage concurrency of {}", concurrency); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { enqueue(msg); } private void enqueue(String msg) { m_lines.add(msg); if (m_enQueued.incrementAndGet() >= DEFAULT_LINES_BUFFER) { final List<String> batch = m_lines; m_lines = Lists.newArrayList(); m_enQueued.set(0); m_executor.execute(new Runnable() { @Override public void run() { List<Sample> samples = Lists.newArrayList(); for (String line : batch) { try { samples.add(parseSample(line)); } catch (Exception e) { m_parent.protocolErrorsInc(); } } try { m_repository.insert(samples); } catch (Exception e) { LOG.warn("Unable to commit batch of {} samples ({})", samples.size(), e.getMessage()); m_parent.storageErrorsInc(); } } }); } } private static final Splitter s_lineTokenizer = Splitter.on(CharMatcher.WHITESPACE).limit(3).trimResults(); private static final Splitter s_pathTokenizer = Splitter.on('.').trimResults(); private static final Joiner s_pathJoiner = Joiner.on(':'); static Resource parseResource(String[] path) { Map<String, String> attributes = Maps.newHashMap(); for (int i = 0; i < path.length; i++) { attributes.put(index(i), path[i]); } return new Resource(s_pathJoiner.join(path), Optional.of(attributes)); } static Sample parseSample(String line) { List<String> parts = s_lineTokenizer.splitToList(line); String[] path = s_pathTokenizer.splitToList(parts.get(0)).toArray(new String[] {}); Resource resource = parseResource(path.length > 1 ? Arrays.copyOf(path, path.length - 1) : path); String name = path.length > 1 ? path[path.length - 1] : "value"; Double value = Double.parseDouble(parts.get(1)); Long stamp = Long.parseLong(parts.get(2)); return sample(fromEpochSeconds(stamp), resource, name, value); } private static Sample sample(Timestamp timestamp, Resource resource, String name, Double value) { return new Sample(timestamp, resource, name, MetricType.GAUGE, ValueType.compose(value, MetricType.GAUGE)); } private static String index(int index) { return String.format("_%d", index); } }