Repository: gchq/sleeper
Issue: Average rates in reports include runs with no records

### Description

The average record rate is computed as `NaN` if runs are present with no records. 
Here is an example of a report where the calculation was incorrect: [compactionPerformance.report.log](https://github.com/gchq/sleeper/files/12367795/compactionPerformance.report.2.log)

### Steps to reproduce

1. Run a compaction job on a compaction task
2. Run a compaction task that wont pick up any jobs
3. Run compaction task status report
4. Observe the average record rate is `NaN`

### Expected behaviour

We should filter runs with no records out of the average record rate calculations



Changed files:
java/core/src/main/java/sleeper/core/record/process/AverageRecordRate.java, java/core/src/test/java/sleeper/core/record/process/AverageRecordRateTest.java

diff --git a/java/core/src/main/java/sleeper/core/record/process/AverageRecordRate.java b/java/core/src/main/java/sleeper/core/record/process/AverageRecordRate.java
index 0218ef974..7941a1a6c 100644
--- a/java/core/src/main/java/sleeper/core/record/process/AverageRecordRate.java
+++ b/java/core/src/main/java/sleeper/core/record/process/AverageRecordRate.java
@@ -113,8 +113,12 @@ public class AverageRecordRate {
             recordsRead += summary.getRecordsRead();
             recordsWritten += summary.getRecordsWritten();
             totalRunDuration = totalRunDuration.plus(summary.getTimeInProcess());
-            totalRecordsReadPerSecond += summary.getRecordsReadPerSecond();
-            totalRecordsWrittenPerSecond += summary.getRecordsWrittenPerSecond();
+            if (Double.isFinite(summary.getRecordsReadPerSecond())) {
+                totalRecordsReadPerSecond += summary.getRecordsReadPerSecond();
+            }
+            if (Double.isFinite(summary.getRecordsWrittenPerSecond())) {
+                totalRecordsWrittenPerSecond += summary.getRecordsWrittenPerSecond();
+            }
             return this;
         }
 
diff --git a/java/core/src/test/java/sleeper/core/record/process/AverageRecordRateTest.java b/java/core/src/test/java/sleeper/core/record/process/AverageRecordRateTest.java
index 7beffc9ad..8dd6a280f 100644
--- a/java/core/src/test/java/sleeper/core/record/process/AverageRecordRateTest.java
+++ b/java/core/src/test/java/sleeper/core/record/process/AverageRecordRateTest.java
@@ -110,6 +110,54 @@ public class AverageRecordRateTest {
         ).containsExactly(1, 100L, 100L, Duration.ofSeconds(20), 5.0, 5.0, 10.0, 10.0);
     }
 
+    @Test
+    void shouldExcludeRunsWithZeroRecordsReadFromAverageRateCalculation() {
+        AverageRecordRate rate = rateFrom(
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(0L, 10L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(10)),
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(10L, 10L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(10)));
+
+        assertThat(rate).extracting("runCount", "recordsRead", "recordsWritten", "totalDuration",
+                "recordsReadPerSecond", "recordsWrittenPerSecond",
+                "averageRunRecordsReadPerSecond", "averageRunRecordsWrittenPerSecond"
+        ).containsExactly(2, 10L, 20L, Duration.ofSeconds(20), 0.5, 1.0, 0.5, 1.0);
+    }
+
+    @Test
+    void shouldExcludeRunsWithZeroRecordsWrittenFromAverageRateCalculation() {
+        AverageRecordRate rate = rateFrom(
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(10L, 0L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(10)),
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(10L, 10L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(10)));
+
+        assertThat(rate).extracting("runCount", "recordsRead", "recordsWritten", "totalDuration",
+                "recordsReadPerSecond", "recordsWrittenPerSecond",
+                "averageRunRecordsReadPerSecond", "averageRunRecordsWrittenPerSecond"
+        ).containsExactly(2, 20L, 10L, Duration.ofSeconds(20), 1.0, 0.5, 1.0, 0.5);
+    }
+
+    @Test
+    void shouldExcludeRunsWithZeroDurationFromAverageRateCalculation() {
+        AverageRecordRate rate = rateFrom(
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(10L, 10L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(0)),
+                new RecordsProcessedSummary(
+                        new RecordsProcessed(10L, 10L),
+                        Instant.parse("2022-10-13T10:18:00.000Z"), Duration.ofSeconds(10)));
+
+        assertThat(rate).extracting("runCount", "recordsRead", "recordsWritten", "totalDuration",
+                "recordsReadPerSecond", "recordsWrittenPerSecond",
+                "averageRunRecordsReadPerSecond", "averageRunRecordsWrittenPerSecond"
+        ).containsExactly(2, 20L, 20L, Duration.ofSeconds(10), 2.0, 2.0, 0.5, 0.5);
+    }
+
     private static AverageRecordRate rateFrom(RecordsProcessedSummary... summaries) {
         return AverageRecordRate.of(Stream.of(summaries)
                 .map(summary -> ProcessRun.finished(DEFAULT_TASK_ID,