/*
* Copyright 2014 LinkedIn Corp. All rights reserved
*
* 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.linkedin.databus.client.pub;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import org.apache.avro.Schema;
//import org.apache.commons.math3.stat.StatUtils;
import org.testng.annotations.Test;
import com.codahale.metrics.MergeableExponentiallyDecayingReservoir;
import com.linkedin.databus.client.pub.mbean.UnifiedClientStats;
import com.linkedin.databus.core.DbusConstants;
import com.linkedin.databus.core.DbusEvent;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusEventInfo;
import com.linkedin.databus.core.DbusEventKey;
import com.linkedin.databus.core.DbusEventV2Factory;
import com.linkedin.databus.core.DbusOpcode;
import com.linkedin.databus.core.KeyTypeNotImplementedException;
import com.linkedin.databus2.schemas.utils.SchemaHelper;
/**
* Mainly tests the histogram/percentile metrics of UnifiedClientStats, including multi-connection
* aggregation (merging).
*
* See also TestGenericDispatcher (unit test; tests numConsumerErrors) and TestRelayBootstrapSwitch
* (integration test; tests all remaining UnifiedClientStats metrics: curBootstrappingPartitions,
* curDeadConnections, numDataEvents, timeLagLastReceivedToNowMs).
*/
public class TestUnifiedClientStats
{
private static final Schema SOURCE1_SCHEMA =
Schema.parse("{\"name\":\"source1\",\"type\":\"record\",\"fields\":[{\"name\":\"s\",\"type\":\"string\"}]}");
private static final String SOURCE1_SCHEMA_STR = SOURCE1_SCHEMA.toString();
private static final byte[] SOURCE1_SCHEMAID = SchemaHelper.getSchemaId(SOURCE1_SCHEMA_STR);
private static final byte[] SOURCE1_PAYLOAD = new byte[] { 0x67, 0x72, 0x6f, 0x6e, 0x6b };
// alternatively: byte[] SOURCE1_PAYLOAD = javax.xml.bind.DatatypeConverter.parseHexBinary("67726f6e6b");
private DbusEventFactory _eventFactory = new DbusEventV2Factory();
private DbusEvent createEvent(long timestampNs)
{
DbusEventInfo eventInfo = new DbusEventInfo(DbusOpcode.UPSERT,
6004L, // SCN
(short) 1, // physical partition ID
(short) 1, // logical partition ID
timestampNs,
(short) 1, // srcId
SOURCE1_SCHEMAID, // payloadSchemaMd5
SOURCE1_PAYLOAD, // payload
false, // enableTracing
true); // autocommit
DbusEventKey key = new DbusEventKey("myKey".getBytes(Charset.forName("UTF-8")));
ByteBuffer buf = ByteBuffer.allocate(1000).order(ByteOrder.BIG_ENDIAN);
try
{
DbusEventFactory.serializeEvent(key, buf, eventInfo);
}
catch (KeyTypeNotImplementedException ex)
{
fail("string key type not supported by DbusEventV2Factory?!? " + ex.getLocalizedMessage());
}
return _eventFactory.createReadOnlyDbusEventFromBuffer(buf, 0);
}
/**
* Tests the basic (non-aggregated) functionality of the histogram/percentile metrics
* (timeLagSourceToReceiptMs and timeLagConsumerCallbacksMs).
*/
@Test
public void testBasicHistogramMetrics()
{
// (1) create stats object
UnifiedClientStats unifiedClientStats = new UnifiedClientStats(3 /* ownerId */, "stats_name", "stats_dim");
for (int i = 0; i < 200; ++i)
{
// Without the ability to override System.currentTimeMillis() (or hacking UnifiedClientStats to use an
// overridable method to provide the time, and then overriding it here), there's a small chance that
// our System.currentTimeMillis() call and that in registerDataEventReceived() will return values that
// differ by a non-constant amount (i.e., jitter). But we can manage that with inequalities in our
// assertions.
// Expected histogram values for timeLagSourceToReceiptMs range from 0 to 1990 ms (approximately).
long sourceTimestampNs = (System.currentTimeMillis() - 10*i) * DbusConstants.NUM_NSECS_IN_MSEC;
// We have perfect control over the values for timeLagConsumerCallbacksMs. Make calculations trivial:
// histogram values will be 0 through 199 ms (exactly).
long callbackTimeElapsedNs = (long)i * DbusConstants.NUM_NSECS_IN_MSEC;
// (2) create 200 fake DbusEvents
DbusEvent dbusEvent = createEvent(sourceTimestampNs);
// (3) call registerDataEventReceived() and registerCallbacksProcessed() for each event
// (normally there are more of the latter since there are more callback types than just onDataEvent(),
// but it doesn't really matter, and it simplifies things if we keep a fixed ratio--here just 1:1)
unifiedClientStats.registerDataEventReceived(dbusEvent);
unifiedClientStats.registerCallbacksProcessed(callbackTimeElapsedNs);
}
// (4) verify histogram values are as expected
// Both metrics-core and Apache Commons Math use the "R-6" quantile-estimation method, as described
// at http://en.wikipedia.org/wiki/Quantile .
//
// N = 200
// p = 0.5, 0.9, 0.95, 0.99
// h = (N+1)*p = 100.5, 180.9, 190.95, 198.99
//
// Q[50th] = x[100-1] + (100.5 - 100)*(x[100-1+1] - x[100-1]) = 99.0 + 0.5 *(100.0 - 99.0) = 99.5
// Q[90th] = x[180-1] + (180.9 - 180)*(x[180-1+1] - x[180-1]) = 179.0 + 0.9 *(180.0 - 179.0) = 179.9
// Q[95th] = x[190-1] + (190.95 - 190)*(x[190-1+1] - x[190-1]) = 189.0 + 0.95*(190.0 - 189.0) = 189.95
// Q[99th] = x[198-1] + (198.99 - 198)*(x[198-1+1] - x[198-1]) = 197.0 + 0.99*(198.0 - 197.0) = 197.99
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile",
99.5,
unifiedClientStats.getTimeLagConsumerCallbacksMs_HistPct_50());
assertEquals("unexpected timeLagConsumerCallbacksMs 90th percentile",
179.9,
unifiedClientStats.getTimeLagConsumerCallbacksMs_HistPct_90());
assertEquals("unexpected timeLagConsumerCallbacksMs 95th percentile",
189.95,
unifiedClientStats.getTimeLagConsumerCallbacksMs_HistPct_95());
assertEquals("unexpected timeLagConsumerCallbacksMs 99th percentile",
197.99,
unifiedClientStats.getTimeLagConsumerCallbacksMs_HistPct_99());
assertEquals("unexpected timeLagConsumerCallbacksMs max value",
199.0,
unifiedClientStats.getTimeLagConsumerCallbacksMs_Max());
// See sourceTimestampNs comment above. Approximately:
// Q[50th] = x[100-1] + (100.5 - 100)*(x[100-1+1] - x[100-1]) = 990.0 + 0.5 *(1000.0 - 990.0) = 995.0
// Q[90th] = x[180-1] + (180.9 - 180)*(x[180-1+1] - x[180-1]) = 1790.0 + 0.9 *(1800.0 - 1790.0) = 1799.0
// Q[95th] = x[190-1] + (190.95 - 190)*(x[190-1+1] - x[190-1]) = 1890.0 + 0.95*(1900.0 - 1890.0) = 1899.5
// Q[99th] = x[198-1] + (198.99 - 198)*(x[198-1+1] - x[198-1]) = 1970.0 + 0.99*(1980.0 - 1970.0) = 1979.9
// ...but allow +/-1 for jitter
double percentile = unifiedClientStats.getTimeLagSourceToReceiptMs_HistPct_50();
assertTrue("unexpected timeLagSourceToReceiptMs 50th percentile: " + percentile,
994.0 <= percentile && percentile <= 996.0); // nominal value is 995.0
percentile = unifiedClientStats.getTimeLagSourceToReceiptMs_HistPct_90();
assertTrue("unexpected timeLagSourceToReceiptMs 90th percentile: " + percentile,
1798.0 <= percentile && percentile <= 1800.0); // nominal value is 1799.0
percentile = unifiedClientStats.getTimeLagSourceToReceiptMs_HistPct_95();
assertTrue("unexpected timeLagSourceToReceiptMs 95th percentile: " + percentile,
1898.5 <= percentile && percentile <= 1900.5); // nominal value is 1899.5, but saw 1900.45 once
percentile = unifiedClientStats.getTimeLagSourceToReceiptMs_HistPct_99();
assertTrue("unexpected timeLagSourceToReceiptMs 99th percentile: " + percentile,
1978.9 <= percentile && percentile <= 1980.9); // nominal value is 1979.9
}
/**
* Tests aggregation (merging) of the timeLagSourceToReceiptMs histogram/percentile metric in the case
* that one of the connections is bootstrapping.
*
* Blast out 1000 data values for stats #1 and #2, but with the latter in bootstrap mode:
* timestampLastDataEventWasReceivedMs will be zero for stats #2 (and its reservoir empty), so
* merging it won't affect the aggregate value for timeLagSourceToReceiptMs; all such aggregate
* stats should be identical to those for stats #1. Also, all values for stats #2 should be -1.0,
* per our design spec. (This is similar to testHistogramMetricsAggregationDeadSourcesConnection().)
*/
@Test
public void testHistogramMetricsAggregationBootstrapMode()
{
// create stats objects: two low-level (per-connection) ones and one aggregator
UnifiedClientStats unifiedClientStats1 = new UnifiedClientStats(1 /* ownerId */, "test1", "dim1");
UnifiedClientStats unifiedClientStats2 = new UnifiedClientStats(2 /* ownerId */, "test2", "dim2");
UnifiedClientStats unifiedClientStatsAgg = new UnifiedClientStats(99 /* ownerId */, "testAgg", "dimAgg");
unifiedClientStats2.setBootstrappingState(true);
for (int i = 0; i < MergeableExponentiallyDecayingReservoir.DEFAULT_SIZE; ++i) // 1028
{
long now = System.currentTimeMillis();
long sourceTimestampNs1 = (now - 1000L - i) * DbusConstants.NUM_NSECS_IN_MSEC;
long sourceTimestampNs2 = (now - 5000L - i) * DbusConstants.NUM_NSECS_IN_MSEC;
unifiedClientStats1.registerDataEventReceived(createEvent(sourceTimestampNs1));
unifiedClientStats2.registerDataEventReceived(createEvent(sourceTimestampNs2));
}
unifiedClientStatsAgg.merge(unifiedClientStats1);
unifiedClientStatsAgg.merge(unifiedClientStats2);
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_50(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_90(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_95(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_99(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_99());
// bootstrap mode => should return -1.0 for all percentiles
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_99());
}
/**
* Tests aggregation (merging) of the timeLagSourceToReceiptMs histogram/percentile metric in the case
* that one of the connections is dead (i.e., no data events received).
*
* Blast out 1000 data values for stats #1 but none for stats #2 (in particular, no registerDataEventReceived()
* calls): timestampLastDataEventWasReceivedMs will be zero for stats #2 (and its reservoir empty), so
* merging it won't affect the aggregate value for timeLagSourceToReceiptMs; all such aggregate stats should
* be identical to those for stats #1. Also, all values for stats #2 should be -1.0, per our design spec.
* (This is similar to testHistogramMetricsAggregationBootstrapMode().)
*/
@Test
public void testHistogramMetricsAggregationDeadSourcesConnection()
{
// create stats objects: two low-level (per-connection) ones and one aggregator
UnifiedClientStats unifiedClientStats1 = new UnifiedClientStats(1 /* ownerId */, "test1", "dim1");
UnifiedClientStats unifiedClientStats2 = new UnifiedClientStats(2 /* ownerId */, "test2", "dim2");
UnifiedClientStats unifiedClientStatsAgg = new UnifiedClientStats(99 /* ownerId */, "testAgg", "dimAgg");
// could break this into two (or more) parts and do multiple merges, but not clear there's any point...
for (int i = 0; i < 2*MergeableExponentiallyDecayingReservoir.DEFAULT_SIZE; ++i) // 2*1028
{
long sourceTimestampNs1 = (System.currentTimeMillis() - 1000L - i) * DbusConstants.NUM_NSECS_IN_MSEC;
// no data events for connection #2 => no need for sourceTimestampNs2
unifiedClientStats1.registerDataEventReceived(createEvent(sourceTimestampNs1));
}
unifiedClientStatsAgg.merge(unifiedClientStats1);
unifiedClientStatsAgg.merge(unifiedClientStats2);
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_50(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_90(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_95(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for aggregated stats",
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_99(),
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_99());
// no data values => should return -1.0 for all percentiles
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_99());
}
/**
* Tests aggregation (merging) of the histogram/percentile metrics (timeLagSourceToReceiptMs and
* timeLagConsumerCallbacksMs) in the case that there have been no data events or callbacks, i.e.,
* there are no data points in the histogram reservoirs. All timeLagSourceToReceiptMs metrics
* and timeLagConsumerCallbacksMs metrics should be -1.0, per the design spec.
*/
@Test
public void testHistogramMetricsAggregationNoData()
{
UnifiedClientStats unifiedClientStats1 = new UnifiedClientStats(1 /* ownerId */, "test1", "dim1");
UnifiedClientStats unifiedClientStats2 = new UnifiedClientStats(2 /* ownerId */, "test2", "dim2");
UnifiedClientStats unifiedClientStatsAgg = new UnifiedClientStats(99 /* ownerId */, "testAgg", "dimAgg");
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_99());
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_99());
assertEquals("unexpected timeLagSourceToReceiptMs 50th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_50());
assertEquals("unexpected timeLagSourceToReceiptMs 90th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_90());
assertEquals("unexpected timeLagSourceToReceiptMs 95th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_95());
assertEquals("unexpected timeLagSourceToReceiptMs 99th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_99());
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_HistPct_50());
assertEquals("unexpected timeLagConsumerCallbacksMs 90th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_HistPct_90());
assertEquals("unexpected timeLagConsumerCallbacksMs 95th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_HistPct_95());
assertEquals("unexpected timeLagConsumerCallbacksMs 99th percentile for connection #1",
-1.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_HistPct_99());
assertEquals("unexpected timeLagConsumerCallbacksMs max for connection #1",
-1.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_Max());
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_HistPct_50());
assertEquals("unexpected timeLagConsumerCallbacksMs 90th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_HistPct_90());
assertEquals("unexpected timeLagConsumerCallbacksMs 95th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_HistPct_95());
assertEquals("unexpected timeLagConsumerCallbacksMs 99th percentile for connection #2",
-1.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_HistPct_99());
assertEquals("unexpected timeLagConsumerCallbacksMs max for connection #2",
-1.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_Max());
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_HistPct_50());
assertEquals("unexpected timeLagConsumerCallbacksMs 90th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_HistPct_90());
assertEquals("unexpected timeLagConsumerCallbacksMs 95th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_HistPct_95());
assertEquals("unexpected timeLagConsumerCallbacksMs 99th percentile for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_HistPct_99());
assertEquals("unexpected timeLagConsumerCallbacksMs max for aggregated stats",
-1.0,
unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_Max());
}
/**
* Tests aggregation (merging) of the histogram/percentile metrics (timeLagSourceToReceiptMs and
* timeLagConsumerCallbacksMs). This is basically the "happy path" case.
*
* Blast out 1000 low data values for stats #1 and 1000 high data values for stats #2 (interleaved so
* timestamps [and therefore priorities] are comparable), then merge and verify that max is within #2's
* range and that median falls between the two ranges. (There's no guarantee that #1's minimum or #2's
* maximum will survive, but roughly half of each one's values should, so the min and max are guaranteed
* to fall within #1's and #2's range, respectively.)
*/
@Test
public void testHistogramMetricsAggregationNonOverlappingRanges()
{
// create stats objects: two low-level (per-connection) ones and one aggregator
UnifiedClientStats unifiedClientStats1 = new UnifiedClientStats(1 /* ownerId */, "test1", "dim1");
UnifiedClientStats unifiedClientStats2 = new UnifiedClientStats(2 /* ownerId */, "test2", "dim2");
UnifiedClientStats unifiedClientStatsAgg = new UnifiedClientStats(99 /* ownerId */, "testAgg", "dimAgg");
for (int i = 0; i < MergeableExponentiallyDecayingReservoir.DEFAULT_SIZE; ++i) // 1028
{
// As noted in testBasicHistogramMetrics(), our dependence on System.currentTimeMillis() may lead
// to some jitter in the data values for timeLagSourceToReceiptMs.
long now = System.currentTimeMillis();
long sourceTimestampNs1 = (now - 1000L - i) * DbusConstants.NUM_NSECS_IN_MSEC;
long sourceTimestampNs2 = (now - 5000L - i) * DbusConstants.NUM_NSECS_IN_MSEC;
long callbackTimeElapsedNs1 = (long)i * DbusConstants.NUM_NSECS_IN_MSEC;
long callbackTimeElapsedNs2 = ((long)i + 2000L) * DbusConstants.NUM_NSECS_IN_MSEC;
DbusEvent dbusEvent1 = createEvent(sourceTimestampNs1);
DbusEvent dbusEvent2 = createEvent(sourceTimestampNs2);
unifiedClientStats1.registerDataEventReceived(dbusEvent1);
unifiedClientStats2.registerDataEventReceived(dbusEvent2);
unifiedClientStats1.registerCallbacksProcessed(callbackTimeElapsedNs1);
unifiedClientStats2.registerCallbacksProcessed(callbackTimeElapsedNs2);
}
unifiedClientStatsAgg.merge(unifiedClientStats1);
unifiedClientStatsAgg.merge(unifiedClientStats2);
// Expected timeLagConsumerCallbacksMs histogram values (exact):
// unifiedClientStats1: 0 to 1027 ms
// unifiedClientStats2: 2000 to 3027 ms
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile for connection #1",
513.5,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_HistPct_50());
assertEquals("unexpected timeLagConsumerCallbacksMs 50th percentile for connection #2",
2513.5,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_HistPct_50());
// The exact value depends on the relative fraction of '1' and '2' values that are retained in the
// aggregate. If equal, the value should be near 1513.5, but even then the exact value depends on
// whether the 1027 and 2000 values get bumped out of the aggregate. In the more common case that
// the fractions retained are unequal, the median will fall between two values near the top end of
// unifiedClientStats1 or near the bottom end of unifiedClientStats2. An allowance of 100 either
// way should be safe.
double percentile = unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_HistPct_50();
assertTrue("unexpected timeLagConsumerCallbacksMs 50th percentile for aggregated stats: " + percentile,
927.0 <= percentile && percentile <= 2100.0);
assertEquals("unexpected timeLagConsumerCallbacksMs max value for connection #1",
1027.0,
unifiedClientStats1.getTimeLagConsumerCallbacksMs_Max());
assertEquals("unexpected timeLagConsumerCallbacksMs max value for connection #2",
3027.0,
unifiedClientStats2.getTimeLagConsumerCallbacksMs_Max());
double max = unifiedClientStatsAgg.getTimeLagConsumerCallbacksMs_Max();
assertTrue("unexpected timeLagConsumerCallbacksMs max value for aggregated stats: " + max,
2000.0 <= max && max <= 3027.0); // nominal value is 3027.0
// Expected timeLagSourceToReceiptMs histogram values (approximate):
// unifiedClientStats1: 1000 to 2027 ms
// unifiedClientStats2: 5000 to 6027 ms
percentile = unifiedClientStats1.getTimeLagSourceToReceiptMs_HistPct_50();
assertTrue("unexpected timeLagSourceToReceiptMs 50th percentile for connection #1: " + percentile,
1512.5 <= percentile && percentile <= 1514.5); // nominal value is 1513.5
percentile = unifiedClientStats2.getTimeLagSourceToReceiptMs_HistPct_50();
assertTrue("unexpected timeLagSourceToReceiptMs 50th percentile for connection #2: " + percentile,
5512.5 <= percentile && percentile <= 5514.5); // nominal value is 5513.5
// same caveat as above: the median depends strongly on the relative proportion of unifiedClientStats1
// and unifiedClientStats2 data points retained in the aggregate, so the inequality is quite loose
percentile = unifiedClientStatsAgg.getTimeLagSourceToReceiptMs_HistPct_50();
assertTrue("unexpected timeLagSourceToReceiptMs 50th percentile for aggregated stats: " + percentile,
1927.0 <= percentile && percentile <= 5100.0);
}
}