| //! @file |
| //! |
| //! Copyright (c) Memfault, Inc. |
| //! See LICENSE for details |
| //! |
| //! @brief |
| //! Memfault metrics API implementation. See header for more details. |
| |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #include "memfault/core/compiler.h" |
| #include "memfault/core/debug_log.h" |
| #include "memfault/core/event_storage_implementation.h" |
| #include "memfault/core/math.h" |
| #include "memfault/core/platform/core.h" |
| #include "memfault/core/platform/overrides.h" |
| #include "memfault/core/reboot_tracking.h" |
| #include "memfault/core/serializer_helper.h" |
| #include "memfault/metrics/battery.h" |
| #include "memfault/metrics/metrics.h" |
| #include "memfault/metrics/platform/connectivity.h" |
| #include "memfault/metrics/platform/overrides.h" |
| #include "memfault/metrics/platform/timer.h" |
| #include "memfault/metrics/reliability.h" |
| #include "memfault/metrics/serializer.h" |
| #include "memfault/metrics/utils.h" |
| |
| //! Disable this warning; it trips when there's no custom macros defined of a |
| //! given type |
| MEMFAULT_DISABLE_WARNING("-Wunused-macros") |
| |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| //! Return codes used in this file |
| #define MEMFAULT_METRICS_KEY_NOT_FOUND (-1) |
| #define MEMFAULT_METRICS_TYPE_INCOMPATIBLE (-2) |
| #define MEMFAULT_METRICS_TYPE_BAD_PARAM (-3) |
| #define MEMFAULT_METRICS_TYPE_NO_CHANGE (-4) |
| #define MEMFAULT_METRICS_STORAGE_TOO_SMALL (-5) |
| #define MEMFAULT_METRICS_TIMER_BOOT_FAILED (-6) |
| #define MEMFAULT_METRICS_VALUE_NOT_SET (-7) |
| |
| // Macros to check for scale values vs metric type |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, _min, _max) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, kMemfaultMetricType_String) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(session_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_name##__##MemfaultSdkMetric_IntervalMs, \ |
| kMemfaultMetricType_Timer) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_name##__##operational_crashes, kMemfaultMetricType_Unsigned) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, 0, 0, session_key) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type, scale_value) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type, scale_value) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) \ |
| MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type, 1) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type, scale_value) \ |
| MEMFAULT_STATIC_ASSERT((scale_value == 1) || (value_type != kMemfaultMetricType_Timer), \ |
| "Scale values are only valid for signed and unsigned integer metrics"); |
| |
| // Generate static asserts to check for non-integer metric types with scale values |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| // END: Macros to check for scale values vs metric type |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, _min, _max) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE(session_key##__##key_name, max_length) |
| |
| //! Sessions have the following built-in keys: |
| //! - "<session name>__MemfaultSdkMetric_IntervalMs" |
| //! - "<session name>__operational_crashes" |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##MemfaultSdkMetric_IntervalMs, \ |
| kMemfaultMetricType_Timer) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##operational_crashes, kMemfaultMetricType_Unsigned) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, 0, 0, session_key) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| //! Pre-declare the sMemfaultMetricValues type, so we can take the size of it |
| //! in the top-level struct for the heartbeat_value_is_set_flags[] member |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) +1 |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) +1 |
| typedef struct { |
| union MemfaultMetricValue values[0 |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| ]; |
| } sMemfaultMetricValues; |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| #define MEMFAULT_METRICS_TIMER_VAL_MAX 0x80000000 |
| typedef struct MemfaultMetricValueMetadata { |
| bool is_running:1; |
| // We'll use 32 bits since the rollover time is ~25 days which is much much greater than a |
| // reasonable heartbeat interval. This let's us track whether or not the timer is running in the |
| // top bit |
| uint32_t start_time_ms:31; |
| } sMemfaultMetricValueMetadata; |
| |
| // Value Set flag data structures and definitions |
| typedef struct MemfaultMetricValueInfo { |
| union MemfaultMetricValue *valuep; |
| sMemfaultMetricValueMetadata *meta_datap; |
| bool is_set; |
| } sMemfaultMetricValueInfo; |
| |
| //! This structure contains all in-memory context for the metrics system |
| static struct sMemfaultMetricsContext { |
| const sMemfaultEventStorageImpl *storage_impl; |
| #if MEMFAULT_METRICS_RESTORE_STATE |
| sMemfaultMetricsReliabilityCtx reliability_ctx; |
| #endif |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, min_value, max_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) +1 |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) |
| |
| MemfaultMetricsSessionStartCb session_start_cbs[0 |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| + 1 // dummy entry to prevent empty array |
| ]; |
| MemfaultMetricsSessionEndCb session_end_cbs[0 |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| + 1 // dummy entry to prevent empty array |
| ]; |
| |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| // From this point forward in this struct definition, higher level macros (i.e. |
| // MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE) are not undefined. |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, _min, _max) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE(session_key##__##key_name, max_length) |
| |
| //! Sessions have the following built-in keys: |
| //! - "<session name>__MemfaultSdkMetric_IntervalMs" |
| //! - "<session name>__operational_crashes" |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##MemfaultSdkMetric_IntervalMs, \ |
| kMemfaultMetricType_Timer) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##operational_crashes, kMemfaultMetricType_Unsigned) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, 0, 0, session_key) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| sMemfaultMetricValues heartbeat_values; |
| |
| // Create a byte array to contain an is-set flag for each entry in heartbeat_values |
| uint8_t heartbeat_value_is_set_flags[MEMFAULT_CEIL_DIV( |
| MEMFAULT_ARRAY_SIZE(((sMemfaultMetricValues *)0)->values), MEMFAULT_IS_SET_FLAGS_PER_BYTE)]; |
| |
| // Timer metadata table |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Unsigned(_name) |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Signed(_name) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Timer(_name) +1 |
| #define MEMFAULT_METRICS_KEY_DEFINE(_name, _type) MEMFAULT_METRICS_STATE_HELPER_##_type(_name) |
| sMemfaultMetricValueMetadata heartbeat_timer_values_metadata[0 |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| ]; |
| // Work-around for unused-macros error in case not all types are used in the .def file: |
| MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Unsigned(_) |
| MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Signed(_) |
| |
| // Allocate storage for string values- additional byte for null terminator. |
| // Packed to ensure no padding bytes are inserted, which would make it |
| // difficult to compute the size of this structure. |
| MEMFAULT_PACKED_STRUCT sMemfaultStringMetricStorage { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE_(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_(key_name, max_length) \ |
| char g_memfault_metrics_string_##key_name[max_length + 1 /* for NUL */]; |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_ |
| char placeholder; // dummy entry to avoid empty struct warnings |
| } |
| string_metrics_storage; |
| |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| #if MEMFAULT_METRICS_LOGS_ENABLE |
| uint32_t last_log_dropped_count; |
| uint32_t last_log_recorded_count; |
| #endif |
| } s_memfault_metrics_ctx = { |
| .session_start_cbs = { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, min_value, max_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) NULL, |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) |
| |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| NULL, // dummy entry to prevent empty array |
| }, |
| .session_end_cbs = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| NULL, // dummy entry to prevent empty array |
| }, |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| }; |
| |
| #if MEMFAULT_METRICS_RESTORE_STATE |
| // clang-format off |
| MEMFAULT_STATIC_ASSERT(sizeof(s_memfault_metrics_ctx) == MEMFAULT_METRICS_CONTEXT_SIZE_BYTES, |
| "s_memfault_metrics_ctx size mismatch"); |
| // Uncomment below to see the size of MEMFAULT_METRICS_CONTEXT_SIZE_BYTES at compile time |
| // #define BOOM(size_) MEMFAULT_PACKED_STRUCT kaboom { char dummy[size_]; }; char (*__kaboom)[sizeof(struct kaboom)] = 1; |
| // BOOM(MEMFAULT_METRICS_CONTEXT_SIZE_BYTES) |
| // BOOM(sizeof(s_memfault_metrics_ctx)) |
| // clang-format on |
| #endif // MEMFAULT_METRICS_RESTORE_STATE |
| |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| |
| // Generate session key to timer name mapping |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, min_value, max_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) \ |
| _MEMFAULT_METRICS_ID_CREATE(MemfaultSdkMetric_IntervalMs, key_name), |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) |
| |
| static const MemfaultMetricId s_memfault_metrics_session_timer_keys[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| { 0 } // dummy entry to prevent empty array |
| }; |
| |
| #if MEMFAULT_METRICS_SESSIONS_ENABLED |
| // Generate session key to operational_crashes metric key mapping |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, min_value, max_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(session_name) \ |
| _MEMFAULT_METRICS_ID_CREATE(operational_crashes, session_name), |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) |
| |
| static const MemfaultMetricId s_memfault_metrics_operational_crashes_keys[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| { 0 } // dummy entry to prevent empty array |
| }; |
| |
| // Active sessions are tracked in a single 32-bit word in the reboot tracking |
| // data. This limits the maximum sessions to 32. |
| MEMFAULT_STATIC_ASSERT(MEMFAULT_ARRAY_SIZE(s_memfault_metrics_operational_crashes_keys) <= 32, |
| "Too many sessions defined. Max allowed defined sessions is 32."); |
| |
| #endif |
| |
| typedef struct MemfaultMetricKVPair { |
| MemfaultMetricId key; |
| eMemfaultMetricType type; |
| // Notes: |
| // - We treat 'min' as a _signed_ integer when the 'type' == kMemfaultMetricType_Signed |
| // - We parse this range information in the Memfault cloud to better normalize data presented in |
| // the UI. |
| uint32_t min; |
| uint32_t range; |
| eMfltMetricsSessionIndex session_key; |
| // Scale down integer metric values by `scale_value` at ingestion by Memfault |
| uint32_t scale_value; |
| } sMemfaultMetricKVPair; |
| |
| #define MEMFAULT_KV_PAIR_ENTRY_(key_name, value_type, min_value, max_value, key_id, session, \ |
| scale_val) \ |
| { \ |
| .key = key_id, \ |
| .type = value_type, \ |
| .min = (uint32_t)min_value, \ |
| .range = ((int64_t)max_value - (int64_t)min_value), \ |
| .session_key = session, \ |
| .scale_value = scale_val, \ |
| }, |
| |
| #define MEMFAULT_KV_PAIR_ENTRY(key_name, value_type, min_value, max_value, key_id, session, \ |
| scale_value) \ |
| MEMFAULT_KV_PAIR_ENTRY_(key_name, value_type, min_value, max_value, key_id, session, scale_value) |
| |
| // Generate heartbeat keys table (ROM): |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session) \ |
| MEMFAULT_KV_PAIR_ENTRY(key_name, value_type, min_value, max_value, \ |
| _MEMFAULT_METRICS_ID_CREATE(key_name, session), \ |
| MEMFAULT_METRICS_SESSION_KEY(session), 1) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, min_value, max_value) \ |
| MEMFAULT_KV_PAIR_ENTRY(key_name, value_type, min_value, max_value, \ |
| _MEMFAULT_METRICS_ID_CREATE(key_name, heartbeat), \ |
| MEMFAULT_METRICS_SESSION_KEY(heartbeat), 1) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, 0, 0, session_key) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, 0, 0) |
| |
| // Store the string max length in the range field (excluding the null terminator) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, kMemfaultMetricType_String, 0, max_length) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, kMemfaultMetricType_String, 0, \ |
| max_length, session_key) |
| |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(session_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(MemfaultSdkMetric_IntervalMs, \ |
| kMemfaultMetricType_Timer, session_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(operational_crashes, kMemfaultMetricType_Unsigned, \ |
| session_name) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) \ |
| MEMFAULT_KV_PAIR_ENTRY(key_name, value_type, 0, 0, \ |
| _MEMFAULT_METRICS_ID_CREATE(key_name, heartbeat), \ |
| MEMFAULT_METRICS_SESSION_KEY(heartbeat), scale_value) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) \ |
| MEMFAULT_KV_PAIR_ENTRY(key_name, value_type, 0, 0, \ |
| _MEMFAULT_METRICS_ID_CREATE(key_name, session_key), \ |
| MEMFAULT_METRICS_SESSION_KEY(session_key), scale_value) |
| |
| static const sMemfaultMetricKVPair s_memfault_heartbeat_keys[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_SESSION_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE |
| }; |
| |
| // From this point forward in the file, higher level macros (i.e. |
| // MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE) are not undefined. This means if you need to redefine a |
| // higher-level macro OR need to redefine the interface of the low-level macros |
| // (MEMFAULT_METRICS_KEY_DEFINE, MEMFAULT_METRICS_STRING_KEY_DEFINE, MEMFAULT_METRICS_KEY_DEFINE_) |
| // you must do this prior to this comment block |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE(key_name, value_type, _min, _max) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_WITH_SESSION(key_name, max_length, session_key) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE(session_key##__##key_name, max_length) |
| |
| //! Sessions have the following built-in keys: |
| //! - "<session name>__MemfaultSdkMetric_IntervalMs" |
| //! - "<session name>__operational_crashes" |
| #define MEMFAULT_METRICS_SESSION_KEY_DEFINE(key_name) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##MemfaultSdkMetric_IntervalMs, \ |
| kMemfaultMetricType_Timer) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name##__##operational_crashes, kMemfaultMetricType_Unsigned) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, min_value, \ |
| max_value, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION(key_name, value_type, session_key) \ |
| MEMFAULT_METRICS_KEY_DEFINE_WITH_RANGE_AND_SESSION(key_name, value_type, 0, 0, session_key) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SCALE_VALUE(key_name, value_type, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE_WITH_SESSION_AND_SCALE_VALUE(key_name, value_type, \ |
| session_key, scale_value) \ |
| MEMFAULT_METRICS_KEY_DEFINE(session_key##__##key_name, value_type) |
| |
| MEMFAULT_STATIC_ASSERT(MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys) != 0, |
| "At least one \"MEMFAULT_METRICS_KEY_DEFINE\" must be defined"); |
| |
| // Generate a mapping of key index to key value position in s_memfault_heartbeat_values. |
| // First produce a sparse enum for the key values that are stored in s_memfault_heartbeat_values. |
| typedef enum MfltMetricKeyToValueIndex { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) \ |
| MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type) |
| #define MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type) kMfltMetricKeyToValueIndex_##key_name, |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| kMfltMetricKeyToValueIndex_Count |
| } eMfltMetricKeyToValueIndex; |
| // Now generate a table mapping the canonical key ID to the index in s_memfault_heartbeat_values |
| static const eMfltMetricKeyToValueIndex s_memfault_heartbeat_key_to_valueindex[] = { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) \ |
| MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type) |
| #define MEMFAULT_METRICS_KEY_DEFINE_(key_name, value_type) kMfltMetricKeyToValueIndex_##key_name, |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| (eMfltMetricKeyToValueIndex)0, // 0 for the placeholder so it's safe to index with |
| |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_KEY_DEFINE_ |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| }; |
| MEMFAULT_STATIC_ASSERT( |
| MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys) == |
| MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_key_to_valueindex), |
| "Mismatch between s_memfault_heartbeat_keys and s_memfault_heartbeat_key_to_valueindex"); |
| // And a similar approach for the strings |
| typedef enum MfltMetricStringKeyToIndex { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| MEMFAULT_METRICS_STRING_KEY_DEFINE_(key_name, max_length) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE_(key_name, max_length) \ |
| kMfltMetricStringKeyToIndex_##key_name, |
| |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE_ |
| kMfltMetricStringKeyToIndex_Count |
| } eMfltMetricStringKeyToIndex; |
| // Now generate a table mapping the canonical key ID to the index in s_memfault_heartbeat_values |
| static const eMfltMetricStringKeyToIndex s_memfault_heartbeat_string_key_to_index[] = { |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) \ |
| (eMfltMetricStringKeyToIndex)0, // 0 for the placeholder so it's safe to index with |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| kMfltMetricStringKeyToIndex_##key_name, |
| |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| }; |
| MEMFAULT_STATIC_ASSERT( |
| MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys) == |
| MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_string_key_to_index), |
| "Mismatch between s_memfault_heartbeat_keys and s_memfault_heartbeat_string_key_to_index"); |
| |
| // String value lookup table. Const- the pointers do not change at runtime, so |
| // this table can be stored in ROM and save a little RAM. |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) \ |
| { .ptr = s_memfault_metrics_ctx.string_metrics_storage.g_memfault_metrics_string_##key_name }, |
| static const union MemfaultMetricValue s_memfault_heartbeat_string_values[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| // include a stub entry to prevent compilation errors when no strings are defined |
| { .ptr = NULL }, |
| }; |
| |
| // We need a key-index table of pointers to timer metadata for fast lookups. |
| // The enum eMfltMetricsTimerIndex will create a subset of indexes for use |
| // in the s_memfault_metrics_ctx.heartbeat_timer_values_metadata[] table. The |
| // s_metric_timer_metadata_mapping[] table provides the mapping from the |
| // exhaustive list of keys to valid timer indexes or -1 if not a timer. |
| #define MEMFAULT_METRICS_KEY_DEFINE(_name, _type) MEMFAULT_METRICS_STATE_HELPER_##_type(_name) |
| |
| // String metrics are not present in eMfltMetricsTimerIndex |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) |
| |
| #undef MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Timer |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Timer(key_name) \ |
| kMfltMetricsTimerIndex_##key_name, |
| |
| typedef enum MfltTimerIndex { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| } eMfltMetricsTimerIndex; |
| |
| #undef MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Unsigned |
| #undef MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Signed |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Unsigned(_name) -1, |
| #define MEMFAULT_METRICS_STATE_HELPER_kMemfaultMetricType_Signed(_name) -1, |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) -1, |
| |
| static const int s_metric_timer_metadata_mapping[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| }; |
| |
| // Helper macros to convert between the various metrics indices |
| #define MEMFAULT_METRICS_ID_TO_KEY(id) ((size_t)(id)._impl) |
| #define MEMFAULT_METRICS_KEY_TO_KV_INDEX(key) (s_memfault_heartbeat_key_to_valueindex[(key)]) |
| #define MEMFAULT_METRICS_ID_TO_KV_INDEX(id) \ |
| (MEMFAULT_METRICS_KEY_TO_KV_INDEX(MEMFAULT_METRICS_ID_TO_KEY(id))) |
| |
| // |
| // Routines which can be overridden by customers |
| // |
| |
| MEMFAULT_WEAK void memfault_metrics_heartbeat_collect_data(void) { } |
| |
| MEMFAULT_WEAK void memfault_metrics_heartbeat_collect_sdk_data(void) { } |
| |
| #if MEMFAULT_METRICS_LOGS_ENABLE |
| static void prv_memfault_collect_log_metrics(void) { |
| const uint32_t log_dropped_count = memfault_log_get_dropped_count(); |
| const uint32_t log_recorded_count = memfault_log_get_recorded_count(); |
| |
| // Note: this will wrap when the counts overflow, but as long as there aren't |
| // > UINT32_MAX dropped/recorded lines in a single heartbeat, the arithmetic |
| // will be valid. |
| const uint32_t delta_log_dropped_count = |
| log_dropped_count - s_memfault_metrics_ctx.last_log_dropped_count; |
| s_memfault_metrics_ctx.last_log_dropped_count = log_dropped_count; |
| MEMFAULT_METRIC_ADD(MemfaultSDKMetric_log_dropped_lines, (int32_t)delta_log_dropped_count); |
| const uint32_t delta_log_recorded_count = |
| log_recorded_count - s_memfault_metrics_ctx.last_log_recorded_count; |
| s_memfault_metrics_ctx.last_log_recorded_count = log_recorded_count; |
| MEMFAULT_METRIC_ADD(MemfaultSDKMetric_log_recorded_lines, (int32_t)delta_log_recorded_count); |
| } |
| #endif |
| |
| // This function calls built in metrics collection functions. |
| static void prv_collect_builtin_data(void) { |
| memfault_metrics_reliability_collect(); |
| #if MEMFAULT_METRICS_BATTERY_ENABLE |
| memfault_metrics_battery_collect_data(); |
| #endif |
| #if MEMFAULT_METRICS_LOGS_ENABLE |
| prv_memfault_collect_log_metrics(); |
| #endif |
| #if MEMFAULT_METRICS_UPTIME_ENABLE |
| // uptime_s is a 32-bit value measuring seconds since boot. Convert the uptime |
| // in milliseconds to seconds using the following approach, to avoid 64-bit |
| // division: |
| // |
| // precomputed_scale_factor = (1 << 32) / 1000 = 0x418937 |
| // uptime_seconds = (uptime_ms * 0x418937) >> 32 |
| // |
| // The maximum millisecond value that can be converted to seconds without |
| // overflowing using this algorithm is: |
| // |
| // (0xFFFF_FFFF_FFFF_FFFF / ((1 << 32) / 1000)) = 4294967592000 milliseconds |
| // |
| // 4294967592000 / 1000 / 60 / 60 / 24 / 365 ≈ 136 years |
| // |
| // 4294967592000 / 1000 = 4294967592 seconds, 0x1_0000_0128 in hex, which |
| // exceeds the possible value for a 32-bit integer, so we don't lose any range |
| // with this algorithm. |
| const uint32_t uptime_seconds = |
| (uint32_t)((memfault_platform_get_time_since_boot_ms() * 0x418937ull) >> 32); |
| MEMFAULT_METRIC_SET_UNSIGNED(uptime_s, uptime_seconds); |
| #endif |
| } |
| |
| // Returns NULL if not a timer type or out of bounds index. |
| static sMemfaultMetricValueMetadata *prv_find_timer_metadatap(eMfltMetricsIndex metric_index) { |
| if (metric_index >= MEMFAULT_ARRAY_SIZE(s_metric_timer_metadata_mapping)) { |
| MEMFAULT_LOG_ERROR("Metric index %u exceeds expected array bounds %d\n", metric_index, |
| (int)MEMFAULT_ARRAY_SIZE(s_metric_timer_metadata_mapping)); |
| return NULL; |
| } |
| |
| const int timer_index = s_metric_timer_metadata_mapping[metric_index]; |
| if (timer_index == -1) { |
| return NULL; |
| } |
| |
| return &s_memfault_metrics_ctx.heartbeat_timer_values_metadata[timer_index]; |
| } |
| |
| //! Helper function to read/write is_set bits for the provided metric |
| //! |
| //! @param id Metric ID to select corresponding is_set field |
| //! @param write Boolean to control whether to write 1 to is_set |
| //! @return Returns the value of metric's is_set field. The updated value is returned if write = |
| //! true |
| static bool prv_read_write_is_value_set(MemfaultMetricId id, bool write) { |
| // Shift the kv index by MEMFAULT_IS_SET_FLAGS_DIVIDER to select byte within |
| // s_memfault_metrics_ctx.heartbeat_value_is_set_flags |
| size_t byte_index = MEMFAULT_METRICS_ID_TO_KV_INDEX(id) >> MEMFAULT_IS_SET_FLAGS_DIVIDER; |
| // Modulo the kv index by MEMFAULT_IS_SET_FLAGS_PER_BYTE to get bit of the selected byte |
| size_t bit_index = MEMFAULT_METRICS_ID_TO_KV_INDEX(id) % MEMFAULT_IS_SET_FLAGS_PER_BYTE; |
| |
| if (write) { |
| s_memfault_metrics_ctx.heartbeat_value_is_set_flags[byte_index] |= (1 << bit_index); |
| } |
| |
| return (s_memfault_metrics_ctx.heartbeat_value_is_set_flags[byte_index] >> bit_index) & 0x01; |
| } |
| |
| static void prv_clear_is_value_set(eMfltMetricKeyToValueIndex key) { |
| // Shift the kv index by MEMFAULT_IS_SET_FLAGS_DIVIDER to select byte within |
| // s_memfault_metrics_ctx.heartbeat_value_is_set_flags |
| size_t byte_index = key >> MEMFAULT_IS_SET_FLAGS_DIVIDER; |
| // Modulo the kv index by MEMFAULT_IS_SET_FLAGS_PER_BYTE to get bit of the selected byte |
| size_t bit_index = key % MEMFAULT_IS_SET_FLAGS_PER_BYTE; |
| |
| s_memfault_metrics_ctx.heartbeat_value_is_set_flags[byte_index] &= ~(1 << bit_index); |
| } |
| |
| static eMemfaultMetricType prv_find_value_for_key(MemfaultMetricId id, |
| sMemfaultMetricValueInfo *value_info_out) { |
| const size_t idx = MEMFAULT_METRICS_ID_TO_KEY(id); |
| if (idx >= MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys)) { |
| *value_info_out = (sMemfaultMetricValueInfo){ 0 }; |
| return kMemfaultMetricType_NumTypes; |
| } |
| |
| // get the index for the value matching this key. |
| eMfltMetricKeyToValueIndex key_index = MEMFAULT_METRICS_KEY_TO_KV_INDEX(idx); |
| // for scalar types, this will be the returned value pointer. non-scalars |
| // will be handled in the switch below |
| union MemfaultMetricValue *value_ptr = &s_memfault_metrics_ctx.heartbeat_values.values[key_index]; |
| |
| eMemfaultMetricType key_type = s_memfault_heartbeat_keys[idx].type; |
| switch (key_type) { |
| case kMemfaultMetricType_String: { |
| // get the string value associated with this key |
| eMfltMetricStringKeyToIndex string_key_index = s_memfault_heartbeat_string_key_to_index[idx]; |
| // cast to uintptr_t then the final pointer type we want to drop the |
| // 'const' and prevent tripping -Wcast-qual. this is safe, because we |
| // never modify *value_ptr, only value_ptr->ptr, for non-scalar types. |
| value_ptr = (union MemfaultMetricValue |
| *)(uintptr_t)&s_memfault_heartbeat_string_values[string_key_index]; |
| |
| } break; |
| |
| case kMemfaultMetricType_Timer: |
| case kMemfaultMetricType_Signed: |
| case kMemfaultMetricType_Unsigned: |
| case kMemfaultMetricType_NumTypes: // To silence -Wswitch-enum |
| default: |
| break; |
| } |
| |
| *value_info_out = (sMemfaultMetricValueInfo){ |
| .valuep = value_ptr, |
| .meta_datap = prv_find_timer_metadatap((eMfltMetricsIndex)idx), |
| }; |
| |
| if (key_type == kMemfaultMetricType_Unsigned || key_type == kMemfaultMetricType_Signed) { |
| value_info_out->is_set = prv_read_write_is_value_set(id, false); |
| } |
| |
| return key_type; |
| } |
| |
| typedef bool (*MemfaultMetricKvIteratorCb)(void *ctx, const sMemfaultMetricKVPair *kv_pair, |
| const sMemfaultMetricValueInfo *value_info); |
| static void prv_metric_iterator(void *ctx, MemfaultMetricKvIteratorCb cb) { |
| for (uint32_t idx = 0; idx < MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys); ++idx) { |
| const sMemfaultMetricKVPair *const kv_pair = &s_memfault_heartbeat_keys[idx]; |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| |
| (void)prv_find_value_for_key(kv_pair->key, &value_info); |
| |
| bool do_continue = cb(ctx, kv_pair, &value_info); |
| |
| if (!do_continue) { |
| break; |
| } |
| } |
| } |
| |
| static const sMemfaultMetricKVPair *prv_find_kvpair_for_key(MemfaultMetricId key) { |
| const size_t idx = (size_t)key._impl; |
| if (idx >= MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys)) { |
| return NULL; |
| } |
| |
| return &s_memfault_heartbeat_keys[idx]; |
| } |
| |
| static int prv_find_value_info_for_type(MemfaultMetricId key, eMemfaultMetricType expected_type, |
| sMemfaultMetricValueInfo *value_info) { |
| const eMemfaultMetricType type = prv_find_value_for_key(key, value_info); |
| if (value_info->valuep == NULL) { |
| return MEMFAULT_METRICS_KEY_NOT_FOUND; |
| } |
| if (type != expected_type) { |
| // To easily get name of metric in gdb, p/s (eMfltMetricsIndex)0 |
| MEMFAULT_LOG_ERROR("Invalid type (%u vs %u) for key: %d", expected_type, type, key._impl); |
| return MEMFAULT_METRICS_TYPE_INCOMPATIBLE; |
| } |
| return 0; |
| } |
| |
| static void prv_set_value_for_key(MemfaultMetricId key, union MemfaultMetricValue *new_value, |
| sMemfaultMetricValueInfo *value_info) { |
| *value_info->valuep = *new_value; |
| prv_read_write_is_value_set(key, true); |
| } |
| |
| static int prv_find_and_set_value_for_key(MemfaultMetricId key, eMemfaultMetricType expected_type, |
| union MemfaultMetricValue *new_value) { |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| int rv = prv_find_value_info_for_type(key, expected_type, &value_info); |
| if (rv != 0) { |
| return rv; |
| } |
| |
| prv_set_value_for_key(key, new_value, &value_info); |
| |
| return 0; |
| } |
| |
| int memfault_metrics_heartbeat_set_signed(MemfaultMetricId key, int32_t signed_value) { |
| int rv; |
| memfault_lock(); |
| { |
| rv = prv_find_and_set_value_for_key(key, kMemfaultMetricType_Signed, |
| &(union MemfaultMetricValue){ .i32 = signed_value }); |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| int memfault_metrics_heartbeat_set_unsigned(MemfaultMetricId key, uint32_t unsigned_value) { |
| int rv; |
| memfault_lock(); |
| { |
| rv = prv_find_and_set_value_for_key(key, kMemfaultMetricType_Unsigned, |
| &(union MemfaultMetricValue){ .u32 = unsigned_value }); |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| int memfault_metrics_heartbeat_set_string(MemfaultMetricId key, const char *value) { |
| int rv; |
| memfault_lock(); |
| { |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| rv = prv_find_value_info_for_type(key, kMemfaultMetricType_String, &value_info); |
| const sMemfaultMetricKVPair *kv = prv_find_kvpair_for_key(key); |
| |
| // error if either the key is bad, or we can't find the kvpair for the key |
| // (both checks should have the same result though) |
| rv = (rv != 0 || kv == NULL) ? MEMFAULT_METRICS_KEY_NOT_FOUND : 0; |
| |
| if (rv == 0) { |
| const size_t len = MEMFAULT_MIN(strlen(value), kv->range); |
| memcpy(value_info.valuep->ptr, value, len); |
| // null terminate |
| ((char *)value_info.valuep->ptr)[len] = '\0'; |
| } |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| typedef enum { |
| kMemfaultTimerOp_Start, |
| kMemfaultTimerOp_Stop, |
| kMemfaultTimerOp_ForceValueUpdate, |
| } eMemfaultTimerOp; |
| |
| static bool prv_update_timer_metric(const sMemfaultMetricValueInfo *value_info, |
| eMemfaultTimerOp op) { |
| sMemfaultMetricValueMetadata *meta_datap = value_info->meta_datap; |
| #if defined(MEMFAULT_UNITTEST) |
| // It's a programming error if we reach this point and meta_datap is NULL. It |
| // should always be valid for a timer metric class. Use c stdlib assert to |
| // prevent the static analyzer from triggering below. |
| #include <assert.h> |
| assert(meta_datap != NULL); |
| #endif |
| const bool timer_running = meta_datap->is_running; |
| |
| // The timer is not running _and_ we received a Start request |
| if (!timer_running && op == kMemfaultTimerOp_Start) { |
| meta_datap->start_time_ms = memfault_platform_get_time_since_boot_ms(); |
| meta_datap->is_running = true; |
| return true; |
| } |
| |
| // the timer is running and we received a Stop or ForceValueUpdate request |
| if (timer_running && op != kMemfaultTimerOp_Start) { |
| const uint32_t stop_time_ms = |
| memfault_platform_get_time_since_boot_ms() & ~MEMFAULT_METRICS_TIMER_VAL_MAX; |
| const uint32_t start_time_ms = meta_datap->start_time_ms; |
| |
| uint32_t delta; |
| if (stop_time_ms >= start_time_ms) { |
| delta = stop_time_ms - start_time_ms; |
| } else { // account for rollover |
| delta = MEMFAULT_METRICS_TIMER_VAL_MAX - start_time_ms + stop_time_ms; |
| } |
| value_info->valuep->u32 += delta; |
| |
| if (op == kMemfaultTimerOp_Stop) { |
| meta_datap->start_time_ms = 0; |
| meta_datap->is_running = false; |
| } else { |
| meta_datap->start_time_ms = stop_time_ms; |
| } |
| |
| return true; |
| } |
| |
| // We were already in the state requested and no update took place |
| return false; |
| } |
| |
| static int prv_find_timer_metric_and_update(MemfaultMetricId key, eMemfaultTimerOp op) { |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| int rv = prv_find_value_info_for_type(key, kMemfaultMetricType_Timer, &value_info); |
| if (rv != 0) { |
| return rv; |
| } |
| |
| // If the value did not change because the timer was already in the state requested return an |
| // error code. This will make it easier for users of the external API to catch if their calls |
| // were unbalanced. |
| const bool did_update = prv_update_timer_metric(&value_info, op); |
| return did_update ? 0 : MEMFAULT_METRICS_TYPE_NO_CHANGE; |
| } |
| |
| int memfault_metrics_heartbeat_timer_start(MemfaultMetricId key) { |
| int rv; |
| memfault_lock(); |
| { rv = prv_find_timer_metric_and_update(key, kMemfaultTimerOp_Start); } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| int memfault_metrics_heartbeat_timer_stop(MemfaultMetricId key) { |
| int rv; |
| memfault_lock(); |
| { rv = prv_find_timer_metric_and_update(key, kMemfaultTimerOp_Stop); } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| static bool prv_tally_and_update_timer_cb(MEMFAULT_UNUSED void *ctx, |
| const sMemfaultMetricKVPair *key, |
| const sMemfaultMetricValueInfo *value) { |
| if (key->type != kMemfaultMetricType_Timer) { |
| return true; |
| } |
| |
| prv_update_timer_metric(value, kMemfaultTimerOp_ForceValueUpdate); |
| return true; |
| } |
| |
| static void prv_reset_metrics(bool full_reset, eMfltMetricsSessionIndex session_key) { |
| if (full_reset) { |
| // if a full reset is indicated zero out all metrics regardless of session. |
| memset(s_memfault_metrics_ctx.heartbeat_values.values, 0, |
| sizeof(s_memfault_metrics_ctx.heartbeat_values.values)); |
| memset(s_memfault_metrics_ctx.heartbeat_value_is_set_flags, 0, |
| sizeof(s_memfault_metrics_ctx.heartbeat_value_is_set_flags)); |
| memset(s_memfault_metrics_ctx.heartbeat_timer_values_metadata, 0, |
| sizeof(s_memfault_metrics_ctx.heartbeat_timer_values_metadata)); |
| |
| // reset all string metric values. -1 to skip the last, stub entry in the |
| // table |
| for (size_t i = 0; i < MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_string_values); i++) { |
| // set null terminator |
| if (s_memfault_heartbeat_string_values[i].ptr) { |
| ((char *)s_memfault_heartbeat_string_values[i].ptr)[0] = 0; |
| } |
| } |
| } else { |
| // otherwise only clear metrics from the specified session |
| for (uint32_t idx = 0; idx < MEMFAULT_ARRAY_SIZE(s_memfault_heartbeat_keys); ++idx) { |
| const sMemfaultMetricKVPair *const kv_pair = &s_memfault_heartbeat_keys[idx]; |
| // Skip all metrics from a different session |
| if (kv_pair->session_key != session_key) { |
| continue; |
| } |
| |
| eMfltMetricStringKeyToIndex string_idx = s_memfault_heartbeat_string_key_to_index[idx]; |
| eMfltMetricKeyToValueIndex key_index = MEMFAULT_METRICS_KEY_TO_KV_INDEX(idx); |
| switch (kv_pair->type) { |
| case kMemfaultMetricType_Timer: |
| case kMemfaultMetricType_Signed: |
| case kMemfaultMetricType_Unsigned: { |
| s_memfault_metrics_ctx.heartbeat_values.values[key_index] = |
| (union MemfaultMetricValue){ 0 }; |
| prv_clear_is_value_set(key_index); |
| break; |
| } |
| case kMemfaultMetricType_String: |
| if (s_memfault_heartbeat_string_values[string_idx].ptr) { |
| ((char *)s_memfault_heartbeat_string_values[string_idx].ptr)[0] = 0; |
| } |
| break; |
| case kMemfaultMetricType_NumTypes: // To silence -Wswitch-enum |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| static void prv_heartbeat_timer_update(void) { |
| // force an update of the timer value for any actively running timers |
| prv_metric_iterator(NULL, prv_tally_and_update_timer_cb); |
| } |
| |
| //! Triggers a heartbeat update only, no timer update |
| static void prv_heartbeat_update(void) { |
| memfault_metrics_heartbeat_collect_sdk_data(); |
| prv_collect_builtin_data(); |
| memfault_metrics_heartbeat_collect_data(); |
| } |
| |
| //! Trigger an update of heartbeat timers + metrics, serialize out to storage, and reset. |
| static void prv_heartbeat_timer(void) { |
| prv_heartbeat_timer_update(); |
| prv_heartbeat_update(); |
| |
| memfault_metrics_heartbeat_serialize(s_memfault_metrics_ctx.storage_impl); |
| |
| prv_reset_metrics(false, MEMFAULT_METRICS_SESSION_KEY(heartbeat)); |
| } |
| |
| static int prv_find_key_and_add(MemfaultMetricId key, int32_t amount) { |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| const eMemfaultMetricType type = prv_find_value_for_key(key, &value_info); |
| if (value_info.valuep == NULL) { |
| return MEMFAULT_METRICS_KEY_NOT_FOUND; |
| } |
| union MemfaultMetricValue *value = value_info.valuep; |
| |
| switch ((int)type) { |
| case kMemfaultMetricType_Signed: { |
| // Clip in case of overflow: |
| int64_t new_value = (int64_t)value->i32 + (int64_t)amount; |
| if (new_value > INT32_MAX) { |
| value->i32 = INT32_MAX; |
| } else if (new_value < INT32_MIN) { |
| value->i32 = INT32_MIN; |
| } else { |
| value->i32 = new_value; |
| } |
| break; |
| } |
| |
| case kMemfaultMetricType_Unsigned: { |
| uint32_t new_value = value->u32 + (uint32_t)amount; |
| const bool amount_is_positive = amount > 0; |
| const bool did_increase = new_value > value->u32; |
| // Clip in case of overflow: |
| if ((uint32_t)amount_is_positive ^ (uint32_t)did_increase) { |
| new_value = amount_is_positive ? UINT32_MAX : 0; |
| } |
| value->u32 = new_value; |
| break; |
| } |
| |
| case kMemfaultMetricType_Timer: |
| case kMemfaultMetricType_String: |
| case kMemfaultMetricType_NumTypes: // To silence -Wswitch-enum |
| default: |
| // To easily get name of metric in gdb, p/s (eMfltMetricsIndex)0 |
| MEMFAULT_LOG_ERROR("Can only add to number types (key: %d)", key._impl); |
| return MEMFAULT_METRICS_TYPE_INCOMPATIBLE; |
| } |
| return 0; |
| } |
| |
| int memfault_metrics_heartbeat_add(MemfaultMetricId key, int32_t amount) { |
| int rv; |
| memfault_lock(); |
| { |
| rv = prv_find_key_and_add(key, amount); |
| if (rv == 0) { |
| prv_read_write_is_value_set(key, true); |
| } |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| static int prv_find_key_of_type(MemfaultMetricId key, eMemfaultMetricType expected_type, |
| union MemfaultMetricValue **value_out) { |
| sMemfaultMetricValueInfo value_info = { 0 }; |
| const eMemfaultMetricType type = prv_find_value_for_key(key, &value_info); |
| if (value_info.valuep == NULL) { |
| return MEMFAULT_METRICS_KEY_NOT_FOUND; |
| } |
| if (type != expected_type) { |
| return MEMFAULT_METRICS_TYPE_INCOMPATIBLE; |
| } |
| if ((type == kMemfaultMetricType_Signed || type == kMemfaultMetricType_Unsigned) && |
| !(value_info.is_set)) { |
| return MEMFAULT_METRICS_VALUE_NOT_SET; |
| } |
| |
| *value_out = value_info.valuep; |
| return 0; |
| } |
| |
| int memfault_metrics_heartbeat_read_unsigned(MemfaultMetricId key, uint32_t *read_val) { |
| if (read_val == NULL) { |
| return MEMFAULT_METRICS_TYPE_BAD_PARAM; |
| } |
| |
| int rv; |
| memfault_lock(); |
| { |
| union MemfaultMetricValue *value; |
| rv = prv_find_key_of_type(key, kMemfaultMetricType_Unsigned, &value); |
| if (rv == 0) { |
| *read_val = value->u32; |
| } |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| int memfault_metrics_heartbeat_read_signed(MemfaultMetricId key, int32_t *read_val) { |
| if (read_val == NULL) { |
| return MEMFAULT_METRICS_TYPE_BAD_PARAM; |
| } |
| |
| int rv; |
| memfault_lock(); |
| { |
| union MemfaultMetricValue *value; |
| rv = prv_find_key_of_type(key, kMemfaultMetricType_Signed, &value); |
| if (rv == 0) { |
| *read_val = value->i32; |
| } |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| |
| int memfault_metrics_heartbeat_timer_read(MemfaultMetricId key, uint32_t *read_val) { |
| if (read_val == NULL) { |
| return MEMFAULT_METRICS_TYPE_BAD_PARAM; |
| } |
| |
| int rv; |
| memfault_lock(); |
| { |
| union MemfaultMetricValue *value; |
| prv_find_timer_metric_and_update(key, kMemfaultTimerOp_ForceValueUpdate); |
| rv = prv_find_key_of_type(key, kMemfaultMetricType_Timer, &value); |
| if (rv == 0) { |
| *read_val = value->u32; |
| } |
| } |
| memfault_unlock(); |
| return rv; |
| } |
| int memfault_metrics_heartbeat_read_string(MemfaultMetricId key, char *read_val, |
| size_t read_val_len) { |
| if ((read_val == NULL) || (read_val_len == 0)) { |
| return MEMFAULT_METRICS_TYPE_BAD_PARAM; |
| } |
| |
| int rv; |
| memfault_lock(); |
| { |
| union MemfaultMetricValue *value; |
| rv = prv_find_key_of_type(key, kMemfaultMetricType_String, &value); |
| const sMemfaultMetricKVPair *kv = prv_find_kvpair_for_key(key); |
| |
| rv = (rv != 0 || kv == NULL) ? MEMFAULT_METRICS_KEY_NOT_FOUND : 0; |
| |
| if (rv == 0) { |
| // copy up to the min of the length of the string and the length of the |
| // provided buffer |
| size_t len = strlen((const char *)value->ptr) + 1; |
| memcpy(read_val, value->ptr, MEMFAULT_MIN(len, read_val_len)); |
| // always null terminate |
| read_val[read_val_len - 1] = '\0'; |
| } |
| } |
| memfault_unlock(); |
| |
| return rv; |
| } |
| |
| static int prv_metrics_session_start(eMfltMetricsSessionIndex session_key, bool start_timer) { |
| int rv = 0; |
| memfault_lock(); |
| { |
| // Reset all metrics for the session. Any changes that happened before the |
| // session was started don't matter and can be discarded. |
| prv_reset_metrics(false, session_key); |
| |
| if (start_timer) { |
| MemfaultMetricId key = s_memfault_metrics_session_timer_keys[session_key]; |
| rv = prv_find_timer_metric_and_update(key, kMemfaultTimerOp_Start); |
| } |
| |
| // Mark the session as active for tracking operational_crashes |
| memfault_reboot_tracking_metrics_session(true, session_key); |
| } |
| memfault_unlock(); |
| |
| MemfaultMetricsSessionStartCb session_start_cb = |
| s_memfault_metrics_ctx.session_start_cbs[session_key]; |
| if (session_start_cb != NULL) { |
| session_start_cb(); |
| } |
| |
| return rv; |
| } |
| |
| int memfault_metrics_session_start(eMfltMetricsSessionIndex session_key) { |
| return prv_metrics_session_start(session_key, true); |
| } |
| |
| static int prv_metrics_session_end(eMfltMetricsSessionIndex session_key, bool stop_timer) { |
| MemfaultMetricsSessionEndCb session_end_cb = s_memfault_metrics_ctx.session_end_cbs[session_key]; |
| if (session_end_cb != NULL) { |
| session_end_cb(); |
| } |
| |
| int rv = 0; |
| memfault_lock(); |
| { |
| if (stop_timer) { |
| MemfaultMetricId key = s_memfault_metrics_session_timer_keys[session_key]; |
| rv = prv_find_timer_metric_and_update(key, kMemfaultTimerOp_Stop); |
| } |
| |
| if (rv == 0) { |
| bool serialize_result = |
| memfault_metrics_session_serialize(s_memfault_metrics_ctx.storage_impl, session_key); |
| if (serialize_result == false) { |
| rv = MEMFAULT_METRICS_STORAGE_TOO_SMALL; |
| } |
| } |
| // Mark the session as inactive for tracking operational_crashes |
| memfault_reboot_tracking_metrics_session(false, session_key); |
| } |
| memfault_unlock(); |
| |
| return rv; |
| } |
| |
| int memfault_metrics_session_end(eMfltMetricsSessionIndex session_key) { |
| return prv_metrics_session_end(session_key, true); |
| } |
| |
| void memfault_metrics_session_reset(eMfltMetricsSessionIndex session_key) { |
| memfault_lock(); |
| { |
| // Reset all metrics for the session, including the session timer |
| MemfaultMetricId key = s_memfault_metrics_session_timer_keys[session_key]; |
| (void)prv_find_timer_metric_and_update(key, kMemfaultTimerOp_Stop); |
| prv_reset_metrics(false, session_key); |
| |
| memfault_reboot_tracking_metrics_session(false, session_key); |
| } |
| memfault_unlock(); |
| } |
| |
| void memfault_metrics_session_register_start_cb(eMfltMetricsSessionIndex session_key, |
| MemfaultMetricsSessionStartCb session_start_cb) { |
| memfault_lock(); |
| { s_memfault_metrics_ctx.session_start_cbs[session_key] = session_start_cb; } |
| memfault_unlock(); |
| } |
| |
| void memfault_metrics_session_register_end_cb(eMfltMetricsSessionIndex session_key, |
| MemfaultMetricsSessionEndCb session_end_cb) { |
| memfault_lock(); |
| { s_memfault_metrics_ctx.session_end_cbs[session_key] = session_end_cb; } |
| memfault_unlock(); |
| } |
| |
| typedef struct { |
| MemfaultMetricIteratorCallback user_cb; |
| void *user_ctx; |
| } sMetricHeartbeatIterateCtx; |
| |
| static bool prv_metrics_heartbeat_iterate_cb(void *ctx, const sMemfaultMetricKVPair *key_info, |
| const sMemfaultMetricValueInfo *value_info) { |
| sMetricHeartbeatIterateCtx *ctx_info = (sMetricHeartbeatIterateCtx *)ctx; |
| |
| if (value_info->valuep == NULL) { |
| return false; |
| } |
| |
| sMemfaultMetricInfo info = { |
| .key = key_info->key, |
| .type = key_info->type, |
| .val = *value_info->valuep, |
| .is_set = value_info->is_set, |
| .session_key = key_info->session_key, |
| }; |
| return ctx_info->user_cb(ctx_info->user_ctx, &info); |
| } |
| |
| void memfault_metrics_heartbeat_iterate(MemfaultMetricIteratorCallback cb, void *ctx) { |
| memfault_lock(); |
| { |
| sMetricHeartbeatIterateCtx user_ctx = { |
| .user_cb = cb, |
| .user_ctx = ctx, |
| }; |
| prv_metric_iterator(&user_ctx, prv_metrics_heartbeat_iterate_cb); |
| } |
| memfault_unlock(); |
| } |
| |
| typedef struct { |
| size_t num_metrics; |
| eMfltMetricsSessionIndex session_key; |
| } sGetNumMetricsCtx; |
| |
| static bool prv_get_num_metrics_iter_cb(void *ctx, const sMemfaultMetricInfo *metric_info) { |
| sGetNumMetricsCtx *num_metrics_ctx = (sGetNumMetricsCtx *)ctx; |
| |
| if (metric_info->session_key == num_metrics_ctx->session_key) { |
| num_metrics_ctx->num_metrics++; |
| } |
| |
| return true; |
| } |
| |
| static size_t prv_get_num_metrics(eMfltMetricsSessionIndex session_key) { |
| sGetNumMetricsCtx ctx = { .session_key = session_key }; |
| |
| memfault_metrics_heartbeat_iterate(prv_get_num_metrics_iter_cb, (void *)&ctx); |
| |
| return ctx.num_metrics; |
| } |
| |
| size_t memfault_metrics_heartbeat_get_num_metrics(void) { |
| return prv_get_num_metrics(MEMFAULT_METRICS_SESSION_KEY(heartbeat)); |
| } |
| |
| size_t memfault_metrics_session_get_num_metrics(eMfltMetricsSessionIndex session_key) { |
| return prv_get_num_metrics(session_key); |
| } |
| |
| #define MEMFAULT_METRICS_KEY_DEFINE(key_name, value_type) MEMFAULT_QUOTE(key_name), |
| #define MEMFAULT_METRICS_STRING_KEY_DEFINE(key_name, max_length) MEMFAULT_QUOTE(key_name), |
| |
| static const char *s_idx_to_metric_name[] = { |
| #include "memfault/metrics/heartbeat_config.def" |
| #include MEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE |
| #undef MEMFAULT_METRICS_KEY_DEFINE |
| #undef MEMFAULT_METRICS_STRING_KEY_DEFINE |
| }; |
| |
| typedef bool(MemfaultMetricDebugPrintFilterCb)(eMfltMetricsSessionIndex ctx_key, |
| eMfltMetricsSessionIndex current_key); |
| |
| typedef struct { |
| MemfaultMetricDebugPrintFilterCb *print_filter; |
| eMfltMetricsSessionIndex session_key; |
| } sMetricDebugPrintCtx; |
| |
| static bool prv_metrics_debug_print(void *ctx, const sMemfaultMetricInfo *metric_info) { |
| sMetricDebugPrintCtx *cb_ctx = (sMetricDebugPrintCtx *)ctx; |
| if (!cb_ctx->print_filter(cb_ctx->session_key, metric_info->session_key)) { |
| return true; |
| } |
| |
| const MemfaultMetricId *key = &metric_info->key; |
| const union MemfaultMetricValue *value = &metric_info->val; |
| |
| const char *key_name = s_idx_to_metric_name[key->_impl]; |
| |
| switch (metric_info->type) { |
| case kMemfaultMetricType_Timer: |
| MEMFAULT_LOG_INFO(" %s: %" PRIu32, key_name, value->u32); |
| break; |
| case kMemfaultMetricType_Unsigned: |
| if (metric_info->is_set) { |
| MEMFAULT_LOG_INFO(" %s: %" PRIu32, key_name, value->u32); |
| } else { |
| MEMFAULT_LOG_INFO(" %s: null", key_name); |
| } |
| break; |
| case kMemfaultMetricType_Signed: |
| if (metric_info->is_set) { |
| MEMFAULT_LOG_INFO(" %s: %" PRIi32, key_name, value->i32); |
| } else { |
| MEMFAULT_LOG_INFO(" %s: null", key_name); |
| } |
| break; |
| case kMemfaultMetricType_String: |
| MEMFAULT_LOG_INFO(" %s: \"%s\"", key_name, (const char *)value->ptr); |
| break; |
| |
| case kMemfaultMetricType_NumTypes: // To silence -Wswitch-enum |
| default: |
| MEMFAULT_LOG_INFO(" %s: <unknown type>", key_name); |
| break; |
| } |
| |
| return true; // continue iterating |
| } |
| |
| //! Print all session metrics, skip heartbeat metrics |
| static bool prv_all_sessions_debug_print_filter(MEMFAULT_UNUSED eMfltMetricsSessionIndex ctx_key, |
| eMfltMetricsSessionIndex current_key) { |
| return (current_key != MEMFAULT_METRICS_SESSION_KEY(heartbeat)); |
| } |
| |
| //! Print all metrics from one session only |
| static bool prv_session_debug_print_filter(eMfltMetricsSessionIndex ctx_key, |
| eMfltMetricsSessionIndex current_key) { |
| return (current_key == ctx_key); |
| } |
| |
| void memfault_metrics_session_debug_print(eMfltMetricsSessionIndex session_key) { |
| prv_heartbeat_timer_update(); |
| MEMFAULT_LOG_INFO("Metrics keys/values:"); |
| sMetricDebugPrintCtx ctx = { |
| .session_key = session_key, |
| .print_filter = &prv_session_debug_print_filter, |
| }; |
| memfault_metrics_heartbeat_iterate(prv_metrics_debug_print, (void *)&ctx); |
| } |
| |
| void memfault_metrics_all_sessions_debug_print(void) { |
| prv_heartbeat_timer_update(); |
| MEMFAULT_LOG_INFO("Metrics keys/values:"); |
| sMetricDebugPrintCtx ctx = { |
| .print_filter = &prv_all_sessions_debug_print_filter, |
| }; |
| memfault_metrics_heartbeat_iterate(prv_metrics_debug_print, (void *)&ctx); |
| } |
| |
| void memfault_metrics_heartbeat_debug_print(void) { |
| memfault_metrics_session_debug_print(MEMFAULT_METRICS_SESSION_KEY(heartbeat)); |
| } |
| |
| void memfault_metrics_heartbeat_debug_trigger(void) { |
| prv_heartbeat_timer(); |
| } |
| |
| void memfault_metrics_heartbeat_collect(void) { |
| prv_heartbeat_update(); |
| } |
| |
| #if MEMFAULT_METRICS_SESSIONS_ENABLED |
| //! Called on boot, this function checks if the reboot was unexpected. If so, |
| //! any session that was active at time of reboot is triggered to record an |
| //! `operational_crash=1` metric, and serialized to storage. |
| static void prv_session_check_for_unexpected_reboot(void) { |
| int rv = -1; |
| bool unexpected_reboot; |
| |
| for (eMfltMetricsSessionIndex session_key = (eMfltMetricsSessionIndex)0; |
| session_key < (MEMFAULT_ARRAY_SIZE(s_memfault_metrics_operational_crashes_keys)); |
| session_key++) { |
| // This table always ends with a blank entry, so we can stop one iteration |
| // early (or if no sessions were defined). Some compilers warn on empty |
| // arrays. |
| if (session_key == MEMFAULT_ARRAY_SIZE(s_memfault_metrics_operational_crashes_keys) - 1) { |
| break; |
| } |
| |
| // Only run the check for unexpected reboot once, if it hasn't been already |
| // run in this function. Ideally this check would be outside the for loop, |
| // but since we can't conditionally compile this whole block based on if |
| // any sessions are defined, we have to do it this way. |
| if (rv != 0) { |
| rv = memfault_reboot_tracking_get_unexpected_reboot_occurred(&unexpected_reboot); |
| if ((rv != 0) || !unexpected_reboot) { |
| break; |
| } |
| } |
| |
| // If the session was active at time of reboot, serialize a session with the |
| // duration_ms=0 and operational_crashes=1. |
| if (memfault_reboot_tracking_metrics_session_was_active(session_key)) { |
| // Do not start the session timer, to keep the session duration = 0ms |
| prv_metrics_session_start(session_key, false); |
| |
| memfault_metrics_heartbeat_add(s_memfault_metrics_operational_crashes_keys[session_key], 1); |
| // Note: the below function clears the reboot tracking bit for this session, |
| // since the session is now inactive. A second crash after this won't be |
| // recorded as an operational_crash for the session (until the session is |
| // activated). |
| prv_metrics_session_end(session_key, false); |
| } |
| } |
| |
| // Unconditionally clear the reboot tracking data for "active sessions". |
| memfault_reboot_tracking_clear_metrics_sessions(); |
| } |
| #endif |
| |
| #if MEMFAULT_METRICS_RESTORE_STATE |
| const void *memfault_metrics_get_state(void) { |
| // Refresh the saved reliability context |
| sMemfaultMetricsReliabilityCtx *ctx = memfault_metrics_reliability_get_ctx(); |
| s_memfault_metrics_ctx.reliability_ctx = *ctx; |
| return (const void *)&s_memfault_metrics_ctx; |
| } |
| #endif |
| |
| int memfault_metrics_boot(const sMemfaultEventStorageImpl *storage_impl, |
| const sMemfaultMetricBootInfo *info) { |
| if (storage_impl == NULL || info == NULL) { |
| return MEMFAULT_METRICS_TYPE_BAD_PARAM; |
| } |
| |
| if (!memfault_serializer_helper_check_storage_size( |
| storage_impl, memfault_metrics_heartbeat_compute_worst_case_storage_size, "metrics")) { |
| return MEMFAULT_METRICS_STORAGE_TOO_SMALL; |
| } |
| |
| #if MEMFAULT_METRICS_RESTORE_STATE |
| const bool restored = memfault_metrics_restore_state(&s_memfault_metrics_ctx); |
| if (restored) { |
| // If we restored the state, attach the storage impl to the restored context |
| s_memfault_metrics_ctx.storage_impl = storage_impl; |
| memfault_metrics_reliability_boot(&s_memfault_metrics_ctx.reliability_ctx); |
| } else |
| // Otherwise, continue with normal metrics boot sequence |
| #endif // MEMFAULT_METRICS_RESTORE_STATE |
| { |
| s_memfault_metrics_ctx.storage_impl = storage_impl; |
| // Reset reliability state |
| memfault_metrics_reliability_boot(NULL); |
| prv_reset_metrics(true, MEMFAULT_METRICS_SESSION_KEY(heartbeat)); |
| |
| int rv = MEMFAULT_METRIC_TIMER_START(MemfaultSdkMetric_IntervalMs); |
| if (rv != 0) { |
| return rv; |
| } |
| |
| rv = MEMFAULT_METRIC_SET_UNSIGNED(MemfaultSdkMetric_UnexpectedRebootCount, |
| info->unexpected_reboot_count); |
| if (rv != 0) { |
| return rv; |
| } |
| |
| #if MEMFAULT_METRICS_SESSIONS_ENABLED |
| prv_session_check_for_unexpected_reboot(); |
| #endif |
| } |
| |
| // Finally, start the heartbeat timer. |
| const bool success = memfault_platform_metrics_timer_boot( |
| MEMFAULT_METRICS_HEARTBEAT_INTERVAL_SECS, prv_heartbeat_timer); |
| if (!success) { |
| return MEMFAULT_METRICS_TIMER_BOOT_FAILED; |
| } |
| |
| #if MEMFAULT_PLATFORM_METRICS_CONNECTIVITY_BOOT |
| memfault_platform_metrics_connectivity_boot(); |
| #endif |
| |
| return 0; |
| } |