From 138e15b275b3e3a7ec5fc74c6266c84868b3feb2 Mon Sep 17 00:00:00 2001 From: "Memfault Inc." Date: Sat, 14 Sep 2024 17:27:12 +0000 Subject: [PATCH] Memfault BORT SDK 5.1.0 (Build 2456766) --- CHANGELOG.md | 17 ++++++ .../metrics/BatterystatsSummaryCollector.kt | 32 +++++++++-- .../memfault/bort/metrics/CrashFreeHours.kt | 10 ++-- .../bort/metrics/MetricsCollectionTask.kt | 29 +++++----- .../BatterystatsSummaryCollectorTest.kt | 54 +++++++++++++++++++ MemfaultPackages/gradle.properties | 4 +- VERSION | 6 +-- 7 files changed, 126 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4fb3d..0a442ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Memfault Bort Changelog +## v5.1.0 - September 13, 2024 + +### :construction: Fixes + +- Fixed Stability Device Vitals: a change to the way metrics are collected in + Bort 4.17.0 meant that `operational_hours` would erroneously be set to zero, + resulting in incorrect Stability Vital charts. + +### :chart_with_upwards_trend: Improvements + +- Added new screen on/off battery drain metrics: + `battery_screen_on_discharge_duration_ms`, `battery_screen_on_soc_pct_drop`, + `battery_screen_off_discharge_duration_ms`, `battery_screen_off_soc_pct_drop`. + These will replace `screen_off_battery_drain_%/hour`/ + `screen_on_battery_drain_%/hour` in the future, once they are supported in the + Memfault dashboard (to more accurately track battery drain across the fleet). + ## v5.0.0 - September 12, 2024 ### :boom: Breaking Changes diff --git a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/BatterystatsSummaryCollector.kt b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/BatterystatsSummaryCollector.kt index 844537e..01b3a5b 100644 --- a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/BatterystatsSummaryCollector.kt +++ b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/BatterystatsSummaryCollector.kt @@ -1,6 +1,7 @@ package com.memfault.bort.metrics import com.memfault.bort.TemporaryFileFactory +import com.memfault.bort.metrics.BatterystatsSummaryCollector.Companion.DP import com.memfault.bort.metrics.HighResTelemetry.DataType.DoubleType import com.memfault.bort.metrics.HighResTelemetry.Datum import com.memfault.bort.metrics.HighResTelemetry.MetricType @@ -102,7 +103,7 @@ class BatterystatsSummaryCollector @Inject constructor( hrt.add(rollup) } - // Screen off drain + // Screen off drain: old metric (to be deleted once we are using the new one, at some point) if (diff.screenOffRealtimeMs > 0 && diff.screenOffDrainPercent != null) { val screenOffBatteryDrainPerHour = JsonPrimitive(diff.screenOffDrainPercent.proRataValuePerHour(diff.screenOffRealtimeMs.milliseconds)) @@ -110,7 +111,17 @@ class BatterystatsSummaryCollector @Inject constructor( report[SCREEN_OFF_BATTERY_DRAIN_PER_HOUR] = screenOffBatteryDrainPerHour } - // Screen on drain + // Screen off drain: new metrics + if (diff.screenOffDrainPercent != null) { + val screenOffDischargeDurationMs = JsonPrimitive(diff.screenOffRealtimeMs) + addHrtRollup(name = SCREEN_OFF_DISCHARGE_DURATION_MS, value = screenOffDischargeDurationMs) + report[SCREEN_OFF_DISCHARGE_DURATION_MS] = screenOffDischargeDurationMs + val screenOffPercentDrop = JsonPrimitive(diff.screenOffDrainPercent.roundTo(DP)) + addHrtRollup(name = SCREEN_OFF_SOC_PCT_DROP, value = screenOffPercentDrop) + report[SCREEN_OFF_SOC_PCT_DROP] = screenOffPercentDrop + } + + // Screen on drain: old metric (to be deleted once we are using the new one, at some point) val screenOnRealtimeMs = diff.batteryRealtimeMs - diff.screenOffRealtimeMs if (screenOnRealtimeMs > 0 && diff.screenOnDrainPercent != null) { val screenOnBatteryDrainPerHour = @@ -119,6 +130,16 @@ class BatterystatsSummaryCollector @Inject constructor( report[SCREEN_ON_BATTERY_DRAIN_PER_HOUR] = screenOnBatteryDrainPerHour } + // Screen on drain: new metrics + if (diff.screenOnDrainPercent != null) { + val screenOnDischargeDurationMs = JsonPrimitive(screenOnRealtimeMs) + addHrtRollup(name = SCREEN_ON_DISCHARGE_DURATION_MS, value = screenOnDischargeDurationMs) + report[SCREEN_ON_DISCHARGE_DURATION_MS] = screenOnDischargeDurationMs + val screenOnPercentDrop = JsonPrimitive(diff.screenOnDrainPercent.roundTo(DP)) + addHrtRollup(name = SCREEN_ON_SOC_PCT_DROP, value = screenOnPercentDrop) + report[SCREEN_ON_SOC_PCT_DROP] = screenOnPercentDrop + } + if (summary.batteryState.estimatedBatteryCapacity > 0) { val estimatedCapacityMah = JsonPrimitive(summary.batteryState.estimatedBatteryCapacity) addHrtRollup(name = ESTIMATED_BATTERY_CAPACITY, value = estimatedCapacityMah) @@ -218,6 +239,11 @@ class BatterystatsSummaryCollector @Inject constructor( const val MIN_BATTERY_CAPACITY = "min_battery_capacity_mah" const val MAX_BATTERY_CAPACITY = "max_battery_capacity_mah" const val BATTERY_STATE_OF_HEALTH = "battery_state_of_health_%" + const val SCREEN_ON_DISCHARGE_DURATION_MS = "battery_screen_on_discharge_duration_ms" + const val SCREEN_ON_SOC_PCT_DROP = "battery_screen_on_soc_pct_drop" + const val SCREEN_OFF_DISCHARGE_DURATION_MS = "battery_screen_off_discharge_duration_ms" + const val SCREEN_OFF_SOC_PCT_DROP = "battery_screen_off_soc_pct_drop" + const val DP = 2 } } @@ -310,7 +336,7 @@ private operator fun PowerUseSummary.minus(other: PowerUseSummary) = PowerUseSum maxCapacityMah = maxCapacityMah - other.maxCapacityMah, ) -private fun Double.proRataValuePerHour(period: Duration, dp: Int = 2) = +private fun Double.proRataValuePerHour(period: Duration, dp: Int = DP) = ((this / period.inWholeMilliseconds.toDouble()) * 1.hours.inWholeMilliseconds.toDouble()).roundTo(dp) fun Double.roundTo(n: Int): Double { diff --git a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/CrashFreeHours.kt b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/CrashFreeHours.kt index 1eff5f5..ba11748 100644 --- a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/CrashFreeHours.kt +++ b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/CrashFreeHours.kt @@ -46,12 +46,12 @@ data class CrashFreeHoursState( ) class CrashFreeHoursMetricLogger @Inject constructor() { - fun incrementOperationalHours(hours: Long) { - OPERATIONAL_HOURS_METRIC.increment(hours) + fun incrementOperationalHours(hours: Int) { + OPERATIONAL_HOURS_METRIC.incrementBy(by = hours) } - fun incrementCrashFreeHours(hours: Long) { - CRASH_FREE_HOURS_METRIC.increment(hours) + fun incrementCrashFreeHours(hours: Int) { + CRASH_FREE_HOURS_METRIC.incrementBy(by = hours) } companion object { @@ -95,7 +95,7 @@ class CrashFreeHours @Inject constructor( override fun process() { val now = timeProvider.now().elapsedRealtime.duration val elapsed = now - storage.state.hourStartedAtElapsedRealtimeMs.toDuration(MILLISECONDS) - val elapsedHours = elapsed.inWholeHours + val elapsedHours = elapsed.inWholeHours.toInt() if (elapsedHours > 0) { metricLogger.incrementOperationalHours(elapsedHours) diff --git a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/MetricsCollectionTask.kt b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/MetricsCollectionTask.kt index f80d5cd..d48ba33 100644 --- a/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/MetricsCollectionTask.kt +++ b/MemfaultPackages/bort/src/main/java/com/memfault/bort/metrics/MetricsCollectionTask.kt @@ -153,7 +153,7 @@ class MetricsCollectionTask @Inject constructor( override fun convertAndValidateInputData(inputData: Data) = Unit private suspend fun enqueueHeartbeatUpload( - collectionTime: CombinedTime, + initialCollectionTime: CombinedTime, lastHeartbeatUptime: BaseLinuxBootRelativeTime, propertiesStore: DevicePropertiesStore, ) { @@ -163,16 +163,16 @@ class MetricsCollectionTask @Inject constructor( val softwareVersionChanged = customMetrics.softwareVersionChanged(deviceSoftwareVersion) val batteryStatsResult = batteryStatsCollector.collect( - collectionTime = collectionTime, + collectionTime = initialCollectionTime, lastHeartbeatUptime = lastHeartbeatUptime, ) Logger.test("Metrics: properties_use_service = ${metricsSettings.propertiesUseMetricService}") Logger.test("Metrics: software version = $heartbeatSoftwareVersion -> $deviceSoftwareVersion") // These write to Custom Metrics - do before finishing the heartbeat report. - storageStatsCollector.collectStorageStats(collectionTime) + storageStatsCollector.collectStorageStats(initialCollectionTime) networkStatsCollector.collectAndRecord( - collectionTime = collectionTime, + collectionTime = initialCollectionTime, lastHeartbeatUptime = lastHeartbeatUptime, ) @@ -224,13 +224,16 @@ class MetricsCollectionTask @Inject constructor( val inMemoryMetrics = listOf( appStorageStatsCollector, databaseSizeCollector, - ).flatMap { collector -> collector.collect(collectionTime) } + ).flatMap { collector -> collector.collect(initialCollectionTime) } val inMemoryHeartbeats = inMemoryMetrics.heartbeatMetrics() val inMemoryInternalHeartbeats = inMemoryMetrics.internalHeartbeatMetrics() - val inMemoryHrtRollups = inMemoryMetrics.hrtRollups(collectionTime) + val inMemoryHrtRollups = inMemoryMetrics.hrtRollups(initialCollectionTime) + // Ensure that we set the "actual" collection time after all metrics have been collected, so that they will all + // be included in the report. + val actualCollectionTime = combinedTimeProvider.now() val heartbeatReport = customMetrics.collectHeartbeat( - endTimestampMs = collectionTime.timestamp.toEpochMilli(), + endTimestampMs = actualCollectionTime.timestamp.toEpochMilli(), forceEndAllReports = softwareVersionChanged, ) @@ -243,7 +246,7 @@ class MetricsCollectionTask @Inject constructor( .takeIf { it.isPositive() } // This duration can also be negative, but it's the same as we had before. - val hourlyHeartbeatDurationFromUptime = collectionTime.elapsedRealtime.duration - + val hourlyHeartbeatDurationFromUptime = actualCollectionTime.elapsedRealtime.duration - lastHeartbeatUptime.elapsedRealtime.duration val hourlyHeartbeatDuration = hourlyHeartbeatDurationFromReport @@ -253,7 +256,7 @@ class MetricsCollectionTask @Inject constructor( val heartbeatReportInternalMetrics = hourlyHeartbeatReport.internalMetrics clientRateLimitCollector.collect( - collectionTime = collectionTime, + collectionTime = actualCollectionTime, internalHeartbeatReportMetrics = heartbeatReportInternalMetrics, ) @@ -261,7 +264,7 @@ class MetricsCollectionTask @Inject constructor( val internalMetrics = heartbeatReportInternalMetrics.ifEmpty { fallbackInternalMetrics } uploadHeartbeat( batteryStatsFile = batteryStatsResult.batteryStatsFileToUpload, - collectionTime = collectionTime, + collectionTime = actualCollectionTime, heartbeatInterval = hourlyHeartbeatDuration, heartbeatReportMetrics = heartbeatReportMetrics + batteryStatsResult.aggregatedMetrics + @@ -280,7 +283,7 @@ class MetricsCollectionTask @Inject constructor( hourlyHeartbeatReport.hrt?.let { hrtFile -> val hrtMetricsToAdd = batteryStatsResult.batteryStatsHrt + inMemoryHrtRollups + - propertiesStore.hrtRollups(timestampMs = collectionTime.timestamp.toEpochMilli()) + propertiesStore.hrtRollups(timestampMs = actualCollectionTime.timestamp.toEpochMilli()) if (hrtMetricsToAdd.isNotEmpty()) { mergeHrtIntoFile(hrtFile, hrtMetricsToAdd) } @@ -295,7 +298,7 @@ class MetricsCollectionTask @Inject constructor( heartbeatReport.dailyHeartbeatReport?.let { report -> uploadHeartbeat( batteryStatsFile = null, - collectionTime = collectionTime, + collectionTime = actualCollectionTime, heartbeatInterval = (report.endTimestampMs - report.startTimestampMs) .toDuration(MILLISECONDS), heartbeatReportMetrics = report.metrics + @@ -315,7 +318,7 @@ class MetricsCollectionTask @Inject constructor( .forEach { session -> uploadHeartbeat( batteryStatsFile = null, - collectionTime = collectionTime, + collectionTime = actualCollectionTime, heartbeatInterval = (session.endTimestampMs - session.startTimestampMs).toDuration(MILLISECONDS), heartbeatReportMetrics = session.metrics, heartbeatReportInternalMetrics = session.internalMetrics, diff --git a/MemfaultPackages/bort/src/test/java/com/memfault/bort/metrics/BatterystatsSummaryCollectorTest.kt b/MemfaultPackages/bort/src/test/java/com/memfault/bort/metrics/BatterystatsSummaryCollectorTest.kt index 3404dc5..3598bad 100644 --- a/MemfaultPackages/bort/src/test/java/com/memfault/bort/metrics/BatterystatsSummaryCollectorTest.kt +++ b/MemfaultPackages/bort/src/test/java/com/memfault/bort/metrics/BatterystatsSummaryCollectorTest.kt @@ -143,6 +143,13 @@ internal class BatterystatsSummaryCollectorTest { createRollup(name = "battery_use_%/hour_unknown", value = 0.11), // (114 / 3777 * 100) / 7.713471111111111 = 0.391298343321669 createRollup(name = "battery_use_%/hour_com.google.android.apps.maps", value = 0.39), + // 27768496 - 13884248 = 13884248 + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 13884248), + // (1128 - 611) * 100 / 3777 = 13.688112258406142 + createRollup(name = "battery_screen_on_soc_pct_drop", value = 13.69), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 13884248), + // 611 * 100 / 3777 = 16.176859941752714 + createRollup(name = "battery_screen_off_soc_pct_drop", value = 16.18), ), aggregatedMetrics = mapOf( "screen_off_battery_drain_%/hour" to JsonPrimitive(4.19), @@ -152,6 +159,10 @@ internal class BatterystatsSummaryCollectorTest { "min_battery_capacity_mah" to JsonPrimitive(3600f), "max_battery_capacity_mah" to JsonPrimitive(3800f), "battery_state_of_health_%" to JsonPrimitive(96.84615), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(13884248), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(13.69), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(13884248), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(16.18), ), internalAggregatedMetrics = emptyMap(), ), @@ -200,6 +211,10 @@ internal class BatterystatsSummaryCollectorTest { createRollup(name = "battery_use_%/hour_com.google.android.youtube", value = 2.35), // ((202 - 0) / 3777 * 100) / 0.66015 = 8.101431364503029 createRollup(name = "battery_use_%/hour_screen", value = 8.10), + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 1782405), + createRollup(name = "battery_screen_on_soc_pct_drop", value = 0.79), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 594135), + createRollup(name = "battery_screen_off_soc_pct_drop", value = 1.99), ), aggregatedMetrics = mapOf( "screen_off_battery_drain_%/hour" to JsonPrimitive(12.03), @@ -209,6 +224,10 @@ internal class BatterystatsSummaryCollectorTest { "min_battery_capacity_mah" to JsonPrimitive(3200f), "max_battery_capacity_mah" to JsonPrimitive(3700f), "battery_state_of_health_%" to JsonPrimitive(96.84615), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(1782405), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(0.79), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(594135), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(1.99), ), internalAggregatedMetrics = emptyMap(), ), @@ -278,9 +297,17 @@ internal class BatterystatsSummaryCollectorTest { batteryStatsFileToUpload = null, batteryStatsHrt = setOf( createRollup(name = "estimated_battery_capacity_mah", value = 1000f), + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 0), + createRollup(name = "battery_screen_on_soc_pct_drop", value = 0.0), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 0), + createRollup(name = "battery_screen_off_soc_pct_drop", value = 0.0), ), aggregatedMetrics = mapOf( "estimated_battery_capacity_mah" to JsonPrimitive(1000f), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(0), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(0.0), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(0), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(0.0), ), internalAggregatedMetrics = emptyMap(), ), @@ -318,10 +345,18 @@ internal class BatterystatsSummaryCollectorTest { batteryStatsHrt = setOf( createRollup(name = "screen_on_battery_drain_%/hour", value = 4.0), createRollup(name = "estimated_battery_capacity_mah", value = 1000f), + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 3600001), + createRollup(name = "battery_screen_on_soc_pct_drop", value = 4.0), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 0), + createRollup(name = "battery_screen_off_soc_pct_drop", value = 6.0), ), aggregatedMetrics = mapOf( "screen_on_battery_drain_%/hour" to JsonPrimitive(4.0), "estimated_battery_capacity_mah" to JsonPrimitive(1000f), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(3600001), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(4.0), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(0), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(6.0), ), internalAggregatedMetrics = emptyMap(), ), @@ -377,6 +412,10 @@ internal class BatterystatsSummaryCollectorTest { createRollup(name = "min_battery_capacity_mah", value = 3600f), createRollup(name = "max_battery_capacity_mah", value = 3800f), createRollup(name = "battery_state_of_health_%", value = 25.641027), + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 1800000), + createRollup(name = "battery_screen_on_soc_pct_drop", value = 5.0), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 1800000), + createRollup(name = "battery_screen_off_soc_pct_drop", value = 5.0), ), aggregatedMetrics = mapOf( "screen_off_battery_drain_%/hour" to JsonPrimitive(10.0), @@ -386,6 +425,10 @@ internal class BatterystatsSummaryCollectorTest { "min_battery_capacity_mah" to JsonPrimitive(3600f), "max_battery_capacity_mah" to JsonPrimitive(3800f), "battery_state_of_health_%" to JsonPrimitive(25.641027), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(1800000), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(5.0), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(1800000), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(5.0), ), internalAggregatedMetrics = emptyMap(), ), @@ -460,6 +503,13 @@ internal class BatterystatsSummaryCollectorTest { createRollup(name = "battery_use_%/hour_com.memfault.bort.ota", value = 0.11), // (114 / 3777 * 100) / 7.713471111111111 = 0.391298343321669 createRollup(name = "battery_use_%/hour_com.google.android.apps.maps", value = 0.39), + // 27768496 - 13884248 = 13884248 + createRollup(name = "battery_screen_on_discharge_duration_ms", value = 13884248), + // (1128 - 611) * 100 / 3777 = 16.176859941752714 + createRollup(name = "battery_screen_on_soc_pct_drop", value = 13.69), + createRollup(name = "battery_screen_off_discharge_duration_ms", value = 13884248), + // 611 * 100 / 3777 = 16.176859941752714 + createRollup(name = "battery_screen_off_soc_pct_drop", value = 16.18), ), aggregatedMetrics = mapOf( "screen_off_battery_drain_%/hour" to JsonPrimitive(4.19), @@ -467,6 +517,10 @@ internal class BatterystatsSummaryCollectorTest { "estimated_battery_capacity_mah" to JsonPrimitive(3777f), "battery_use_%/hour_screen" to JsonPrimitive(0.75), "battery_use_%/hour_gmaps" to JsonPrimitive(0.39), + "battery_screen_on_discharge_duration_ms" to JsonPrimitive(13884248), + "battery_screen_on_soc_pct_drop" to JsonPrimitive(13.69), + "battery_screen_off_discharge_duration_ms" to JsonPrimitive(13884248), + "battery_screen_off_soc_pct_drop" to JsonPrimitive(16.18), ), internalAggregatedMetrics = mapOf( "battery_use_%/hour_bort" to JsonPrimitive(0.11), diff --git a/MemfaultPackages/gradle.properties b/MemfaultPackages/gradle.properties index fa3e515..7bbb05f 100644 --- a/MemfaultPackages/gradle.properties +++ b/MemfaultPackages/gradle.properties @@ -13,7 +13,7 @@ UPLOAD_COMPRESSION_ENABLED=true MEMFAULT_API_BASE_URL=https://device.memfault.com UPSTREAM_MAJOR_VERSION=5 -UPSTREAM_MINOR_VERSION=0 +UPSTREAM_MINOR_VERSION=1 UPSTREAM_PATCH_VERSION=0 # Only set for versions where we ship multiple builds (e.g. release candidates): @@ -26,4 +26,4 @@ UPSTREAM_SUFFIX= kotlin.incremental.usePreciseJavaTracking=false # GENERATED - DO NOT EDIT -UPSTREAM_GIT_SHA=12762a67bc \ No newline at end of file +UPSTREAM_GIT_SHA=644dff46b2 \ No newline at end of file diff --git a/VERSION b/VERSION index e3e026c..9136be7 100644 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ -BUILD ID: 2450655 -GIT COMMIT: 12762a67bc -VERSION: 5.0.0 +BUILD ID: 2456766 +GIT COMMIT: 644dff46b2 +VERSION: 5.1.0