Skip to content

Commit f3c53f5

Browse files
committed
Apply rate limits:
* send only most recent data if it is coming in faster than it can be uploaded/timeout (due to API outage etc) so avoid backlog * drop really stale data (age > 1 hour) * drop really fast data (interval < 1 minute)
1 parent bdb31b3 commit f3c53f5

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

sensor-data-bridge/src/main/java/nl/bertriksikken/pm/SensorData.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package nl.bertriksikken.pm;
22

3+
import java.time.Instant;
34
import java.util.LinkedHashMap;
45
import java.util.Map;
56
import java.util.stream.Collectors;
@@ -11,6 +12,15 @@ public final class SensorData {
1112

1213
// start with a simple map containing one Number value per item
1314
private final Map<ESensorItem, Number> items = new LinkedHashMap<>();
15+
private final Instant timestamp;
16+
17+
public SensorData() {
18+
this.timestamp = Instant.now();
19+
}
20+
21+
public Instant getCreationTime() {
22+
return timestamp;
23+
}
1424

1525
public void putValue(ESensorItem item, Number value) {
1626
if (value == null) {

sensor-data-bridge/src/main/java/nl/bertriksikken/senscom/SensComWorker.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.HashMap;
2323
import java.util.Map;
2424
import java.util.Objects;
25+
import java.util.concurrent.ConcurrentHashMap;
2526
import java.util.concurrent.ExecutorService;
2627
import java.util.concurrent.Executors;
2728

@@ -41,6 +42,10 @@ final class SensComWorker {
4142

4243
// map from device id to sensor.community id
4344
private final Map<AppDeviceId, String> sensComIds = new HashMap<>();
45+
// map with only *latest* measurement per AppDeviceId
46+
private final Map<AppDeviceId, SensorData> measurements = new ConcurrentHashMap<>();
47+
// map from senscom id -> last upload time
48+
private final Map<String, Instant> lastUploadTimes = new HashMap<>();
4449

4550
SensComWorker(ObjectMapper mapper, ISensComApi restClient, String softwareVersion, String appId) {
4651
this.mapper = Objects.requireNonNull(mapper);
@@ -76,18 +81,49 @@ void stop() {
7681

7782
// schedules an upload to all pins
7883
void scheduleUpload(AppDeviceId appDeviceId, SensorData data) {
79-
executor.execute(new CatchingRunnable(LOG, () -> performUpload(appDeviceId, data)));
84+
// store measurement in cache of most-recent data and schedule upload
85+
SensorData oldData = measurements.put(appDeviceId, data);
86+
if (oldData != null) {
87+
LOG.warn("Replaced old sensor data for '{}'", appDeviceId);
88+
}
89+
executor.execute(new CatchingRunnable(LOG, () -> performUpload(appDeviceId)));
8090
}
8191

8292
// uploads to all pins, runs on our executor
83-
private void performUpload(AppDeviceId appDeviceId, SensorData data) {
93+
private void performUpload(AppDeviceId appDeviceId) {
94+
// check if we still have data to upload, or it was already sent
95+
SensorData data = measurements.remove(appDeviceId);
96+
if (data == null) {
97+
return;
98+
}
99+
84100
// look up custom sensor.community id
85101
String sensorId = sensComIds.getOrDefault(appDeviceId, "");
86102
if (sensorId.isEmpty()) {
87-
// no sensor.community id found, so no upload
88103
return;
89104
}
90105

106+
// drop stale data
107+
Duration age = Duration.between(data.getCreationTime(), Instant.now());
108+
LOG.info("Sensor data age {} before upload for {}", age, appDeviceId);
109+
if (age.toSeconds() > 3600) {
110+
LOG.warn("Dropped stale data for {}: {}", appDeviceId, data);
111+
return;
112+
}
113+
114+
// rate limit on sensor.community id
115+
Instant now = Instant.now();
116+
Instant lastUpload = lastUploadTimes.get(sensorId);
117+
if (lastUpload != null) {
118+
Duration interval = Duration.between(lastUpload, now);
119+
LOG.info("Upload interval for {}: {}", appDeviceId, interval);
120+
if (interval.toSeconds() < 60) {
121+
LOG.warn("Dropped by rate limit: {}", appDeviceId);
122+
return;
123+
}
124+
}
125+
lastUploadTimes.put(sensorId, now);
126+
91127
// pin 1 (dust sensors)
92128
if (data.hasValue(ESensorItem.PM10) || data.hasValue(ESensorItem.PM2_5) || data.hasValue(ESensorItem.PM1_0)
93129
|| data.hasValue(ESensorItem.PM4_0)) {

0 commit comments

Comments
 (0)