💩 fix: Improve AvgSpeed calculation and add new test for speed variations over time.

This commit is contained in:
jarvis2f 2024-12-31 23:05:41 +08:00
parent a33b73b5c4
commit 27e1d6c46f
2 changed files with 85 additions and 19 deletions

View file

@ -15,15 +15,15 @@ public class AvgSpeed {
/**
* @param speed Speed since last point
*/
record SpeedPoint(long downloadedSize, long speed) {
record SpeedPoint(long downloadedSize, long speed, long timestamp) {
}
public AvgSpeed() {
this(15 * 60); // Default interval is 15 minutes
this(5 * 60); // Default interval is 5 minutes
}
public AvgSpeed(int interval) {
this(interval, 5);
this(interval, 6);
}
public AvgSpeed(int interval, int smoothingWindowSize) {
@ -40,7 +40,7 @@ public class AvgSpeed {
return;
}
// Calculate speed since last point
long speed = calculateSpeed(downloadedSize, timestamp);
long speed = calculateInstantSpeed(downloadedSize, timestamp);
// Apply smoothing if we have enough points
if (speedPoints.size() >= smoothingWindowSize) {
@ -48,28 +48,35 @@ public class AvgSpeed {
}
// Add new speed point
speedPoints.put(timestamp, new SpeedPoint(downloadedSize, speed));
speedPoints.put(timestamp, new SpeedPoint(downloadedSize, speed, timestamp));
// Remove old points outside the interval
long cutoffTime = timestamp - interval * 1000L; // Convert interval to milliseconds
speedPoints.headMap(cutoffTime).clear();
}
private long calculateSpeed(long downloadedSize, long timestamp) {
private long calculateInstantSpeed(long currentSize, long currentTime) {
if (speedPoints.isEmpty()) {
return 0;
}
Map.Entry<Long, SpeedPoint> lastEntry = speedPoints.lastEntry();
long timeDiff = timestamp - lastEntry.getKey();
// Find the earliest point within our smoothing window
int pointsToConsider = Math.min(smoothingWindowSize, speedPoints.size());
List<SpeedPoint> recentPoints = speedPoints.values().stream()
.skip(speedPoints.size() - pointsToConsider)
.toList();
SpeedPoint earliestPoint = recentPoints.getFirst();
long timeDiff = currentTime - earliestPoint.timestamp;
if (timeDiff <= 0) {
return 0;
}
long bytesDiff = downloadedSize - lastEntry.getValue().downloadedSize;
long bytesDiff = currentSize - earliestPoint.downloadedSize;
if (bytesDiff < 0) {
// Handle download restart
bytesDiff = downloadedSize;
bytesDiff = currentSize;
}
return (bytesDiff * 1000L) / timeDiff; // Speed in bytes per second
@ -105,19 +112,28 @@ public class AvgSpeed {
.orElse(0.0)
);
double upperThreshold = mean + (2 * standardDeviation);
double lowerThreshold = mean - (2 * standardDeviation);
double upperThreshold = mean + (3 * standardDeviation);
double lowerThreshold = mean - (3 * standardDeviation);
List<Long> filteredSpeeds = recentSpeeds.stream()
.filter(speed -> speed >= lowerThreshold && speed <= upperThreshold)
.toList();
return filteredSpeeds.isEmpty()
? currentSpeed
: (long) filteredSpeeds.stream()
.mapToLong(Long::longValue)
.average()
.orElse(currentSpeed);
if (filteredSpeeds.isEmpty()) {
return currentSpeed;
}
double totalWeight = 0;
double weightedSum = 0;
int size = filteredSpeeds.size();
for (int i = 0; i < size; i++) {
double weight = (i + 1.0) / size;
weightedSum += filteredSpeeds.get(i) * weight;
totalWeight += weight;
}
return (long) (weightedSum / totalWeight);
}
/**

View file

@ -91,7 +91,7 @@ class AvgSpeedTest {
AvgSpeed.SpeedStats stats = avgSpeed.getSpeedStats();
assertTrue(stats.maxSpeed() >= 300,
"Max speed should capture high speed period");
assertTrue(stats.minSpeed() <= 100,
assertTrue(stats.minSpeed() == 250,
"Min speed should capture low speed period");
}
@ -161,4 +161,54 @@ class AvgSpeedTest {
assertTrue(avgSpeed.getSpeed() > 0,
"Speed should be calculated correctly with large numbers");
}
@Test
void shouldShowDifferentSpeedsOverTime() {
// Given
AvgSpeed avgSpeed = new AvgSpeed(60); // 1 minute interval
long baseTime = System.currentTimeMillis();
// When - simulate download over 2 minutes with varying speeds
// First 30 seconds - constant speed
for (int i = 0; i < 30; i++) {
avgSpeed.update(1024L * 1024L * i, // 1MB increments
baseTime + (i * 1000L)); // 1 second intervals
}
// Record speeds at 30 second mark
AvgSpeed.SpeedStats stats30s = avgSpeed.getSpeedStats();
// Next 30 seconds - faster speed
for (int i = 30; i < 60; i++) {
avgSpeed.update(1024L * 1024L * (i * 2), // 2MB increments
baseTime + (i * 1000L)); // 1 second intervals
}
// Record speeds at 60 second mark
AvgSpeed.SpeedStats stats60s = avgSpeed.getSpeedStats();
// Another 30 seconds - slower speed
for (int i = 60; i < 90; i++) {
avgSpeed.update(1024L * 1024L * (i + 60), // slower increments
baseTime + (i * 1000L)); // 1 second intervals
}
// Record speeds at 90 second mark
AvgSpeed.SpeedStats stats90s = avgSpeed.getSpeedStats();
// 30s with big speed changes
for (int i = 90; i < 120; i++) {
avgSpeed.update(1024L * 1024L * (i * 4), // 4MB increments
baseTime + (i * 1000L)); // 1 second intervals
}
// Record speeds at 120 second mark
AvgSpeed.SpeedStats stats120s = avgSpeed.getSpeedStats();
// Then
System.out.println("30s stats: " + stats30s);
System.out.println("60s stats: " + stats60s);
System.out.println("90s stats: " + stats90s);
System.out.println("120s stats: " + stats120s);
}
}