blob: 3197378c072e297f3f8ef65e3b0689e2fc135794 [file] [log] [blame]
//! @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;
}