Skip to content

Commit 7967a7b

Browse files
author
Memfault Inc.
committed
Memfault BORT SDK 5.4.1 (Build 3239791)
1 parent d0eb179 commit 7967a7b

File tree

38 files changed

+576
-409
lines changed

38 files changed

+576
-409
lines changed

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66
This project currently does not attempt to adhere to Semantic Versioning, but
77
breaking changes are avoided unless absolutely necessary.
88

9+
## v5.4.1 - July 22, 2025
10+
11+
### :rocket: New Features
12+
13+
- Added new SDK Setting to upload collected Android Bugreports immediately
14+
outside of the periodic MAR batch task. Added new `trace_id` and `extra_info`
15+
fields to `BugReportRequest`s.
16+
17+
### :construction: Fixes
18+
19+
- Fixed reporting of the new Wi-Fi metrics to visualize on the device timeline
20+
more appropriately. Fixed an error when recording null per second metrics.
21+
22+
### :chart_with_upwards_trend: Improvements
23+
24+
- Added some bort_cli.py comments.
25+
- Deleted unused code in Logger.kt.
26+
- Refactored Bugreport Intent logic.
27+
928
## v5.4.0 - July 1, 2025
1029

1130
### :rocket: New Features
@@ -48,6 +67,31 @@ breaking changes are avoided unless absolutely necessary.
4867
- Otherwise, the top 10 processes exceeding the 50% CPU usage threshold will
4968
also be recorded. Please reach out to customer support to configure these
5069
thresholds.
70+
- Added new general connectivity metrics reported by NetworkCapabilities.
71+
- `connectivity.roaming` captures whether the connected network is roaming.
72+
This metric is only available in HRT, on API 28+.
73+
- `connectivity.unmetered_temporarily` captures whether the connected network
74+
was temporarily unmetered. This metric is only available in HRT, on API 30+.
75+
- `connectivity.metered.latest` captures whether the connected network is
76+
metered.
77+
- Added new basic Wi-Fi from the WifiInfo reported by NetworkCapabilities.
78+
- `connectivity.wifi.frequency` captures the Wi-Fi frequency of the associated
79+
link in MHz.
80+
- `connectivity.wifi.link_speed_mbps` captures the current link speed of the
81+
associated link with the highest RSSI in Mbps.
82+
- `connectivity.wifi.security_type` captures the security type of the current
83+
802.11 network connection as a human-readable string.
84+
- `connectivity.wifi.standard_version` captures connection Wi-Fi standard as a
85+
human-readable string.
86+
- `connectivity.wifi.lost_tx_packets_per_second` captures the average rate of
87+
lost transmitted packets, in units of packets per second.
88+
- `connectivity.wifi.retried_tx_packets_per_second` captures the average rate
89+
of transmitted retry packets, in units of packets per second.
90+
- `connectivity.wifi.successful_tx_packets_per_second` captures the average
91+
rate of successfully transmitted unicast packets, in units of packets per
92+
second.
93+
- `connectivity.wifi.successful_rx_packets_per_second` captures the average
94+
rate of received unicast data packets, in units of packets per second.
5195
- Added support for Android 15.
5296

5397
### :chart_with_upwards_trend: Improvements

MemfaultPackages/bort-ota/src/main/java/com/memfault/bort/ota/OtaApp.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class OtaApp : Application(), Configuration.Provider {
3939
LoggerSettings(
4040
minLogcatLevel = otaLoggerSettings.minLogcatLevel,
4141
minStructuredLevel = LogLevel.INFO,
42-
hrtEnabled = false,
4342
),
4443
)
4544

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/connectivity/ConnectivityMetrics.kt

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ import com.memfault.bort.connectivity.ConnectivityState.UNKNOWN
2929
import com.memfault.bort.connectivity.ConnectivityState.USB
3030
import com.memfault.bort.connectivity.ConnectivityState.VPN
3131
import com.memfault.bort.connectivity.ConnectivityState.WIFI
32+
import com.memfault.bort.reporting.NumericAgg
3233
import com.memfault.bort.reporting.Reporting
33-
import com.memfault.bort.reporting.StateAgg.LATEST_VALUE
34+
import com.memfault.bort.reporting.StateAgg
3435
import com.memfault.bort.reporting.StateAgg.TIME_PER_HOUR
3536
import com.memfault.bort.reporting.StateAgg.TIME_TOTALS
3637
import com.memfault.bort.scopes.Scope
@@ -77,31 +78,41 @@ class ConnectivityMetrics
7778

7879
private val roamingMetric = Reporting.report().boolStateTracker(name = "connectivity.roaming")
7980
private val meteredMetric = Reporting.report()
80-
.boolStateTracker(name = "connectivity.metered", aggregations = listOf(LATEST_VALUE))
81+
.boolStateTracker(name = "connectivity.metered", aggregations = listOf(StateAgg.LATEST_VALUE))
8182
private val unmeteredTemporarilyMetric =
8283
Reporting.report().boolStateTracker(name = "connectivity.unmetered_temporarily")
8384

84-
private val wifiFrequencyMetric = Reporting.report().numberProperty(name = "connectivity.wifi.frequency")
85-
private val wifiLinkSpeedMbpsMetric = Reporting.report().numberProperty(name = "connectivity.wifi.link_speed_mbps")
86-
private val wifiSecurityMetric = Reporting.report().stringProperty(
85+
private val wifiFrequencyMetric = Reporting.report().distribution(
86+
name = "connectivity.wifi.frequency",
87+
aggregations = listOf(NumericAgg.LATEST_VALUE),
88+
)
89+
private val wifiLinkSpeedMbpsMetric = Reporting.report().distribution(
90+
name = "connectivity.wifi.link_speed_mbps",
91+
aggregations = listOf(NumericAgg.LATEST_VALUE),
92+
)
93+
private val wifiSecurityMetric = Reporting.report().stringStateTracker(
8794
name = "connectivity.wifi.security_type",
88-
addLatestToReport = true,
95+
aggregations = listOf(StateAgg.LATEST_VALUE),
8996
)
90-
private val wifiStandardVersionMetric = Reporting.report().stringProperty(
97+
private val wifiStandardVersionMetric = Reporting.report().stringStateTracker(
9198
name = "connectivity.wifi.standard_version",
92-
addLatestToReport = true,
99+
aggregations = listOf(StateAgg.LATEST_VALUE),
93100
)
94-
private val wifiLostTxPacketsPerSecondMetric = Reporting.report().numberProperty(
101+
private val wifiLostTxPacketsPerSecondMetric = Reporting.report().distribution(
95102
name = "connectivity.wifi.lost_tx_packets_per_second",
103+
aggregations = listOf(NumericAgg.LATEST_VALUE),
96104
)
97-
private val wifiRetriedTxPacketsPerSecondMetric = Reporting.report().numberProperty(
105+
private val wifiRetriedTxPacketsPerSecondMetric = Reporting.report().distribution(
98106
name = "connectivity.wifi.retried_tx_packets_per_second",
107+
aggregations = listOf(NumericAgg.LATEST_VALUE),
99108
)
100-
private val wifiSuccessfulTxPacketsPerSecond = Reporting.report().numberProperty(
109+
private val wifiSuccessfulTxPacketsPerSecond = Reporting.report().distribution(
101110
name = "connectivity.wifi.successful_tx_packets_per_second",
111+
aggregations = listOf(NumericAgg.LATEST_VALUE),
102112
)
103-
private val wifiSuccessfulRxPacketsPerSecond = Reporting.report().numberProperty(
113+
private val wifiSuccessfulRxPacketsPerSecond = Reporting.report().distribution(
104114
name = "connectivity.wifi.successful_rx_packets_per_second",
115+
aggregations = listOf(NumericAgg.LATEST_VALUE),
105116
)
106117

107118
private val replaySettingsFlow = MutableSharedFlow<Unit>()
@@ -254,21 +265,21 @@ class ConnectivityMetrics
254265
}
255266

256267
private fun reportWifiInfo(wifiInfo: WifiInfo?) {
257-
wifiFrequencyMetric.update(wifiInfo?.frequency)
258-
wifiLinkSpeedMbpsMetric.update(wifiInfo?.linkSpeed)
259-
wifiLostTxPacketsPerSecondMetric.update(wifiInfo?.getLostTxPacketsPerSecondReflectively())
260-
wifiRetriedTxPacketsPerSecondMetric.update(wifiInfo?.getRetriedTxPacketsPerSecondReflectively())
261-
wifiSuccessfulRxPacketsPerSecond.update(wifiInfo?.getSuccessfulRxPacketsPerSecondReflectively())
262-
wifiSuccessfulTxPacketsPerSecond.update(wifiInfo?.getSuccessfulTxPacketsPerSecondReflectively())
268+
wifiInfo?.frequency?.let { wifiFrequencyMetric.record(it.toLong()) }
269+
wifiInfo?.linkSpeed?.let { wifiLinkSpeedMbpsMetric.record(it.toLong()) }
270+
wifiInfo?.getLostTxPacketsPerSecondReflectively()?.let { wifiLostTxPacketsPerSecondMetric.record(it) }
271+
wifiInfo?.getRetriedTxPacketsPerSecondReflectively()?.let { wifiRetriedTxPacketsPerSecondMetric.record(it) }
272+
wifiInfo?.getSuccessfulRxPacketsPerSecondReflectively()?.let { wifiSuccessfulRxPacketsPerSecond.record(it) }
273+
wifiInfo?.getSuccessfulTxPacketsPerSecondReflectively()?.let { wifiSuccessfulTxPacketsPerSecond.record(it) }
263274

264275
// Android 11+ has a new API to get the wifi standard
265276
if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
266-
wifiStandardVersionMetric.update(humanReadableWifiStandard(wifiInfo?.wifiStandard))
277+
wifiStandardVersionMetric.state(humanReadableWifiStandard(wifiInfo?.wifiStandard))
267278
}
268279

269280
// Android 12+ has a new API to get the wifi security type
270281
if (Build.VERSION.SDK_INT >= VERSION_CODES.S) {
271-
wifiSecurityMetric.update(humanReadableWifiSecurityType(wifiInfo?.currentSecurityType))
282+
wifiSecurityMetric.state(humanReadableWifiSecurityType(wifiInfo?.currentSecurityType))
272283
}
273284
}
274285

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/requester/FileCleanup.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fun cleanupFiles(
5252
bytesUsed += fileSize
5353
}
5454
}
55-
if (!deleted.isEmpty()) {
55+
if (deleted.isNotEmpty()) {
5656
Logger.i("file_cleanup_deleted", mapOf("deleted" to deleted))
5757
}
5858
return FileCleanupResult(

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/settings/FetchedSettings.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ data class FetchedSettings(
7373
@SerialName("bug_report.periodic_rate_limiting_percent")
7474
val bugReportPeriodicRateLimitingPercentOfPeriod: Int = 50,
7575

76+
@SerialName("bug_report.unbatch_uploads")
77+
val bugReportUnbatchUploads: Boolean = false,
78+
7679
@SerialName("chronicler.mar_enabled")
7780
val chroniclerMarEnabled: Boolean = true,
7881

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/shared/BugReportRequest.kt

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import android.content.Intent
44
import kotlinx.serialization.SerialName
55
import kotlinx.serialization.Serializable
66

7+
const val INTENT_ACTION_BUG_REPORT_START = "com.memfault.intent.action.BUG_REPORT_START"
8+
9+
private const val INTENT_EXTRA_BUG_REPORT_MINIMAL_MODE_BOOL = "com.memfault.intent.extra.BUG_REPORT_MINIMAL_MODE_BOOL"
10+
const val INTENT_EXTRA_BUG_REPORT_REQUEST_ID = "com.memfault.intent.extra.BUG_REPORT_REQUEST_ID"
11+
private const val BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH = 40
12+
private const val INTENT_EXTRA_BUG_REPORT_REQUEST_REPLY_RECEIVER =
13+
"com.memfault.intent.extra.BUG_REPORT_REQUEST_REPLY_RECEIVER"
14+
private const val INTENT_EXTRA_BUG_REPORT_TRACE_ID = "com.memfault.intent.extra.BUG_REPORT_TRACE_ID"
15+
private const val INTENT_EXTRA_BUG_REPORT_EXTRA_INFO = "com.memfault.intent.extra.BUG_REPORT_EXTRA_INFO"
16+
const val INTENT_EXTRA_BUG_REPORT_REQUEST_TIMEOUT_MS = "com.memfault.intent.extra.BUG_REPORT_REQUEST_TIMEOUT_MS"
17+
718
@Serializable
819
data class BugReportOptions(
920
val minimal: Boolean = false,
@@ -19,6 +30,12 @@ data class BugReportRequest(
1930

2031
@SerialName("reply_receiver")
2132
val replyReceiver: Component? = null,
33+
34+
@SerialName("trace_id")
35+
val traceId: String? = null,
36+
37+
@SerialName("extra_info")
38+
val extraInfo: String? = null,
2239
) {
2340
@Serializable
2441
data class Component(val pkg: String, val cls: String) {
@@ -27,9 +44,10 @@ data class BugReportRequest(
2744
companion object {
2845
fun fromString(pkgAndCls: String): Component {
2946
val pattern = Regex("(^[^/]+)/(.+)$")
30-
val match = pattern.matchEntire(pkgAndCls) ?: throw IllegalArgumentException(
31-
"Failed to parse component string '$pkgAndCls'. Expecting 'com.package.id/qualified.class.name'",
32-
)
47+
val match = pattern.matchEntire(pkgAndCls)
48+
requireNotNull(match) {
49+
"Failed to parse component string '$pkgAndCls'. Expecting 'com.package.id/qualified.class.name'"
50+
}
3351
val (pkg, cls) = match.destructured
3452
return Component(pkg, cls)
3553
}
@@ -44,25 +62,27 @@ data class BugReportRequest(
4462
replyReceiver?.let {
4563
intent.putExtra(INTENT_EXTRA_BUG_REPORT_REQUEST_REPLY_RECEIVER, replyReceiver.toString())
4664
}
65+
traceId?.let { intent.putExtra(INTENT_EXTRA_BUG_REPORT_TRACE_ID, it) }
66+
extraInfo?.let { intent.putExtra(INTENT_EXTRA_BUG_REPORT_EXTRA_INFO, it) }
4767
}
4868

4969
companion object {
5070
fun fromIntent(intent: Intent) =
5171
BugReportRequest(
52-
options = BugReportOptions(intent.getBooleanExtra(INTENT_EXTRA_BUG_REPORT_MINIMAL_MODE_BOOL, false)),
53-
requestId = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_REQUEST_ID)?.let(::validateRequestId),
54-
replyReceiver = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_REQUEST_REPLY_RECEIVER)?.let(
55-
Component::fromString,
72+
options = BugReportOptions(
73+
minimal = intent.getBooleanExtra(INTENT_EXTRA_BUG_REPORT_MINIMAL_MODE_BOOL, false),
5674
),
75+
requestId = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_REQUEST_ID)
76+
?.also(::validateRequestId),
77+
replyReceiver = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_REQUEST_REPLY_RECEIVER)
78+
?.let(Component::fromString),
79+
traceId = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_TRACE_ID),
80+
extraInfo = intent.getStringExtra(INTENT_EXTRA_BUG_REPORT_EXTRA_INFO),
5781
)
5882

59-
private fun validateRequestId(requestId: String): String =
60-
requestId.also {
61-
if (it.length > BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH) {
62-
throw IllegalArgumentException(
63-
"Bug report request ID is longer than $BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH chars: $it",
64-
)
65-
}
83+
private fun validateRequestId(requestId: String) =
84+
require(requestId.length <= BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH) {
85+
"Bug report request ID is longer than $BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH chars: $requestId"
6686
}
6787
}
6888
}

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/shared/Constants.kt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
package com.memfault.bort.shared
22

3-
const val INTENT_ACTION_BUG_REPORT_START = "com.memfault.intent.action.BUG_REPORT_START"
4-
5-
const val INTENT_EXTRA_BUG_REPORT_MINIMAL_MODE_BOOL = "com.memfault.intent.extra.BUG_REPORT_MINIMAL_MODE_BOOL"
6-
const val INTENT_EXTRA_BUG_REPORT_REQUEST_ID = "com.memfault.intent.extra.BUG_REPORT_REQUEST_ID"
7-
const val BUG_REPORT_REQUEST_ID_MAXIMUM_LENGTH = 40
8-
const val INTENT_EXTRA_BUG_REPORT_REQUEST_REPLY_RECEIVER = "com.memfault.intent.extra.BUG_REPORT_REQUEST_REPLY_RECEIVER"
9-
const val INTENT_EXTRA_BUG_REPORT_REQUEST_TIMEOUT_MS = "com.memfault.intent.extra.BUG_REPORT_REQUEST_TIMEOUT_MS"
10-
11-
const val INTENT_ACTION_BUG_REPORT_REQUEST_REPLY = "com.memfault.intent.action.BUG_REPORT_REQUEST_REPLY"
12-
const val INTENT_EXTRA_BUG_REPORT_REQUEST_STATUS = "com.memfault.intent.extra.BUG_REPORT_REQUEST_STATUS"
13-
143
const val DROPBOX_ENTRY_ADDED_RECEIVER_QUALIFIED_NAME = "com.memfault.bort.receivers.DropBoxEntryAddedReceiver"
154

165
const val APPLICATION_ID_MEMFAULT_STRUCTUREDLOGD = "com.memfault.structuredlogd"

MemfaultPackages/bort-shared/src/main/java/com/memfault/bort/shared/Logging.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ enum class LogLevel(val level: Int) {
3737
data class LoggerSettings(
3838
val minLogcatLevel: LogLevel,
3939
val minStructuredLevel: LogLevel,
40-
val hrtEnabled: Boolean,
4140
)
4241

4342
object Logger {
@@ -46,7 +45,6 @@ object Logger {
4645
private var settings: LoggerSettings = LoggerSettings(
4746
minLogcatLevel = LogLevel.NONE,
4847
minStructuredLevel = LogLevel.NONE,
49-
hrtEnabled = false,
5048
)
5149

5250
fun initSettings(settings: LoggerSettings) {

MemfaultPackages/bort/src/debug/java/com/memfault/bort/TestSettingsProvider.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.memfault.bort
22

33
import android.content.SharedPreferences
4+
import com.memfault.bort.settings.BugReportSettings
45
import com.memfault.bort.settings.DataScrubbingSettings
56
import com.memfault.bort.settings.DropBoxSettings
67
import com.memfault.bort.settings.DynamicSettingsProvider
@@ -101,4 +102,8 @@ class TestSettingsProvider @Inject constructor(
101102
override val alwaysCreateCpuProcessMetrics: Boolean
102103
get() = true
103104
}
105+
106+
override val bugReportSettings = object : BugReportSettings by settings.bugReportSettings {
107+
override val dataSourceEnabled: Boolean = false
108+
}
104109
}

MemfaultPackages/bort/src/debug/java/com/memfault/bort/TestStartBugReport.kt

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)