com.clearspring.metriccatcher.MetricCatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metriccatcher Show documentation
Show all versions of metriccatcher Show documentation
Receives metrics on UDP, sends them to Ganglia or Graphite
/*
* Copyright (C) 2012 Clearspring Technologies, 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.clearspring.metriccatcher;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Counter;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.Histogram;
import com.yammer.metrics.core.Meter;
import com.yammer.metrics.core.Metric;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.Timer;
public class MetricCatcher extends Thread {
private static final Logger logger = LoggerFactory.getLogger(MetricCatcher.class);
AtomicBoolean shutdown = new AtomicBoolean();
private ObjectMapper mapper = new ObjectMapper();
private DatagramSocket socket;
private Map metricCache;
public MetricCatcher(DatagramSocket socket, Map metricCache) throws IOException {
this.socket = socket;
this.metricCache = metricCache;
}
/**
* Grab metric messages off the listening socket, creating and updating
* them as needed.
*/
@Override
public void run() {
// Arbitrary. One metric with a reasonable name is less than 200b
// This (http://stackoverflow.com/q/3712151/17339) implies that 64 bit
// leenuks will handle packets up to 24,258b, so let's assume we won't
// get anything larger than that. Note that this is a hard limit-you
// can't accumulate from the socket so anything larger is truncated.
byte[] data = new byte[24258];
// Keep track of the last 1000 packets we've seen
byte[] json = null;
while (shutdown.get() == false) {
DatagramPacket received = new DatagramPacket(data, data.length);
try {
// Pull in network data
socket.receive(received);
json = received.getData();
if (logger.isDebugEnabled()) {
logger.debug("Got packet from " + received.getAddress() + ":" + received.getPort());
if (logger.isTraceEnabled()) {
String jsonString = new String(json);
logger.trace("JSON: " + jsonString);
}
}
// Turn bytes of JSON into JSONMetric objects
TypeReference> typeRef = new TypeReference>() {};
List jsonMetrics = mapper.readValue(json, typeRef);
// Parse all of the metrics in the message
for (JSONMetric jsonMetric : jsonMetrics) {
if (!metricCache.containsKey(jsonMetric.getName())) {
logger.info("Creating new " + jsonMetric.getType().name() + " metric for '" + jsonMetric.getName() + "'");
Metric newMetric = createMetric(jsonMetric);
metricCache.put(jsonMetric.getName(), newMetric);
}
// Record the update
logger.debug("Updating " + jsonMetric.getType() + " '" + jsonMetric.getName() + "' with <" + jsonMetric.getValue() + ">");
updateMetric(metricCache.get(jsonMetric.getName()), jsonMetric.getValue());
}
} catch (IOException e) {
logger.warn("IO error: " + e);
String jsonString = new String(json);
logger.warn("JSON: " + jsonString);
}
}
socket.close();
}
/**
* Create a Metric object from a JSONMetric
*
* @param jsonMetric A JSONMetric to make a Metric from
* @return A Metric equivalent to the given JSONMetric
*/
protected Metric createMetric(JSONMetric jsonMetric) {
// Split the name from the JSON on dots for the metric group/type/name
MetricName metricName;
ArrayList parts = new ArrayList(Arrays.asList(jsonMetric.getName().split("\\.")));
if (parts.size() >= 3)
metricName = new MetricName(parts.remove(0), parts.remove(0), StringUtils.join(parts, "."));
else
metricName = new MetricName(jsonMetric.getName(), "", "");
Class> metricType = jsonMetric.getMetricClass();
if (metricType == Gauge.class) {
return Metrics.newGauge(metricName, new GaugeMetricImpl());
} else if (metricType == Counter.class) {
return Metrics.newCounter(metricName);
} else if (metricType == Meter.class) {
// TODO timeunit
return Metrics.newMeter(metricName, jsonMetric.getName(), TimeUnit.MINUTES);
} else if (metricType == Histogram.class) {
if (jsonMetric.getType().equals("biased"))
return Metrics.newHistogram(metricName, true);
else
return Metrics.newHistogram(metricName, false);
} else if (metricType == Timer.class) {
return Metrics.newTimer(metricName, TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
}
// Uh-oh
return null;
}
/**
* Update various metric types.
*
* Gauge:
*
* Counter:
* Increment or decrement based upon sign of value
* Clear counter if given 0
*
* Meter:
* mark() the meter with the given value
*
* Histogram:
* update() the histogram with the given value
*
* Timer:
*
* @param metric The metric to update
* @param value The value to supply when updating the metric
*/
protected void updateMetric(Metric metric, double value) {
if (metric.getClass() == Gauge.class) {
((GaugeMetricImpl)metric).setValue((long)value);
} else if (metric.getClass() == Counter.class) {
if (value > 0)
((Counter)metric).inc((long)value);
else if (value < 0)
((Counter)metric).dec((long)value * -1);
else
((Counter)metric).clear();
} else if (metric.getClass() == Meter.class) {
((Meter)metric).mark((long)value);
} else if (metric.getClass() == Histogram.class) {
// TODO clearing? How about no, so that we can record 0 values; it'll clear over time...
((Histogram)metric).update((long)value);
} else if (metric.getClass() == Timer.class) {
((Timer)metric).update((long)value, TimeUnit.MICROSECONDS);
}
}
/**
* Shutdown this MetricCatcher
*/
public void shutdown() {
logger.info("Got shutdown signal");
shutdown.set(true);
logger.debug("Shutdown set");
this.interrupt();
logger.info("Done shutting down");
}
}