From e931c3228244f976ce598f11a2ac78f5d2d6fa86 Mon Sep 17 00:00:00 2001 From: Lucas Pedroza Date: Fri, 25 Oct 2024 14:51:09 -0700 Subject: [PATCH 1/2] Add stats collector interface and impl --- src/main/java/com/fauna/client/Stats.java | 59 ++++++++ .../java/com/fauna/client/StatsCollector.java | 24 +++ .../com/fauna/client/StatsCollectorImpl.java | 93 ++++++++++++ .../fauna/client/TestStatsCollectorImpl.java | 140 ++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 src/main/java/com/fauna/client/Stats.java create mode 100644 src/main/java/com/fauna/client/StatsCollector.java create mode 100644 src/main/java/com/fauna/client/StatsCollectorImpl.java create mode 100644 src/test/java/com/fauna/client/TestStatsCollectorImpl.java diff --git a/src/main/java/com/fauna/client/Stats.java b/src/main/java/com/fauna/client/Stats.java new file mode 100644 index 00000000..71bb0f56 --- /dev/null +++ b/src/main/java/com/fauna/client/Stats.java @@ -0,0 +1,59 @@ +package com.fauna.client; + +public final class Stats { + private final long readOps; + private final long computeOps; + private final long writeOps; + private final long queryTimeMs; + private final int contentionRetries; + private final long storageBytesRead; + private final long storageBytesWrite; + private final long processingTimeMs; + private final int queryCount; + + private final int rateLimitedReadQueryCount; + private final int rateLimitedComputeQueryCount; + private final int rateLimitedWriteQueryCount; + + public Stats( + long readOps, + long computeOps, + long writeOps, + long queryTimeMs, + int contentionRetries, + long storageBytesRead, + long storageBytesWrite, + long processingTimeMs, + int queryCount, + int rateLimitedReadQueryCount, + int rateLimitedComputeQueryCount, + int rateLimitedWriteQueryCount + ) { + this.readOps = readOps; + this.computeOps = computeOps; + this.writeOps = writeOps; + this.queryTimeMs = queryTimeMs; + this.contentionRetries = contentionRetries; + this.storageBytesRead = storageBytesRead; + this.storageBytesWrite = storageBytesWrite; + this.processingTimeMs = processingTimeMs; + this.queryCount = queryCount; + this.rateLimitedReadQueryCount = rateLimitedReadQueryCount; + this.rateLimitedComputeQueryCount = rateLimitedComputeQueryCount; + this.rateLimitedWriteQueryCount = rateLimitedWriteQueryCount; + } + + public long getReadOps() { return readOps; } + public long getComputeOps() { return computeOps; } + public long getWriteOps() { return writeOps; } + public long getQueryTimeMs() { return queryTimeMs; } + public int getContentionRetries() { return contentionRetries; } + public long getStorageBytesRead() { return storageBytesRead; } + public long getStorageBytesWrite() { return storageBytesWrite; } + public long getProcessingTimeMs() { return processingTimeMs; } + public int getQueryCount() { return queryCount; } + + public int getRateLimitedReadQueryCount() { return rateLimitedReadQueryCount; } + public int getRateLimitedComputeQueryCount() { return rateLimitedComputeQueryCount; } + public int getRateLimitedWriteQueryCount() { return rateLimitedWriteQueryCount; } +} diff --git a/src/main/java/com/fauna/client/StatsCollector.java b/src/main/java/com/fauna/client/StatsCollector.java new file mode 100644 index 00000000..c9e90baf --- /dev/null +++ b/src/main/java/com/fauna/client/StatsCollector.java @@ -0,0 +1,24 @@ +package com.fauna.client; + +import com.fauna.response.QueryStats; + +public interface StatsCollector { + + /** + * Add the QueryStats to the current counts. + * @param stats QueryStats object + */ + void add(QueryStats stats); + + /** + * Return the collected Stats. + * @return Stats object + */ + Stats read(); + + /** + * Return the collected Stats and reset counts. + * @return Stats object + */ + Stats readAndReset(); +} diff --git a/src/main/java/com/fauna/client/StatsCollectorImpl.java b/src/main/java/com/fauna/client/StatsCollectorImpl.java new file mode 100644 index 00000000..995a688b --- /dev/null +++ b/src/main/java/com/fauna/client/StatsCollectorImpl.java @@ -0,0 +1,93 @@ +package com.fauna.client; + +import com.fauna.response.QueryStats; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class StatsCollectorImpl implements StatsCollector { + + private static final String RATE_LIMIT_READ_OPS = "read"; + private static final String RATE_LIMIT_COMPUTE_OPS = "compute"; + private static final String RATE_LIMIT_WRITE_OPS = "write"; + + private final AtomicLong readOps = new AtomicLong(); + private final AtomicLong computeOps = new AtomicLong(); + private final AtomicLong writeOps = new AtomicLong(); + private final AtomicLong queryTimeMs = new AtomicLong(); + private final AtomicInteger contentionRetries = new AtomicInteger(); + private final AtomicLong storageBytesRead = new AtomicLong(); + private final AtomicLong storageBytesWrite = new AtomicLong(); + private final AtomicLong processingTimeMs = new AtomicLong(); + private final AtomicInteger queryCount = new AtomicInteger(); + private final AtomicInteger rateLimitedReadQueryCount = new AtomicInteger(); + private final AtomicInteger rateLimitedComputeQueryCount = new AtomicInteger(); + private final AtomicInteger rateLimitedWriteQueryCount = new AtomicInteger(); + + @Override + public void add(QueryStats stats) { + readOps.addAndGet(stats.readOps); + computeOps.addAndGet(stats.computeOps); + writeOps.addAndGet(stats.writeOps); + queryTimeMs.addAndGet(stats.queryTimeMs); + contentionRetries.addAndGet(stats.contentionRetries); + storageBytesRead.addAndGet(stats.storageBytesRead); + storageBytesWrite.addAndGet(stats.storageBytesWrite); + processingTimeMs.addAndGet(stats.processingTimeMs); + + List rateLimitsHit = stats.rateLimitsHit; + rateLimitsHit.forEach(limitHit -> { + switch (limitHit) { + case RATE_LIMIT_READ_OPS: + rateLimitedReadQueryCount.incrementAndGet(); + break; + case RATE_LIMIT_COMPUTE_OPS: + rateLimitedComputeQueryCount.incrementAndGet(); + break; + case RATE_LIMIT_WRITE_OPS: + rateLimitedWriteQueryCount.incrementAndGet(); + break; + } + }); + + queryCount.incrementAndGet(); + } + + @Override + public Stats read() { + return new Stats( + readOps.get(), + computeOps.get(), + writeOps.get(), + queryTimeMs.get(), + contentionRetries.get(), + storageBytesRead.get(), + storageBytesWrite.get(), + processingTimeMs.get(), + queryCount.get(), + rateLimitedReadQueryCount.get(), + rateLimitedComputeQueryCount.get(), + rateLimitedWriteQueryCount.get() + ); + } + + @Override + public Stats readAndReset() { + return new Stats( + readOps.getAndSet(0), + computeOps.getAndSet(0), + writeOps.getAndSet(0), + queryTimeMs.getAndSet(0), + contentionRetries.getAndSet(0), + storageBytesRead.getAndSet(0), + storageBytesWrite.getAndSet(0), + processingTimeMs.getAndSet(0), + queryCount.getAndSet(0), + rateLimitedReadQueryCount.getAndSet(0), + rateLimitedComputeQueryCount.getAndSet(0), + rateLimitedWriteQueryCount.getAndSet(0) + ); + } +} + diff --git a/src/test/java/com/fauna/client/TestStatsCollectorImpl.java b/src/test/java/com/fauna/client/TestStatsCollectorImpl.java new file mode 100644 index 00000000..7dd96d84 --- /dev/null +++ b/src/test/java/com/fauna/client/TestStatsCollectorImpl.java @@ -0,0 +1,140 @@ +package com.fauna.client; + +import com.fauna.response.QueryStats; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.Collections; + +public class TestStatsCollectorImpl { + + private StatsCollectorImpl statsCollector; + + @BeforeEach + public void setUp() { + statsCollector = new StatsCollectorImpl(); + } + + @Test + public void testAdd_singleQueryStats_updatesCorrectly() { + // Arrange + QueryStats stats = new QueryStats( + 10, + 20, + 5, + 100, + 1, + 500, + 300, + 50, + Arrays.asList("read", "compute") + ); + + // Act + statsCollector.add(stats); + + // Assert + Stats result = statsCollector.read(); + assertEquals(10, result.getComputeOps()); + assertEquals(20, result.getReadOps()); + assertEquals(5, result.getWriteOps()); + assertEquals(100, result.getQueryTimeMs()); + assertEquals(1, result.getContentionRetries()); + assertEquals(500, result.getStorageBytesRead()); + assertEquals(300, result.getStorageBytesWrite()); + assertEquals(50, result.getProcessingTimeMs()); + assertEquals(1, result.getQueryCount()); + assertEquals(1, result.getRateLimitedReadQueryCount()); + assertEquals(1, result.getRateLimitedComputeQueryCount()); + assertEquals(0, result.getRateLimitedWriteQueryCount()); + } + + @Test + public void testAdd_multipleQueryStats_accumulatesValuesCorrectly() { + // Arrange + QueryStats stats1 = new QueryStats(10, 20, 5, 100, 1, 500, 300, 30, Collections.singletonList("read")); + QueryStats stats2 = new QueryStats(15, 25, 10, 200, 2, 600, 400, 40, Collections.singletonList("write")); + + // Act + statsCollector.add(stats1); + statsCollector.add(stats2); + + // Assert + Stats result = statsCollector.read(); + assertEquals(25, result.getComputeOps()); + assertEquals(45, result.getReadOps()); + assertEquals(15, result.getWriteOps()); + assertEquals(300, result.getQueryTimeMs()); + assertEquals(3, result.getContentionRetries()); + assertEquals(1100, result.getStorageBytesRead()); + assertEquals(700, result.getStorageBytesWrite()); + assertEquals(70, result.getProcessingTimeMs()); + assertEquals(2, result.getQueryCount()); + assertEquals(1, result.getRateLimitedReadQueryCount()); + assertEquals(0, result.getRateLimitedComputeQueryCount()); + assertEquals(1, result.getRateLimitedWriteQueryCount()); + } + + @Test + public void testRead_initialStats_returnsZeroStats() { + // Act + Stats result = statsCollector.read(); + + // Assert + assertEquals(0, result.getComputeOps()); + assertEquals(0, result.getReadOps()); + assertEquals(0, result.getWriteOps()); + assertEquals(0, result.getQueryTimeMs()); + assertEquals(0, result.getContentionRetries()); + assertEquals(0, result.getStorageBytesRead()); + assertEquals(0, result.getStorageBytesWrite()); + assertEquals(0, result.getProcessingTimeMs()); + assertEquals(0, result.getQueryCount()); + assertEquals(0, result.getRateLimitedReadQueryCount()); + assertEquals(0, result.getRateLimitedComputeQueryCount()); + assertEquals(0, result.getRateLimitedWriteQueryCount()); + } + + @Test + public void testReadAndReset_returnsAndResetsStats() { + // Arrange + QueryStats stats = new QueryStats( + 10, 20, 5, 100, 1, 500, 300, 75, Arrays.asList("read", "write") + ); + statsCollector.add(stats); + + // Act + Stats beforeReset = statsCollector.readAndReset(); + Stats afterReset = statsCollector.read(); + + // Assert the stats before reset + assertEquals(10, beforeReset.getComputeOps()); + assertEquals(20, beforeReset.getReadOps()); + assertEquals(5, beforeReset.getWriteOps()); + assertEquals(100, beforeReset.getQueryTimeMs()); + assertEquals(1, beforeReset.getContentionRetries()); + assertEquals(500, beforeReset.getStorageBytesRead()); + assertEquals(300, beforeReset.getStorageBytesWrite()); + assertEquals(75, beforeReset.getProcessingTimeMs()); + assertEquals(1, beforeReset.getQueryCount()); + assertEquals(1, beforeReset.getRateLimitedReadQueryCount()); + assertEquals(0, beforeReset.getRateLimitedComputeQueryCount()); + assertEquals(1, beforeReset.getRateLimitedWriteQueryCount()); + + // Assert the stats after reset + assertEquals(0, afterReset.getReadOps()); + assertEquals(0, afterReset.getComputeOps()); + assertEquals(0, afterReset.getWriteOps()); + assertEquals(0, afterReset.getQueryTimeMs()); + assertEquals(0, afterReset.getContentionRetries()); + assertEquals(0, afterReset.getStorageBytesRead()); + assertEquals(0, afterReset.getStorageBytesWrite()); + assertEquals(0, afterReset.getProcessingTimeMs()); + assertEquals(0, afterReset.getQueryCount()); + assertEquals(0, afterReset.getRateLimitedReadQueryCount()); + assertEquals(0, afterReset.getRateLimitedComputeQueryCount()); + assertEquals(0, afterReset.getRateLimitedWriteQueryCount()); + } +} \ No newline at end of file From 9e8c0f9c50117b93ac61096a50e40369653a6247 Mon Sep 17 00:00:00 2001 From: Lucas Pedroza Date: Tue, 29 Oct 2024 14:32:50 +0100 Subject: [PATCH 2/2] Rename and document Stats --- .../{Stats.java => QueryStatsSummary.java} | 68 ++++++++++++++++++- .../java/com/fauna/client/StatsCollector.java | 4 +- .../com/fauna/client/StatsCollectorImpl.java | 8 +-- .../fauna/client/TestStatsCollectorImpl.java | 10 +-- 4 files changed, 77 insertions(+), 13 deletions(-) rename src/main/java/com/fauna/client/{Stats.java => QueryStatsSummary.java} (54%) diff --git a/src/main/java/com/fauna/client/Stats.java b/src/main/java/com/fauna/client/QueryStatsSummary.java similarity index 54% rename from src/main/java/com/fauna/client/Stats.java rename to src/main/java/com/fauna/client/QueryStatsSummary.java index 71bb0f56..4e10a9cd 100644 --- a/src/main/java/com/fauna/client/Stats.java +++ b/src/main/java/com/fauna/client/QueryStatsSummary.java @@ -1,6 +1,12 @@ package com.fauna.client; -public final class Stats { +/** + * A class for representing aggregate query stats. This should be used when collecting query stats + * across multiple requests. + *

+ * For a single request, use @link com.fauna.response.QueryStats instead. + */ +public final class QueryStatsSummary { private final long readOps; private final long computeOps; private final long writeOps; @@ -15,7 +21,7 @@ public final class Stats { private final int rateLimitedComputeQueryCount; private final int rateLimitedWriteQueryCount; - public Stats( + public QueryStatsSummary( long readOps, long computeOps, long writeOps, @@ -43,17 +49,75 @@ public Stats( this.rateLimitedWriteQueryCount = rateLimitedWriteQueryCount; } + /** + * Gets the aggregate read ops. + * @return A long representing the aggregate read ops + */ public long getReadOps() { return readOps; } + + /** + * Gets the aggregate compute ops. + * @return A long representing the aggregate compute ops + */ public long getComputeOps() { return computeOps; } + + /** + * Gets the aggregate write ops. + * @return A long representing the aggregate write ops + */ public long getWriteOps() { return writeOps; } + + /** + * Gets the aggregate query time in milliseconds. + * @return A long representing the aggregate query time in milliseconds. + */ public long getQueryTimeMs() { return queryTimeMs; } + + /** + * Gets the count of retries due to contention. + * @return An int representing the count of retries due to contention. + */ public int getContentionRetries() { return contentionRetries; } + + /** + * Gets the aggregate storage bytes read. + * @return A long representing the aggregate number of storage bytes read. + */ public long getStorageBytesRead() { return storageBytesRead; } + + /** + * Gets the aggregate storage bytes written. + * @return A long representing the aggregate number of storage bytes written. + */ public long getStorageBytesWrite() { return storageBytesWrite; } + + /** + * Gets the aggregate processing time in milliseconds. + * @return A long representing the aggregate processing time in milliseconds. + */ public long getProcessingTimeMs() { return processingTimeMs; } + + /** + * Gets the count of queries summarized on this instance. + * @return An int representing the count of queries summarized. + */ public int getQueryCount() { return queryCount; } + /** + * Gets the count of rate limited queries due to read limits. + * @return An int representing the count of rate limited queries. + */ public int getRateLimitedReadQueryCount() { return rateLimitedReadQueryCount; } + + /** + * Gets the count of rate limited queries due to compute limits. + * @return An int representing the count of rate limited queries. + */ public int getRateLimitedComputeQueryCount() { return rateLimitedComputeQueryCount; } + + /** + * Gets the count of rate limited queries due to write limits. + * @return An int representing the count of rate limited queries. + */ public int getRateLimitedWriteQueryCount() { return rateLimitedWriteQueryCount; } } diff --git a/src/main/java/com/fauna/client/StatsCollector.java b/src/main/java/com/fauna/client/StatsCollector.java index c9e90baf..07321c9e 100644 --- a/src/main/java/com/fauna/client/StatsCollector.java +++ b/src/main/java/com/fauna/client/StatsCollector.java @@ -14,11 +14,11 @@ public interface StatsCollector { * Return the collected Stats. * @return Stats object */ - Stats read(); + QueryStatsSummary read(); /** * Return the collected Stats and reset counts. * @return Stats object */ - Stats readAndReset(); + QueryStatsSummary readAndReset(); } diff --git a/src/main/java/com/fauna/client/StatsCollectorImpl.java b/src/main/java/com/fauna/client/StatsCollectorImpl.java index 995a688b..2c8da78d 100644 --- a/src/main/java/com/fauna/client/StatsCollectorImpl.java +++ b/src/main/java/com/fauna/client/StatsCollectorImpl.java @@ -55,8 +55,8 @@ public void add(QueryStats stats) { } @Override - public Stats read() { - return new Stats( + public QueryStatsSummary read() { + return new QueryStatsSummary( readOps.get(), computeOps.get(), writeOps.get(), @@ -73,8 +73,8 @@ public Stats read() { } @Override - public Stats readAndReset() { - return new Stats( + public QueryStatsSummary readAndReset() { + return new QueryStatsSummary( readOps.getAndSet(0), computeOps.getAndSet(0), writeOps.getAndSet(0), diff --git a/src/test/java/com/fauna/client/TestStatsCollectorImpl.java b/src/test/java/com/fauna/client/TestStatsCollectorImpl.java index 7dd96d84..e8036541 100644 --- a/src/test/java/com/fauna/client/TestStatsCollectorImpl.java +++ b/src/test/java/com/fauna/client/TestStatsCollectorImpl.java @@ -36,7 +36,7 @@ public void testAdd_singleQueryStats_updatesCorrectly() { statsCollector.add(stats); // Assert - Stats result = statsCollector.read(); + QueryStatsSummary result = statsCollector.read(); assertEquals(10, result.getComputeOps()); assertEquals(20, result.getReadOps()); assertEquals(5, result.getWriteOps()); @@ -62,7 +62,7 @@ public void testAdd_multipleQueryStats_accumulatesValuesCorrectly() { statsCollector.add(stats2); // Assert - Stats result = statsCollector.read(); + QueryStatsSummary result = statsCollector.read(); assertEquals(25, result.getComputeOps()); assertEquals(45, result.getReadOps()); assertEquals(15, result.getWriteOps()); @@ -80,7 +80,7 @@ public void testAdd_multipleQueryStats_accumulatesValuesCorrectly() { @Test public void testRead_initialStats_returnsZeroStats() { // Act - Stats result = statsCollector.read(); + QueryStatsSummary result = statsCollector.read(); // Assert assertEquals(0, result.getComputeOps()); @@ -106,8 +106,8 @@ public void testReadAndReset_returnsAndResetsStats() { statsCollector.add(stats); // Act - Stats beforeReset = statsCollector.readAndReset(); - Stats afterReset = statsCollector.read(); + QueryStatsSummary beforeReset = statsCollector.readAndReset(); + QueryStatsSummary afterReset = statsCollector.read(); // Assert the stats before reset assertEquals(10, beforeReset.getComputeOps());