Loading...
tests/microstackshot_tests.c xnu-12377.101.15 /dev/null
--- xnu/xnu-12377.101.15/tests/microstackshot_tests.c
+++ /dev/null
@@ -1,550 +0,0 @@
-/* Copyright (c) 2018-2021,2024-2025 Apple Inc.  All rights reserved. */
-
-#include <kern/telemetry.h>
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <darwintest.h>
-#include <darwintest_utils.h>
-#include <dispatch/dispatch.h>
-#include <ktrace/ktrace.h>
-#include <kperf/kperf.h>
-#include <kern/debug.h>
-#include <mach/mach_time.h>
-#include <notify.h>
-#include <stdio.h>
-#include <sys/kdebug.h>
-#include <sys/sysctl.h>
-#include <TargetConditionals.h>
-
-#include "test_utils.h"
-#include "ktrace/ktrace_helpers.h"
-
-T_GLOBAL_META(T_META_NAMESPACE("xnu.stackshot.microstackshot"),
-    T_META_RADAR_COMPONENT_NAME("xnu"),
-    T_META_RADAR_COMPONENT_VERSION("stackshot"),
-    T_META_OWNER("mwidmann"),
-    T_META_CHECK_LEAKS(false),
-    T_META_ASROOT(true));
-
-/*
- * Data Analytics (da) also has a microstackshot configuration -- set a PMI
- * cycle interval of 0 to force it to disable microstackshot on PMI.
- */
-
-static void
-set_da_microstackshot_period(CFNumberRef num)
-{
-	CFPreferencesSetValue(CFSTR("microstackshotPMICycleInterval"), num,
-	    CFSTR("com.apple.da"),
-#if TARGET_OS_IPHONE
-	    CFSTR("mobile"),
-#else // TARGET_OS_IPHONE
-	    CFSTR("root"),
-#endif // !TARGET_OS_IPHONE
-	    kCFPreferencesCurrentHost);
-
-	notify_post("com.apple.da.tasking_changed");
-}
-
-static void
-disable_da_microstackshots(void)
-{
-	int64_t zero = 0;
-	CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt64Type, &zero);
-	set_da_microstackshot_period(num);
-	T_LOG("notified da of tasking change, sleeping");
-#if TARGET_OS_WATCH
-	sleep(8);
-#else /* TARGET_OS_WATCH */
-	sleep(3);
-#endif /* !TARGET_OS_WATCH */
-}
-
-/*
- * Unset the preference to allow da to reset its configuration.
- */
-static void
-reenable_da_microstackshots(void)
-{
-	set_da_microstackshot_period(NULL);
-}
-
-/*
- * Clean up the test's configuration and allow da to activate again.
- */
-static void
-telemetry_cleanup(void)
-{
-	(void)__telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_NONE, 0, 0, 0, 0);
-	reenable_da_microstackshots();
-}
-
-/*
- * Make sure da hasn't configured the microstackshots -- otherwise the PMI
- * setup command will return EBUSY.
- */
-static void
-telemetry_init(void)
-{
-	disable_da_microstackshots();
-	T_LOG("installing cleanup handler");
-	T_ATEND(telemetry_cleanup);
-}
-
-volatile static bool spinning = true;
-
-static void *
-thread_spin(__unused void *arg)
-{
-	while (spinning) {
-	}
-	return NULL;
-}
-
-volatile static bool grabbing = true;
-
-static void *
-thread_use_memory(__unused void *arg)
-{
-	char path[MAXPATHLEN];
-	snprintf(path, MAXPATHLEN, "%s/microstackshot_tests-upl-induce", dt_tmpdir());
-	int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0777);
-	T_ASSERT_POSIX_SUCCESS(fd, "open file for writing for reading");
-
-	while (grabbing) {
-		mach_vm_address_t addr = 0;
-		mach_vm_size_t size = 1 << 20;
-
-		kern_return_t kr = mach_vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_ANYWHERE);
-		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "allocated %llu bytes", size);
-		T_QUIET; T_ASSERT_NE_ULLONG(0ULL, addr, "allocated address is not NULL");
-		memset((char *)addr, 0, size);
-		ssize_t bytes = write(fd, (char *)addr, size / 4);
-		T_QUIET; T_ASSERT_GT(bytes, 0L, "wrote to file");
-		kr = mach_vm_deallocate(mach_task_self(), addr, size);
-		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocated %llu bytes", size);
-	}
-
-	close(fd);
-	unlink(path);
-
-	return NULL;
-}
-
-static bool
-query_pmi_params(unsigned int *pmi_counter, uint64_t *pmi_period)
-{
-	bool pmi_support = true;
-	size_t sysctl_size = sizeof(pmi_counter);
-	int ret = sysctlbyname(
-			"kern.microstackshot.pmi_sample_counter",
-			pmi_counter, &sysctl_size, NULL, 0);
-	if (ret == -1 && errno == ENOENT) {
-		pmi_support = false;
-		T_LOG("no PMI support");
-	} else {
-		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "query PMI counter");
-	}
-	if (pmi_support) {
-		sysctl_size = sizeof(*pmi_period);
-		T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname(
-				"kern.microstackshot.pmi_sample_period",
-				pmi_period, &sysctl_size, NULL, 0),
-				"query PMI period");
-	}
-	return pmi_support;
-}
-
-__enum_closed_decl(record_type_t, uint16_t, {
-	REC_PMI = 0,
-	REC_IO,
-	REC_VM_FAULT,
-	REC_PAGE_GRAB,
-	REC_INTERRUPT,
-	REC_INVALID,
-	REC_COUNT,
-});
-
-struct record_info {
-	uint32_t ri_bits;
-	const char *ri_name;
-};
-
-static struct record_info record_info[REC_COUNT] = {
-	[REC_PMI] = { .ri_bits = kPMIRecord, .ri_name = "PMI", },
-	[REC_IO] = { .ri_bits = kIORecord, .ri_name = "I/O", },
-	[REC_VM_FAULT] = { .ri_bits = kVMFaultRecord, .ri_name = "VM fault", },
-	[REC_PAGE_GRAB] = { .ri_bits = kPageGrabRecord, .ri_name = "page grab", },
-	[REC_INTERRUPT] = { .ri_bits = kInterruptRecord, .ri_name = "interrupt", },
-	[REC_INVALID] = {
-		.ri_bits = ~(kPMIRecord | kIORecord | kVMFaultRecord | kPageGrabRecord),
-		.ri_name = "invalid",
-	},
-};
-
-struct record_stats {
-	unsigned int rs_total_count;
-	unsigned int rs_counts[REC_COUNT];
-	uint64_t rs_time_range_mach[2];
-	double rs_duration_secs;
-};
-
-static void
-_record_stats_handle(struct record_stats *stats, uint64_t type, uint64_t time_mach)
-{
-	if (stats->rs_time_range_mach[0] == 0) {
-		stats->rs_time_range_mach[0] = time_mach;
-	}
-
-	for (uint16_t i = 0; i < REC_COUNT; i++) {
-		if (record_info[i].ri_bits & type) {
-			stats->rs_counts[i] += 1;
-		}
-	}
-	stats->rs_total_count += 1;
-
-	stats->rs_time_range_mach[1] = time_mach;
-}
-
-static void
-_record_stats_log(struct record_stats *stats)
-{
-	uint64_t duration_mach = stats->rs_time_range_mach[1] - stats->rs_time_range_mach[0];
-	mach_timebase_info_data_t tb = { 0 };
-	(void)mach_timebase_info(&tb);
-	uint64_t duration_ns = duration_mach * tb.numer / tb.denom;
-	T_LOG("saw record events over %.3f seconds (%.1f%% of expected)",
-	    (double)duration_ns / 1e9,
-	    (double)duration_ns / 1e9 / stats->rs_duration_secs * 100.0);
-
-	for (uint16_t i = 0; i < REC_COUNT; i++) {
-		struct record_info *info = &record_info[i];
-		uint32_t count = stats->rs_counts[i];
-		if (count == 0) {
-			T_LOG("saw no %s record events", info->ri_name);			
-		} else {
-			double rate = (double)count / (double)stats->rs_duration_secs;
-			T_LOG("saw %.2f %s record events per second, %.1f%% of total",
-			    rate, info->ri_name,
-			    (double)count / (double)stats->rs_total_count * 100.0);
-		}
-	}
-}
-
-#define MT_MICROSTACKSHOT KDBG_EVENTID(DBG_MONOTONIC, 2, 1)
-#define MS_RECORD MACHDBG_CODE(DBG_MACH_STACKSHOT, \
-	        MICROSTACKSHOT_RECORD)
-#if defined(__arm64__)
-#define INSTRS_PERIOD (100ULL * 1000 * 1000)
-#else /* defined(__arm64__) */
-#define INSTRS_PERIOD (1ULL * 1000 * 1000 * 1000)
-#endif /* defined(__arm64__) */
-#define SLEEP_SECS 10
-
-T_DECL(pmi_sampling, "attempt to configure microstackshots on PMI",
-		T_META_REQUIRES_SYSCTL_EQ("kern.monotonic.supported", 1), T_META_TAG_VM_NOT_ELIGIBLE)
-{
-	start_controlling_ktrace();
-
-	T_SETUPBEGIN;
-	ktrace_session_t s = ktrace_session_create();
-	T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(s, "session create");
-
-	__block struct record_stats stats = { .rs_duration_secs = SLEEP_SECS, };
-	__block int empty_records = 0;
-
-	ktrace_events_single_paired(s, MS_RECORD,
-	    ^(struct trace_point *start, __unused struct trace_point *end) {
-	    _record_stats_handle(&stats, start->arg1, start->timestamp);
-
-		if (start->arg2 == end->arg2) {
-			/*
-			 * The buffer didn't grow for this record -- there was
-			 * an error.
-			 */
-			empty_records++;
-		}
-	});
-
-	ktrace_set_completion_handler(s, ^{
-		ktrace_session_destroy(s);
-		_record_stats_log(&stats);
-		T_LOG("saw %d empty records", empty_records);
-		T_EXPECT_GT(stats.rs_counts[REC_PMI], 0, "saw non-zero PMI record events");
-		T_EXPECT_GT(stats.rs_total_count, 0, "saw non-zero microstackshot record events");
-		T_EXPECT_NE(empty_records, stats.rs_total_count, "saw non-empty records");
-
-		T_END;
-	});
-
-	T_SETUPEND;
-
-	telemetry_init();
-
-	/*
-	 * Start sampling via telemetry on the instructions PMI.
-	 */
-	int ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS,
-			INSTRS_PERIOD, 0, 0, 0);
-	T_ASSERT_POSIX_SUCCESS(ret,
-			"telemetry syscall succeeded, started microstackshots");
-
-	unsigned int pmi_counter = 0;
-	uint64_t pmi_period = 0;
-	bool pmi_support = query_pmi_params(&pmi_counter, &pmi_period);
-	T_QUIET; T_ASSERT_TRUE(pmi_support, "PMI should be supported");
-
-	T_LOG("PMI counter: %u", pmi_counter);
-	T_LOG("PMI period: %llu", pmi_period);
-#if defined(__arm64__)
-	const unsigned int instrs_counter = 1;
-#else
-	const unsigned int instrs_counter = 0;
-#endif // defined(__arm64__)
-	T_QUIET; T_ASSERT_EQ(pmi_counter, instrs_counter,
-			"PMI on instructions retired");
-	T_QUIET; T_ASSERT_EQ(pmi_period, INSTRS_PERIOD, "PMI period is set");
-
-	pthread_t thread;
-	int error = pthread_create(&thread, NULL, thread_spin, NULL);
-	T_ASSERT_POSIX_ZERO(error, "started thread to spin");
-
-	error = ktrace_start(s, dispatch_get_main_queue());
-	T_ASSERT_POSIX_ZERO(error, "started tracing");
-
-	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, SLEEP_SECS * NSEC_PER_SEC),
-			dispatch_get_main_queue(), ^{
-		spinning = false;
-		ktrace_end(s, 0);
-		(void)pthread_join(thread, NULL);
-		T_LOG("ending trace session after %d seconds", SLEEP_SECS);
-	});
-
-	dispatch_main();
-}
-
-static void
-_reset_telemetry_memory_usage(void)
-{
-	(void)__telemetry(TELEMETRY_CMD_MEMORY_USAGE_SETUP, 0, 0, 0, 0, 0);
-}
-
-#if TARGET_OS_BRIDGE || defined(__x86_64__)
-#define SUPPORTS_MEMORY_MICROSTACKSHOTS false
-#else /* TARGET_OS_BRIDGE || defined(__x86_64__) */
-#define SUPPORTS_MEMORY_MICROSTACKSHOTS true
-#endif /* !(TARGET_OS_BRIDGE || defined(__x86_64__)) */ 
-
-T_DECL(memory_sampling, "attempt to configure microstackshots on memory usage",
-		XNU_T_META_REQUIRES_DEVELOPMENT_KERNEL,
-		T_META_ENABLED(SUPPORTS_MEMORY_MICROSTACKSHOTS))
-{
-	start_controlling_ktrace();
-
-	T_SETUPBEGIN;
-	ktrace_session_t s = ktrace_session_create();
-	T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(s, "session create");
-
-	__block struct record_stats stats = { .rs_duration_secs = SLEEP_SECS, };
-	__block int empty_records = 0;
-
-	ktrace_events_single_paired(s, MS_RECORD,
-	    ^(struct trace_point *start, __unused struct trace_point *end) {
-	    _record_stats_handle(&stats, start->arg1, start->timestamp);
-		if (start->arg2 == end->arg2) {
-			/*
-			 * The buffer didn't grow for this record -- there was
-			 * an error.
-			 */
-			empty_records++;
-		}
-	});
-
-	ktrace_set_completion_handler(s, ^{
-		ktrace_session_destroy(s);
-		_record_stats_log(&stats);
-		T_EXPECT_GT(stats.rs_counts[REC_VM_FAULT], 0, "saw non-zero VM fault record events");
-		T_EXPECT_GT(stats.rs_counts[REC_PAGE_GRAB], 0, "saw non-zero page grab record events");
-		T_EXPECT_GT(stats.rs_total_count, 0, "saw non-zero microstackshot record events");
-		T_EXPECT_NE(empty_records, stats.rs_total_count, "saw non-empty records");
-		T_END;
-	});
-
-	T_SETUPEND;
-
-	telemetry_init();
-
-	/*
-	 * Start sampling via telemetry on faults and page grabs.
-	 */
-	int ret = __telemetry(TELEMETRY_CMD_MEMORY_USAGE_SETUP, 5,
-			(1 << 20) / (16 << 10), 0, 0, 0);
-	T_ASSERT_POSIX_SUCCESS(ret,
-			"telemetry syscall succeeded, started memory microstackshots");
-	T_ATEND(_reset_telemetry_memory_usage);
-
-	pthread_t thread;
-	int error = pthread_create(&thread, NULL, thread_use_memory, NULL);
-	T_ASSERT_POSIX_ZERO(error, "started thread to use memory");
-
-	error = ktrace_start(s, dispatch_get_main_queue());
-	T_ASSERT_POSIX_ZERO(error, "started tracing");
-
-	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, SLEEP_SECS * NSEC_PER_SEC),
-			dispatch_get_main_queue(), ^{
-		grabbing = false;
-		ktrace_end(s, 0);
-		(void)pthread_join(thread, NULL);
-		T_LOG("ending trace session after %d seconds", SLEEP_SECS);
-	});
-
-	dispatch_main();
-}
-
-T_DECL(error_handling,
-		"ensure that error conditions for the telemetry syscall are observed",
-		T_META_REQUIRES_SYSCTL_EQ("kern.monotonic.supported", 1), T_META_TAG_VM_NOT_ELIGIBLE)
-{
-	telemetry_init();
-
-	int ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS,
-	    1, 0, 0, 0);
-	T_EXPECT_EQ(ret, -1, "telemetry shouldn't allow PMI every instruction");
-
-	ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_INSTRS,
-	    1000 * 1000, 0, 0, 0);
-	T_EXPECT_EQ(ret, -1,
-	    "telemetry shouldn't allow PMI every million instructions");
-
-	ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_CYCLES,
-	    1, 0, 0, 0);
-	T_EXPECT_EQ(ret, -1, "telemetry shouldn't allow PMI every cycle");
-
-	ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_CYCLES,
-	    1000 * 1000, 0, 0, 0);
-	T_EXPECT_EQ(ret, -1,
-	    "telemetry shouldn't allow PMI every million cycles");
-
-	ret = __telemetry(TELEMETRY_CMD_PMI_SETUP, TELEMETRY_PMI_CYCLES,
-	    UINT64_MAX, 0, 0, 0);
-	T_EXPECT_EQ(ret, -1, "telemetry shouldn't allow PMI every UINT64_MAX cycles");
-}
-
-#define START_EVENT (0xfeedfad0)
-#define STOP_EVENT (0xfeedfac0)
-
-T_DECL(excessive_sampling,
-		"ensure that microstackshots are not being sampled too frequently",
-		T_META_REQUIRES_SYSCTL_EQ("kern.monotonic.supported", 1), T_META_TAG_VM_NOT_ELIGIBLE)
-{
-	unsigned int pmi_counter = 0;
-	uint64_t pmi_period = 0;
-	(void)query_pmi_params(&pmi_counter, &pmi_period);
-
-	T_LOG("PMI counter: %u", pmi_counter);
-	T_LOG("PMI period: %llu", pmi_period);
-
-	start_controlling_ktrace();
-
-	T_SETUPBEGIN;
-	ktrace_session_t s = ktrace_session_create();
-	T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(s, "session create");
-
-	__block struct record_stats stats = { .rs_duration_secs = SLEEP_SECS, };
-	__block uint64_t first_timestamp_ns = 0;
-	__block uint64_t last_timestamp_ns = 0;
-	__block int empty_records = 0;
-
-	ktrace_events_single_paired(s, MS_RECORD,
-			^(struct trace_point *start, __unused struct trace_point *end) {
-		_record_stats_handle(&stats, start->arg1, start->timestamp);
-		if (start->arg2 == end->arg2) {
-			/*
-			 * The buffer didn't grow for this record -- there was
-			 * an error.
-			 */
-			empty_records++;
-		}
-	});
-
-	ktrace_events_single(s, START_EVENT, ^(struct trace_point *tp) {
-		int error = ktrace_convert_timestamp_to_nanoseconds(s,
-				tp->timestamp, &first_timestamp_ns);
-		T_QUIET;
-		T_ASSERT_POSIX_ZERO(error, "converted timestamp to nanoseconds");
-	});
-
-	ktrace_events_single(s, STOP_EVENT, ^(struct trace_point *tp) {
-		int error = ktrace_convert_timestamp_to_nanoseconds(s,
-				tp->timestamp, &last_timestamp_ns);
-		T_QUIET;
-		T_ASSERT_POSIX_ZERO(error, "converted timestamp to nanoseconds");
-		ktrace_end(s, 1);
-	});
-
-	ktrace_set_completion_handler(s, ^{
-		ktrace_session_destroy(s);
-		_record_stats_log(&stats);
-
-		T_MAYFAIL;
-		T_EXPECT_EQ(stats.rs_counts[REC_INVALID], 0, "saw zero invalid record events");
-		T_MAYFAIL;
-		T_EXPECT_GT(stats.rs_total_count, 0,
-				"saw non-zero microstackshot record events");
-
-		double record_rate_hz = (double)stats.rs_total_count / stats.rs_duration_secs;
-
-		T_EXPECT_LE(record_rate_hz, (double)(dt_ncpu() * 50),
-				"found appropriate rate of microstackshots");
-
-		T_END;
-	});
-
-	pthread_t thread;
-	int error = pthread_create(&thread, NULL, thread_spin, NULL);
-	T_ASSERT_POSIX_ZERO(error, "started thread to spin");
-
-	T_SETUPEND;
-
-	error = ktrace_start(s, dispatch_get_main_queue());
-	T_ASSERT_POSIX_ZERO(error, "started tracing");
-	kdebug_trace(START_EVENT, 0, 0, 0, 0);
-
-	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, SLEEP_SECS * NSEC_PER_SEC),
-			dispatch_get_main_queue(), ^{
-		spinning = false;
-		kdebug_trace(STOP_EVENT, 0, 0, 0, 0);
-		(void)pthread_join(thread, NULL);
-		T_LOG("ending trace session after %d seconds", SLEEP_SECS);
-	});
-
-	dispatch_main();
-}
-
-T_HELPER_DECL(read_kernel_microstackshots,
-    "read kernel thread microstackshots to a file")
-{
-	extern int __microstackshot(char *tracebuf, uint32_t tracebuf_size, uint32_t flags);
-
-	if (argc < 1) {
-		T_ASSERT_FAIL("usage: microstackshot_tests -n read_kernel_microstackshots <file>");
-	}
-
-	const char *path = argv[0];
-
-	char tracebuf[16 * 1024] = {};
-	uint32_t size = (uint32_t)sizeof(tracebuf);
-
-	int ret = __microstackshot(tracebuf, size, 0x08);
-	T_QUIET;
-	T_ASSERT_POSIX_SUCCESS(ret, "microstackshot(2)");
-
-	T_LOG("read %d bytes from microstackshot syscall ", ret);
-
-	if (ret > 0) {
-		FILE *tmp = fopen(path, "w");
-		fwrite(tracebuf, ret, 1, tmp);
-		fclose(tmp);
-	}
-	T_LOG("wrote microstackshot data to %s", path);
-}