/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.btrace.services.impl; import com.sun.btrace.BTraceRuntime; import com.sun.btrace.SharedSettings; import com.sun.btrace.services.spi.SimpleService; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.Collection; import java.util.Formatter; import java.util.LinkedList; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; /** * A simple way to submit <a href="https://github.com/etsy/statsd/">statsd</a> metrics. * <p> * Use the following code to obtain an instance: * <pre> * <code> * {@literal @}Injected(factoryMethod = "getInstance") * private static Statsd s; * </code> * </pre> * @author Jaroslav Bachorik */ final public class Statsd extends SimpleService { private static final Charset CHARSET = Charset.forName("ascii"); public static enum Priority { NORMAL, LOW } public static enum AlertType { INFO, WARNING, ERROR, SUCCESS } private final static class Singleton { private final static Statsd INSTANCE = new Statsd(); } private final BlockingQueue<String> q = new ArrayBlockingQueue<>(120000); private final ExecutorService e = Executors.newSingleThreadExecutor( new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "jStatsD Client Submitter"); t.setDaemon(true); return t; } } ); public static Statsd getInstance() { return Singleton.INSTANCE; } @SuppressWarnings("FutureReturnValueIgnored") private Statsd() { e.submit(new Runnable() { @Override public void run() { DatagramSocket ds = null; boolean entered = BTraceRuntime.enter(); try { ds = new DatagramSocket(); DatagramPacket dp = new DatagramPacket(new byte[0], 0); try { dp.setAddress(InetAddress.getByName(SharedSettings.GLOBAL.getStatsdHost())); } catch (UnknownHostException e) { System.err.println("[statsd] invalid host defined: " + SharedSettings.GLOBAL.getStatsdHost()); dp.setAddress(InetAddress.getLoopbackAddress()); } catch (SecurityException e) { dp.setAddress(InetAddress.getLoopbackAddress()); } dp.setPort(SharedSettings.GLOBAL.getStatsdPort()); while (true) { Collection<String> msgs = new LinkedList<>(); msgs.add(q.take()); q.drainTo(msgs); StringBuilder sb = new StringBuilder(); for(String m : msgs) { if (sb.length() + m.length() < 511) { sb.append(m).append('\n'); } else { dp.setData(sb.toString().getBytes(CHARSET)); ds.send(dp); sb.setLength(0); } } if (sb.length() > 0) { dp.setData(sb.toString().getBytes(CHARSET)); ds.send(dp); } } } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { if (entered) { BTraceRuntime.leave(); } } } }); } /** * Increase the given counter by 1 * @param name the counter name */ public void increment(String name) { delta(name, 1, 0.0d, null); } /** * Increase the given counter by 1 * @param name the counter name * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void increment(String name, String tags) { delta(name, 1, 0.0d, tags); } /** * Increase the given counter by 1 * @param name the counter name * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. */ public void increment(String name, double sampleRate) { delta(name, 1, sampleRate, null); } /** * Increase the given counter by 1 * @param name the counter name * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void increment(String name, double sampleRate, String tags) { delta(name, 1, sampleRate, tags); } /** * Decrease the given counter by 1 * @param name the counter name */ public void decrement(String name) { delta(name, -1, 0.0d, null); } /** * Decrease the given counter by 1 * @param name the counter name * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void decrement(String name, String tags) { delta(name, -1, 0.0d, tags); } /** * Decrease the given counter by 1 * @param name the counter name * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. */ public void decrement(String name, double sampleRate) { delta(name, -1, sampleRate, null); } /** * Decrease the given counter by 1 * @param name the counter name * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void decrement(String name, double sampleRate, String tags) { delta(name, -1, sampleRate, tags); } /** * Adjusts the specified counter by a given delta. * * @param name * the name of the counter to adjust * @param count * the counter value */ public void count(String name, long count) { count(name, count, null); } /** * Adjusts the specified counter by a given delta. * * @param name * the name of the counter to adjust * @param count * the counter value * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void count(String name, long count, String tags) { count(name, count, 0d, tags); } /** * Adjusts the specified counter by a given delta. * * @param name * the name of the counter to adjust * @param count * the counter value * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. */ public void count(String name, long count, double sampleRate) { count(name, count, sampleRate, null); } /** * Adjusts the specified counter by a given delta. * * @param name * the name of the counter to adjust * @param count * the counter value * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this counter is being sent * sampled every 1/10th of the time. * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void count(String name, long count, double sampleRate, String tags) { submit(name, count, sampleRate, "c", tags); } /** * Sets the specified gauge to a given value. * * @param name * the name of the gauge to set * @param value * the value to set the gauge to */ public void gauge(String name, long value) { gauge(name, value, null); } /** * Sets the specified gauge to a given value. * * @param name * the name of the gauge to set * @param value * the value to set the gauge to * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void gauge(String name, long value, String tags) { submit(name, value, 0d, "g", tags); } /** * Records the timing information for the specified metric. * * @param name * the metric name * @param value * the measured time */ public void time(String name, long value) { time(name, value, null); } /** * Records the timing information for the specified metric. * * @param name * the metric name * @param value * the measured time * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this metric timing is being sent * sampled every 1/10th of the time. */ public void time(String name, long value, double sampleRate) { time(name, value, sampleRate, null); } /** * Records the timing information for the specified metric. * * @param name * the metric name * @param value * the measured time * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void time(String name, long value, String tags) { time(name, value, 0d, tags); } /** * Records the timing information for the specified metric. * * @param name * the metric name * @param value * the measured time * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this metric timing is being sent * sampled every 1/10th of the time. * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void time(String name, long value, double sampleRate, String tags) { submit(name, value, sampleRate, "ms", tags); } /** * Adds a value to the named histogram. * * @param name * the histogram name * @param value * the measured value */ public void histo(String name, long value) { histo(name, value, null); } /** * Adds a value to the named histogram. * * @param name * the histogram name * @param value * the measured value * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this metric value is being sent * sampled every 1/10th of the time. */ public void histo(String name, long value, double sampleRate) { histo(name, value, sampleRate, null); } /** * Adds a value to the named histogram. * * @param name * the histogram name * @param value * the measured value * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void histo(String name, long value, String tags) { histo(name, value, 0d, tags); } /** * Adds a value to the named histogram. * * @param name * the histogram name * @param value * the measured value * @param sampleRate * the sampling rate being employed. For example, a rate of 0.1 would * tell StatsD that this metric value is being sent * sampled every 1/10th of the time. * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void histo(String name, long value, double sampleRate, String tags) { submit(name, value, sampleRate, "h", tags); } /** * StatsD supports counting unique occurrences of events between flushes. * Call this method to records an occurrence of the specified named event. * * @param name * the name of the set * @param id * the value to be added to the set * @param tags * Only for DogStatsD compatible collectors. * Assigned comma delimited tags. A tag value is delimited by colon. */ public void unique(String name, String id, String tags) { submit(name, id, "s", tags); } /** * StatsD supports counting unique occurrences of events between flushes. * Call this method to records an occurrence of the specified named event. * * @param name * the name of the set * @param id * the value to be added to the set */ public void unique(String name, String id) { unique(name, id, null); } /** * Sends an event to a DogStatsD compatible collector * * @param title event name * @param text event text */ public void event(String title, String text) { event(title, text, 0, null, null, null, null, null, null); } /** * Sends an event to a DogStatsD compatible collector * * @param title The event name * @param text The event text * @param tags * Assigned comma delimited tags. A tag value is delimited by colon. */ public void event(String title, String text, String tags) { event(title, text, 0, null, null, null, null, null, tags); } /** * Sends an event to a DogStatsD compatible collector * * @param title event name * @param text event text * @param timestamp * Assign a timestamp to the event. * 0 means the current date * @param host * Assign a hostname to the event. * May be null * @param group * Assign an aggregation key to the event, to group it with some others. * May be null * @param sourceType * Assign a source type to the event. * May be null * @param priority * {@linkplain Priority} - may be null for NORMAL * @param alertType * {@linkplain AlertType} - may be null for INFO * @param tags * Assigned comma delimited tags. A tag value is delimited by colon. */ public void event(String title, String text, long timestamp, String host, String group, String sourceType, Priority priority, AlertType alertType, String tags) { StringBuilder sb = new StringBuilder("_e{"); sb.append(title.length()).append(',') .append(text.length()).append('}'); sb.append(':').append(title).append('|').append(text); if (timestamp >= 0) { sb.append("|d:").append(timestamp == 0 ? System.currentTimeMillis() : timestamp); } if (host != null) { sb.append("|h:").append(host); } if (group != null) { sb.append("|k:").append(group); } if (sourceType != null) { sb.append("|s:").append(sourceType); } if (priority != null) { sb.append("|p:").append(priority); } if (alertType != null) { sb.append("|t:").append(alertType); } appendTags(tags, sb); q.offer(sb.toString()); } private void delta(String name, long value, double sampleRate, String tags) { StringBuilder sb = new StringBuilder(name); Formatter fmt = new Formatter(sb); sb.append(':'); if (value > 0) { sb.append('+'); } else if (value < 0) { sb.append('-'); } sb.append(value).append('|').append('g'); appendSampleRate(sampleRate, sb, fmt); appendTags(tags, sb); q.offer(sb.toString()); } private void submit(String name, long value, double sampleRate, String type, String tags) { StringBuilder sb = new StringBuilder(name); Formatter fmt = new Formatter(sb); sb.append(':').append(value).append('|').append(type); appendSampleRate(sampleRate, sb, fmt); appendTags(tags, sb); q.offer(sb.toString()); } private void submit(String name, String value, String type, String tags) { StringBuilder sb = new StringBuilder(name); sb.append(':').append(value).append('|').append(type); appendTags(tags, sb); q.offer(sb.toString()); } private void appendTags(String tags, StringBuilder sb) { if (tags != null && !tags.isEmpty()) { sb.append("|#").append(tags); } } private void appendSampleRate(double sampleRate, StringBuilder sb, Formatter fmt) { if (sampleRate > 0) { sb.append("|@"); fmt.format("%.3f", sampleRate); } } }