
com.arpnetworking.metrics.common.sources.StatsdSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metrics-aggregator-daemon Show documentation
Show all versions of metrics-aggregator-daemon Show documentation
Aggregates samples published by metrics client libraries and writes to destinations.
/*
* Copyright 2017 Inscope Metrics, 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.arpnetworking.metrics.common.sources;
import com.arpnetworking.metrics.common.parsers.Parser;
import com.arpnetworking.metrics.common.parsers.exceptions.ParsingException;
import com.arpnetworking.metrics.incubator.PeriodicMetrics;
import com.arpnetworking.metrics.mad.model.Metric;
import com.arpnetworking.metrics.mad.model.Record;
import com.arpnetworking.metrics.mad.model.statistics.StatisticFactory;
import com.arpnetworking.metrics.mad.parsers.StatsdToRecordParser;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.arpnetworking.tsdcore.model.CalculatedValue;
import com.fasterxml.jackson.annotation.JacksonInject;
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNull;
import net.sf.oval.constraint.Range;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.io.Udp;
import org.apache.pekko.io.UdpMessage;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Source that uses Statsd as input.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot io)
*/
public final class StatsdSource extends ActorSource {
@Override
protected Props createProps() {
return Actor.props(this);
}
/**
* Protected constructor.
*
* @param builder Instance of {@link Builder}.
*/
private StatsdSource(final Builder builder) {
super(builder);
_host = builder._host;
_port = builder._port;
_periodicMetrics = builder._periodicMetrics;
}
private final String _host;
private final int _port;
private final PeriodicMetrics _periodicMetrics;
private static final Logger LOGGER = LoggerFactory.getLogger(StatsdSource.class);
private static final Parser, ByteBuffer> PARSER = new StatsdToRecordParser();
/**
* Name of the actor created to receive the Statsd datagrams.
*/
public static final String ACTOR_NAME = "statsd";
/**
* Internal actor to process requests.
*/
/* package private */ static final class Actor extends AbstractActor {
/**
* Creates a {@link Props} for this actor.
*
* @param source The {@link StatsdSource} to send notifications through.
* @return A new {@link Props}
*/
/* package private */ static Props props(final StatsdSource source) {
return Props.create(Actor.class, () -> new Actor(source));
}
@Override
public void preStart() throws Exception, Exception {
super.preStart();
final ActorRef udpManager = Udp.get(getContext().system()).getManager();
udpManager.tell(
UdpMessage.bind(getSelf(), new InetSocketAddress(_host, _port)),
getSelf());
_periodicMetrics.registerPolledMetric(m -> {
// TODO(vkoskela): There needs to be a way to deregister these callbacks
// This is not an immediate issue since new Aggregator instances are
// only created when pipelines are reloaded. To avoid recording values
// for dead pipelines this explicitly avoids recording zeroes.
final long samples = _receivedSamples.getAndSet(0);
final long packets = _receivedPackets.getAndSet(0);
if (samples > 0) {
m.recordCounter(String.format("sources/statsd/%s/metric_samples", _metricSafeName), samples);
}
if (packets > 0) {
m.recordCounter(String.format("sources/statsd/%s/packets", _metricSafeName), packets);
}
});
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(IS_READY, message -> {
getSender().tell(_isReady, getSelf());
})
.match(Udp.Bound.class, updBound -> {
_socket = getSender();
_isReady = true;
LOGGER.info()
.setMessage("Statsd server binding complete")
.addData("address", updBound.localAddress().getAddress().getHostAddress())
.addData("port", updBound.localAddress().getPort())
.addData("socket", _socket)
.log();
})
.match(Udp.Received.class, this::updReceived)
.matchEquals(UdpMessage.unbind(), message -> {
LOGGER.debug()
.setMessage("Statsd unbind")
.addData("socket", _socket)
.log();
_socket.tell(message, getSelf());
})
.match(Udp.Unbound.class, message -> {
LOGGER.debug()
.setMessage("Statsd unbound")
.addData("socket", _socket)
.log();
getContext().stop(getSelf());
})
.build();
}
private void updReceived(final Udp.Received updReceived) {
LOGGER.trace()
.setMessage("Statsd received datagram")
.addData("bytes", updReceived.data().size())
.addData("socket", _socket)
.log();
try {
// NOTE: The parsing occurs in the actor itself which can become a bottleneck
// if there are more records to be parsed then a single thread can handle.
final List records = PARSER.parse(updReceived.data().toByteBuffer());
long samples = 0;
for (final Record record : records) {
for (final Metric metric : record.getMetrics().values()) {
samples += metric.getValues().size();
final List> countStatistic =
metric.getStatistics().get(STATISTIC_FACTORY.getStatistic("count"));
if (countStatistic != null) {
samples += countStatistic.stream()
.map(s -> s.getValue().getValue())
.reduce(Double::sum)
.map(Double::longValue)
.orElse(0L);
}
}
}
_receivedSamples.addAndGet(samples);
_receivedPackets.addAndGet(1);
records.forEach(_sink::notify);
} catch (final ParsingException e) {
BAD_REQUEST_LOGGER.warn()
.setMessage("Error handling statsd datagram")
.addData("socket", _socket)
.setThrowable(e)
.log();
}
}
/**
* Constructor.
*
* @param source The {@link StatsdSource} to send notifications through.
*/
/* package private */ Actor(final StatsdSource source) {
_sink = source;
_host = source._host;
_port = source._port;
_periodicMetrics = source._periodicMetrics;
_metricSafeName = source.getMetricSafeName();
}
private boolean _isReady = false;
private ActorRef _socket;
private final StatsdSource _sink;
private final String _host;
private final int _port;
private static final String IS_READY = "IsReady";
private static final Logger BAD_REQUEST_LOGGER =
LoggerFactory.getRateLimitLogger(StatsdSource.class, Duration.ofSeconds(30));
private static final StatisticFactory STATISTIC_FACTORY = new StatisticFactory();
private final AtomicLong _receivedSamples = new AtomicLong(0);
private final AtomicLong _receivedPackets = new AtomicLong(0);
private final PeriodicMetrics _periodicMetrics;
private final String _metricSafeName;
}
/**
* StatsdSource {@link BaseSource.Builder} implementation.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot io)
*/
public static final class Builder extends ActorSource.Builder {
/**
* Public constructor.
*/
public Builder() {
super(StatsdSource::new);
setActorName(ACTOR_NAME);
}
/**
* Sets the host to bind to. Optional. Cannot be null or empty.
*
* @param value the port to listen on
* @return This builder
*/
public Builder setHost(final String value) {
_host = value;
return self();
}
/**
* Sets the port to listen on. Optional. Cannot be null. Must be
* between 1 and 65535 (inclusive). Default is 8125.
*
* @param value the port to listen on
* @return This builder
*/
public Builder setPort(final Integer value) {
_port = value;
return self();
}
/**
* Sets the periodic metrics instance.
*
* @param value The periodic metrics.
* @return This instance of {@link HttpSource.Builder}
*/
public Builder setPeriodicMetrics(final PeriodicMetrics value) {
_periodicMetrics = value;
return self();
}
@Override
protected Builder self() {
return this;
}
@NotNull
@NotEmpty
private String _host = "localhost";
@NotNull
@Range(min = 1, max = 65535)
private Integer _port = 8125;
@NotNull
@JacksonInject
private PeriodicMetrics _periodicMetrics;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy