@@ -1612,6 +1612,17 @@ void testInflightRetryCountHealthMetricExactlyOnce() throws Exception {
16121612 assertTrue (healthCheckFields .responseCodes .containsKey (Status .Code .OK .value ()));
16131613 assertEquals (3 , healthCheckFields .responseCodes .get (Status .Code .OK .value ()));
16141614 assertEquals ("projects/p1/datasets/d1/tables/t1/streams/s1" , healthCheckFields .streamName );
1615+
1616+ AppendRowsRequest secondRequest = testBigQueryWrite .getAppendRequests ().get (1 );
1617+ assertThat (secondRequest .getClientStats ().getRequestStats ().hasQueuedRequestsCount ()).isTrue ();
1618+ assertThat (secondRequest .getClientStats ().getRequestStats ().getQueuedRequestsCount ())
1619+ .isEqualTo (2 );
1620+ // When both requests are retried together in localQueue, only the first request in localQueue
1621+ // should have window_stats populated.
1622+ AppendRowsRequest retryFirstInLocalQueue = testBigQueryWrite .getAppendRequests ().get (2 );
1623+ AppendRowsRequest retrySecondInLocalQueue = testBigQueryWrite .getAppendRequests ().get (3 );
1624+ assertThat (retryFirstInLocalQueue .getClientStats ().hasWindowStats ()).isTrue ();
1625+ assertThat (retrySecondInLocalQueue .getClientStats ().hasWindowStats ()).isFalse ();
16151626 }
16161627
16171628 @ Test
@@ -1722,6 +1733,88 @@ void testEarliestSendTime_retryScenario() throws Exception {
17221733
17231734 // Once exhausted, state should be null
17241735 assertThat (connectionWorker .getEarliestSendTime ()).isNull ();
1736+
1737+ AppendRowsRequest initialRequest = testBigQueryWrite .getAppendRequests ().get (0 );
1738+ AppendRowsRequest retryRequest = testBigQueryWrite .getAppendRequests ().get (1 );
1739+ assertThat (initialRequest .getClientStats ().getRequestStats ().hasSendTimeMillis ()).isTrue ();
1740+ assertThat (retryRequest .getClientStats ().getRequestStats ().hasSendTimeMillis ()).isTrue ();
1741+ assertThat (retryRequest .getClientStats ().getRequestStats ().getSendTimeMillis ())
1742+ .isAtLeast (initialRequest .getClientStats ().getRequestStats ().getSendTimeMillis ());
1743+ assertThat (initialRequest .getClientStats ().getRequestStats ().hasQueuedRequestsCount ())
1744+ .isTrue ();
1745+ assertThat (initialRequest .getClientStats ().getRequestStats ().getQueuedRequestsCount ())
1746+ .isEqualTo (1 );
1747+ assertThat (retryRequest .getClientStats ().getRequestStats ().hasQueuedRequestsCount ()).isTrue ();
1748+ assertThat (retryRequest .getClientStats ().getRequestStats ().getQueuedRequestsCount ())
1749+ .isEqualTo (1 );
1750+ }
1751+ }
1752+
1753+ @ Test
1754+ void testWindowStats () throws Exception {
1755+ long beforeCreationMillis = Instant .now ().toEpochMilli ();
1756+ ProtoSchema schema1 = createProtoSchema ("foo" );
1757+ StreamWriter sw1 =
1758+ StreamWriter .newBuilder (TEST_STREAM_1 , client )
1759+ .setLocation ("us" )
1760+ .setWriterSchema (schema1 )
1761+ .build ();
1762+ try (ConnectionWorker connectionWorker = createConnectionWorker ()) {
1763+ long afterCreationMillis = Instant .now ().toEpochMilli ();
1764+ int msecResponseDelay = 300 ;
1765+ testBigQueryWrite .setResponseSleep (java .time .Duration .ofMillis (msecResponseDelay ));
1766+ testBigQueryWrite .addResponse (createAppendResponse (0 ));
1767+ testBigQueryWrite .addResponse (createAppendResponse (1 ));
1768+
1769+ // First request: no prior responses or wait times have been recorded yet.
1770+ ApiFuture <AppendRowsResponse > future1 =
1771+ sendTestMessage (connectionWorker , sw1 , createFooProtoRows (new String [] {"0" }), 0 );
1772+ future1 .get ();
1773+
1774+ AppendRowsRequest firstRequest = testBigQueryWrite .getAppendRequests ().get (0 );
1775+ long sizePerRequest = firstRequest .getProtoRows ().getSerializedSize ();
1776+ assertThat (firstRequest .getClientStats ().hasWindowStats ()).isTrue ();
1777+ ClientStats .WindowStats firstWindowStats = firstRequest .getClientStats ().getWindowStats ();
1778+ assertThat (firstWindowStats .hasMaxResponseLatencyMillis ()).isFalse ();
1779+ assertThat (firstWindowStats .hasAvgResponseLatencyMillis ()).isFalse ();
1780+ assertThat (firstWindowStats .hasLongestWaitNoResponseMillis ()).isFalse ();
1781+ assertThat (firstWindowStats .hasRequestsSentCount ()).isTrue ();
1782+ assertThat (firstWindowStats .getRequestsSentCount ()).isEqualTo (1 );
1783+ assertThat (firstWindowStats .hasResponsesReceivedCount ()).isFalse ();
1784+ assertThat (firstWindowStats .hasBytesSentCount ()).isTrue ();
1785+ assertThat (firstWindowStats .getBytesSentCount ()).isEqualTo (sizePerRequest );
1786+ assertThat (firstWindowStats .hasWindowStartTimeEpochMillis ()).isTrue ();
1787+ assertThat (firstWindowStats .getWindowStartTimeEpochMillis ()).isAtLeast (beforeCreationMillis );
1788+ assertThat (firstWindowStats .getWindowStartTimeEpochMillis ()).isAtMost (afterCreationMillis );
1789+ assertThat (firstWindowStats .hasWindowMillis ()).isTrue ();
1790+ assertThat (firstWindowStats .getWindowMillis ()).isAtLeast (0L );
1791+
1792+ // Second request: sent after firstRequest waited in-flight (>100ms) and received a response
1793+ // (>=300ms latency), so all expected WindowStats fields are now non-zero and populated.
1794+ ApiFuture <AppendRowsResponse > future2 =
1795+ sendTestMessage (connectionWorker , sw1 , createFooProtoRows (new String [] {"1" }), 1 );
1796+ future2 .get ();
1797+
1798+ AppendRowsRequest secondRequest = testBigQueryWrite .getAppendRequests ().get (1 );
1799+ assertThat (secondRequest .getClientStats ().hasWindowStats ()).isTrue ();
1800+ ClientStats .WindowStats secondWindowStats = secondRequest .getClientStats ().getWindowStats ();
1801+ assertThat (secondWindowStats .hasMaxResponseLatencyMillis ()).isTrue ();
1802+ assertThat (secondWindowStats .getMaxResponseLatencyMillis ()).isAtLeast (msecResponseDelay );
1803+ assertThat (secondWindowStats .hasAvgResponseLatencyMillis ()).isTrue ();
1804+ assertThat (secondWindowStats .getAvgResponseLatencyMillis ()).isAtLeast (msecResponseDelay );
1805+ assertThat (secondWindowStats .hasLongestWaitNoResponseMillis ()).isTrue ();
1806+ assertThat (secondWindowStats .getLongestWaitNoResponseMillis ()).isGreaterThan (0L );
1807+ assertThat (secondWindowStats .hasRequestsSentCount ()).isTrue ();
1808+ assertThat (secondWindowStats .getRequestsSentCount ()).isEqualTo (2 );
1809+ assertThat (secondWindowStats .hasResponsesReceivedCount ()).isTrue ();
1810+ assertThat (secondWindowStats .getResponsesReceivedCount ()).isEqualTo (1 );
1811+ assertThat (secondWindowStats .hasBytesSentCount ()).isTrue ();
1812+ assertThat (secondWindowStats .getBytesSentCount ()).isEqualTo (2 * sizePerRequest );
1813+ assertThat (secondWindowStats .hasWindowStartTimeEpochMillis ()).isTrue ();
1814+ assertThat (secondWindowStats .getWindowStartTimeEpochMillis ())
1815+ .isEqualTo (firstWindowStats .getWindowStartTimeEpochMillis ());
1816+ assertThat (secondWindowStats .hasWindowMillis ()).isTrue ();
1817+ assertThat (secondWindowStats .getWindowMillis ()).isAtLeast (msecResponseDelay );
17251818 }
17261819 }
17271820}
0 commit comments