xref: /xnu-10002.61.3/osfmk/kern/core_analytics.c (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
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 */
49 static struct {
50 	core_analytics_family_service_t *ts_core_analytics_service;
51 } telemetry_state = {0};
52 
53 static struct mpsc_daemon_queue core_analytics_event_queue;
54 
55 const char *core_analytics_ca_bool_c_stringified = _CA_STRINGIFY_EXPAND(CA_BOOL);
56 extern const char *core_analytics_ca_bool_cpp_stringified;
57 
58 size_t
core_analytics_field_is_string(const char * field_spec)59 core_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(ca_str_prefix) - 1;
64 	if (strncmp(field_spec, ca_str_prefix, 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 
71 static size_t
event_field_size(const char ** field_spec)72 event_field_size(const char **field_spec)
73 {
74 	size_t size = 0;
75 	size_t str_len = 0;
76 	if (strcmp(*field_spec, _CA_STRINGIFY_EXPAND(CA_INT)) == 0) {
77 		size = sizeof(const uint64_t);
78 	} else if ((strcmp(*field_spec, core_analytics_ca_bool_cpp_stringified) == 0) ||
79 	    (strcmp(*field_spec, core_analytics_ca_bool_c_stringified) == 0)) {
80 		size = sizeof(const bool);
81 	} else if ((str_len = core_analytics_field_is_string(*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(*field_spec) + 1;
88 	/* Skip over the key */
89 	*field_spec += strlen(*field_spec) + 1;
90 	return size;
91 }
92 
93 size_t
core_analytics_event_size(const char * event_spec)94 core_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(event_spec) + 1;
99 	while (strlen(curr) != 0) {
100 		size += event_field_size(&curr);
101 	}
102 	return size;
103 }
104 
105 static void
core_analytics_event_queue_invoke(mpsc_queue_chain_t e,mpsc_daemon_queue_t queue __unused)106 core_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(telemetry_state.ts_core_analytics_service, event->format_str, event);
115 	CA_EVENT_DEALLOCATE(event);
116 }
117 
118 __startup_func
119 static void
telemetry_init(void * arg __unused)120 telemetry_init(void *arg __unused)
121 {
122 	kern_return_t result;
123 	result = mpsc_daemon_queue_init_with_thread(&core_analytics_event_queue,
124 	    core_analytics_event_queue_invoke, CORE_ANALYTICS_EVENT_QUEUE_PRIORITY,
125 	    "daemon.core-analytics-events", MPSC_DAEMON_INIT_NONE);
126 }
127 STARTUP_ARG(EARLY_BOOT, STARTUP_RANK_MIDDLE, telemetry_init, NULL);
128 
129 void
core_analytics_send_event(ca_event_t event)130 core_analytics_send_event(ca_event_t event)
131 {
132 	mpsc_daemon_enqueue(&core_analytics_event_queue, &event->link, MPSC_QUEUE_DISABLE_PREEMPTION);
133 }
134 
135 void
core_analytics_send_event_preemption_disabled(ca_event_t event)136 core_analytics_send_event_preemption_disabled(ca_event_t event)
137 {
138 	mpsc_daemon_enqueue(&core_analytics_event_queue, &event->link, MPSC_QUEUE_NONE);
139 }
140 
141 ca_event_t
core_analytics_allocate_event(size_t data_size,const char * format_str,zalloc_flags_t flags)142 core_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