| /* |
| * Copyright (c) 2018 Google, Inc. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * Task starts out as a CPU hog and then becomes small. |
| * Task placement and downmigration latency 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_DOWNMIGRATE_LATENCY_US 100000 |
| #define MAX_INCORRECT_CLUSTER_PCT 10 |
| #define BURN_SEC 3 |
| static void *task_fn(void *arg LTP_ATTRIBUTE_UNUSED) |
| { |
| task_tid = gettid(); |
| |
| printf("Big task executing for %ds...\n", BURN_SEC); |
| burn(BURN_SEC * USEC_PER_SEC, 0); |
| |
| printf("Changing to small task...\n"); |
| SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "SMALL TASK"); |
| burn(BURN_SEC * USEC_PER_SEC, 1); |
| |
| 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 big_task_us = 0; |
| unsigned long long small_task_us = 0; |
| unsigned long long smalltask_ts_usec = 0; |
| unsigned long long downmigrate_ts_usec = 0; |
| unsigned long long 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 && |
| !strcmp(trace[i].event_data, "SMALL TASK")) { |
| smalltask_ts_usec = TS_TO_USEC(trace[i].ts); |
| 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 (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 (!smalltask_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 (smalltask_ts_usec) |
| small_task_us += segment_us; |
| else |
| big_task_us += segment_us; |
| } |
| |
| 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: " |
| "%lld usec (%d%% of big task CPU time)\n", too_small_cpu_us, |
| pct); |
| 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); |
| |
| if (downmigrate_ts_usec) { |
| downmigrate_latency_usec = downmigrate_ts_usec - |
| smalltask_ts_usec; |
| printf("Downmigration latency: %lld usec\n", |
| downmigrate_latency_usec); |
| } else { |
| printf("Task never downmigrated!\n"); |
| downmigrate_latency_usec = UINT_MAX; |
| } |
| |
| return (rv || 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); |
| |
| /* 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/migration latency goals " |
| "not met.\n"); |
| else |
| tst_res(TPASS, "Task placement/migration latency goals " |
| "met.\n"); |
| } |
| |
| static struct tst_test test = { |
| .test_all = run, |
| .cleanup = trace_cleanup, |
| }; |