Tracks the delay between the pull and the actual bucket boundary.

Test: atest statsd_test
Change-Id: I08880eafb54d599c9d1adb3c23b19af1f7fac886
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index c774719..c4034ff 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -458,6 +458,15 @@
     getAtomMetricStats(metricId).invalidatedBucket++;
 }
 
+void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs) {
+    lock_guard<std::mutex> lock(mLock);
+    AtomMetricStats& pullStats = getAtomMetricStats(metricId);
+    pullStats.maxBucketBoundaryDelayNs =
+            std::max(pullStats.maxBucketBoundaryDelayNs, timeDelayNs);
+    pullStats.minBucketBoundaryDelayNs =
+            std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs);
+}
+
 StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int metricId) {
     auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
     if (atomMetricStatsIter != mAtomMetricStats.end()) {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index ed42b1f..2999b64 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -375,6 +375,12 @@
     void noteInvalidatedBucket(int64_t metricId);
 
     /**
+     * For pulls at bucket boundaries, it represents the misalignment between the real timestamp and
+     * the end of the bucket.
+     */
+    void noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs);
+
+    /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
      * to collect stats after reset() has been called.
@@ -420,6 +426,8 @@
         long conditionChangeInNextBucket = 0;
         long invalidatedBucket = 0;
         long bucketDropped = 0;
+        int64_t minBucketBoundaryDelayNs = 0;
+        int64_t maxBucketBoundaryDelayNs = 0;
     } AtomMetricStats;
 
 private:
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 4fc9c37..ac6c27a 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -387,6 +387,8 @@
         // If the sleep was very long, we skip more than one bucket before sleep. In this case,
         // if the diff base will be cleared and this new data will serve as new diff base.
         int64_t bucketEndTime = calcPreviousBucketEndTime(originalPullTimeNs) - 1;
+        StatsdStats::getInstance().noteBucketBoundaryDelayNs(
+                mMetricId, originalPullTimeNs - bucketEndTime);
         accumulateEvents(allData, originalPullTimeNs, bucketEndTime);
 
         // We can probably flush the bucket. Since we used bucketEndTime when calling
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 73aab48..6a07a3f 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -419,6 +419,8 @@
       optional int64 condition_change_in_next_bucket = 6;
       optional int64 invalidated_bucket = 7;
       optional int64 bucket_dropped = 8;
+      optional int64 min_bucket_boundary_delay_ns = 9;
+      optional int64 max_bucket_boundary_delay_ns = 10;
     }
     repeated AtomMetricStats atom_metric_stats = 17;
 
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index f76a9ad..aa8cfc5 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -80,6 +80,8 @@
 const int FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET = 6;
 const int FIELD_ID_INVALIDATED_BUCKET = 7;
 const int FIELD_ID_BUCKET_DROPPED = 8;
+const int FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS = 9;
+const int FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS = 10;
 
 namespace {
 
@@ -500,6 +502,10 @@
                        (long long)pair.second.invalidatedBucket);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_DROPPED,
                        (long long)pair.second.bucketDropped);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MIN_BUCKET_BOUNDARY_DELAY_NS,
+                       (long long)pair.second.minBucketBoundaryDelayNs);
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_MAX_BUCKET_BOUNDARY_DELAY_NS,
+                       (long long)pair.second.maxBucketBoundaryDelayNs);
     protoOutput->end(token);
 }