Skip to content

Conversation

cvinayak
Copy link
Contributor

Introduce channel metrics events that report number of good reception on each ACL data channel in use.

Manual testing:

cmake -GNinja -DBOARD=nrf54l15dk/nrf54l15/cpuapp ../../samples/bluetooth/central_gatt_write
cmake -GNinja -DBOARD=nrf54l15dk/nrf54l15/cpuapp ../../samples/bluetooth/peripheral_gatt_write

Example print of throughput interleaved by the channel metrics (playing youtube video on the laptop over wifi connection):

write_cmd_cb: count= 235, len= 57340, rate= 459805 bps.
le_param_req: int (0x0028, 0x0028) lat 0 to 30
le_param_updated: int 0x0028 lat 0 to 30
write_cmd_cb: count= 341, len= 83204, rate= 666207 bps.
Parameter Update Count: 41. 4: 0x28 0x28 0 30
write_cmd_cb: count= 232, len= 56608, rate= 674778 bps.
write_cmd_cb: count= 401, len= 97844, rate= 784042 bps.
write_cmd_cb: count= 317, len= 77348, rate= 618856 bps.
write_cmd_cb: count= 374, len= 91256, rate= 731887 bps.
write_cmd_cb: count= 327, len= 79788, rate= 638374 bps.
le_param_req: int (0x0029, 0x0029) lat 0 to 31
le_param_updated: int 0x0029 lat 0 to 31
lll_chan_metrics_print:
00: (03584 / 03584) 100 - ****************************************************************************************************
01: (03468 / 03468) 100 - ****************************************************************************************************
02: (03660 / 03661) 099 - ***************************************************************************************************
03: (03013 / 03013) 100 - ****************************************************************************************************
04: (02976 / 02976) 100 - ****************************************************************************************************
05: (02958 / 02958) 100 - ****************************************************************************************************
06: (03085 / 03087) 099 - ***************************************************************************************************
07: (03309 / 03319) 099 - ***************************************************************************************************
08: (03416 / 03429) 099 - ***************************************************************************************************
09: (03117 / 03127) 099 - ***************************************************************************************************
10: (03319 / 03321) 099 - ***************************************************************************************************
11: (03466 / 03466) 100 - ****************************************************************************************************
12: (03285 / 03285) 100 - ****************************************************************************************************
13: (03525 / 03525) 100 - ****************************************************************************************************
14: (03243 / 03243) 100 - ****************************************************************************************************
15: (03439 / 03439) 100 - ****************************************************************************************************
16: (03108 / 03108) 100 - ****************************************************************************************************
17: (03048 / 03048) 100 - ****************************************************************************************************
18: (03182 / 03183) 099 - ***************************************************************************************************
19: (03003 / 03003) 100 - ****************************************************************************************************
20: (03578 / 03578) 100 - ****************************************************************************************************
21: (03349 / 03349) 100 - ****************************************************************************************************
22: (03226 / 03227) 099 - ***************************************************************************************************
23: (03289 / 03293) 099 - ***************************************************************************************************
24: (02758 / 02796) 098 - **************************************************************************************************
25: (02758 / 02799) 098 - **************************************************************************************************
26: (02746 / 02793) 098 - **************************************************************************************************
27: (02609 / 02650) 098 - **************************************************************************************************
28: (02442 / 02471) 098 - **************************************************************************************************
29: (02660 / 02697) 098 - **************************************************************************************************
30: (02375 / 02418) 098 - **************************************************************************************************
31: (02631 / 02673) 098 - **************************************************************************************************
32: (02843 / 02878) 098 - **************************************************************************************************
33: (03185 / 03193) 099 - ***************************************************************************************************
34: (03611 / 03612) 099 - ***************************************************************************************************
35: (03471 / 03471) 100 - ****************************************************************************************************
36: (03994 / 03994) 100 - ****************************************************************************************************
write_cmd_cb: count= 185, len= 45140, rate= 361630 bps.
Parameter Update Count: 42. 5: 0x29 0x29 0 31
write_cmd_cb: count= 387, len= 94428, rate= 757233 bps.
write_cmd_cb: count= 368, len= 89792, rate= 719402 bps.
write_cmd_cb: count= 386, len= 94184, rate= 755279 bps.

Modifications to get the above output (forcing the prints regularly instead of the implemented bad count threshold logic):

diff --git a/samples/bluetooth/central_gatt_write/prj.conf b/samples/bluetooth/central_gatt_write/prj.conf
index e92eecda0f6..8d2e771aae2 100644
--- a/samples/bluetooth/central_gatt_write/prj.conf
+++ b/samples/bluetooth/central_gatt_write/prj.conf
@@ -10,4 +10,9 @@ CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
 
 CONFIG_BT_L2CAP_TX_MTU=247
 
-CONFIG_LOG=y
+CONFIG_LOG=n
+
+CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
+
+CONFIG_BT_CTLR_ADVANCED_FEATURES=y
+CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
diff --git a/samples/bluetooth/peripheral_gatt_write/prj.conf b/samples/bluetooth/peripheral_gatt_write/prj.conf
index eac63333f75..8d51338d784 100644
--- a/samples/bluetooth/peripheral_gatt_write/prj.conf
+++ b/samples/bluetooth/peripheral_gatt_write/prj.conf
@@ -11,4 +11,9 @@ CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
 
 CONFIG_BT_L2CAP_TX_MTU=247
 
-CONFIG_LOG=y
+CONFIG_LOG=n
+
+CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
+
+CONFIG_BT_CTLR_ADVANCED_FEATURES=y
+CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
diff --git a/subsys/bluetooth/controller/ll_sw/ull_conn.c b/subsys/bluetooth/controller/ll_sw/ull_conn.c
index 6e56f27579e..73e24b80bc3 100644
--- a/subsys/bluetooth/controller/ll_sw/ull_conn.c
+++ b/subsys/bluetooth/controller/ll_sw/ull_conn.c
@@ -1317,7 +1317,10 @@ void ull_conn_done(struct node_rx_event_done *done)
        }
 #endif /* CONFIG_BT_CTLR_CONN_RSSI_EVENT */
 
-       if (lll_chan_metrics_is_notify()) {
+       static uint8_t x;
+
+       x++;
+       if (lll_chan_metrics_is_notify() || x == 0U) {
                struct node_rx_pdu *rx;
 
                rx = ll_pdu_rx_alloc();

@cvinayak cvinayak force-pushed the github_chan_metrics branch 2 times, most recently from 43a2ec7 to 72f8e8b Compare September 14, 2025 13:54
@cvinayak cvinayak closed this Sep 15, 2025
@cvinayak cvinayak reopened this Sep 26, 2025
@cvinayak cvinayak requested a review from Copilot September 26, 2025 08:16
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces channel metrics events for the Bluetooth controller to report reception statistics on each ACL data channel in use. The feature enables monitoring of channel quality by tracking good and bad receptions per channel and generating events based on configurable thresholds.

  • Adds channel metrics tracking infrastructure with per-channel statistics
  • Implements periodic or threshold-based event generation for channel quality reporting
  • Provides debug printing functionality for channel metrics visualization

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
subsys/bluetooth/controller/ll_sw/ull_conn.c Adds periodic channel metrics event generation logic
subsys/bluetooth/controller/ll_sw/ull_adv.c Initializes channel tracking for peripheral connections
subsys/bluetooth/controller/ll_sw/ull.c Adds channel metrics event handling in RX path
subsys/bluetooth/controller/ll_sw/nordic/lll/lll_peripheral.c Tracks current/previous channel state for peripheral role
subsys/bluetooth/controller/ll_sw/nordic/lll/lll_conn.c Implements channel quality tracking and CRC error handling
subsys/bluetooth/controller/ll_sw/nordic/lll/lll_central.c Tracks current channel state for central role
subsys/bluetooth/controller/ll_sw/nordic/lll/lll.c Removes stray whitespace
subsys/bluetooth/controller/ll_sw/lll_conn.h Adds channel state fields to connection structures
subsys/bluetooth/controller/ll_sw/lll_chan.h Defines channel metrics API and inline stubs
subsys/bluetooth/controller/ll_sw/lll_chan.c Implements core channel metrics functionality
subsys/bluetooth/controller/ll_sw/lll.h Adds new NODE_RX_TYPE_CHAN_METRICS event type
subsys/bluetooth/controller/hci/hci.c Adds channel metrics event processing and debug output
subsys/bluetooth/controller/Kconfig.ll_sw_split Adds configuration options for channel metrics
samples/bluetooth/peripheral_gatt_write/prj.conf Enables channel metrics for testing
samples/bluetooth/peripheral_gatt_write/dts/arm/nordic/override.dtsi Sets IRQ priority for testing
samples/bluetooth/central_gatt_write/prj.conf Enables channel metrics for testing
samples/bluetooth/central_gatt_write/dts/arm/nordic/override.dtsi Sets IRQ priority for testing

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 1130 to 1146
if (false) {
#if defined(CONFIG_BT_CENTRAL)
/* Central role and consecutive good channel reception */
} else if (lll->role == BT_HCI_ROLE_CENTRAL) {
lll_chan_metrics_chan_good(lll->chan_curr);
#endif /* CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_PERIPHERAL)
} else if (lll->role == BT_HCI_ROLE_PERIPHERAL) {
lll_chan_metrics_chan_good(lll->periph.chan_prev);
lll->periph.chan_prev = lll->periph.chan_curr;
#endif /* CONFIG_BT_PERIPHERAL */
}
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if (false) creates unreachable code. This appears to be debug code that should be removed or replaced with a proper condition.

Suggested change
if (false) {
#if defined(CONFIG_BT_CENTRAL)
/* Central role and consecutive good channel reception */
} else if (lll->role == BT_HCI_ROLE_CENTRAL) {
lll_chan_metrics_chan_good(lll->chan_curr);
#endif /* CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_PERIPHERAL)
} else if (lll->role == BT_HCI_ROLE_PERIPHERAL) {
lll_chan_metrics_chan_good(lll->periph.chan_prev);
lll->periph.chan_prev = lll->periph.chan_curr;
#endif /* CONFIG_BT_PERIPHERAL */
}
#if defined(CONFIG_BT_CENTRAL)
/* Central role and consecutive good channel reception */
if (lll->role == BT_HCI_ROLE_CENTRAL) {
lll_chan_metrics_chan_good(lll->chan_curr);
}
#endif /* CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_PERIPHERAL)
if (lll->role == BT_HCI_ROLE_PERIPHERAL) {
lll_chan_metrics_chan_good(lll->periph.chan_prev);
lll->periph.chan_prev = lll->periph.chan_curr;
}
#endif /* CONFIG_BT_PERIPHERAL */

Copilot uses AI. Check for mistakes.

@cvinayak cvinayak force-pushed the github_chan_metrics branch 2 times, most recently from 0d8c484 to 2791586 Compare September 26, 2025 11:21
@cvinayak cvinayak marked this pull request as ready for review October 3, 2025 04:00
@zephyrbot zephyrbot requested a review from alwa-nordic October 3, 2025 04:02
@cvinayak cvinayak added this to the v4.3.0 milestone Oct 3, 2025
@cvinayak cvinayak added the Enhancement Changes/Updates/Additions to existing features label Oct 3, 2025
@jhedberg
Copy link
Member

jhedberg commented Oct 3, 2025

@cvinayak do you want to add the Copyright/SPDX stuff to suppress the Compliance warnings?

@cvinayak
Copy link
Contributor Author

cvinayak commented Oct 3, 2025

@cvinayak do you want to add the Copyright/SPDX stuff to suppress the Compliance warnings?

Ah... will fix them. I was so blind to those github actions warnings!... why are these not failing CI in the Compliance Checks actions?

Comment on lines 13 to 18
CONFIG_LOG=n

# CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... need to move this to overlay-bt_ll_sw_split.conf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the file... this was for manual testing

Comment on lines 14 to 19
CONFIG_LOG=n

# CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... need to move this to overlay-bt_ll_sw_split.conf

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the file... this was for manual testing

Comment on lines +1157 to +1164
config BT_CTLR_CHAN_METRICS_EVENT
bool "Channel Metrics event"
help
Generate events for channel usage metrics.

config BT_CTLR_CHAN_METRICS_BAD_COUNT
int "Channel bad count threshold"
depends on BT_CTLR_CHAN_METRICS_EVENT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could consider doing so that BT_CTLR_CHAN_METRICS_BAD_COUNT defaults to 0 and omit BT_CTLR_CHAN_METRICS_EVENT, and then you can just use a single Kconfig option for this purpose. If you want to have a bool, you could also do

config BT_CTLR_CHAN_METRICS_EVENT
        defbool BT_CTLR_CHAN_METRICS_BAD_COUNT > 0

config BT_CTLR_CHAN_METRICS_BAD_COUNT
	int "Channel bad count threshold"

It is also OK as is if you prefer that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the current way because this can be extended in the future to measure (or have threshold for) other details like CRC error counts, or retransmission counts etc,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure :) Just FYI, you would still be able to extend with my suggestion assuming that BT_CTLR_CHAN_METRICS_BAD_COUNT still needs to be minimum 1 for the feature to be enabled

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, say, RSSI metrics per channel is added. Then, CONFIG_BT_CTLR_CHAN_METRICS_BAD_COUNT=0 and CONFIG_BT_CTLR_CHAN_METRICS_LOW_RSSI_COUNT=3 to generate event on 3 consecutive low RSSI measurement on the channel.

{
uint8_t req;

req = chan_metrics.req + 1U;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could potentially overflow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is intentional, the follow check ensured the assignment does not rollover.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow.

if chan_metrics.req is 255, then req becomes 0, and then chan_metrics.req becomes 0 if req != chan_metrics.ack.

If overflow is intentional and support (although I'm not fully following the logic here), then please add a comment stating that. We should avoid having implicit over- and underflows IMO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 36 to 62
#if !defined(CONFIG_BT_CTLR_CHAN_METRICS_EVENT)
LLL_CHAN_METRICS void lll_chan_metrics_init(void)
{
}

LLL_CHAN_METRICS void lll_chan_metrics_chan_bad(uint8_t chan_curr)
{
}

LLL_CHAN_METRICS void lll_chan_metrics_chan_good(uint8_t chan_curr)
{
}

LLL_CHAN_METRICS bool lll_chan_metrics_is_notify(void)
{
return false;
}

LLL_CHAN_METRICS bool lll_chan_metrics_notify_clear(void)
{
return false;
}

LLL_CHAN_METRICS void lll_chan_metrics_print(void)
{
}
#endif /* !CONFIG_BT_CTLR_CHAN_METRICS_EVENT */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than doing this, I would suggest to remove the guard and guard all calls to these with CONFIG_BT_CTLR_CHAN_METRICS_EVENT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caller's of the interface do not need to have conditional compiles, these empty functions get eliminated by linker,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow - Calls to conditional features can/should be guarded by IS_ENABLED

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When CONFIG_BT_CTLR_CHAN_METRICS_EVENT=n

This header file will have, for example a definition:

LLL_CHAN_METRICS bool lll_chan_metrics_is_notify(void)
{
	return false;
}

Where #define LLL_CHAN_METRICS static __attribute__((always_inline)) inline

Calls like here:
https://github.com/cvinayak/zephyr/blob/7dc31ca862c1bc306df716a17658188d9333018c/subsys/bluetooth/controller/ll_sw/ull_conn.c#L1319-L1333

i.e. if (lll_chan_metrics_is_notify()) { evaluates to false eliminating the entire code block.

there is no need to have if (IS_ENABLED(CONFIG_BT_CTLR_CHAN_METRICS_EVENT)) to guard the function calls and rest of the code block there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker :)

@cvinayak cvinayak force-pushed the github_chan_metrics branch from 2791586 to eac0b7e Compare October 8, 2025 16:34
Comment on lines 13 to 18
CONFIG_LOG=n

# CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the file... this was for manual testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the file... this was for manual testing

Comment on lines 14 to 19
CONFIG_LOG=n

# CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

CONFIG_BT_CTLR_ADVANCED_FEATURES=y
CONFIG_BT_CTLR_CHAN_METRICS_EVENT=y
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the file... this was for manual testing

Comment on lines +1157 to +1164
config BT_CTLR_CHAN_METRICS_EVENT
bool "Channel Metrics event"
help
Generate events for channel usage metrics.

config BT_CTLR_CHAN_METRICS_BAD_COUNT
int "Channel bad count threshold"
depends on BT_CTLR_CHAN_METRICS_EVENT
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the current way because this can be extended in the future to measure (or have threshold for) other details like CRC error counts, or retransmission counts etc,

Comment on lines 339 to 341
uint16_t count;
uint16_t prev;
uint16_t good;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

count is the total calls to collect metrics of which good is the good channel transmission. count - good gives the count for channels that have missed transmission (poor throughput).

Introduce channel metrics events that report number of good
reception on each ACL data channel in use.

Signed-off-by: Vinayak Kariappa Chettimada <[email protected]>
@cvinayak cvinayak force-pushed the github_chan_metrics branch from eac0b7e to 7dc31ca Compare October 8, 2025 20:03
Copy link

sonarqubecloud bot commented Oct 8, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
E Reliability Rating on New Code (required ≥ C)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@cvinayak cvinayak requested a review from Copilot October 8, 2025 20:09
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


/* Half the count and keep the current consecutive bad versus good difference.
* We do not want to rollover `UINT16_MAX`, hence we normalize the `count` and
* `good`, but keep the absolute `diff` between `total` and `prev`.
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions 'absolute diff between total and prev' but the variable is called count, not total. Update the comment to use consistent variable names.

Suggested change
* `good`, but keep the absolute `diff` between `total` and `prev`.
* `good`, but keep the absolute `diff` between `count` and `prev`.

Copilot uses AI. Check for mistakes.

const uint8_t max = 100U;

printk("%s:\n", __func__);
printk("chan #: (actual / expected reception) percentage -\n");
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment 'expected reception' is misleading since count represents total attempts (both good and bad), not expected receptions. Consider changing to 'total attempts' for clarity.

Suggested change
printk("chan #: (actual / expected reception) percentage -\n");
printk("chan #: (actual / total attempts) percentage -\n");

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected reception represents best the expected reception count (for the attempts)

c = '-';
}

printk("%02d: (%05u / %05u) %03u - ", i, chan->good, chan->count, cnt);
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic numbers 02d, 05u, and 03u in the printf format string should be defined as constants to improve maintainability and make the output format more consistent.

Copilot uses AI. Check for mistakes.

@cvinayak cvinayak removed this from the v4.3.0 milestone Oct 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Bluetooth Controller area: Bluetooth Host Bluetooth Host (excluding BR/EDR) area: Bluetooth area: Samples Samples Enhancement Changes/Updates/Additions to existing features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants