@@ -38,6 +38,7 @@ MEMFAULT_DISABLE_WARNING("-Wunused-macros")
3838#define MEMFAULT_METRICS_TYPE_NO_CHANGE (-4)
3939#define MEMFAULT_METRICS_STORAGE_TOO_SMALL (-5)
4040#define MEMFAULT_METRICS_TIMER_BOOT_FAILED (-6)
41+ #define MEMFAULT_METRICS_VALUE_NOT_SET (-7)
4142
4243typedef struct MemfaultMetricKVPair {
4344 MemfaultMetricId key ;
@@ -90,7 +91,7 @@ MEMFAULT_STATIC_ASSERT(MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys) != 0,
9091
9192#define MEMFAULT_METRICS_TIMER_VAL_MAX 0x80000000
9293typedef struct MemfaultMetricValueMetadata {
93- bool is_running : 1 ;
94+ bool is_running : 1 ;
9495 // We'll use 32 bits since the rollover time is ~25 days which is much much greater than a
9596 // reasonable heartbeat interval. This let's us track whether or not the timer is running in the
9697 // top bit
@@ -100,6 +101,7 @@ typedef struct MemfaultMetricValueMetadata {
100101typedef struct MemfaultMetricValueInfo {
101102 union MemfaultMetricValue * valuep ;
102103 sMemfaultMetricValueMetadata * meta_datap ;
104+ bool is_set ;
103105} sMemfaultMetricValueInfo ;
104106
105107
@@ -180,6 +182,21 @@ static union MemfaultMetricValue s_memfault_heartbeat_values[] = {
180182 #undef MEMFAULT_METRICS_STRING_KEY_DEFINE
181183};
182184
185+ // Value Set flag data structures and definitions
186+ // MEMFAULT_IS_SET_FLAGS_PER_BYTE must be a power of 2
187+ // MEMFAULT_IS_SET_FLAGS_DIVIDER must be equal to log2(MEMFAULT_IS_SET_FLAGS_PER_BYTE)
188+ #define MEMFAULT_IS_SET_FLAGS_PER_BYTE 8
189+ #define MEMFAULT_IS_SET_FLAGS_DIVIDER 3
190+
191+ // Create a byte array to contain an is-set flag for each entry in s_memfault_heartbeat_values
192+ static uint8_t s_memfault_heatbeat_value_is_set_flags [MEMFAULT_CEIL_DIV (
193+ MEMFAULT_ARRAY_SIZE (s_memfault_heartbeat_values ), MEMFAULT_IS_SET_FLAGS_PER_BYTE )];
194+
195+ MEMFAULT_STATIC_ASSERT (
196+ MEMFAULT_ARRAY_SIZE (s_memfault_heatbeat_value_is_set_flags ) >=
197+ (MEMFAULT_ARRAY_SIZE (s_memfault_heartbeat_values ) / MEMFAULT_IS_SET_FLAGS_PER_BYTE ),
198+ "Mismatch between s_memfault_heatbeat_value_is_set_flags and s_memfault_heartbeat_values" );
199+
183200// String value lookup table. Const- the pointers do not change at runtime, so
184201// this table can be stored in ROM and save a little RAM.
185202#define MEMFAULT_METRICS_KEY_DEFINE (key_name , value_type )
@@ -250,6 +267,12 @@ static const int s_metric_timer_metadata_mapping[] = {
250267 #undef MEMFAULT_METRICS_STRING_KEY_DEFINE
251268};
252269
270+ // Helper macros to convert between the various metrics indices
271+ #define MEMFAULT_METRICS_ID_TO_KEY (id ) ((size_t)(id)._impl)
272+ #define MEMFAULT_METRICS_KEY_TO_KV_INDEX (key ) (s_memfault_heartbeat_key_to_valueindex[(key)])
273+ #define MEMFAULT_METRICS_ID_TO_KV_INDEX (id ) \
274+ (MEMFAULT_METRICS_KEY_TO_KV_INDEX(MEMFAULT_METRICS_ID_TO_KEY(id)))
275+
253276static struct {
254277 const sMemfaultEventStorageImpl * storage_impl ;
255278} s_memfault_metrics_ctx ;
@@ -280,16 +303,36 @@ static sMemfaultMetricValueMetadata *prv_find_timer_metadatap(eMfltMetricsIndex
280303 return & s_memfault_heartbeat_timer_values_metadata [timer_index ];
281304}
282305
283- static eMemfaultMetricType prv_find_value_for_key (MemfaultMetricId key ,
306+ //! Helper function to read/write is_set bits for the provided metric
307+ //!
308+ //! @param id Metric ID to select corresponding is_set field
309+ //! @param write Boolean to control whether to write 1 to is_set
310+ //! @return Returns the value of metric's is_set field. The updated value is returned if write =
311+ //! true
312+ static bool prv_read_write_is_value_set (MemfaultMetricId id , bool write ) {
313+ // Shift the kv index by MEMFAULT_IS_SET_FLAGS_DIVIDER to select byte within
314+ // s_memfault_heartbeat_value_is_set_flags
315+ size_t byte_index = MEMFAULT_METRICS_ID_TO_KV_INDEX (id ) >> MEMFAULT_IS_SET_FLAGS_DIVIDER ;
316+ // Modulo the kv index by MEMFAULT_IS_SET_FLAGS_PER_BYTE to get bit of the selected byte
317+ size_t bit_index = MEMFAULT_METRICS_ID_TO_KV_INDEX (id ) % MEMFAULT_IS_SET_FLAGS_PER_BYTE ;
318+
319+ if (write ) {
320+ s_memfault_heatbeat_value_is_set_flags [byte_index ] |= (1 << bit_index );
321+ }
322+
323+ return (s_memfault_heatbeat_value_is_set_flags [byte_index ] >> bit_index ) & 0x01 ;
324+ }
325+
326+ static eMemfaultMetricType prv_find_value_for_key (MemfaultMetricId id ,
284327 sMemfaultMetricValueInfo * value_info_out ) {
285- const size_t idx = ( size_t ) key . _impl ;
328+ const size_t idx = MEMFAULT_METRICS_ID_TO_KEY ( id ) ;
286329 if (idx >= MEMFAULT_ARRAY_SIZE (s_memfault_heartbeat_keys )) {
287330 * value_info_out = (sMemfaultMetricValueInfo ){0 };
288331 return kMemfaultMetricType_NumTypes ;
289332 }
290333
291334 // get the index for the value matching this key.
292- eMfltMetricKeyToValueIndex key_index = s_memfault_heartbeat_key_to_valueindex [ idx ] ;
335+ eMfltMetricKeyToValueIndex key_index = MEMFAULT_METRICS_KEY_TO_KV_INDEX ( idx ) ;
293336 // for scalar types, this will be the returned value pointer. non-scalars
294337 // will be handled in the switch below
295338 union MemfaultMetricValue * value_ptr = & s_memfault_heartbeat_values [key_index ];
@@ -321,6 +364,10 @@ static eMemfaultMetricType prv_find_value_for_key(MemfaultMetricId key,
321364 .meta_datap = prv_find_timer_metadatap ((eMfltMetricsIndex )idx ),
322365 };
323366
367+ if (key_type == kMemfaultMetricType_Unsigned || key_type == kMemfaultMetricType_Signed ) {
368+ value_info_out -> is_set = prv_read_write_is_value_set (id , false);
369+ }
370+
324371 return key_type ;
325372}
326373
@@ -374,6 +421,7 @@ static int prv_find_and_set_value_for_key(
374421 }
375422
376423 * value_info .valuep = * new_value ;
424+ prv_read_write_is_value_set (key , true);
377425 return 0 ;
378426}
379427
@@ -516,6 +564,7 @@ static bool prv_tally_and_update_timer_cb(MEMFAULT_UNUSED void *ctx,
516564static void prv_reset_metrics (void ) {
517565 // reset all scalar metric values
518566 memset (s_memfault_heartbeat_values , 0 , sizeof (s_memfault_heartbeat_values ));
567+ memset (s_memfault_heatbeat_value_is_set_flags , 0 , sizeof (s_memfault_heatbeat_value_is_set_flags ));
519568
520569 // reset all string metric values. -1 to skip the last, stub entry in the
521570 // table
@@ -525,6 +574,13 @@ static void prv_reset_metrics(void) {
525574 ((char * )s_memfault_heartbeat_string_values [i ].ptr )[0 ] = 0 ;
526575 }
527576 }
577+
578+ // Set MemfaultSdkMetric_UnexpectedRebootDidOccur to 0 at the end of each reset.
579+ // This must be explicitly set because we use 0 to track number of operational hours
580+ // Without setting to 0, this defaults to null and will not be counted in the sum
581+ // of total operational hours
582+ memfault_metrics_heartbeat_set_unsigned (
583+ MEMFAULT_METRICS_KEY (MemfaultSdkMetric_UnexpectedRebootDidOccur ), 0 );
528584}
529585
530586static void prv_heartbeat_timer_update (void ) {
@@ -608,6 +664,11 @@ static int prv_find_key_of_type(MemfaultMetricId key, eMemfaultMetricType expect
608664 if (type != expected_type ) {
609665 return MEMFAULT_METRICS_TYPE_INCOMPATIBLE ;
610666 }
667+ if ((type == kMemfaultMetricType_Signed || type == kMemfaultMetricType_Unsigned ) &&
668+ !(value_info .is_set )) {
669+ return MEMFAULT_METRICS_VALUE_NOT_SET ;
670+ }
671+
611672 * value_out = value_info .valuep ;
612673 return 0 ;
613674}
@@ -707,7 +768,8 @@ static bool prv_metrics_heartbeat_iterate_cb(void *ctx,
707768 sMemfaultMetricInfo info = {
708769 .key = key_info -> key ,
709770 .type = key_info -> type ,
710- .val = * value_info -> valuep
771+ .val = * value_info -> valuep ,
772+ .is_set = value_info -> is_set ,
711773 };
712774 return ctx_info -> user_cb (ctx_info -> user_ctx , & info );
713775}
@@ -748,12 +810,22 @@ static bool prv_heartbeat_debug_print(MEMFAULT_UNUSED void *ctx,
748810 const char * key_name = s_idx_to_metric_name [key -> _impl ];
749811
750812 switch (metric_info -> type ) {
751- case kMemfaultMetricType_Unsigned :
752813 case kMemfaultMetricType_Timer :
753814 MEMFAULT_LOG_DEBUG (" %s: %" PRIu32 , key_name , value -> u32 );
754815 break ;
816+ case kMemfaultMetricType_Unsigned :
817+ if (metric_info -> is_set ) {
818+ MEMFAULT_LOG_DEBUG (" %s: %" PRIu32 , key_name , value -> u32 );
819+ } else {
820+ MEMFAULT_LOG_DEBUG (" %s: null" , key_name );
821+ }
822+ break ;
755823 case kMemfaultMetricType_Signed :
756- MEMFAULT_LOG_DEBUG (" %s: %" PRIi32 , key_name , value -> i32 );
824+ if (metric_info -> is_set ) {
825+ MEMFAULT_LOG_DEBUG (" %s: %" PRIi32 , key_name , value -> i32 );
826+ } else {
827+ MEMFAULT_LOG_DEBUG (" %s: null" , key_name );
828+ }
757829 break ;
758830 case kMemfaultMetricType_String :
759831 MEMFAULT_LOG_DEBUG (" %s: \"%s\"" , key_name , (const char * )value -> ptr );
0 commit comments