Implemented a static, thread-safe LRU cache that persists across provider instances:

This commit is contained in:
Gideon Zenz
2025-12-20 19:07:34 +01:00
committed by José Rebelo
parent 290379a462
commit ca532a6350
2 changed files with 23 additions and 16 deletions
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -43,24 +44,29 @@ import nodomain.freeyourgadget.gadgetbridge.model.HrvValueSample;
* - Last night average and 5-min high (based on actual sleep sessions detected from activity data) * - Last night average and 5-min high (based on actual sleep sessions detected from activity data)
* - Baseline values using Garmin's method: mean ± standard deviation of 28 overnight averages * - Baseline values using Garmin's method: mean ± standard deviation of 28 overnight averages
* <p> * <p>
* Summaries are computed on-demand and cached for performance. * Summaries are computed on-demand and cached in a static cache shared across all instances.
* This allows computed summaries to be reused even when the provider is recreated.
*/ */
public class ComputedHrvSummarySampleProvider implements TimeSampleProvider<HrvSummarySample> { public class ComputedHrvSummarySampleProvider implements TimeSampleProvider<HrvSummarySample> {
private static final int DAYS_FOR_WEEKLY_AVG = 7; private static final int DAYS_FOR_WEEKLY_AVG = 7;
private static final int DAYS_FOR_BASELINE = 28; // Use 28 days (4 weeks) for baseline calculation private static final int DAYS_FOR_BASELINE = 28; // Use 28 days (4 weeks) for baseline calculation
private static final int CACHE_SIZE = 100; // Cache up to 100 computed summaries private static final int CACHE_SIZE = 500; // Cache up to 500 computed summaries per device
// Static cache shared across all instances, keyed by "deviceId:timestamp"
// Using synchronizedMap for thread-safety
private static final Map<String, HrvSummarySample> SUMMARY_CACHE = Collections.synchronizedMap(
new LinkedHashMap<String, HrvSummarySample>(CACHE_SIZE, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, HrvSummarySample> eldest) {
return size() > CACHE_SIZE;
}
}
);
private final TimeSampleProvider<? extends HrvValueSample> valueProvider; private final TimeSampleProvider<? extends HrvValueSample> valueProvider;
private final GBDevice device; private final GBDevice device;
private final DaoSession session; private final DaoSession session;
// LRU cache for computed summaries
private final Map<Long, HrvSummarySample> summaryCache = new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, HrvSummarySample> eldest) {
return size() > CACHE_SIZE;
}
};
public ComputedHrvSummarySampleProvider(final TimeSampleProvider<? extends HrvValueSample> valueProvider, public ComputedHrvSummarySampleProvider(final TimeSampleProvider<? extends HrvValueSample> valueProvider,
final GBDevice device, final GBDevice device,
@@ -113,9 +119,11 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider<HrvS
* The timestamp should be at the end of the day (23:59:59). * The timestamp should be at the end of the day (23:59:59).
*/ */
private HrvSummarySample generateSummaryForDay(long dayEndTimestamp) { private HrvSummarySample generateSummaryForDay(long dayEndTimestamp) {
// Check cache first // Check cache first - use device MAC address as part of cache key to separate devices
if (summaryCache.containsKey(dayEndTimestamp)) { final String cacheKey = device.getAddress() + ":" + dayEndTimestamp;
return summaryCache.get(dayEndTimestamp); final HrvSummarySample cached = SUMMARY_CACHE.get(cacheKey);
if (cached != null) {
return cached;
} }
final Calendar cal = Calendar.getInstance(); final Calendar cal = Calendar.getInstance();
@@ -205,8 +213,8 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider<HrvS
status status
); );
// Cache the computed summary // Cache the computed summary for future use
summaryCache.put(dayEndTimestamp, summary); SUMMARY_CACHE.put(cacheKey, summary);
return summary; return summary;
} }
@@ -526,3 +534,4 @@ public class ComputedHrvSummarySampleProvider implements TimeSampleProvider<HrvS
} }
} }
} }
@@ -40,7 +40,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.yawell.ring.samples.ColmiStr
import nodomain.freeyourgadget.gadgetbridge.devices.yawell.ring.samples.ColmiTemperatureSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.yawell.ring.samples.ColmiTemperatureSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHeartRateSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiHrvValueSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepSessionSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.ColmiSleepStageSampleDao;
@@ -67,7 +66,6 @@ public abstract class AbstractYawellRingCoordinator extends AbstractBLEDeviceCoo
put(session.getColmiStressSampleDao(), ColmiStressSampleDao.Properties.DeviceId); put(session.getColmiStressSampleDao(), ColmiStressSampleDao.Properties.DeviceId);
put(session.getColmiSleepSessionSampleDao(), ColmiSleepSessionSampleDao.Properties.DeviceId); put(session.getColmiSleepSessionSampleDao(), ColmiSleepSessionSampleDao.Properties.DeviceId);
put(session.getColmiSleepStageSampleDao(), ColmiSleepStageSampleDao.Properties.DeviceId); put(session.getColmiSleepStageSampleDao(), ColmiSleepStageSampleDao.Properties.DeviceId);
put(session.getColmiHrvSummarySampleDao(), ColmiHrvSummarySampleDao.Properties.DeviceId);
put(session.getColmiHrvValueSampleDao(), ColmiHrvValueSampleDao.Properties.DeviceId); put(session.getColmiHrvValueSampleDao(), ColmiHrvValueSampleDao.Properties.DeviceId);
}}; }};
} }