2020import java .util .concurrent .ThreadLocalRandom ;
2121import java .util .concurrent .TimeUnit ;
2222
23+
2324/**
2425 * A simple StatsD client implementation facilitating metrics recording.
2526 *
4243 * IO operations being carried out in a separate thread. Furthermore, these methods are guaranteed
4344 * not to throw an exception which may disrupt application execution.
4445 *
46+ * <p>Some methods allow recording a value for a specific point in time by taking an extra
47+ * timestamp parameter. Such values are exempt from aggregation and the value should indicate the
48+ * final metric value at the given time. Please refer to Datadog documentation for the range of
49+ * accepted timestamp values.
50+ *
4551 * <p>As part of a clean system shutdown, the {@link #stop()} method should be invoked
4652 * on any StatsD clients.</p>
4753 *
@@ -57,6 +63,8 @@ public class NonBlockingStatsDClient implements StatsDClient {
5763 private static final String ENTITY_ID_TAG_NAME = "dd.internal.entity_id" ;
5864 public static final String ORIGIN_DETECTION_ENABLED_ENV_VAR = "DD_ORIGIN_DETECTION_ENABLED" ;
5965
66+ private static final long MIN_TIMESTAMP = 1 ;
67+
6068 enum Literal {
6169 SERVICE ,
6270 ENV ,
@@ -487,10 +495,12 @@ ClientChannel createByteChannel(Callable<SocketAddress> addressLookup, int timeo
487495
488496 abstract class StatsDMessage <T extends Number > extends NumericMessage <T > {
489497 final double sampleRate ; // NaN for none
498+ final long timestamp ; // zero for none
490499
491- protected StatsDMessage (String aspect , Message .Type type , T value , double sampleRate , String [] tags ) {
500+ protected StatsDMessage (String aspect , Message .Type type , T value , double sampleRate , long timestamp , String [] tags ) {
492501 super (aspect , type , value , tags );
493502 this .sampleRate = sampleRate ;
503+ this .timestamp = timestamp ;
494504 }
495505
496506 @ Override
@@ -501,6 +511,9 @@ public final void writeTo(StringBuilder builder, String containerID) {
501511 if (!Double .isNaN (sampleRate )) {
502512 builder .append ('|' ).append ('@' ).append (format (SAMPLE_RATE_FORMATTER , sampleRate ));
503513 }
514+ if (timestamp != 0 ) {
515+ builder .append ("|T" ).append (timestamp );
516+ }
504517 tagString (this .tags , builder );
505518 if (containerID != null && !containerID .isEmpty ()) {
506519 builder .append ("|c:" ).append (containerID );
@@ -509,6 +522,12 @@ public final void writeTo(StringBuilder builder, String containerID) {
509522 builder .append ('\n' );
510523 }
511524
525+ @ Override
526+ public boolean canAggregate () {
527+ // Timestamped values can not be aggregated.
528+ return super .canAggregate () && this .timestamp == 0 ;
529+ }
530+
512531 protected abstract void writeValue (StringBuilder builder );
513532 }
514533
@@ -528,8 +547,8 @@ private boolean send(final Message message) {
528547 return success ;
529548 }
530549
531- // send double with sample rate
532- private void send (String aspect , final double value , Message .Type type , double sampleRate , String [] tags ) {
550+ // send double with sample rate and timestamp
551+ private void send (String aspect , final double value , Message .Type type , double sampleRate , long timestamp , String [] tags ) {
533552 if (statsDProcessor .getAggregator ().getFlushInterval () != 0 && !Double .isNaN (sampleRate )) {
534553 switch (type ) {
535554 case COUNT :
@@ -542,21 +561,25 @@ private void send(String aspect, final double value, Message.Type type, double s
542561
543562 if (Double .isNaN (sampleRate ) || !isInvalidSample (sampleRate )) {
544563
545- sendMetric (new StatsDMessage <Double >(aspect , type , Double .valueOf (value ), sampleRate , tags ) {
564+ sendMetric (new StatsDMessage <Double >(aspect , type , Double .valueOf (value ), sampleRate , timestamp , tags ) {
546565 @ Override protected void writeValue (StringBuilder builder ) {
547566 builder .append (format (NUMBER_FORMATTER , this .value ));
548567 }
549568 });
550569 }
551570 }
552571
572+ private void send (String aspect , final double value , Message .Type type , double sampleRate , String [] tags ) {
573+ send (aspect , value , type , sampleRate , 0 , tags );
574+ }
575+
553576 // send double without sample rate
554577 private void send (String aspect , final double value , Message .Type type , String [] tags ) {
555- send (aspect , value , type , Double .NaN , tags );
578+ send (aspect , value , type , Double .NaN , 0 , tags );
556579 }
557580
558581 // send long with sample rate
559- private void send (String aspect , final long value , Message .Type type , double sampleRate , String [] tags ) {
582+ private void send (String aspect , final long value , Message .Type type , double sampleRate , long timestamp , String [] tags ) {
560583 if (statsDProcessor .getAggregator ().getFlushInterval () != 0 && !Double .isNaN (sampleRate )) {
561584 switch (type ) {
562585 case COUNT :
@@ -569,17 +592,36 @@ private void send(String aspect, final long value, Message.Type type, double sam
569592
570593 if (Double .isNaN (sampleRate ) || !isInvalidSample (sampleRate )) {
571594
572- sendMetric (new StatsDMessage <Long >(aspect , type , value , sampleRate , tags ) {
595+ sendMetric (new StatsDMessage <Long >(aspect , type , value , sampleRate , timestamp , tags ) {
573596 @ Override protected void writeValue (StringBuilder builder ) {
574597 builder .append (this .value );
575598 }
576599 });
577600 }
578601 }
579602
603+ private void send (String aspect , final long value , Message .Type type , double sampleRate , String [] tags ) {
604+ send (aspect , value , type , sampleRate , 0 , tags );
605+ }
606+
580607 // send long without sample rate
581608 private void send (String aspect , final long value , Message .Type type , String [] tags ) {
582- send (aspect , value , type , Double .NaN , tags );
609+ send (aspect , value , type , Double .NaN , 0 , tags );
610+ }
611+
612+ private void sendWithTimestamp (String aspect , final double value , Message .Type type , long timestamp , String [] tags ) {
613+ if (timestamp < MIN_TIMESTAMP ) {
614+ timestamp = MIN_TIMESTAMP ;
615+ }
616+ send (aspect , value , type , Double .NaN , timestamp , tags );
617+ }
618+
619+ private void sendWithTimestamp (String aspect , final long value , Message .Type type , long timestamp , String [] tags ) {
620+ if (timestamp < MIN_TIMESTAMP ) {
621+ timestamp = MIN_TIMESTAMP ;
622+ }
623+
624+ send (aspect , value , type , Double .NaN , timestamp , tags );
583625 }
584626
585627 /**
@@ -632,6 +674,22 @@ public void count(final String aspect, final double delta, final double sampleRa
632674 send (aspect , delta , Message .Type .COUNT , sampleRate , tags );
633675 }
634676
677+ /**
678+ * {@inheritDoc}
679+ */
680+ @ Override
681+ public void countWithTimestamp (final String aspect , final long value , final long timestamp , final String ...tags ) {
682+ sendWithTimestamp (aspect , value , Message .Type .COUNT , timestamp , tags );
683+ }
684+
685+ /**
686+ * {@inheritDoc}
687+ */
688+ @ Override
689+ public void countWithTimestamp (final String aspect , final double value , final long timestamp , final String ...tags ) {
690+ sendWithTimestamp (aspect , value , Message .Type .COUNT , timestamp , tags );
691+ }
692+
635693 /**
636694 * Increments the specified counter by one.
637695 *
@@ -776,7 +834,6 @@ public void gauge(final String aspect, final double value, final double sampleRa
776834 recordGaugeValue (aspect , value , sampleRate , tags );
777835 }
778836
779-
780837 /**
781838 * Convenience method equivalent to {@link #recordGaugeValue(String, long, String[])}.
782839 */
@@ -793,6 +850,22 @@ public void gauge(final String aspect, final long value, final double sampleRate
793850 recordGaugeValue (aspect , value , sampleRate , tags );
794851 }
795852
853+ /**
854+ * {@inheritDoc}
855+ */
856+ @ Override
857+ public void gaugeWithTimestamp (final String aspect , final double value , final long timestamp , final String ... tags ) {
858+ sendWithTimestamp (aspect , value , Message .Type .GAUGE , timestamp , tags );
859+ }
860+
861+ /**
862+ * {@inheritDoc}
863+ */
864+ @ Override
865+ public void gaugeWithTimestamp (final String aspect , final long value , final long timestamp , final String ... tags ) {
866+ sendWithTimestamp (aspect , value , Message .Type .GAUGE , timestamp , tags );
867+ }
868+
796869 /**
797870 * Records an execution time in milliseconds for the specified named operation.
798871 *
0 commit comments