/* * Copyright (c) 2018 Google, Inc. * * SPDX-License-Identifier: GPL-2.0-or-later * * A task alternates between being big and small. Max up and down migration * latencies and task placement are verified. */ #define _GNU_SOURCE #include <errno.h> #include <pthread.h> #include <sched.h> #include <time.h> #include "tst_test.h" #include "tst_safe_file_ops.h" #include "tst_safe_pthread.h" #include "trace_parse.h" #include "util.h" #define TRACE_EVENTS "sched_switch" static int task_tid; #define MAX_UPMIGRATE_LATENCY_US 100000 #define MAX_DOWNMIGRATE_LATENCY_US 100000 #define MAX_INCORRECT_CLUSTER_PCT 10 #define BURN_SEC 1 #define NUM_LOOPS 10 static void *task_fn(void *arg LTP_ATTRIBUTE_UNUSED) { int loops = NUM_LOOPS; task_tid = gettid(); while (loops--) { SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "SMALL TASK"); burn(BURN_SEC * USEC_PER_SEC, 1); SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "CPU HOG"); burn(BURN_SEC * USEC_PER_SEC, 0); } return NULL; } static int parse_results(void) { int i, pct, rv = 0; unsigned long long exec_start_us = 0; unsigned long long too_big_cpu_us = 0; unsigned long long too_small_cpu_us = 0; unsigned long long small_task_us = 0; unsigned long long big_task_us = 0; unsigned long long smalltask_ts_usec = 0; unsigned long long cpuhog_ts_usec = 0; unsigned long long upmigrate_ts_usec = 0; unsigned long long downmigrate_ts_usec = 0; unsigned long long max_upmigrate_latency_usec = 0; unsigned long long max_downmigrate_latency_usec = 0; cpu_set_t cpuset; if (find_cpus_with_capacity(0, &cpuset)) { printf("Failed to find the CPUs in the little cluster.\n"); return -1; } for (i = 0; i < num_trace_records; i++) { unsigned long long segment_us; struct trace_sched_switch *t = trace[i].event_data; if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE) { if (!strcmp(trace[i].event_data, "CPU HOG")) { /* Task is transitioning to cpu hog. */ cpuhog_ts_usec = TS_TO_USEC(trace[i].ts); if (downmigrate_ts_usec) { unsigned long long temp_latency; temp_latency = downmigrate_ts_usec - smalltask_ts_usec; if (temp_latency > max_downmigrate_latency_usec) max_downmigrate_latency_usec = temp_latency; } else if (smalltask_ts_usec) { printf("Warning: small task never " "downmigrated.\n"); rv = 1; } downmigrate_ts_usec = 0; smalltask_ts_usec = 0; } else if (!strcmp(trace[i].event_data, "SMALL TASK")) { smalltask_ts_usec = TS_TO_USEC(trace[i].ts); if (upmigrate_ts_usec) { unsigned long long temp_latency; temp_latency = upmigrate_ts_usec - cpuhog_ts_usec; if (temp_latency > max_upmigrate_latency_usec) max_upmigrate_latency_usec = temp_latency; } else if (cpuhog_ts_usec) { printf("Warning: big task never " "upmigrated.\n"); rv = 1; } upmigrate_ts_usec = 0; cpuhog_ts_usec = 0; } continue; } if (trace[i].event_type != TRACE_RECORD_SCHED_SWITCH) continue; if (t->next_pid == task_tid) { /* Start of task execution segment. */ if (exec_start_us) { printf("Trace parse fail: double exec start\n"); return -1; } exec_start_us = TS_TO_USEC(trace[i].ts); if (cpuhog_ts_usec && !upmigrate_ts_usec && !CPU_ISSET(trace[i].cpu, &cpuset)) upmigrate_ts_usec = exec_start_us; if (smalltask_ts_usec && !downmigrate_ts_usec && CPU_ISSET(trace[i].cpu, &cpuset)) downmigrate_ts_usec = exec_start_us; continue; } if (t->prev_pid != task_tid) continue; /* End of task execution segment. */ segment_us = TS_TO_USEC(trace[i].ts); segment_us -= exec_start_us; exec_start_us = 0; if (CPU_ISSET(trace[i].cpu, &cpuset)) { /* Task is running on little CPUs. */ if (cpuhog_ts_usec) { /* * Upmigration is accounted separately, so only * record mis-scheduled time here if it happened * after upmigration. */ if (upmigrate_ts_usec) too_small_cpu_us += segment_us; } } else { /* Task is running on big CPUs. */ if (smalltask_ts_usec) { /* * Downmigration is accounted separately, so * only record mis-scheduled time here if it * happened after downmigration. */ if (downmigrate_ts_usec) too_big_cpu_us += segment_us; } } if (cpuhog_ts_usec) big_task_us += segment_us; if (smalltask_ts_usec) small_task_us += segment_us; } pct = (too_big_cpu_us * 100) / small_task_us; rv |= (pct > MAX_INCORRECT_CLUSTER_PCT); printf("Time incorrectly scheduled on big when task was small, " "after downmigration: " "%lld usec (%d%% of small task CPU time)\n", too_big_cpu_us, pct); pct = (too_small_cpu_us * 100) / big_task_us; rv |= (pct > MAX_INCORRECT_CLUSTER_PCT); printf("Time incorrectly scheduled on small when task was big, " "after upmigration: " "%lld usec (%d%% of big task CPU time)\n", too_small_cpu_us, pct); printf("small task time: %lld\nbig task time: %lld\n", small_task_us, big_task_us); printf("Maximum upmigration time: %lld\n", max_upmigrate_latency_usec); printf("Maximum downmigration time: %lld\n", max_downmigrate_latency_usec); return (rv || max_upmigrate_latency_usec > MAX_UPMIGRATE_LATENCY_US || max_downmigrate_latency_usec > MAX_DOWNMIGRATE_LATENCY_US); } static void run(void) { pthread_t task_thread; tst_res(TINFO, "Maximum incorrect cluster time percentage: %d%%", MAX_INCORRECT_CLUSTER_PCT); tst_res(TINFO, "Maximum downmigration latency: %d usec", MAX_DOWNMIGRATE_LATENCY_US); tst_res(TINFO, "Maximum upmigration latency: %d usec", MAX_UPMIGRATE_LATENCY_US); printf("Task alternating between big and small for %d sec\n", BURN_SEC * NUM_LOOPS * 2); /* configure and enable tracing */ SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0"); SAFE_FILE_PRINTF(TRACING_DIR "buffer_size_kb", "16384"); SAFE_FILE_PRINTF(TRACING_DIR "set_event", TRACE_EVENTS); SAFE_FILE_PRINTF(TRACING_DIR "trace", "\n"); SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "1"); SAFE_PTHREAD_CREATE(&task_thread, NULL, task_fn, NULL); SAFE_PTHREAD_JOIN(task_thread, NULL); /* disable tracing */ SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0"); LOAD_TRACE(); if (parse_results()) tst_res(TFAIL, "Task placement and migration latency goals " "were not met.\n"); else tst_res(TPASS, "Task placement and migration latency goals " "were met.\n"); } static struct tst_test test = { .test_all = run, .cleanup = trace_cleanup, };