@Test
  public void testBucketingBasic() throws StoreException, InterruptedException {
    assumeTrue(bucketingEnabled);
    final CountDownLatch scanStartedLatch = new CountDownLatch(1);
    MockThrottler mockThrottler = new MockThrottler(scanStartedLatch, new CountDownLatch(0));
    throttlers.put(BlobStoreStats.IO_SCHEDULER_JOB_TYPE, mockThrottler);
    long logSegmentForecastOffsetMs = state.time.milliseconds();
    int bucketCount = 2 * (int) (logSegmentForecastOffsetMs / BUCKET_SPAN_IN_MS);
    long expiresAtInMs = ((long) bucketCount - 2) * BUCKET_SPAN_IN_MS;

    // add 3 puts with expiry
    state.addPutEntries(3, PUT_RECORD_SIZE, expiresAtInMs);
    // add records so that we will have two consecutive deletes and undeletes for each PUT
    List<IndexEntry> puts = state.addPutEntries(2, PUT_RECORD_SIZE, Utils.Infinite_Time);
    MockId id1 = (MockId) puts.get(0).getKey();
    MockId id2 = (MockId) puts.get(1).getKey();

    state.addDeleteEntry(id1);
    state.addDeleteEntry(id1, null, (short) 1);

    state.addDeleteEntry(id2);
    state.addUndeleteEntry(id2);
    state.addUndeleteEntry(id2, (short) 2);

    int expectedThrottleCount = state.referenceIndex.size();
    long logSegmentForecastStartTimeInMs = state.time.milliseconds() - logSegmentForecastOffsetMs;
    long logSegmentForecastEndTimeInMs = logSegmentForecastStartTimeInMs + bucketCount * BUCKET_SPAN_IN_MS;
    long containerForecastEndTimeInMs = state.time.milliseconds() + bucketCount * BUCKET_SPAN_IN_MS;
    BlobStoreStats blobStoreStats = setupBlobStoreStats(bucketCount, logSegmentForecastOffsetMs);
    // proceed only when the scan is started
    assertTrue("IndexScanner took too long to start", scanStartedLatch.await(5, TimeUnit.SECONDS));
    verifyContainerStorageStatsAndGetTotalValidSize(blobStoreStats, state.time.milliseconds());
    for (long i = state.beginningTime; i <= state.time.milliseconds() + TEST_TIME_INTERVAL_IN_MS;
        i += TEST_TIME_INTERVAL_IN_MS) {
      verifyAndGetLogSegmentValidSize(blobStoreStats, new TimeRange(i, 0L));
    }
    // advance time to let the added puts to expire
    long timeToLiveInMs = expiresAtInMs - state.time.milliseconds() < 0 ? 0 : expiresAtInMs - state.time.milliseconds();
    state.advanceTime(timeToLiveInMs + Time.MsPerSec);
    for (long i = state.beginningTime; i <= state.time.milliseconds() + TEST_TIME_INTERVAL_IN_MS;
        i += TEST_TIME_INTERVAL_IN_MS) {
      verifyAndGetLogSegmentValidSize(blobStoreStats, new TimeRange(i, 0L));
    }
    verifyContainerStorageStatsAndGetTotalValidSize(blobStoreStats, state.time.milliseconds());
    // advance time near the end of log segment forecast time
    state.advanceTime(logSegmentForecastEndTimeInMs - state.time.milliseconds() - 1);
    verifyAndGetLogSegmentValidSize(blobStoreStats, new TimeRange(state.time.milliseconds(), Time.MsPerSec));
    // advance time near the end of container forecast time
    state.advanceTime(containerForecastEndTimeInMs - state.time.milliseconds() - 1);
    verifyContainerStorageStatsAndGetTotalValidSize(blobStoreStats, state.time.milliseconds());
    assertEquals("Throttle count mismatch from expected value", expectedThrottleCount,
        mockThrottler.throttleCount.get());

    CountDownLatch secondScanStartedLatch = new CountDownLatch(1);
    mockThrottler = new MockThrottler(secondScanStartedLatch, new CountDownLatch(0));
    throttlers.put(BlobStoreStats.IO_SCHEDULER_JOB_TYPE, mockThrottler);
    blobStoreStats.onCompactionFinished();
    // Make sure index scanner starts again
    assertTrue("IndexScanner took too long to start", secondScanStartedLatch.await(5, TimeUnit.SECONDS));
    blobStoreStats.close();
  }
Basic test to verify that the {@link BlobStoreStats} can scan the index, populate the buckets and use these buckets
to report stats correctly.
@throws StoreException
@throws InterruptedException
ambry-store/src/test/java/com/github/ambry/store/BlobStoreStatsTest.java