430 lines · 11.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Copyright IBM Corp. 2024 */ #define pr_fmt(fmt) "hd: " fmt /* * Hiperdispatch: * Dynamically calculates the optimum number of high capacity COREs * by considering the state the system is in. When hiperdispatch decides * that a capacity update is necessary, it schedules a topology update. * During topology updates the CPU capacities are always re-adjusted. * * There is two places where CPU capacities are being accessed within * hiperdispatch. * -> hiperdispatch's reoccuring work function reads CPU capacities to *    determine high capacity CPU count. * -> during a topology update hiperdispatch's adjustment function *    updates CPU capacities. * These two can run on different CPUs in parallel which can cause * hiperdispatch to make wrong decisions. This can potentially cause * some overhead by leading to extra rebuild_sched_domains() calls * for correction. Access to capacities within hiperdispatch has to be * serialized to prevent the overhead. * * Hiperdispatch decision making revolves around steal time. * HD_STEAL_THRESHOLD value is taken as reference. Whenever steal time * crosses the threshold value hiperdispatch falls back to giving high * capacities to entitled CPUs. When steal time drops below the * threshold boundary, hiperdispatch utilizes all CPUs by giving all * of them high capacity. * * The theory behind HD_STEAL_THRESHOLD is related to the SMP thread * performance. Comparing the throughput of; * - single CORE, with N threads, running N tasks * - N separate COREs running N tasks, * using individual COREs for individual tasks yield better * performance. This performance difference is roughly ~30% (can change * between machine generations) * * Hiperdispatch tries to hint scheduler to use individual COREs for * each task, as long as steal time on those COREs are less than 30%, * therefore delaying the throughput loss caused by using SMP threads. */ #include <linux/cpufeature.h>#include <linux/cpumask.h>#include <linux/debugfs.h>#include <linux/device.h>#include <linux/kernel_stat.h>#include <linux/kstrtox.h>#include <linux/ktime.h>#include <linux/sysctl.h>#include <linux/types.h>#include <linux/workqueue.h>#include <asm/hiperdispatch.h>#include <asm/setup.h>#include <asm/smp.h>#include <asm/topology.h> #define CREATE_TRACE_POINTS#include <asm/trace/hiperdispatch.h> #define HD_DELAY_FACTOR			(4)#define HD_DELAY_INTERVAL		(HZ / 4)#define HD_STEAL_THRESHOLD		10#define HD_STEAL_AVG_WEIGHT		16 static cpumask_t hd_vl_coremask;	/* Mask containing all vertical low COREs */static cpumask_t hd_vmvl_cpumask;	/* Mask containing vertical medium and low CPUs */static int hd_high_capacity_cores;	/* Current CORE count with high capacity */static int hd_entitled_cores;		/* Total vertical high and medium CORE count */static int hd_online_cores;		/* Current online CORE count */ static unsigned long hd_previous_steal;	/* Previous iteration's CPU steal timer total */static unsigned long hd_high_time;	/* Total time spent while all cpus have high capacity */static unsigned long hd_low_time;	/* Total time spent while vl cpus have low capacity */static atomic64_t hd_adjustments;	/* Total occurrence count of hiperdispatch adjustments */ static unsigned int hd_steal_threshold = HD_STEAL_THRESHOLD;static unsigned int hd_delay_factor = HD_DELAY_FACTOR;static int hd_enabled; static void hd_capacity_work_fn(struct work_struct *work);static DECLARE_DELAYED_WORK(hd_capacity_work, hd_capacity_work_fn); static int hd_set_hiperdispatch_mode(int enable){	if (!cpu_has_topology())		enable = 0;	if (hd_enabled == enable)		return 0;	hd_enabled = enable;	return 1;} void hd_reset_state(void){	cpumask_clear(&hd_vl_coremask);	cpumask_clear(&hd_vmvl_cpumask);	hd_entitled_cores = 0;	hd_online_cores = 0;} void hd_add_core(int cpu){	const struct cpumask *siblings;	int polarization; 	hd_online_cores++;	polarization = smp_cpu_get_polarization(cpu);	siblings = topology_sibling_cpumask(cpu);	switch (polarization) {	case POLARIZATION_VH:		hd_entitled_cores++;		break;	case POLARIZATION_VM:		hd_entitled_cores++;		cpumask_or(&hd_vmvl_cpumask, &hd_vmvl_cpumask, siblings);		break;	case POLARIZATION_VL:		cpumask_set_cpu(cpu, &hd_vl_coremask);		cpumask_or(&hd_vmvl_cpumask, &hd_vmvl_cpumask, siblings);		break;	}} /* Serialize update and read operations of debug counters. */static DEFINE_MUTEX(hd_counter_mutex); static void hd_update_times(void){	static ktime_t prev;	ktime_t now; 	/*	 * Check if hiperdispatch is active, if not set the prev to 0.	 * This way it is possible to differentiate the first update iteration after	 * enabling hiperdispatch.	 */	if (hd_entitled_cores == 0 || hd_enabled == 0) {		prev = ktime_set(0, 0);		return;	}	now = ktime_get();	if (ktime_after(prev, 0)) {		if (hd_high_capacity_cores == hd_online_cores)			hd_high_time += ktime_ms_delta(now, prev);		else			hd_low_time += ktime_ms_delta(now, prev);	}	prev = now;} static void hd_update_capacities(void){	int cpu, upscaling_cores;	unsigned long capacity; 	upscaling_cores = hd_high_capacity_cores - hd_entitled_cores;	capacity = upscaling_cores > 0 ? CPU_CAPACITY_HIGH : CPU_CAPACITY_LOW;	hd_high_capacity_cores = hd_entitled_cores;	for_each_cpu(cpu, &hd_vl_coremask) {		smp_set_core_capacity(cpu, capacity);		if (capacity != CPU_CAPACITY_HIGH)			continue;		hd_high_capacity_cores++;		upscaling_cores--;		if (upscaling_cores == 0)			capacity = CPU_CAPACITY_LOW;	}} void hd_disable_hiperdispatch(void){	cancel_delayed_work_sync(&hd_capacity_work);	hd_high_capacity_cores = hd_online_cores;	hd_previous_steal = 0;} int hd_enable_hiperdispatch(void){	mutex_lock(&hd_counter_mutex);	hd_update_times();	mutex_unlock(&hd_counter_mutex);	if (hd_enabled == 0)		return 0;	if (hd_entitled_cores == 0)		return 0;	if (hd_online_cores <= hd_entitled_cores)		return 0;	mod_delayed_work(system_dfl_wq, &hd_capacity_work, HD_DELAY_INTERVAL * hd_delay_factor);	hd_update_capacities();	return 1;} static unsigned long hd_steal_avg(unsigned long new){	static unsigned long steal; 	steal = (steal * (HD_STEAL_AVG_WEIGHT - 1) + new) / HD_STEAL_AVG_WEIGHT;	return steal;} static unsigned long hd_calculate_steal_percentage(void){	unsigned long time_delta, steal_delta, steal, percentage;	static ktime_t prev;	int cpus, cpu;	ktime_t now; 	cpus = 0;	steal = 0;	percentage = 0;	for_each_cpu(cpu, &hd_vmvl_cpumask) {		steal += kcpustat_cpu(cpu).cpustat[CPUTIME_STEAL];		cpus++;	}	/*	 * If there is no vertical medium and low CPUs steal time	 * is 0 as vertical high CPUs shouldn't experience steal time.	 */	if (cpus == 0)		return percentage;	now = ktime_get();	time_delta = ktime_to_ns(ktime_sub(now, prev));	if (steal > hd_previous_steal && hd_previous_steal != 0) {		steal_delta = (steal - hd_previous_steal) * 100 / time_delta;		percentage = steal_delta / cpus;	}	hd_previous_steal = steal;	prev = now;	return percentage;} static void hd_capacity_work_fn(struct work_struct *work){	unsigned long steal_percentage, new_cores; 	mutex_lock(&smp_cpu_state_mutex);	/*	 * If online cores are less or equal to entitled cores hiperdispatch	 * does not need to make any adjustments, call a topology update to	 * disable hiperdispatch.	 * Normally this check is handled on topology update, but during cpu	 * unhotplug, topology and cpu mask updates are done in reverse	 * order, causing hd_enable_hiperdispatch() to get stale data.	 */	if (hd_online_cores <= hd_entitled_cores) {		topology_schedule_update();		mutex_unlock(&smp_cpu_state_mutex);		return;	}	steal_percentage = hd_steal_avg(hd_calculate_steal_percentage());	if (steal_percentage < hd_steal_threshold)		new_cores = hd_online_cores;	else		new_cores = hd_entitled_cores;	if (hd_high_capacity_cores != new_cores) {		trace_s390_hd_rebuild_domains(hd_high_capacity_cores, new_cores);		hd_high_capacity_cores = new_cores;		atomic64_inc(&hd_adjustments);		topology_schedule_update();	}	trace_s390_hd_work_fn(steal_percentage, hd_entitled_cores, hd_high_capacity_cores);	mutex_unlock(&smp_cpu_state_mutex);	schedule_delayed_work(&hd_capacity_work, HD_DELAY_INTERVAL);} static int hiperdispatch_ctl_handler(const struct ctl_table *ctl, int write,				     void *buffer, size_t *lenp, loff_t *ppos){	int hiperdispatch;	int rc;	struct ctl_table ctl_entry = {		.procname	= ctl->procname,		.data		= &hiperdispatch,		.maxlen		= sizeof(int),		.extra1		= SYSCTL_ZERO,		.extra2		= SYSCTL_ONE,	}; 	hiperdispatch = hd_enabled;	rc = proc_douintvec_minmax(&ctl_entry, write, buffer, lenp, ppos);	if (rc < 0 || !write)		return rc;	mutex_lock(&smp_cpu_state_mutex);	if (hd_set_hiperdispatch_mode(hiperdispatch))		topology_schedule_update();	mutex_unlock(&smp_cpu_state_mutex);	return 0;} static const struct ctl_table hiperdispatch_ctl_table[] = {	{		.procname	= "hiperdispatch",		.mode		= 0644,		.proc_handler	= hiperdispatch_ctl_handler,	},}; static ssize_t hd_steal_threshold_show(struct device *dev,				       struct device_attribute *attr,				       char *buf){	return sysfs_emit(buf, "%u\n", hd_steal_threshold);} static ssize_t hd_steal_threshold_store(struct device *dev,					struct device_attribute *attr,					const char *buf,					size_t count){	unsigned int val;	int rc; 	rc = kstrtouint(buf, 0, &val);	if (rc)		return rc;	if (val > 100)		return -ERANGE;	hd_steal_threshold = val;	return count;} static DEVICE_ATTR_RW(hd_steal_threshold); static ssize_t hd_delay_factor_show(struct device *dev,				    struct device_attribute *attr,				    char *buf){	return sysfs_emit(buf, "%u\n", hd_delay_factor);} static ssize_t hd_delay_factor_store(struct device *dev,				     struct device_attribute *attr,				     const char *buf,				     size_t count){	unsigned int val;	int rc; 	rc = kstrtouint(buf, 0, &val);	if (rc)		return rc;	if (!val)		return -ERANGE;	hd_delay_factor = val;	return count;} static DEVICE_ATTR_RW(hd_delay_factor); static struct attribute *hd_attrs[] = {	&dev_attr_hd_steal_threshold.attr,	&dev_attr_hd_delay_factor.attr,	NULL,}; static const struct attribute_group hd_attr_group = {	.name  = "hiperdispatch",	.attrs = hd_attrs,}; static int hd_greedy_time_get(void *unused, u64 *val){	mutex_lock(&hd_counter_mutex);	hd_update_times();	*val = hd_high_time;	mutex_unlock(&hd_counter_mutex);	return 0;} DEFINE_SIMPLE_ATTRIBUTE(hd_greedy_time_fops, hd_greedy_time_get, NULL, "%llu\n"); static int hd_conservative_time_get(void *unused, u64 *val){	mutex_lock(&hd_counter_mutex);	hd_update_times();	*val = hd_low_time;	mutex_unlock(&hd_counter_mutex);	return 0;} DEFINE_SIMPLE_ATTRIBUTE(hd_conservative_time_fops, hd_conservative_time_get, NULL, "%llu\n"); static int hd_adjustment_count_get(void *unused, u64 *val){	*val = atomic64_read(&hd_adjustments);	return 0;} DEFINE_SIMPLE_ATTRIBUTE(hd_adjustments_fops, hd_adjustment_count_get, NULL, "%llu\n"); static void __init hd_create_debugfs_counters(void){	struct dentry *dir; 	dir = debugfs_create_dir("hiperdispatch", arch_debugfs_dir);	debugfs_create_file("conservative_time_ms", 0400, dir, NULL, &hd_conservative_time_fops);	debugfs_create_file("greedy_time_ms", 0400, dir, NULL, &hd_greedy_time_fops);	debugfs_create_file("adjustment_count", 0400, dir, NULL, &hd_adjustments_fops);} static void __init hd_create_attributes(void){	struct device *dev; 	dev = bus_get_dev_root(&cpu_subsys);	if (!dev)		return;	if (sysfs_create_group(&dev->kobj, &hd_attr_group))		pr_warn("Unable to create hiperdispatch attribute group\n");	put_device(dev);} static int __init hd_init(void){	if (IS_ENABLED(CONFIG_HIPERDISPATCH_ON)) {		hd_set_hiperdispatch_mode(1);		topology_schedule_update();	}	if (!register_sysctl("s390", hiperdispatch_ctl_table))		pr_warn("Failed to register s390.hiperdispatch sysctl attribute\n");	hd_create_debugfs_counters();	hd_create_attributes();	return 0;}late_initcall(hd_init);