1/* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
2 *
3 * This file contains Original Code and/or Modifications of Original Code
4 * as defined in and that are subject to the Apple Public Source License
5 * Version 2.0 (the 'License'). You may not use this file except in
6 * compliance with the License. The rights granted to you under the License
7 * may not be used to create, or enable the creation or redistribution of,
8 * unlawful or unlicensed copies of an Apple operating system, or to
9 * circumvent, violate, or enable the circumvention or violation of, any
10 * terms of an Apple operating system software license agreement.
11 *
12 * Please obtain a copy of the License at
13 * http://www.opensource.apple.com/apsl/ and read it before using this file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
24 */
25
26#include <kern/mpsc_queue.h>
27#include <kern/thread.h>
28#include <libkern/coreanalytics/coreanalytics.h>
29#include <libkern/coreanalytics/coreanalytics_shim.h>
30#include <os/log.h>
31#include <stdlib.h>
32
33/*
34 * xnu telemetry is meant to be extremely lightweight.
35 * Clients put a buffer in a mpsc queue & the telemetry thread
36 * drains the queue.
37 * Serialization happens in the telemetry thread.
38 * Currently we serialize to an OSDictionary & send it to
39 * the CoreAnalyticsFamily kext (which sticks it on its own queue and
40 * has another thread to serialize & send to osanalyticsd).
41 * This is fine for a low volume of events.
42 * But long term we should send directly to osanlyticsd from here
43 * & kexts should send their events to xnu rather than rely on another kext.
44 */
45
46#define CORE_ANALYTICS_EVENT_QUEUE_PRIORITY MAXPRI_USER
47
48/* Holds private state used by the telemetry thread */
49static struct {
50 core_analytics_family_service_t *ts_core_analytics_service;
51} telemetry_state = {0};
52
53static struct mpsc_daemon_queue core_analytics_event_queue;
54
55const char *core_analytics_ca_bool_c_stringified = _CA_STRINGIFY_EXPAND(CA_BOOL);
56extern const char *core_analytics_ca_bool_cpp_stringified;
57
58size_t
59core_analytics_field_is_string(const char *field_spec)
60{
61 size_t size = 0;
62 static const char *ca_str_prefix = _CA_STRINGIFY_EXPAND(CA_STATIC_STRING());
63 size_t ca_str_len = strlen(s: ca_str_prefix) - 1;
64 if (strncmp(s1: field_spec, s2: ca_str_prefix, n: ca_str_len) == 0) {
65 const char *sizep = field_spec + ca_str_len;
66 size = strtoul(sizep, NULL, 10);
67 }
68 return size;
69}
70
71static size_t
72event_field_size(const char **field_spec)
73{
74 size_t size = 0;
75 size_t str_len = 0;
76 if (strcmp(s1: *field_spec, _CA_STRINGIFY_EXPAND(CA_INT)) == 0) {
77 size = sizeof(const uint64_t);
78 } else if ((strcmp(s1: *field_spec, s2: core_analytics_ca_bool_cpp_stringified) == 0) ||
79 (strcmp(s1: *field_spec, s2: core_analytics_ca_bool_c_stringified) == 0)) {
80 size = sizeof(const bool);
81 } else if ((str_len = core_analytics_field_is_string(field_spec: *field_spec)) != 0) {
82 size = str_len;
83 } else {
84 panic("Unknown CA event type: %s.", *field_spec);
85 }
86 /* Skip over the type */
87 *field_spec += strlen(s: *field_spec) + 1;
88 /* Skip over the key */
89 *field_spec += strlen(s: *field_spec) + 1;
90 return size;
91}
92
93size_t
94core_analytics_event_size(const char *event_spec)
95{
96 size_t size = 0;
97 /* Skip over the event name. */
98 const char *curr = event_spec + strlen(s: event_spec) + 1;
99 while (strlen(s: curr) != 0) {
100 size += event_field_size(field_spec: &curr);
101 }
102 return size;
103}
104
105static void
106core_analytics_event_queue_invoke(mpsc_queue_chain_t e, mpsc_daemon_queue_t queue __unused)
107{
108 if (!telemetry_state.ts_core_analytics_service) {
109 /* First event since boot. Ensure the CoreAnalytics IOService is running. */
110 telemetry_state.ts_core_analytics_service = core_analytics_family_match();
111 }
112 ca_event_t event;
113 event = mpsc_queue_element(e, struct _ca_event, link);
114 core_analytics_send_event_lazy(core_analytics_hub: telemetry_state.ts_core_analytics_service, event_spec: event->format_str, event);
115 CA_EVENT_DEALLOCATE(event);
116}
117
118__startup_func
119static void
120telemetry_init(void *arg __unused)
121{
122 kern_return_t result;
123 result = mpsc_daemon_queue_init_with_thread(dq: &core_analytics_event_queue,
124 invoke: core_analytics_event_queue_invoke, CORE_ANALYTICS_EVENT_QUEUE_PRIORITY,
125 name: "daemon.core-analytics-events", flags: MPSC_DAEMON_INIT_NONE);
126}
127STARTUP_ARG(EARLY_BOOT, STARTUP_RANK_MIDDLE, telemetry_init, NULL);
128
129void
130core_analytics_send_event(ca_event_t event)
131{
132 mpsc_daemon_enqueue(dq: &core_analytics_event_queue, elm: &event->link, options: MPSC_QUEUE_DISABLE_PREEMPTION);
133}
134
135void
136core_analytics_send_event_preemption_disabled(ca_event_t event)
137{
138 mpsc_daemon_enqueue(dq: &core_analytics_event_queue, elm: &event->link, options: MPSC_QUEUE_NONE);
139}
140
141ca_event_t
142core_analytics_allocate_event(size_t data_size, const char *format_str, zalloc_flags_t flags)
143{
144 ca_event_t event = kalloc_type(struct _ca_event, flags);
145 if (!event) {
146 return NULL;
147 }
148 event->data = kalloc_data(data_size, flags);
149 if (!event->data) {
150 kfree_type(struct _ca_event, event);
151 return NULL;
152 }
153 event->format_str = format_str;
154 return event;
155}
156