/*
 * Copyright (c) 2018 Google, Inc.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * A task executes as small then as big. Upmigration latency and task placement
 * are verified.
 */

#define _GNU_SOURCE
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <sys/types.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_INCORRECT_CLUSTER_PCT 10
#define BURN_SEC 3
static void *task_fn(void *arg LTP_ATTRIBUTE_UNUSED)
{
	task_tid = gettid();

	printf("Small task executing for %ds...\n", BURN_SEC);
	burn(BURN_SEC * USEC_PER_SEC, 1);

	printf("Changing to big task...\n");
	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 cpuhog_ts_usec = 0;
	unsigned long long upmigrate_ts_usec = 0;
	unsigned long long upmigrate_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, "CPU HOG")) {
			cpuhog_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 (cpuhog_ts_usec && !upmigrate_ts_usec &&
			    !CPU_ISSET(trace[i].cpu, &cpuset))
				upmigrate_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 (!cpuhog_ts_usec)
				too_big_cpu_us += segment_us;
		}
		if (cpuhog_ts_usec)
			big_task_us += segment_us;
		else
			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: "
	       "%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);

	if (upmigrate_ts_usec) {
		upmigrate_latency_usec = upmigrate_ts_usec - cpuhog_ts_usec;
		printf("Upmigration latency: %lld usec\n",
		       upmigrate_latency_usec);
	} else {
		printf("Task never upmigrated!\n");
		upmigrate_latency_usec = UINT_MAX;
	}

	return (rv || upmigrate_latency_usec > MAX_UPMIGRATE_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 upmigration latency: %d usec",
		MAX_UPMIGRATE_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 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,
};