mirror of
https://github.com/google/pebble.git
synced 2025-07-19 12:34:48 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
212
src/fw/services/normal/analytics/analytics.c
Normal file
212
src/fw/services/normal/analytics/analytics.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "apps/system_apps/launcher/launcher_app.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "os/tick.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/list.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/analytics/analytics_storage.h"
|
||||
#include "services/common/analytics/analytics_metric.h"
|
||||
#include "services/common/analytics/analytics_heartbeat.h"
|
||||
#include "services/common/analytics/analytics_logging.h"
|
||||
#include "services/common/analytics/analytics_external.h"
|
||||
|
||||
// Stopwatches
|
||||
typedef struct {
|
||||
ListNode node;
|
||||
AnalyticsMetric metric;
|
||||
RtcTicks starting_ticks;
|
||||
uint32_t count_per_sec;
|
||||
AnalyticsClient client;
|
||||
} AnalyticsStopwatchNode;
|
||||
|
||||
static ListNode *s_stopwatch_list = NULL;
|
||||
static bool prv_is_stopwatch_for_metric(ListNode *found_node, void *data);
|
||||
|
||||
void analytics_init(void) {
|
||||
analytics_metric_init();
|
||||
analytics_storage_init();
|
||||
analytics_logging_init();
|
||||
}
|
||||
|
||||
void analytics_set(AnalyticsMetric metric, int64_t value, AnalyticsClient client) {
|
||||
analytics_set_for_uuid(metric, value, analytics_uuid_for_client(client));
|
||||
}
|
||||
|
||||
void analytics_max(AnalyticsMetric metric, int64_t val, AnalyticsClient client) {
|
||||
const Uuid *uuid = analytics_uuid_for_client(client);
|
||||
|
||||
analytics_storage_take_lock();
|
||||
|
||||
AnalyticsHeartbeat *heartbeat = analytics_storage_find(metric, uuid, AnalyticsClient_Ignore);
|
||||
if (heartbeat) {
|
||||
const int64_t prev_value = analytics_heartbeat_get(heartbeat, metric);
|
||||
if (prev_value < val) {
|
||||
analytics_heartbeat_set(heartbeat, metric, val);
|
||||
}
|
||||
}
|
||||
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
void analytics_set_for_uuid(AnalyticsMetric metric, int64_t value, const Uuid *uuid) {
|
||||
analytics_storage_take_lock();
|
||||
|
||||
AnalyticsHeartbeat *heartbeat = analytics_storage_find(metric, uuid, AnalyticsClient_Ignore);
|
||||
if (heartbeat) {
|
||||
// We allow only a limited number of app heartbeats to accumulate. A NULL means we reached the
|
||||
// limit
|
||||
analytics_heartbeat_set(heartbeat, metric, value);
|
||||
}
|
||||
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
void analytics_set_entire_array(AnalyticsMetric metric, const void *value, AnalyticsClient client) {
|
||||
analytics_storage_take_lock();
|
||||
|
||||
AnalyticsHeartbeat *heartbeat = analytics_storage_find(metric, NULL, client);
|
||||
if (heartbeat) {
|
||||
// We allow only a limited number of app heartbeats to accumulate. A NULL means we reached the
|
||||
// limite
|
||||
analytics_heartbeat_set_entire_array(heartbeat, metric, value);
|
||||
}
|
||||
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
|
||||
void analytics_inc(AnalyticsMetric metric, AnalyticsClient client) {
|
||||
analytics_add(metric, 1, client);
|
||||
}
|
||||
|
||||
void analytics_inc_for_uuid(AnalyticsMetric metric, const Uuid *uuid) {
|
||||
analytics_add_for_uuid(metric, 1, uuid);
|
||||
}
|
||||
|
||||
void analytics_add_for_uuid(AnalyticsMetric metric, int64_t amount, const Uuid *uuid) {
|
||||
analytics_storage_take_lock();
|
||||
|
||||
// We don't currently allow incrementing signed integers, because the
|
||||
// only intended use of this API call is to increment counters, and counters
|
||||
// should always be unsigned. This restriction could be changed in the future,
|
||||
// however.
|
||||
PBL_ASSERTN(analytics_metric_is_unsigned(metric));
|
||||
|
||||
AnalyticsHeartbeat *heartbeat = analytics_storage_find(metric, uuid, AnalyticsClient_Ignore);
|
||||
if (heartbeat) {
|
||||
// We allow only a limited number of app heartbeats to accumulate. A NULL means we reached the
|
||||
// limit
|
||||
uint64_t val = analytics_heartbeat_get(heartbeat, metric);
|
||||
analytics_heartbeat_set(heartbeat, metric, val + amount);
|
||||
}
|
||||
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
void analytics_add(AnalyticsMetric metric, int64_t amount, AnalyticsClient client) {
|
||||
analytics_add_for_uuid(metric, amount, analytics_uuid_for_client(client));
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// Stopwatches
|
||||
static bool prv_is_stopwatch_for_metric(ListNode *found_node, void *data) {
|
||||
AnalyticsStopwatchNode* stopwatch_node = (AnalyticsStopwatchNode*)found_node;
|
||||
return stopwatch_node->metric == (AnalyticsMetric)data;
|
||||
}
|
||||
AnalyticsStopwatchNode *prv_find_stopwatch(AnalyticsMetric metric) {
|
||||
ListNode *node = list_find(s_stopwatch_list, prv_is_stopwatch_for_metric, (void*)metric);
|
||||
return (AnalyticsStopwatchNode*)node;
|
||||
}
|
||||
|
||||
static uint32_t prv_stopwatch_elapsed_ms(AnalyticsStopwatchNode *stopwatch, uint64_t current_ticks) {
|
||||
const uint64_t dt_ms = ticks_to_milliseconds(current_ticks - stopwatch->starting_ticks);
|
||||
return (((uint64_t) stopwatch->count_per_sec) * dt_ms) / MS_PER_SECOND;
|
||||
}
|
||||
|
||||
void analytics_stopwatch_start(AnalyticsMetric metric, AnalyticsClient client) {
|
||||
analytics_stopwatch_start_at_rate(metric, MS_PER_SECOND, client);
|
||||
}
|
||||
|
||||
void analytics_stopwatch_start_at_rate(AnalyticsMetric metric, uint32_t count_per_sec, AnalyticsClient client) {
|
||||
analytics_storage_take_lock();
|
||||
|
||||
// Stopwatch metrics must be UINT32!
|
||||
PBL_ASSERTN(analytics_metric_element_type(metric) == ANALYTICS_METRIC_ELEMENT_TYPE_UINT32);
|
||||
|
||||
if (prv_find_stopwatch(metric)) {
|
||||
// TODO: Increment this back up to LOG_LEVEL_WARNING when it doesn't happen
|
||||
// on every bootup (PBL-5393)
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Analytics stopwatch for metric %d already started!", metric);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
AnalyticsStopwatchNode *stopwatch = kernel_malloc_check(sizeof(*stopwatch));
|
||||
*stopwatch = (AnalyticsStopwatchNode) {
|
||||
.metric = metric,
|
||||
.starting_ticks = rtc_get_ticks(),
|
||||
.count_per_sec = count_per_sec,
|
||||
.client = client,
|
||||
};
|
||||
|
||||
list_init(&stopwatch->node);
|
||||
s_stopwatch_list = list_prepend(s_stopwatch_list, &stopwatch->node);
|
||||
|
||||
unlock:
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
void analytics_stopwatch_stop(AnalyticsMetric metric) {
|
||||
analytics_storage_take_lock();
|
||||
|
||||
AnalyticsStopwatchNode *stopwatch = prv_find_stopwatch(metric);
|
||||
if (!stopwatch) {
|
||||
// TODO: Incerement this back up to LOG_LEVEL_WARNING when it doesn't happen
|
||||
// on every bootup (PBL-5393)
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Analytics stopwatch for metric %d already stopped!", metric);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
analytics_add(metric, prv_stopwatch_elapsed_ms(stopwatch, rtc_get_ticks()), stopwatch->client);
|
||||
|
||||
list_remove(&stopwatch->node, &s_stopwatch_list, NULL);
|
||||
kernel_free(stopwatch);
|
||||
|
||||
unlock:
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
void analytics_stopwatches_update(uint64_t current_ticks) {
|
||||
PBL_ASSERTN(analytics_storage_has_lock());
|
||||
|
||||
ListNode *cur = s_stopwatch_list;
|
||||
while (cur != NULL) {
|
||||
AnalyticsStopwatchNode *node = (AnalyticsStopwatchNode*)cur;
|
||||
analytics_add(node->metric, prv_stopwatch_elapsed_ms(node, current_ticks), node->client);
|
||||
node->starting_ticks = current_ticks;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
75
src/fw/services/normal/analytics/analytics_data_syscalls.c
Normal file
75
src/fw/services/normal/analytics/analytics_data_syscalls.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/analytics/analytics_event.h"
|
||||
#include "services/common/analytics/analytics_logging.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_set, AnalyticsMetric metric, uint64_t value,
|
||||
AnalyticsClient client) {
|
||||
analytics_set(metric, value, client);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_set_entire_array, AnalyticsMetric metric, const void *value,
|
||||
AnalyticsClient client) {
|
||||
analytics_set_entire_array(metric, value, client);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_add, AnalyticsMetric metric, uint64_t increment,
|
||||
AnalyticsClient client) {
|
||||
analytics_add(metric, increment, client);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_inc, AnalyticsMetric metric, AnalyticsClient client) {
|
||||
analytics_inc(metric, client);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_stopwatch_start, AnalyticsMetric metric,
|
||||
AnalyticsClient client) {
|
||||
analytics_stopwatch_start(metric, client);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_stopwatch_stop, AnalyticsMetric metric) {
|
||||
analytics_stopwatch_stop(metric);
|
||||
}
|
||||
|
||||
static bool prv_is_event_allowed(const AnalyticsEventBlob *const event_blob) {
|
||||
switch (event_blob->event) {
|
||||
case AnalyticsEvent_AppOOMNative:
|
||||
case AnalyticsEvent_AppOOMRocky:
|
||||
return true;
|
||||
|
||||
default:
|
||||
// Don't allow any other event types:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_logging_log_event, AnalyticsEventBlob *event_blob) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(event_blob, sizeof(*event_blob));
|
||||
}
|
||||
if (!prv_is_event_allowed(event_blob)) {
|
||||
syscall_failed();
|
||||
}
|
||||
analytics_logging_log_event(event_blob);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(void, sys_analytics_max, AnalyticsMetric metric, int64_t val,
|
||||
AnalyticsClient client) {
|
||||
analytics_max(metric, val, client);
|
||||
}
|
701
src/fw/services/normal/analytics/analytics_event.c
Normal file
701
src/fw/services/normal/analytics/analytics_event.c
Normal file
|
@ -0,0 +1,701 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/analytics/analytics_event.h"
|
||||
#include "services/common/analytics/analytics_logging.h"
|
||||
#include "services/common/analytics/analytics_storage.h"
|
||||
|
||||
#include "apps/system_apps/launcher/launcher_app.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "comm/bt_lock.h"
|
||||
#include "comm/ble/gap_le_connection.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "services/common/comm_session/session_internal.h"
|
||||
#include "services/normal/alarms/alarm.h"
|
||||
#include "services/normal/timeline/timeline.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
_Static_assert(sizeof(AnalyticsEventBlob) == 36,
|
||||
"When the blob format or size changes, be sure to bump up ANALYTICS_EVENT_BLOB_VERSION");
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log an app launch event
|
||||
static bool prv_send_uuid(AnalyticsEvent event_enum, const Uuid *uuid) {
|
||||
if (uuid_is_invalid(uuid) || uuid_is_system(uuid)) {
|
||||
// No need to log apps with invalid uuids. This is typically built-in test apps like "Light
|
||||
// config" that we don't bother to declare a UUID for
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: The sdkshell doesn't have a launcher menu so this causes a linker error. Maybe the
|
||||
// mapping of events to analytics should also be shell-specific?
|
||||
#ifndef SHELL_SDK
|
||||
// No need to log the launcher menu app
|
||||
if (uuid_equal(uuid, &launcher_menu_app_get_app_info()->uuid)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log an out-of-memory situation for an app.
|
||||
|
||||
void analytics_event_app_oom(AnalyticsEvent type,
|
||||
uint32_t requested_size, uint32_t total_size, uint32_t total_free,
|
||||
uint32_t largest_free_block) {
|
||||
PBL_ASSERTN(type == AnalyticsEvent_AppOOMNative || type == AnalyticsEvent_AppOOMRocky);
|
||||
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = type,
|
||||
.app_oom = {
|
||||
.requested_size = requested_size,
|
||||
.total_size = total_size,
|
||||
.total_free = MIN(total_free, UINT16_MAX),
|
||||
.largest_free_block = MIN(largest_free_block, UINT16_MAX),
|
||||
},
|
||||
};
|
||||
if (!sys_process_manager_get_current_process_uuid(&event_blob.app_oom.app_uuid)) {
|
||||
// Process has no UUID
|
||||
return;
|
||||
}
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
ANALYTICS_LOG_DEBUG("app oom: is_rocky=%u, req_sz=%"PRIu32" tot_sz=%"PRIu32" free=%"PRIu32
|
||||
" max_free=%"PRIu32,
|
||||
(type == AnalyticsEvent_AppOOMRocky),
|
||||
requested_size, total_size, total_free, largest_free_block);
|
||||
#endif
|
||||
|
||||
sys_analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a generic app launch event
|
||||
void analytics_event_app_launch(const Uuid *uuid) {
|
||||
if (!prv_send_uuid(AnalyticsEvent_AppLaunch, uuid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_AppLaunch,
|
||||
.app_launch.uuid = *uuid,
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_string[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(uuid, uuid_string);
|
||||
ANALYTICS_LOG_DEBUG("app launch event: uuid %s", uuid_string);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin open/create/update event.
|
||||
static void prv_simple_pin_event(time_t timestamp, const Uuid *parent_id,
|
||||
AnalyticsEvent event_enum, const char *verb) {
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = event_enum,
|
||||
.pin_open_create_update.time_utc = timestamp,
|
||||
.pin_open_create_update.parent_id = *parent_id,
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_string[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(&event_blob.pin_open_create_update.parent_id, uuid_string);
|
||||
ANALYTICS_LOG_DEBUG("pin %s event: timestamp: %"PRIu32", uuid:%s", verb,
|
||||
event_blob.pin_open_create_update.time_utc, uuid_string);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin open event.
|
||||
void analytics_event_pin_open(time_t timestamp, const Uuid *parent_id) {
|
||||
prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinOpen, "open");
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin created event.
|
||||
void analytics_event_pin_created(time_t timestamp, const Uuid *parent_id) {
|
||||
prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinCreated, "created");
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin updated event.
|
||||
void analytics_event_pin_updated(time_t timestamp, const Uuid *parent_id) {
|
||||
prv_simple_pin_event(timestamp, parent_id, AnalyticsEvent_PinUpdated, "updated");
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin action event.
|
||||
void analytics_event_pin_action(time_t timestamp, const Uuid *parent_id,
|
||||
TimelineItemActionType action_type) {
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_PinAction,
|
||||
.pin_action.time_utc = timestamp,
|
||||
.pin_action.parent_id = *parent_id,
|
||||
.pin_action.type = action_type,
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_string[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(&event_blob.pin_action.parent_id, uuid_string);
|
||||
ANALYTICS_LOG_DEBUG("pin action event: timestamp: %"PRIu32", uuid:%s, action:%"PRIu8,
|
||||
event_blob.pin_action.time_utc, uuid_string, action_type);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a pin app launch event.
|
||||
void analytics_event_pin_app_launch(time_t timestamp, const Uuid *parent_id) {
|
||||
if (!prv_send_uuid(AnalyticsEvent_PinAppLaunch, parent_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_PinAppLaunch,
|
||||
.pin_app_launch.time_utc = timestamp,
|
||||
.pin_app_launch.parent_id = *parent_id,
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_string[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(&event_blob.pin_app_launch.parent_id, uuid_string);
|
||||
ANALYTICS_LOG_DEBUG("pin app launch event: timestamp: %"PRIu32", uuid:%s",
|
||||
event_blob.pin_app_launch.time_utc, uuid_string);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a canned response event
|
||||
void analytics_event_canned_response(const char *response, bool successfully_sent) {
|
||||
// Format the event specific info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = successfully_sent ? AnalyticsEvent_CannedReponseSent
|
||||
: AnalyticsEvent_CannedReponseFailed,
|
||||
};
|
||||
|
||||
if (!response) {
|
||||
event_blob.canned_response.response_size_bytes = 0;
|
||||
} else {
|
||||
event_blob.canned_response.response_size_bytes = strlen(response);
|
||||
}
|
||||
|
||||
if (successfully_sent) {
|
||||
ANALYTICS_LOG_DEBUG("canned response sent event: response_size_bytes:%d",
|
||||
event_blob.canned_response.response_size_bytes);
|
||||
} else {
|
||||
ANALYTICS_LOG_DEBUG("canned response failed event: response_size_bytes:%d",
|
||||
event_blob.canned_response.response_size_bytes);
|
||||
}
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a voice response event
|
||||
void analytics_event_voice_response(AnalyticsEvent event_type, uint16_t response_size_bytes,
|
||||
uint16_t response_len_chars, uint32_t response_len_ms,
|
||||
uint8_t error_count, uint8_t num_sessions, Uuid *app_uuid) {
|
||||
|
||||
PBL_ASSERTN((event_type >= AnalyticsEvent_VoiceTranscriptionAccepted) &&
|
||||
(event_type <= AnalyticsEvent_VoiceTranscriptionAutomaticallyAccepted));
|
||||
|
||||
// Format the event specific info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = event_type,
|
||||
};
|
||||
|
||||
event_blob.voice_response = (AnalyticsEventVoiceResponse) {
|
||||
.response_size_bytes = response_size_bytes,
|
||||
.response_len_chars = response_len_chars,
|
||||
.response_len_ms = response_len_ms,
|
||||
.num_sessions = num_sessions,
|
||||
.error_count = error_count,
|
||||
.app_uuid = *app_uuid,
|
||||
};
|
||||
|
||||
const char *msg = "Other";
|
||||
switch (event_type) {
|
||||
case AnalyticsEvent_VoiceTranscriptionAccepted:
|
||||
msg = "Accepted";
|
||||
break;
|
||||
case AnalyticsEvent_VoiceTranscriptionRejected:
|
||||
msg = "Rejected";
|
||||
break;
|
||||
case AnalyticsEvent_VoiceTranscriptionAutomaticallyAccepted:
|
||||
msg = "Automatically accepted";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ANALYTICS_LOG_DEBUG("voice response %s event: size: %"PRIu16"; length (chars): %"PRIu16
|
||||
"; length (ms): %"PRIu32"; Errors: %"PRIu8"; Sessions: %"PRIu8, msg,
|
||||
event_blob.voice_response.response_size_bytes, event_blob.voice_response.response_len_chars,
|
||||
event_blob.voice_response.response_len_ms, event_blob.voice_response.error_count,
|
||||
event_blob.voice_response.num_sessions);
|
||||
// Use syscall because this is called by voice_window
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a BLE HRM event
|
||||
void analytics_event_ble_hrm(BleHrmEventSubtype subtype) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_BleHrmEvent,
|
||||
.ble_hrm = {
|
||||
.subtype = subtype,
|
||||
},
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("BLE HRM Event %u", subtype);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a bluetooth disconnection event
|
||||
void analytics_event_bt_connection_or_disconnection(AnalyticsEvent type, uint8_t reason) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = type,
|
||||
};
|
||||
|
||||
event_blob.bt_connection_disconnection.reason = reason;
|
||||
|
||||
ANALYTICS_LOG_DEBUG("Event %d - BT (dis)connection: Reason: %"PRIu8,
|
||||
event_blob.event,
|
||||
event_blob.bt_connection_disconnection.reason);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
void analytics_event_bt_le_disconnection(uint8_t reason, uint8_t remote_bt_version,
|
||||
uint16_t remote_bt_company_id,
|
||||
uint16_t remote_bt_subversion) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_BtLeDisconnect,
|
||||
.ble_disconnection = {
|
||||
.reason = reason,
|
||||
.remote_bt_version = remote_bt_version,
|
||||
.remote_bt_company_id = remote_bt_company_id,
|
||||
.remote_bt_subversion_number = remote_bt_subversion,
|
||||
}
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("Event %d - BT disconnection: Reason: %"PRIu8, event_blob.event,
|
||||
event_blob.bt_connection_disconnection.reason);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a bluetooth error
|
||||
void analytics_event_bt_error(AnalyticsEvent type, uint32_t error) {
|
||||
AnalyticsEventBlob event_blob = {};
|
||||
event_blob.event = type,
|
||||
event_blob.bt_error.error_code = error;
|
||||
|
||||
ANALYTICS_LOG_DEBUG("bluetooth event %d - error: %"PRIu32,
|
||||
event_blob.event,
|
||||
event_blob.bt_error.error_code);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
//! Log when app_launch trigger failed.
|
||||
void analytics_event_bt_app_launch_error(uint8_t gatt_error) {
|
||||
analytics_event_bt_error(AnalyticsEvent_BtAppLaunchError, gatt_error);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
//! Log when a Pebble Protocol session is closed.
|
||||
void analytics_event_session_close(bool is_system_session, const Uuid *optional_app_uuid,
|
||||
CommSessionCloseReason reason, uint16_t session_duration_mins) {
|
||||
AnalyticsEventBlob event_blob = {};
|
||||
event_blob.event = (is_system_session ? AnalyticsEvent_PebbleProtocolSystemSessionEnd :
|
||||
AnalyticsEvent_PebbleProtocolAppSessionEnd);
|
||||
event_blob.pp_common_session_close.close_reason = reason;
|
||||
event_blob.pp_common_session_close.duration_minutes = session_duration_mins;
|
||||
|
||||
if (!is_system_session && optional_app_uuid) {
|
||||
memcpy(&event_blob.pp_app_session_close.app_uuid, optional_app_uuid, sizeof(Uuid));
|
||||
}
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_str[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(optional_app_uuid, uuid_str);
|
||||
ANALYTICS_LOG_DEBUG("Session close event. is_system_session=%u, uuid=%s, "
|
||||
"reason=%u, duration_mins=%"PRIu16,
|
||||
is_system_session, uuid_str, reason, session_duration_mins);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
//! Log when the CC2564x BT chip becomes unresponsive
|
||||
void analytics_event_bt_cc2564x_lockup_error(void) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_BtLockupError,
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("CC2564x lockup event");
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a crash event
|
||||
void analytics_event_crash(uint8_t crash_code, uint32_t link_register) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_Crash,
|
||||
.crash_report.crash_code = crash_code,
|
||||
.crash_report.link_register = link_register
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("Crash occured: Code %"PRIu8" / LR: %"PRIu32,
|
||||
event_blob.crash_report.crash_code, event_blob.crash_report.link_register);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log local bluetooth disconnection reason
|
||||
void analytics_event_local_bt_disconnect(uint16_t conn_handle, uint32_t lr) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_LocalBtDisconnect,
|
||||
};
|
||||
|
||||
event_blob.local_bt_disconnect.lr = lr;
|
||||
event_blob.local_bt_disconnect.conn_handle = conn_handle;
|
||||
|
||||
ANALYTICS_LOG_DEBUG("Event %d - BT Disconnect: Handle:%"PRIu16" LR: %"PRIu32,
|
||||
event_blob.event,
|
||||
conn_handle,
|
||||
lr);
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log an Apple Media Service event.
|
||||
void analytics_event_ams(uint8_t type, int32_t aux_info) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_BtLeAMS,
|
||||
.ams = {
|
||||
.type = type,
|
||||
.aux_info = aux_info,
|
||||
},
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("Event %d - AMS: type:%d aux_info: %"PRId32, event_blob.event, type, aux_info);
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log stationary mode events
|
||||
void analytics_event_stationary_state_change(time_t timestamp, uint8_t state_change) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_StationaryModeSwitch,
|
||||
.sd = {
|
||||
.timestamp = timestamp,
|
||||
.state_change = state_change,
|
||||
}
|
||||
};
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a health insight created event.
|
||||
void analytics_event_health_insight_created(time_t timestamp,
|
||||
ActivityInsightType insight_type,
|
||||
PercentTier pct_tier) {
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_HealthInsightCreated,
|
||||
.health_insight_created = {
|
||||
.time_utc = timestamp,
|
||||
.insight_type = insight_type,
|
||||
.percent_tier = pct_tier,
|
||||
}
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
ANALYTICS_LOG_DEBUG("health insight created event: timestamp: %"PRIu32", type:%"PRIu8,
|
||||
timestamp, insight_type);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log a health insight response event.
|
||||
void analytics_event_health_insight_response(time_t timestamp, ActivityInsightType insight_type,
|
||||
ActivitySessionType activity_type,
|
||||
ActivityInsightResponseType response_id) {
|
||||
// Format the event specifc info in the blob. The analytics_logging_log_event() method will fill
|
||||
// in the common fields
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_HealthInsightResponse,
|
||||
.health_insight_response = {
|
||||
.time_utc = timestamp,
|
||||
.insight_type = insight_type,
|
||||
.activity_type = activity_type,
|
||||
.response_id = response_id,
|
||||
}
|
||||
};
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
ANALYTICS_LOG_DEBUG("health insight response event: timestamp: %"PRIu32", type:%"PRIu8 \
|
||||
", response:%"PRIu8, timestamp, insight_type, response_id);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Log an App Crash event
|
||||
void analytics_event_app_crash(const Uuid *uuid, uint32_t pc, uint32_t lr,
|
||||
const uint8_t *build_id, bool is_rocky_app) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = (is_rocky_app ? AnalyticsEvent_RockyAppCrash : AnalyticsEvent_AppCrash),
|
||||
.app_crash_report = {
|
||||
.uuid = *uuid,
|
||||
.pc = pc,
|
||||
.lr = lr,
|
||||
},
|
||||
};
|
||||
|
||||
if (build_id) {
|
||||
memcpy(event_blob.app_crash_report.build_id_slice, build_id,
|
||||
sizeof(event_blob.app_crash_report.build_id_slice));
|
||||
}
|
||||
|
||||
#if LOG_DOMAIN_ANALYTICS
|
||||
char uuid_string[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(uuid, uuid_string);
|
||||
ANALYTICS_LOG_DEBUG("App Crash event: uuid:%s, pc: %p, lr: %p",
|
||||
uuid_string, (void *)pc, (void *)lr);
|
||||
#endif
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
extern bool comm_session_is_valid(const CommSession *session);
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
static bool prv_get_connection_details(CommSession *session, bool *is_ppogatt,
|
||||
uint16_t *conn_interval) {
|
||||
bt_lock();
|
||||
|
||||
if (!session || !comm_session_is_valid(session)) {
|
||||
bt_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool tmp_is_ppogatt =
|
||||
(comm_session_analytics_get_transport_type(session) == CommSessionTransportType_PPoGATT);
|
||||
|
||||
uint16_t tmp_conn_interval = 0;
|
||||
if (tmp_is_ppogatt) {
|
||||
GAPLEConnection *conn = gap_le_connection_get_gateway();
|
||||
if (conn) {
|
||||
tmp_conn_interval = conn->conn_params.conn_interval_1_25ms;
|
||||
}
|
||||
}
|
||||
|
||||
bt_unlock();
|
||||
|
||||
if (is_ppogatt) {
|
||||
*is_ppogatt = tmp_is_ppogatt;
|
||||
}
|
||||
if (conn_interval) {
|
||||
*conn_interval = tmp_conn_interval;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void analytics_event_put_byte_stats(
|
||||
CommSession *session, bool crc_good, uint8_t type,
|
||||
uint32_t bytes_transferred, uint32_t elapsed_time_ms,
|
||||
uint32_t conn_events, uint32_t sync_errors, uint32_t skip_errors, uint32_t other_errors) {
|
||||
|
||||
bool is_ppogatt = false;
|
||||
uint16_t conn_interval = 0;
|
||||
if (!prv_get_connection_details(session, &is_ppogatt, &conn_interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_PutByteTime,
|
||||
.pb_time = {
|
||||
.ppogatt = is_ppogatt,
|
||||
.conn_intvl_1_25ms = MIN(conn_interval, UINT8_MAX),
|
||||
.crc_good = crc_good,
|
||||
.type = type,
|
||||
.bytes_transferred = bytes_transferred,
|
||||
.elapsed_time_ms = elapsed_time_ms,
|
||||
.conn_events = MIN(conn_events, UINT32_MAX),
|
||||
.sync_errors = MIN(sync_errors, UINT16_MAX),
|
||||
.skip_errors = MIN(skip_errors, UINT16_MAX),
|
||||
.other_errors = MIN(other_errors, UINT16_MAX),
|
||||
},
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("PutBytes event: is_ppogatt: %d, bytes: %d, time ms: %d",
|
||||
(int)event_blob.pb_time.ppogatt,
|
||||
(int)event_blob.pb_time.bytes_transferred,
|
||||
(int)event_blob.pb_time.elapsed_time_ms);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
#if !PLATFORM_TINTIN
|
||||
void analytics_event_vibe_access(VibePatternFeature vibe_feature, VibeScoreId pattern_id) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_VibeAccess,
|
||||
. vibe_access_data = {
|
||||
.feature = (uint8_t) vibe_feature,
|
||||
.vibe_pattern_id = (uint8_t) pattern_id
|
||||
}
|
||||
};
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
void analytics_event_alarm(AnalyticsEvent event_type, const AlarmInfo *info) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = event_type,
|
||||
.alarm = {
|
||||
.hour = info->hour,
|
||||
.minute = info->minute,
|
||||
.is_smart = info->is_smart,
|
||||
.kind = info->kind,
|
||||
},
|
||||
};
|
||||
|
||||
memcpy(event_blob.alarm.scheduled_days, info->scheduled_days,
|
||||
sizeof(event_blob.alarm.scheduled_days));
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
void analytics_event_bt_chip_boot(uint8_t build_id[BUILD_ID_EXPECTED_LEN],
|
||||
uint32_t crash_lr, uint32_t reboot_reason_code) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_BtChipBoot,
|
||||
.bt_chip_boot = {
|
||||
.crash_lr = crash_lr,
|
||||
.reboot_reason = reboot_reason_code,
|
||||
},
|
||||
};
|
||||
|
||||
memcpy(event_blob.bt_chip_boot.build_id, build_id, sizeof(BUILD_ID_EXPECTED_LEN));
|
||||
|
||||
ANALYTICS_LOG_DEBUG("BtChipBoot event: crash_lr: 0x%x, reboot_reason: %"PRIu32,
|
||||
(int)event_blob.bt_chip_boot.crash_lr,
|
||||
event_blob.bt_chip_boot.reboot_reason);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
void analytics_event_PPoGATT_disconnect(time_t timestamp, bool successful_reconnect) {
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_PPoGATTDisconnect,
|
||||
.ppogatt_disconnect = {
|
||||
.successful_reconnect = successful_reconnect,
|
||||
.time_utc = timestamp,
|
||||
},
|
||||
};
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
||||
|
||||
|
||||
void analytics_event_get_bytes_stats(CommSession *session, uint8_t type,
|
||||
uint32_t bytes_transferred, uint32_t elapsed_time_ms,
|
||||
uint32_t conn_events, uint32_t sync_errors,
|
||||
uint32_t skip_errors, uint32_t other_errors) {
|
||||
bool is_ppogatt = false;
|
||||
uint16_t conn_interval = 0;
|
||||
if (!prv_get_connection_details(session, &is_ppogatt, &conn_interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnalyticsEventBlob event_blob = {
|
||||
.event = AnalyticsEvent_GetBytesStats,
|
||||
.get_bytes_stats = {
|
||||
.ppogatt = is_ppogatt,
|
||||
.conn_intvl_1_25ms = MIN(conn_interval, UINT8_MAX),
|
||||
.type = type,
|
||||
.bytes_transferred = bytes_transferred,
|
||||
.elapsed_time_ms = elapsed_time_ms,
|
||||
.conn_events = conn_events,
|
||||
.sync_errors = MIN(sync_errors, UINT16_MAX),
|
||||
.skip_errors = MIN(skip_errors, UINT16_MAX),
|
||||
.other_errors = MIN(other_errors, UINT16_MAX),
|
||||
},
|
||||
};
|
||||
|
||||
ANALYTICS_LOG_DEBUG("GetBytesStats event: type: 0x%x, num_bytes: %"PRIu32", elapsed_ms: %"PRIu32,
|
||||
type, bytes_transferred, elapsed_time_ms);
|
||||
|
||||
analytics_logging_log_event(&event_blob);
|
||||
}
|
48
src/fw/services/normal/analytics/analytics_external.c
Normal file
48
src/fw/services/normal/analytics/analytics_external.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "bluetooth/analytics.h"
|
||||
#include "services/common/analytics/analytics_external.h"
|
||||
|
||||
void analytics_external_update(void) {
|
||||
analytics_external_collect_battery();
|
||||
analytics_external_collect_accel_xyz_delta();
|
||||
analytics_external_collect_app_cpu_stats();
|
||||
analytics_external_collect_app_flash_read_stats();
|
||||
analytics_external_collect_cpu_stats();
|
||||
analytics_external_collect_stop_inhibitor_stats(rtc_get_ticks());
|
||||
analytics_external_collect_chip_specific_parameters();
|
||||
analytics_external_collect_bt_pairing_info();
|
||||
analytics_external_collect_ble_parameters();
|
||||
analytics_external_collect_ble_pairing_info();
|
||||
analytics_external_collect_system_flash_statistics();
|
||||
analytics_external_collect_backlight_settings();
|
||||
analytics_external_collect_notification_settings();
|
||||
analytics_external_collect_system_theme_settings();
|
||||
analytics_external_collect_ancs_info();
|
||||
analytics_external_collect_dls_stats();
|
||||
analytics_external_collect_i2c_stats();
|
||||
analytics_external_collect_stack_free();
|
||||
analytics_external_collect_alerts_preferences();
|
||||
analytics_external_collect_timeline_pin_stats();
|
||||
#if PLATFORM_SPALDING
|
||||
analytics_external_collect_display_offset();
|
||||
#endif
|
||||
analytics_external_collect_pfs_stats();
|
||||
analytics_external_collect_bt_chip_heartbeat();
|
||||
analytics_external_collect_kernel_heap_stats();
|
||||
analytics_external_collect_accel_samples_received();
|
||||
}
|
282
src/fw/services/normal/analytics/analytics_heartbeat.c
Normal file
282
src/fw/services/normal/analytics/analytics_heartbeat.c
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/analytics/analytics_heartbeat.h"
|
||||
#include "services/common/analytics/analytics_metric.h"
|
||||
#include "services/common/analytics/analytics_logging.h"
|
||||
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
uint32_t analytics_heartbeat_kind_data_size(AnalyticsHeartbeatKind kind) {
|
||||
AnalyticsMetric last = ANALYTICS_METRIC_INVALID;
|
||||
switch (kind) {
|
||||
case ANALYTICS_HEARTBEAT_KIND_DEVICE:
|
||||
last = ANALYTICS_DEVICE_METRIC_END - 1;
|
||||
break;
|
||||
case ANALYTICS_HEARTBEAT_KIND_APP:
|
||||
last = ANALYTICS_APP_METRIC_END - 1;
|
||||
break;
|
||||
}
|
||||
PBL_ASSERTN(last != ANALYTICS_METRIC_INVALID);
|
||||
return analytics_metric_offset(last) + analytics_metric_size(last);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// Private
|
||||
static bool prv_verify_kinds_match(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric) {
|
||||
AnalyticsMetricKind metric_kind = analytics_metric_kind(metric);
|
||||
if ((metric_kind == ANALYTICS_METRIC_KIND_DEVICE) &&
|
||||
(heartbeat->kind == ANALYTICS_HEARTBEAT_KIND_DEVICE)) {
|
||||
return true;
|
||||
} else if ((metric_kind == ANALYTICS_METRIC_KIND_APP) &&
|
||||
(heartbeat->kind == ANALYTICS_HEARTBEAT_KIND_APP)) {
|
||||
return true;
|
||||
} else {
|
||||
PBL_CROAK("Metric kind does not match heartbeat kind! %d %d", metric_kind, heartbeat->kind);
|
||||
}
|
||||
}
|
||||
static uint8_t *prv_heartbeat_get_location(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric) {
|
||||
prv_verify_kinds_match(heartbeat, metric);
|
||||
if (analytics_metric_is_array(metric)) {
|
||||
PBL_CROAK("Attempt to use integer value for array metric.");
|
||||
}
|
||||
return heartbeat->data + analytics_metric_offset(metric);
|
||||
}
|
||||
static uint8_t *prv_heartbeat_get_array_location(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric,
|
||||
uint32_t index) {
|
||||
prv_verify_kinds_match(heartbeat, metric);
|
||||
if (!analytics_metric_is_array(metric)) {
|
||||
PBL_CROAK("Attempt to use array value for integer metric.");
|
||||
}
|
||||
uint32_t len = analytics_metric_num_elements(metric);
|
||||
uint32_t element_size = analytics_metric_element_size(metric);
|
||||
if (index > len) {
|
||||
PBL_CROAK("Attempt to use array value at invalid index %" PRId32 " (len %" PRId32 ")",
|
||||
index, len);
|
||||
}
|
||||
return heartbeat->data + analytics_metric_offset(metric) + index*element_size;
|
||||
}
|
||||
|
||||
static void prv_location_set_value(uint8_t *location, int64_t val, AnalyticsMetricElementType type) {
|
||||
switch (type) {
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_NIL:
|
||||
WTF;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT8:
|
||||
{
|
||||
*((uint8_t*)location) = (uint8_t)CLIP(val, 0, UINT8_MAX);
|
||||
return;
|
||||
}
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT16:
|
||||
{
|
||||
*((uint16_t*)location) = (uint16_t)CLIP(val, 0, UINT16_MAX);
|
||||
return;
|
||||
}
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT32:
|
||||
{
|
||||
*((uint32_t*)location) = (uint32_t)CLIP(val, 0, UINT32_MAX);
|
||||
return;
|
||||
}
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT8:
|
||||
{
|
||||
*((int8_t*)location) = (int8_t)CLIP(val, INT8_MIN, INT8_MAX);
|
||||
return;
|
||||
}
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT16:
|
||||
{
|
||||
*((int16_t*)location) = (int16_t)CLIP(val, INT16_MIN, INT16_MAX);
|
||||
return;
|
||||
}
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT32:
|
||||
{
|
||||
*((int32_t*)location) = (int32_t)CLIP(val, INT32_MIN, INT32_MAX);
|
||||
return;
|
||||
}
|
||||
}
|
||||
WTF; // Should not get here!
|
||||
}
|
||||
static int64_t prv_location_get_value(uint8_t *location, AnalyticsMetricElementType type) {
|
||||
switch (type) {
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_NIL:
|
||||
WTF;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT8:
|
||||
return *(uint8_t*)location;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT16:
|
||||
return *(uint16_t*)location;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT32:
|
||||
return *(uint32_t*)location;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT8:
|
||||
return *(int8_t*)location;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT16:
|
||||
return *(int16_t*)location;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT32:
|
||||
return *(int32_t*)location;
|
||||
}
|
||||
WTF; // Should not get here!
|
||||
}
|
||||
|
||||
//////////
|
||||
// Set
|
||||
void analytics_heartbeat_set(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric, int64_t val) {
|
||||
uint8_t *location = prv_heartbeat_get_location(heartbeat, metric);
|
||||
prv_location_set_value(location, val, analytics_metric_element_type(metric));
|
||||
}
|
||||
|
||||
void analytics_heartbeat_set_array(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric, uint32_t index, int64_t val) {
|
||||
uint8_t *location = prv_heartbeat_get_array_location(heartbeat, metric, index);
|
||||
prv_location_set_value(location, val, analytics_metric_element_type(metric));
|
||||
}
|
||||
|
||||
void analytics_heartbeat_set_entire_array(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric, const void* data) {
|
||||
uint8_t *location = prv_heartbeat_get_array_location(heartbeat, metric, 0);
|
||||
uint32_t size = analytics_metric_size(metric);
|
||||
memcpy(location, data, size);
|
||||
}
|
||||
|
||||
/////////
|
||||
// Get
|
||||
int64_t analytics_heartbeat_get(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric) {
|
||||
uint8_t *location = prv_heartbeat_get_location(heartbeat, metric);
|
||||
return prv_location_get_value(location, analytics_metric_element_type(metric));
|
||||
}
|
||||
|
||||
int64_t analytics_heartbeat_get_array(AnalyticsHeartbeat *heartbeat, AnalyticsMetric metric, uint32_t index) {
|
||||
uint8_t *location = prv_heartbeat_get_array_location(heartbeat, metric, index);
|
||||
return prv_location_get_value(location, analytics_metric_element_type(metric));
|
||||
}
|
||||
|
||||
const Uuid *analytics_heartbeat_get_uuid(AnalyticsHeartbeat *heartbeat) {
|
||||
return (const Uuid*)prv_heartbeat_get_array_location(heartbeat, ANALYTICS_APP_METRIC_UUID, 0);
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// Create / Clear
|
||||
AnalyticsHeartbeat *analytics_heartbeat_create(AnalyticsHeartbeatKind kind) {
|
||||
uint32_t size = sizeof(AnalyticsHeartbeat) + analytics_heartbeat_kind_data_size(kind);
|
||||
AnalyticsHeartbeat *heartbeat = kernel_malloc_check(size);
|
||||
heartbeat->kind = kind;
|
||||
analytics_heartbeat_clear(heartbeat);
|
||||
return heartbeat;
|
||||
}
|
||||
|
||||
AnalyticsHeartbeat *analytics_heartbeat_device_create() {
|
||||
AnalyticsHeartbeat *hb = analytics_heartbeat_create(ANALYTICS_HEARTBEAT_KIND_DEVICE);
|
||||
analytics_heartbeat_set(hb, ANALYTICS_DEVICE_METRIC_BLOB_KIND,
|
||||
ANALYTICS_BLOB_KIND_DEVICE_HEARTBEAT);
|
||||
analytics_heartbeat_set(hb, ANALYTICS_DEVICE_METRIC_BLOB_VERSION,
|
||||
ANALYTICS_DEVICE_HEARTBEAT_BLOB_VERSION);
|
||||
return hb;
|
||||
}
|
||||
|
||||
AnalyticsHeartbeat *analytics_heartbeat_app_create(const Uuid *uuid) {
|
||||
AnalyticsHeartbeat *hb = analytics_heartbeat_create(ANALYTICS_HEARTBEAT_KIND_APP);
|
||||
analytics_heartbeat_set_entire_array(hb, ANALYTICS_APP_METRIC_UUID, uuid);
|
||||
analytics_heartbeat_set(hb, ANALYTICS_APP_METRIC_BLOB_KIND,
|
||||
ANALYTICS_BLOB_KIND_APP_HEARTBEAT);
|
||||
analytics_heartbeat_set(hb, ANALYTICS_APP_METRIC_BLOB_VERSION,
|
||||
ANALYTICS_APP_HEARTBEAT_BLOB_VERSION);
|
||||
return hb;
|
||||
}
|
||||
|
||||
void analytics_heartbeat_clear(AnalyticsHeartbeat *heartbeat) {
|
||||
AnalyticsHeartbeatKind kind = heartbeat->kind;
|
||||
uint32_t size = sizeof(AnalyticsHeartbeat) + analytics_heartbeat_kind_data_size(kind);
|
||||
memset(heartbeat, 0, size);
|
||||
heartbeat->kind = kind;
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// Debug
|
||||
#ifdef ANALYTICS_DEBUG
|
||||
// Function to get the name of a macro given it's runtime value. (i.e. mapping
|
||||
// (1: "ANALYTICS_DEVICE_METRIC_MSG_ID"),
|
||||
// (2: "ANALYTICS_DEVICE_METRIC_VERSION"),
|
||||
// ...
|
||||
// )
|
||||
#define CASE(name, ...) case name: return #name;
|
||||
static const char *prv_get_metric_name(AnalyticsMetric metric) {
|
||||
switch (metric) {
|
||||
ANALYTICS_METRIC_TABLE(CASE,CASE,CASE,,,,,,)
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
#undef CASE
|
||||
|
||||
static void prv_print_heartbeat(AnalyticsHeartbeat *heartbeat, AnalyticsMetric start, AnalyticsMetric end) {
|
||||
for (AnalyticsMetric metric = start + 1; metric < end; metric++) {
|
||||
const char *name = prv_get_metric_name(metric);
|
||||
if (!analytics_metric_is_array(metric)) {
|
||||
int64_t val = analytics_heartbeat_get(heartbeat, metric);
|
||||
if (val >= 0) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "%3" PRIu32 ": %s: %" PRIu32 " (0x%" PRIx32")",
|
||||
analytics_metric_offset(metric), name, (uint32_t)val, (uint32_t)val);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "%3" PRIu32 ": %s: %" PRId32 " (0x%" PRIx32")",
|
||||
analytics_metric_offset(metric), name, (int32_t)val, (int32_t)val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const size_t BUF_LENGTH = 256;
|
||||
char buf[BUF_LENGTH];
|
||||
uint32_t written = 0;
|
||||
for (uint32_t i = 0; i < analytics_metric_num_elements(metric); i++) {
|
||||
if (written > BUF_LENGTH) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "print buffer overflow by %lu bytes",
|
||||
BUF_LENGTH - written);
|
||||
continue;
|
||||
}
|
||||
int64_t val = analytics_heartbeat_get_array(heartbeat, metric, i);
|
||||
const char *sep = (i == 0 ? "" : ", ");
|
||||
if (val >= 0) {
|
||||
written += snprintf(buf + written, BUF_LENGTH - written,
|
||||
"%s%" PRIu32 " (0x%" PRIx32 ")", sep, (uint32_t)val, (uint32_t)val);
|
||||
} else {
|
||||
written += snprintf(buf + written, BUF_LENGTH - written,
|
||||
"%s%" PRId32 " (0x%" PRIx32 ")", sep, (int32_t)val, (int32_t)val);
|
||||
}
|
||||
}
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "%3" PRIu32 ": %s: %s", analytics_metric_offset(metric), name, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void analytics_heartbeat_print(AnalyticsHeartbeat *heartbeat) {
|
||||
switch (heartbeat->kind) {
|
||||
case ANALYTICS_HEARTBEAT_KIND_DEVICE:
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Device heartbeat:");
|
||||
prv_print_heartbeat(heartbeat, ANALYTICS_DEVICE_METRIC_START, ANALYTICS_DEVICE_METRIC_END);
|
||||
break;
|
||||
case ANALYTICS_HEARTBEAT_KIND_APP: {
|
||||
const Uuid *uuid = analytics_heartbeat_get_uuid(heartbeat);
|
||||
char uuid_buf[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(uuid, uuid_buf);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App heartbeat for %s:", uuid_buf);
|
||||
prv_print_heartbeat(heartbeat, ANALYTICS_APP_METRIC_START, ANALYTICS_APP_METRIC_END);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Unable to print heartbeat: Unrecognized kind %d", heartbeat->kind);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void analytics_heartbeat_print(AnalyticsHeartbeat *heartbeat) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Turn on ANALYTICS_DEBUG to get heartbeat printing support.");
|
||||
}
|
||||
#endif
|
277
src/fw/services/normal/analytics/analytics_logging.c
Normal file
277
src/fw/services/normal/analytics/analytics_logging.c
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "applib/data_logging.h"
|
||||
#include "comm/bt_lock.h"
|
||||
#include "drivers/rtc.h"
|
||||
|
||||
#include "os/tick.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
|
||||
#include "services/normal/data_logging/data_logging_service.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
|
||||
#include "services/common/analytics/analytics_event.h"
|
||||
#include "services/common/analytics/analytics_external.h"
|
||||
#include "services/common/analytics/analytics_heartbeat.h"
|
||||
#include "services/common/analytics/analytics_logging.h"
|
||||
#include "services/common/analytics/analytics_metric.h"
|
||||
#include "services/common/analytics/analytics_storage.h"
|
||||
#include "services/common/system_task.h"
|
||||
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
enum {
|
||||
#ifdef ANALYTICS_DEBUG
|
||||
HEARTBEAT_INTERVAL = 10 * 1000, // 10 seconds
|
||||
#else
|
||||
HEARTBEAT_INTERVAL = 60 * 60 * 1000, // 1 hour
|
||||
#endif
|
||||
};
|
||||
|
||||
static int s_heartbeat_timer;
|
||||
static uint32_t s_previous_send_ticks;
|
||||
|
||||
DataLoggingSessionRef s_device_heartbeat_session = NULL;
|
||||
DataLoggingSessionRef s_app_heartbeat_session = NULL;
|
||||
DataLoggingSessionRef s_event_session = NULL;
|
||||
|
||||
static void prv_schedule_retry();
|
||||
static void prv_create_event_session_cb(void *ignored);
|
||||
|
||||
static void prv_reset_local_session_ptrs(void) {
|
||||
s_device_heartbeat_session = NULL;
|
||||
s_app_heartbeat_session = NULL;
|
||||
s_event_session = NULL;
|
||||
}
|
||||
|
||||
static void prv_timer_callback(void *data) {
|
||||
if (!dls_initialized()) {
|
||||
// We need to wait until data logging is initialized before we can log heartbeats
|
||||
prv_schedule_retry();
|
||||
return;
|
||||
}
|
||||
if (!s_event_session) {
|
||||
// If the event session has not been created yet, create that. The only time we may have to do
|
||||
// this here is if the first call to prv_create_event_session_cb() during boot failed to create
|
||||
// the session.
|
||||
launcher_task_add_callback(prv_create_event_session_cb, NULL);
|
||||
}
|
||||
system_task_add_callback(analytics_logging_system_task_cb, NULL);
|
||||
new_timer_start(s_heartbeat_timer, HEARTBEAT_INTERVAL, prv_timer_callback, NULL, 0);
|
||||
}
|
||||
|
||||
static void prv_schedule_retry() {
|
||||
new_timer_start(s_heartbeat_timer, 5000, prv_timer_callback, NULL, 0);
|
||||
}
|
||||
|
||||
static DataLoggingSessionRef prv_create_dls(AnalyticsBlobKind kind, uint32_t item_length) {
|
||||
Uuid system_uuid = UUID_SYSTEM;
|
||||
bool buffered = false;
|
||||
const char *kind_str;
|
||||
uint32_t tag;
|
||||
|
||||
if (kind == ANALYTICS_BLOB_KIND_DEVICE_HEARTBEAT) {
|
||||
kind_str = "Device";
|
||||
tag = DlsSystemTagAnalyticsDeviceHeartbeat;
|
||||
} else if (kind == ANALYTICS_BLOB_KIND_APP_HEARTBEAT) {
|
||||
kind_str = "App";
|
||||
tag = DlsSystemTagAnalyticsAppHeartbeat;
|
||||
} else if (kind == ANALYTICS_BLOB_KIND_EVENT) {
|
||||
kind_str = "Event";
|
||||
buffered = true;
|
||||
tag = DlsSystemTagAnalyticsEvent;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
// TODO: Use different tag ids for device_hb and app_hb sessions.
|
||||
// https://pebbletechnology.atlassian.net/browse/PBL-5463
|
||||
const bool resume = false;
|
||||
DataLoggingSessionRef dls_session = dls_create(
|
||||
tag, DATA_LOGGING_BYTE_ARRAY, item_length, buffered, resume, &system_uuid);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "%s HB Session: %p", kind_str, dls_session);
|
||||
if (!dls_session) {
|
||||
// Data logging full at boot. Reset it and try again 5s later
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Data logging full at boot. Clearing...");
|
||||
// We reset all data logging here, including data logging for applications,
|
||||
// because an inability to allocate a new session means all 200+ session
|
||||
// IDs are exhausted, likely caused by a misbehaving app_hb. See discussion at:
|
||||
// https://github.com/pebble/tintin/pull/1967#discussion-diff-11746345
|
||||
// And issue about removing/moving this at
|
||||
// https://pebbletechnology.atlassian.net/browse/PBL-5473
|
||||
prv_reset_local_session_ptrs();
|
||||
dls_clear();
|
||||
prv_schedule_retry();
|
||||
return NULL;
|
||||
}
|
||||
return dls_session;
|
||||
}
|
||||
|
||||
static void prv_dls_log(AnalyticsHeartbeat *device_hb, AnalyticsHeartbeatList *app_hbs) {
|
||||
dls_log(s_device_heartbeat_session, device_hb->data, 1);
|
||||
#ifdef ANALYTICS_DEBUG
|
||||
analytics_heartbeat_print(device_hb);
|
||||
#endif
|
||||
kernel_free(device_hb);
|
||||
|
||||
AnalyticsHeartbeatList *app_hb_node = app_hbs;
|
||||
while (app_hb_node) {
|
||||
AnalyticsHeartbeat *app_hb = app_hb_node->heartbeat;
|
||||
#ifdef ANALYTICS_DEBUG
|
||||
analytics_heartbeat_print(app_hb);
|
||||
#endif
|
||||
dls_log(s_app_heartbeat_session, app_hb->data, 1);
|
||||
|
||||
AnalyticsHeartbeatList *next = (AnalyticsHeartbeatList*)app_hb_node->node.next;
|
||||
kernel_free(app_hb);
|
||||
kernel_free(app_hb_node);
|
||||
app_hb_node = next;
|
||||
}
|
||||
}
|
||||
|
||||
// System task callback used to prepare and log the heartbeats using dls_log().
|
||||
void analytics_logging_system_task_cb(void *ignored) {
|
||||
if (!s_device_heartbeat_session) {
|
||||
uint32_t size = analytics_heartbeat_kind_data_size(ANALYTICS_HEARTBEAT_KIND_DEVICE);
|
||||
s_device_heartbeat_session = prv_create_dls(ANALYTICS_BLOB_KIND_DEVICE_HEARTBEAT, size);
|
||||
if (!s_device_heartbeat_session) return;
|
||||
}
|
||||
|
||||
// Tell the watchdog timer that we are still awake. dls_create() could take up to 4 seconds if
|
||||
// we can't get the ispp send buffer
|
||||
system_task_watchdog_feed();
|
||||
|
||||
if (!s_app_heartbeat_session) {
|
||||
uint32_t size = analytics_heartbeat_kind_data_size(ANALYTICS_HEARTBEAT_KIND_APP);
|
||||
s_app_heartbeat_session = prv_create_dls(ANALYTICS_BLOB_KIND_APP_HEARTBEAT, size);
|
||||
if (!s_app_heartbeat_session) return;
|
||||
}
|
||||
|
||||
// Tell the watchdog timer that we are still awake. dls_create() could take up to 4 seconds if
|
||||
// we can't get the ispp send buffer
|
||||
system_task_watchdog_feed();
|
||||
|
||||
analytics_external_update();
|
||||
|
||||
// Tell the watchdog timer that we are still awake. Occasionally, analytics_external_update()
|
||||
// could take a while to execute if it needs to wait for the bt_lock() for example
|
||||
system_task_watchdog_feed();
|
||||
|
||||
// The phone and proxy server expect us to send local time. The phone will imbed the time zone
|
||||
// offset into the blob and the proxy server will then use that to convert to UTC before it
|
||||
// gets placed into the database
|
||||
uint32_t timestamp = time_utc_to_local(rtc_get_time());
|
||||
uint64_t current_ticks = rtc_get_ticks();
|
||||
|
||||
AnalyticsHeartbeat *device_hb = NULL;
|
||||
AnalyticsHeartbeatList *app_hbs = NULL;
|
||||
|
||||
{
|
||||
analytics_storage_take_lock();
|
||||
|
||||
extern void analytics_stopwatches_update(uint64_t current_ticks);
|
||||
analytics_stopwatches_update(current_ticks);
|
||||
|
||||
// Hijack the device_hb and app_hb heartbeats from analytics_storage.
|
||||
// After this point, we own the memory, so analytics_storage will not
|
||||
// modify it anymore. Thus, we do not need to hold the lock while
|
||||
// logging.
|
||||
device_hb = analytics_storage_hijack_device_heartbeat();
|
||||
app_hbs = analytics_storage_hijack_app_heartbeats();
|
||||
|
||||
analytics_storage_give_lock();
|
||||
}
|
||||
|
||||
uint32_t dt_ticks = current_ticks - s_previous_send_ticks;
|
||||
uint32_t dt_ms = ticks_to_milliseconds(dt_ticks);
|
||||
s_previous_send_ticks = current_ticks;
|
||||
|
||||
analytics_heartbeat_set(device_hb, ANALYTICS_DEVICE_METRIC_TIMESTAMP, timestamp);
|
||||
analytics_heartbeat_set(device_hb, ANALYTICS_DEVICE_METRIC_DEVICE_UP_TIME, current_ticks);
|
||||
analytics_heartbeat_set(device_hb, ANALYTICS_DEVICE_METRIC_TIME_INTERVAL, dt_ms);
|
||||
|
||||
AnalyticsHeartbeatList *app_hb_node = app_hbs;
|
||||
while (app_hb_node) {
|
||||
AnalyticsHeartbeat *app_hb = app_hb_node->heartbeat;
|
||||
analytics_heartbeat_set(app_hb, ANALYTICS_APP_METRIC_TIMESTAMP, timestamp);
|
||||
analytics_heartbeat_set(app_hb, ANALYTICS_APP_METRIC_TIME_INTERVAL, dt_ms);
|
||||
app_hb_node = (AnalyticsHeartbeatList*)app_hb_node->node.next;
|
||||
}
|
||||
|
||||
prv_dls_log(device_hb, app_hbs);
|
||||
}
|
||||
|
||||
|
||||
// Launcher task callback used to create our event logging session.
|
||||
static void prv_create_event_session_cb(void *ignored) {
|
||||
if (!s_event_session) {
|
||||
s_event_session = prv_create_dls(ANALYTICS_BLOB_KIND_EVENT, sizeof(AnalyticsEventBlob));
|
||||
// If the above call fails, it will schedule our timer to try again in a few seconds
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_handle_log_event(AnalyticsEventBlob *event_blob) {
|
||||
if (!s_event_session) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Event dropped because session not created yet");
|
||||
return;
|
||||
}
|
||||
|
||||
// Log it
|
||||
dls_log(s_event_session, event_blob, 1);
|
||||
}
|
||||
|
||||
static void prv_handle_async_event_logging(void *data) {
|
||||
AnalyticsEventBlob *event_blob = (AnalyticsEventBlob *)data;
|
||||
prv_handle_log_event(event_blob);
|
||||
kernel_free(event_blob);
|
||||
}
|
||||
|
||||
void analytics_logging_log_event(AnalyticsEventBlob *event_blob) {
|
||||
// Fill in the meta info
|
||||
event_blob->kind = ANALYTICS_BLOB_KIND_EVENT;
|
||||
event_blob->version = ANALYTICS_EVENT_BLOB_VERSION;
|
||||
event_blob->timestamp = time_utc_to_local(rtc_get_time());
|
||||
|
||||
// TODO: We should be able to remove this once PBL-23925 is fixed
|
||||
if (bt_lock_is_held()) {
|
||||
// We run the risk of deadlocking if we hold the bt_lock at this point. If
|
||||
// it's the case then schedule a callback so the dls code runs while we no
|
||||
// longer hold the lock
|
||||
AnalyticsEventBlob *event_blob_copy =
|
||||
kernel_malloc_check(sizeof(AnalyticsEventBlob));
|
||||
memcpy(event_blob_copy, event_blob, sizeof(*event_blob_copy));
|
||||
system_task_add_callback(prv_handle_async_event_logging, event_blob_copy);
|
||||
} else {
|
||||
prv_handle_log_event(event_blob);
|
||||
}
|
||||
}
|
||||
|
||||
void analytics_logging_init(void) {
|
||||
s_heartbeat_timer = new_timer_create();
|
||||
s_previous_send_ticks = rtc_get_ticks();
|
||||
new_timer_start(s_heartbeat_timer, HEARTBEAT_INTERVAL, prv_timer_callback, NULL, 0);
|
||||
|
||||
// Create the event session on a launcher task callback because we have to wait for
|
||||
// services (like data logging service) to be initialized)
|
||||
launcher_task_add_callback(prv_create_event_session_cb, NULL);
|
||||
}
|
151
src/fw/services/normal/analytics/analytics_metric.c
Normal file
151
src/fw/services/normal/analytics/analytics_metric.c
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "services/common/analytics/analytics_metric.h"
|
||||
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
|
||||
typedef struct {
|
||||
AnalyticsMetricElementType element_type;
|
||||
uint8_t num_elements;
|
||||
} AnalyticsMetricDataType;
|
||||
|
||||
// http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments
|
||||
#define GET_MACRO(_1, _2, _3, NAME, ...) NAME
|
||||
|
||||
#define ENTRY3(name, element_type, num_elements) {element_type, num_elements},
|
||||
#define ENTRY2(name, element_type) {element_type, 1},
|
||||
#define ENTRY1(name) {ANALYTICS_METRIC_ELEMENT_TYPE_NIL, 0},
|
||||
#define ENTRY(...) GET_MACRO(__VA_ARGS__, ENTRY3, ENTRY2, ENTRY1)(__VA_ARGS__)
|
||||
|
||||
// Mapping from type index to data type of metric. We waste some space here
|
||||
// by including the marker metrics, but it makes the code a fair bit simpler
|
||||
// since we don't need an index translation table.
|
||||
static const AnalyticsMetricDataType s_heartbeat_template[] = {
|
||||
ANALYTICS_METRIC_TABLE(ENTRY, ENTRY, ENTRY,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_UINT8,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_UINT16,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_UINT32,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_INT8,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_INT16,
|
||||
ANALYTICS_METRIC_ELEMENT_TYPE_INT32)
|
||||
};
|
||||
|
||||
#define NUM_METRICS ARRAY_LENGTH(s_heartbeat_template)
|
||||
|
||||
static const AnalyticsMetricDataType *prv_get_metric_data_type(AnalyticsMetric metric) {
|
||||
PBL_ASSERTN(analytics_metric_kind(metric) != ANALYTICS_METRIC_KIND_UNKNOWN);
|
||||
return &s_heartbeat_template[metric];
|
||||
}
|
||||
|
||||
AnalyticsMetricElementType analytics_metric_element_type(AnalyticsMetric metric) {
|
||||
return prv_get_metric_data_type(metric)->element_type;
|
||||
}
|
||||
|
||||
uint32_t analytics_metric_num_elements(AnalyticsMetric metric) {
|
||||
return prv_get_metric_data_type(metric)->num_elements;
|
||||
}
|
||||
|
||||
uint32_t analytics_metric_element_size(AnalyticsMetric metric) {
|
||||
const AnalyticsMetricDataType *type = prv_get_metric_data_type(metric);
|
||||
switch (type->element_type) {
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_NIL:
|
||||
return 0;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT8:
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT8:
|
||||
return 1;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT16:
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT16:
|
||||
return 2;
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_UINT32:
|
||||
case ANALYTICS_METRIC_ELEMENT_TYPE_INT32:
|
||||
return 4;
|
||||
}
|
||||
PBL_CROAK("no such element_type %d", type->element_type);
|
||||
}
|
||||
|
||||
uint32_t analytics_metric_size(AnalyticsMetric metric) {
|
||||
uint32_t num_elements = analytics_metric_num_elements(metric);
|
||||
uint32_t element_size = analytics_metric_element_size(metric);
|
||||
return num_elements * element_size;
|
||||
}
|
||||
|
||||
bool analytics_metric_is_array(AnalyticsMetric metric) {
|
||||
const AnalyticsMetricDataType *type = prv_get_metric_data_type(metric);
|
||||
return (type->num_elements > 1);
|
||||
}
|
||||
|
||||
bool analytics_metric_is_unsigned(AnalyticsMetric metric) {
|
||||
const AnalyticsMetricDataType *type = prv_get_metric_data_type(metric);
|
||||
return (type->element_type == ANALYTICS_METRIC_ELEMENT_TYPE_UINT32 ||
|
||||
type->element_type == ANALYTICS_METRIC_ELEMENT_TYPE_UINT16 ||
|
||||
type->element_type == ANALYTICS_METRIC_ELEMENT_TYPE_UINT8);
|
||||
}
|
||||
|
||||
static uint16_t s_metric_heartbeat_offset[NUM_METRICS];
|
||||
|
||||
void analytics_metric_init(void) {
|
||||
uint32_t device_offset = 0;
|
||||
uint32_t app_offset = 0;
|
||||
const uint16_t INVALID_OFFSET = ~0;
|
||||
for (AnalyticsMetric metric = ANALYTICS_METRIC_START;
|
||||
metric < ANALYTICS_METRIC_END; metric++) {
|
||||
switch (analytics_metric_kind(metric)) {
|
||||
case ANALYTICS_METRIC_KIND_DEVICE:
|
||||
s_metric_heartbeat_offset[metric] = device_offset;
|
||||
device_offset += analytics_metric_size(metric);
|
||||
PBL_ASSERTN(device_offset < INVALID_OFFSET);
|
||||
break;
|
||||
case ANALYTICS_METRIC_KIND_APP:
|
||||
s_metric_heartbeat_offset[metric] = app_offset;
|
||||
app_offset += analytics_metric_size(metric);
|
||||
PBL_ASSERTN(app_offset < INVALID_OFFSET);
|
||||
break;
|
||||
case ANALYTICS_METRIC_KIND_MARKER:
|
||||
// Marker metrics do not actually exist in either heartbeat, they are
|
||||
// markers only.
|
||||
s_metric_heartbeat_offset[metric] = INVALID_OFFSET;
|
||||
break;
|
||||
case ANALYTICS_METRIC_KIND_UNKNOWN:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t analytics_metric_offset(AnalyticsMetric metric) {
|
||||
AnalyticsMetricKind kind = analytics_metric_kind(metric);
|
||||
PBL_ASSERTN((kind == ANALYTICS_METRIC_KIND_DEVICE) || (kind == ANALYTICS_METRIC_KIND_APP));
|
||||
return s_metric_heartbeat_offset[metric];
|
||||
}
|
||||
|
||||
AnalyticsMetricKind analytics_metric_kind(AnalyticsMetric metric) {
|
||||
if ((metric > ANALYTICS_DEVICE_METRIC_START)
|
||||
&& (metric < ANALYTICS_DEVICE_METRIC_END)) {
|
||||
return ANALYTICS_METRIC_KIND_DEVICE;
|
||||
} else if ((metric > ANALYTICS_APP_METRIC_START)
|
||||
&& (metric < ANALYTICS_APP_METRIC_END)) {
|
||||
return ANALYTICS_METRIC_KIND_APP;
|
||||
} else if ((metric >= ANALYTICS_METRIC_START)
|
||||
&& (metric <= ANALYTICS_METRIC_END)) {
|
||||
// "Marker" metrics are not actual real metrics, they are only used
|
||||
// to easily find the position of other metrics. (i.e. ANALYTICS_METRIC_START
|
||||
// is a "marker" metric).
|
||||
return ANALYTICS_METRIC_KIND_MARKER;
|
||||
} else {
|
||||
return ANALYTICS_METRIC_KIND_UNKNOWN;
|
||||
}
|
||||
}
|
170
src/fw/services/normal/analytics/analytics_storage.c
Normal file
170
src/fw/services/normal/analytics/analytics_storage.c
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "os/mutex.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/analytics/analytics_metric.h"
|
||||
#include "services/common/analytics/analytics_storage.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
static PebbleRecursiveMutex *s_analytics_storage_mutex = NULL;
|
||||
|
||||
static AnalyticsHeartbeat *s_device_heartbeat = NULL;
|
||||
static AnalyticsHeartbeatList *s_app_heartbeat_list = NULL;
|
||||
|
||||
#define MAX_APP_HEARTBEATS 8
|
||||
|
||||
void analytics_storage_init(void) {
|
||||
s_analytics_storage_mutex = mutex_create_recursive();
|
||||
PBL_ASSERTN(s_analytics_storage_mutex);
|
||||
s_device_heartbeat = analytics_heartbeat_device_create();
|
||||
PBL_ASSERTN(s_device_heartbeat);
|
||||
}
|
||||
|
||||
//////////
|
||||
// Lock
|
||||
void analytics_storage_take_lock(void) {
|
||||
mutex_lock_recursive(s_analytics_storage_mutex);
|
||||
}
|
||||
|
||||
bool analytics_storage_has_lock(void) {
|
||||
bool has_lock = mutex_is_owned_recursive(s_analytics_storage_mutex);
|
||||
if (!has_lock) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Analytics lock is not held when it should be!");
|
||||
}
|
||||
return has_lock;
|
||||
}
|
||||
|
||||
void analytics_storage_give_lock(void) {
|
||||
mutex_unlock_recursive(s_analytics_storage_mutex);
|
||||
}
|
||||
|
||||
///////
|
||||
// Get
|
||||
AnalyticsHeartbeat *analytics_storage_hijack_device_heartbeat() {
|
||||
PBL_ASSERTN(analytics_storage_has_lock());
|
||||
|
||||
AnalyticsHeartbeat *device = s_device_heartbeat;
|
||||
|
||||
s_device_heartbeat = analytics_heartbeat_device_create();
|
||||
PBL_ASSERTN(s_device_heartbeat);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
AnalyticsHeartbeatList *analytics_storage_hijack_app_heartbeats() {
|
||||
PBL_ASSERTN(analytics_storage_has_lock());
|
||||
|
||||
AnalyticsHeartbeatList *apps = s_app_heartbeat_list;
|
||||
s_app_heartbeat_list = NULL;
|
||||
return apps;
|
||||
}
|
||||
|
||||
///////////
|
||||
// Search
|
||||
static bool prv_is_app_node_with_uuid(ListNode *found_node, void *data) {
|
||||
const Uuid *searching_for_uuid = (const Uuid*)data;
|
||||
AnalyticsHeartbeatList *app_node = (AnalyticsHeartbeatList*)found_node;
|
||||
const Uuid *found_uuid = analytics_heartbeat_get_uuid(app_node->heartbeat);
|
||||
return uuid_equal(searching_for_uuid, found_uuid);
|
||||
}
|
||||
|
||||
static AnalyticsHeartbeatList *prv_app_node_create(const Uuid *uuid) {
|
||||
AnalyticsHeartbeatList *app_heartbeat_node = kernel_malloc_check(sizeof(AnalyticsHeartbeatList));
|
||||
|
||||
list_init(&app_heartbeat_node->node);
|
||||
app_heartbeat_node->heartbeat = analytics_heartbeat_app_create(uuid);
|
||||
|
||||
return app_heartbeat_node;
|
||||
}
|
||||
|
||||
const Uuid *analytics_uuid_for_client(AnalyticsClient client) {
|
||||
const PebbleProcessMd *md;
|
||||
if (client == AnalyticsClient_CurrentTask) {
|
||||
PebbleTask task = pebble_task_get_current();
|
||||
if (task == PebbleTask_App) {
|
||||
client = AnalyticsClient_App;
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
client = AnalyticsClient_Worker;
|
||||
} else {
|
||||
return NULL; // System UUID
|
||||
}
|
||||
}
|
||||
|
||||
if (client == AnalyticsClient_App) {
|
||||
md = app_manager_get_current_app_md();
|
||||
} else if (client == AnalyticsClient_Worker) {
|
||||
md = worker_manager_get_current_worker_md();
|
||||
} else if (client == AnalyticsClient_System) {
|
||||
return NULL;
|
||||
} else if (client == AnalyticsClient_Ignore) {
|
||||
return NULL;
|
||||
} else {
|
||||
WTF;
|
||||
}
|
||||
|
||||
if (md != NULL) {
|
||||
return &md->uuid;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AnalyticsHeartbeat *analytics_storage_find(AnalyticsMetric metric, const Uuid *uuid,
|
||||
AnalyticsClient client) {
|
||||
PBL_ASSERTN(analytics_storage_has_lock());
|
||||
|
||||
switch (analytics_metric_kind(metric)) {
|
||||
case ANALYTICS_METRIC_KIND_DEVICE:
|
||||
PBL_ASSERTN(client == AnalyticsClient_Ignore || client == AnalyticsClient_System);
|
||||
return s_device_heartbeat;
|
||||
case ANALYTICS_METRIC_KIND_APP: {
|
||||
PBL_ASSERTN(client == AnalyticsClient_Ignore || client != AnalyticsClient_System);
|
||||
const Uuid uuid_system = UUID_SYSTEM;
|
||||
if (!uuid) {
|
||||
uuid = analytics_uuid_for_client(client);
|
||||
if (!uuid) {
|
||||
// There is a brief period of time where no app is running, which we
|
||||
// attribute to the system UUID. For now, this lets us track how
|
||||
// much time we are missing, although we probably want to try and
|
||||
// tighten this up as much as possible going forward.
|
||||
uuid = &uuid_system;
|
||||
}
|
||||
}
|
||||
ListNode *node = list_find((ListNode*)s_app_heartbeat_list,
|
||||
prv_is_app_node_with_uuid, (void*)uuid);
|
||||
AnalyticsHeartbeatList *app_node = (AnalyticsHeartbeatList*)node;
|
||||
if (!app_node) {
|
||||
if (list_count((ListNode *)s_app_heartbeat_list) >= MAX_APP_HEARTBEATS) {
|
||||
ANALYTICS_LOG_DEBUG("No more app heartbeat sessions available");
|
||||
return NULL;
|
||||
}
|
||||
app_node = prv_app_node_create(uuid);
|
||||
s_app_heartbeat_list = (AnalyticsHeartbeatList*)list_prepend(
|
||||
(ListNode*)s_app_heartbeat_list, &app_node->node);
|
||||
}
|
||||
return app_node->heartbeat;
|
||||
}
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue