diff mbox series

[RFC,v4,1/1] selftests/cpuidle: Add support for cpuidle latency measurement

Message ID 20200902114506.45809-2-psampat@linux.ibm.com (mailing list archive)
State New
Headers show
Series Selftest for cpuidle latency measurement | expand

Commit Message

Pratik R. Sampat Sept. 2, 2020, 11:45 a.m. UTC
Measure cpuidle latencies on wakeup to determine and compare with the
advertsied wakeup latencies for each idle state.

Cpuidle wakeup latencies are determined for IPIs and Timer events and
can help determine any deviations from what is advertsied by the
hardware.

A baseline measurement for each case of IPI and timers is taken at
100 percent CPU usage to quantify for the kernel-userpsace overhead
during execution.

Signed-off-by: Pratik Rajesh Sampat <psampat@linux.ibm.com>
---
 tools/testing/selftests/Makefile          |   1 +
 tools/testing/selftests/cpuidle/Makefile  |   7 +
 tools/testing/selftests/cpuidle/cpuidle.c | 616 ++++++++++++++++++++++
 tools/testing/selftests/cpuidle/settings  |   1 +
 4 files changed, 625 insertions(+)
 create mode 100644 tools/testing/selftests/cpuidle/Makefile
 create mode 100644 tools/testing/selftests/cpuidle/cpuidle.c
 create mode 100644 tools/testing/selftests/cpuidle/settings

Comments

Artem Bityutskiy Sept. 2, 2020, 3:25 p.m. UTC | #1
On Wed, 2020-09-02 at 17:15 +0530, Pratik Rajesh Sampat wrote:
> Measure cpuidle latencies on wakeup to determine and compare with the
> advertsied wakeup latencies for each idle state.

It looks like the measurements include more than just C-state wake,
they also include the overhead of waking up the proces, context switch,
and potentially any interrupts that happen on that CPU. I am not saying
this is not interesting data, it surely is, but it is going to be
larger than you see in cpuidle latency tables. Potentially
significantly larger.

Therefore, I am not sure this program should be advertised as "cpuidle
measurement". It really measures the "IPI latency" in case of the IPI
method.

> A baseline measurement for each case of IPI and timers is taken at
> 100 percent CPU usage to quantify for the kernel-userpsace overhead
> during execution.

At least on Intel platforms, this will mean that the IPI method won't
cover deep C-states like, say, PC6, because one CPU is busy. Again, not
saying this is not interesting, just pointing out the limitation.

I was working on a somewhat similar stuff for x86 platforms, and I am
almost ready to publish that on github. I can notify you when I do so
if you are interested. But here is a small presentation of the approach
that I did on Plumbers last year:

https://youtu.be/Opk92aQyvt0?t=8266

(the link points to the start of my talk)
Pratik R. Sampat Sept. 3, 2020, noon UTC | #2
Hello Artem,

On 02/09/20 8:55 pm, Artem Bityutskiy wrote:
> On Wed, 2020-09-02 at 17:15 +0530, Pratik Rajesh Sampat wrote:
>> Measure cpuidle latencies on wakeup to determine and compare with the
>> advertsied wakeup latencies for each idle state.

Thank you for pointing me to your talk. It was very interesting!
I certainly did not know about that the Intel architecture being aware
of timers and pre-wakes the CPUs which makes the timer experiment
observations void.

> It looks like the measurements include more than just C-state wake,
> they also include the overhead of waking up the proces, context switch,
> and potentially any interrupts that happen on that CPU. I am not saying
> this is not interesting data, it surely is, but it is going to be
> larger than you see in cpuidle latency tables. Potentially
> significantly larger.

The measurements will definitely include overhead than just the C-State
wakeup.

However, we are also collecting a baseline measurement wherein we run
the same test on a 100% busy CPU and the measurement of latency from
that could be considered to the kernel-userspace overhead.
The rest of the measurements would be considered keeping this baseline
in mind.

> Therefore, I am not sure this program should be advertised as "cpuidle
> measurement". It really measures the "IPI latency" in case of the IPI
> method.

Now with the new found knowledge of timers in Intel, I understand that
this really only seems to measure IPI latency and not timer latency,
although both the observations shouldn't be too far off anyways.

>> A baseline measurement for each case of IPI and timers is taken at
>> 100 percent CPU usage to quantify for the kernel-userpsace overhead
>> during execution.
> At least on Intel platforms, this will mean that the IPI method won't
> cover deep C-states like, say, PC6, because one CPU is busy. Again, not
> saying this is not interesting, just pointing out the limitation.

That's a valid point. We have similar deep idle states in POWER too.
The idea here is that this test should be run on an already idle
system, of course there will be kernel jitters along the way
which can cause little skewness in observations across some CPUs but I
believe the observations overall should be stable.

Another solution to this could be using isolcpus, but that just
increases the complexity all the more.
If you have any suggestions of any other way that could guarantee
idleness that would be great.

>
> I was working on a somewhat similar stuff for x86 platforms, and I am
> almost ready to publish that on github. I can notify you when I do so
> if you are interested. But here is a small presentation of the approach
> that I did on Plumbers last year:
>
> https://youtu.be/Opk92aQyvt0?t=8266
>
> (the link points to the start of my talk)
>
Sure thing. Do notify me when it comes up.
I would be happy to have a look at it.

--
Thanks!
Pratik
Artem Bityutskiy Sept. 3, 2020, 2:50 p.m. UTC | #3
On Thu, 2020-09-03 at 17:30 +0530, Pratik Sampat wrote:
> I certainly did not know about that the Intel architecture being aware
> of timers and pre-wakes the CPUs which makes the timer experiment
> observations void.

Well, things depend on platform, it is really "void", it is just
different and it measures an optimized case. The result may be smaller
observed latency. And things depend on the platform.

> However, we are also collecting a baseline measurement wherein we run
> the same test on a 100% busy CPU and the measurement of latency from
> that could be considered to the kernel-userspace overhead.
> The rest of the measurements would be considered keeping this baseline
> in mind.

Yes, this should give the idea of the overhead, but still, at least for
many Intel platforms I would not be comfortable using the resulting
number (measured latency - baseline) for a cpuidle driver, because
there are just too many variables there. I am not sure I could assume
the baseline measured this way is an invariant - it could be noticeably
different depending on whether you use C-states or not.

> > At least on Intel platforms, this will mean that the IPI method won't
> > cover deep C-states like, say, PC6, because one CPU is busy. Again, not
> > saying this is not interesting, just pointing out the limitation.
> 
> That's a valid point. We have similar deep idle states in POWER too.
> The idea here is that this test should be run on an already idle
> system, of course there will be kernel jitters along the way
> which can cause little skewness in observations across some CPUs but I
> believe the observations overall should be stable.

If baseline and cpuidle latency are numbers of same order of magnitude,
and you are measuring in a controlled lab system, may be yes. But if
baseline is, say, in milliseconds, and you are measuring a 10
microseconds C-state, then probably no.

> Another solution to this could be using isolcpus, but that just
> increases the complexity all the more.
> If you have any suggestions of any other way that could guarantee
> idleness that would be great.

Well, I did not try to guarantee idleness. I just use timers and
external device (the network card), so no CPUs needs to be busy and the
system can enter deep C-states. Then I just look at median, 99%-th
percentile, etc.

But by all means IPI is also a very interesting experiment. Just covers
a different usage scenario.

When I started experimenting in this area, one of my main early
takeaways was realization that C-state latency really depends on the
event source.

HTH,
Artem.
Artem Bityutskiy Sept. 3, 2020, 4:13 p.m. UTC | #4
On Thu, 2020-09-03 at 17:50 +0300, Artem Bityutskiy wrote:
> Well, things depend on platform, it is really "void", it is just
> different and it measures an optimized case. The result may be smaller
> observed latency.

Sorry, I meant to say it is _not_ really "void".
Pratik R. Sampat Sept. 14, 2020, 6:31 a.m. UTC | #5
On 03/09/20 8:20 pm, Artem Bityutskiy wrote:
> On Thu, 2020-09-03 at 17:30 +0530, Pratik Sampat wrote:
>> I certainly did not know about that the Intel architecture being aware
>> of timers and pre-wakes the CPUs which makes the timer experiment
>> observations void.
> Well, things depend on platform, it is really "void", it is just
> different and it measures an optimized case. The result may be smaller
> observed latency. And things depend on the platform.

Of course, this will be for just software observability and hardware
can be more complex with each architecture behaving differently.

>> However, we are also collecting a baseline measurement wherein we run
>> the same test on a 100% busy CPU and the measurement of latency from
>> that could be considered to the kernel-userspace overhead.
>> The rest of the measurements would be considered keeping this baseline
>> in mind.
> Yes, this should give the idea of the overhead, but still, at least for
> many Intel platforms I would not be comfortable using the resulting
> number (measured latency - baseline) for a cpuidle driver, because
> there are just too many variables there. I am not sure I could assume
> the baseline measured this way is an invariant - it could be noticeably
> different depending on whether you use C-states or not.
>
>>> At least on Intel platforms, this will mean that the IPI method won't
>>> cover deep C-states like, say, PC6, because one CPU is busy. Again, not
>>> saying this is not interesting, just pointing out the limitation.
>> That's a valid point. We have similar deep idle states in POWER too.
>> The idea here is that this test should be run on an already idle
>> system, of course there will be kernel jitters along the way
>> which can cause little skewness in observations across some CPUs but I
>> believe the observations overall should be stable.
> If baseline and cpuidle latency are numbers of same order of magnitude,
> and you are measuring in a controlled lab system, may be yes. But if
> baseline is, say, in milliseconds, and you are measuring a 10
> microseconds C-state, then probably no.

This makes complete sense. The magnitude of deviations being greater
than the scope of the experiment may not be very useful in quantifying
the latency metric.

One way is to minimize the baseline overhead is to make this a kernel
module https://lkml.org/lkml/2020/7/21/567. However, the overhead is
unavoidable but definetly can be further minimized by using an external
approach suggested by you in your LPC talk

>> Another solution to this could be using isolcpus, but that just
>> increases the complexity all the more.
>> If you have any suggestions of any other way that could guarantee
>> idleness that would be great.
> Well, I did not try to guarantee idleness. I just use timers and
> external device (the network card), so no CPUs needs to be busy and the
> system can enter deep C-states. Then I just look at median, 99%-th
> percentile, etc.
>
> But by all means IPI is also a very interesting experiment. Just covers
> a different usage scenario.
>
> When I started experimenting in this area, one of my main early
> takeaways was realization that C-state latency really depends on the
> event source.

That is an interesting observation, on POWER systems where we don't
have timer related wakeup optimizations, the readings from this test do
signify a difference between latencies of IPI versus the latency
gathered after a timer interrupt.

However, these timer based variations weren't as prominent on my Intel
based ThinkPad t480, therefore in confirmation with your observations.

This discussions does help!
Although this approach may not help quantify latency deviations at a
hardware-accurate level but could still be helpful in quantifying this
metric from a software observability point of view.


Thanks!
Pratik
Gautham R Shenoy Sept. 14, 2020, 5:46 p.m. UTC | #6
On Wed, Sep 02, 2020 at 05:15:06PM +0530, Pratik Rajesh Sampat wrote:
> Measure cpuidle latencies on wakeup to determine and compare with the
> advertsied wakeup latencies for each idle state.
> 
> Cpuidle wakeup latencies are determined for IPIs and Timer events and
> can help determine any deviations from what is advertsied by the
> hardware.
> 
> A baseline measurement for each case of IPI and timers is taken at
> 100 percent CPU usage to quantify for the kernel-userpsace overhead
> during execution.
> 
> Signed-off-by: Pratik Rajesh Sampat <psampat@linux.ibm.com>
> ---
>  tools/testing/selftests/Makefile          |   1 +
>  tools/testing/selftests/cpuidle/Makefile  |   7 +
>  tools/testing/selftests/cpuidle/cpuidle.c | 616 ++++++++++++++++++++++
>  tools/testing/selftests/cpuidle/settings  |   1 +
>  4 files changed, 625 insertions(+)
>  create mode 100644 tools/testing/selftests/cpuidle/Makefile
>  create mode 100644 tools/testing/selftests/cpuidle/cpuidle.c
>  create mode 100644 tools/testing/selftests/cpuidle/settings
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 9018f45d631d..2bb0e87f76fd 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -8,6 +8,7 @@ TARGETS += cgroup
>  TARGETS += clone3
>  TARGETS += core
>  TARGETS += cpufreq
> +TARGETS += cpuidle
>  TARGETS += cpu-hotplug
>  TARGETS += drivers/dma-buf
>  TARGETS += efivarfs
> diff --git a/tools/testing/selftests/cpuidle/Makefile b/tools/testing/selftests/cpuidle/Makefile
> new file mode 100644
> index 000000000000..d332485e1bc5
> --- /dev/null
> +++ b/tools/testing/selftests/cpuidle/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +TEST_GEN_PROGS := cpuidle
> +
> +CFLAGS += -O2
> +LDLIBS += -lpthread
> +
> +include ../lib.mk
> diff --git a/tools/testing/selftests/cpuidle/cpuidle.c b/tools/testing/selftests/cpuidle/cpuidle.c
> new file mode 100644
> index 000000000000..4b1e7a91f75c
> --- /dev/null
> +++ b/tools/testing/selftests/cpuidle/cpuidle.c
> @@ -0,0 +1,616 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Cpuidle latency measurement microbenchmark
> + *
> + * A mechanism to measure wakeup latency for IPI and Timer based interrupts
> + * Results of this microbenchmark can be used to check and validate against the
> + * advertised latencies for each cpuidle state
> + *
> + * IPIs (using pipes) and Timers are used to wake the CPU up and measure the
> + * time difference
> + *
> + * Usage:
> + *	./cpuidle --mode <full / quick / num_cpus> --output <output location>
> + *
> + * Copyright (C) 2020 Pratik Rajesh Sampat <psampat@linux.ibm.com>, IBM
> + */
> +
> +#define _GNU_SOURCE
> +#include <assert.h>
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <inttypes.h>
> +#include <limits.h>
> +#include <pthread.h>
> +#include <sched.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/time.h>
> +#include <unistd.h>
> +
> +#define READ		0
> +#define WRITE		1
> +#define TIMEOUT_US	500000
> +
> +static int pipe_fd[2];
> +static int *cpu_list;
> +static int cpus;
> +static int idle_states;
> +static uint64_t *latency_list;
> +static uint64_t *residency_list;
> +
> +static char *log_file = "cpuidle.log";
> +
> +static int get_online_cpus(int *online_cpu_list, int total_cpus)
> +{
> +	char filename[80];
> +	int i, index = 0;
> +	FILE *fptr;
> +
> +	for (i = 0; i < total_cpus; i++) {
> +		char status;
> +
> +		sprintf(filename, "/sys/devices/system/cpu/cpu");
> +		sprintf(filename + strlen(filename), "%d%s", i, "/online");
> +		fptr = fopen(filename, "r");
> +		if (!fptr)
> +			continue;
> +		assert(fscanf(fptr, "%c", &status) != EOF);
> +		if (status == '1')
> +			online_cpu_list[index++] = i;
> +		fclose(fptr);
> +	}
> +	return index;
> +}
> +
> +static uint64_t us_to_ns(uint64_t val)
> +{
> +	return val * 1000;
> +}
> +
> +static void get_latency(int cpu)
> +{
> +	char filename[80];
> +	uint64_t latency;
> +	FILE *fptr;
> +	int state;
> +
> +	for (state = 0; state < idle_states; state++) {
> +		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
> +			cpu, "/cpuidle/state",
> +			state, "/latency");
> +		fptr = fopen(filename, "r");
> +		assert(fptr);
> +
> +		assert(fscanf(fptr, "%ld", &latency) != EOF);
> +		latency_list[state] = latency;
> +		fclose(fptr);
> +	}
> +}
> +
> +static void get_residency(int cpu)
> +{
> +	uint64_t residency;
> +	char filename[80];
> +	FILE *fptr;
> +	int state;
> +
> +	for (state = 0; state < idle_states; state++) {
> +		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
> +			cpu, "/cpuidle/state",
> +			state, "/residency");
> +		fptr = fopen(filename, "r");
> +		assert(fptr);
> +
> +		assert(fscanf(fptr, "%ld", &residency) != EOF);
> +		residency_list[state] = residency;
> +		fclose(fptr);
> +	}
> +}
> +
> +static int get_idle_state_count(int cpu)
> +{
> +	struct dirent *entry;
> +	int dir_count = 0;
> +	char filename[80];
> +	DIR *dirp;
> +
> +	sprintf(filename, "%s%d%s", "/sys/devices/system/cpu/cpu",
> +		cpu, "/cpuidle");
> +
> +	dirp = opendir(filename);
> +	if (!dirp)
> +		return -1;
> +	while (entry = readdir(dirp)) {
> +		if (entry->d_type == DT_DIR) {
> +			if (strcmp(entry->d_name, ".") == 0 ||
> +			    strcmp(entry->d_name, "..") == 0)
> +				continue;
> +			dir_count++;
> +		}
> +	}
> +	closedir(dirp);
> +	return dir_count;
> +}
> +
> +/* Enable or disable all idle states */
> +static int state_all_idle(char *disable)
> +{
> +	char filename[80];
> +	FILE *fptr;
> +	int i, j;
> +
> +	for (i = 0; i < cpus; i++) {
> +		for (j = 0; j < idle_states; j++) {
> +			sprintf(filename, "%s%d%s%d%s",
> +				"/sys/devices/system/cpu/cpu", cpu_list[i],
> +				"/cpuidle/state", j, "/disable");
> +			fptr = fopen(filename, "w");
> +			assert(fptr);
> +			fprintf(fptr, "%s", disable);
> +			fclose(fptr);
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* Disable all idle states */
> +static int cpuidle_disable_all_states(void)
> +{
> +	return state_all_idle("1");
> +}
> +
> +static int cpuidle_enable_all_states(void)
> +{
> +	return state_all_idle("0");
> +}
> +
> +static int state_operation(char *disable, int state)
> +{
> +	char filename[80];
> +	FILE *fptr;
> +	int i;
> +
> +	for (i = 0; i < cpus; i++) {
> +		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
> +			cpu_list[i], "/cpuidle/state", state, "/disable");
> +		fptr = fopen(filename, "w");
> +		assert(fptr);
> +		fprintf(fptr, "%s", disable);
> +		fclose(fptr);
> +	}
> +	return 0;
> +}
> +
> +static int cpuidle_enable_state(int state)
> +{
> +	return state_operation("0", state);
> +}
> +
> +static int cpuidle_disable_state(int state)
> +{
> +	return state_operation("1", state);
> +}
> +
> +static uint64_t average(uint64_t *arr, int size)
> +{
> +	int i, sum = 0;
> +
> +	assert(size != 0);
> +	for (i = 0; i < size; i++)
> +		sum += arr[i];
> +	return sum / size;
> +}
> +
> +static pthread_t start_thread_on(void *(*fn)(void *), void *arg, uint64_t cpu)
> +{
> +	pthread_attr_t attr;
> +	cpu_set_t cpuset;
> +	pthread_t tid;
> +	int rc;
> +
> +	CPU_ZERO(&cpuset);
> +	CPU_SET(cpu, &cpuset);
> +
> +	rc = pthread_attr_init(&attr);
> +	if (rc) {
> +		perror("pthread_attr_init");
> +		exit(1);
> +	}
> +
> +	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
> +	if (rc) {
> +		perror("pthread_attr_setaffinity_np");
> +		exit(1);
> +	}
> +
> +	rc = pthread_create(&tid, &attr, fn, arg);
> +	if (rc) {
> +		perror("pthread_create");
> +		exit(1);
> +	}
> +	return tid;
> +}
> +
> +void *util_full_cpu(void *unused)
> +{
> +	FILE *fptr;
> +
> +	fptr = fopen("/dev/null", "w");
> +	assert(fptr);
> +	while (1) {
> +		fprintf(fptr, "0");
> +		nanosleep((const struct timespec[]){{0, 10L}}, NULL);

It is not clear why the nanosleep is needed. Perhaps you want
the equivalent of cpu_relax() in the linux kernel ?





> +	}
> +	fclose(fptr);
> +}
> +
> +/* IPI based wakeup latencies */
> +struct latency {
> +	unsigned int src_cpu;
> +	unsigned int dest_cpu;
> +	struct timespec time_start;
> +	struct timespec time_end;
> +	uint64_t latency_ns;
> +} ipi_wakeup;
> +
> +static void *writer(void *unused)
> +{
> +	signed char c = 'P';
> +
> +	assert(write(pipe_fd[WRITE], &c, 1) == 1);
> +	ipi_wakeup.src_cpu = sched_getcpu();
> +
> +	return NULL;
> +}
> +
> +static void *reader(void *unused)
> +{
> +	signed char c;
> +
> +	assert(read(pipe_fd[READ], &c, 1) ==  1);
> +	ipi_wakeup.dest_cpu = sched_getcpu();
> +
> +	return NULL;
> +}
> +
> +static void ipi_test_once(int baseline, int src_cpu, int dest_cpu)
> +{
> +	pthread_t tid, tid1, baseline_tid;
> +
> +	if (baseline) {
> +		baseline_tid = start_thread_on(util_full_cpu, NULL, dest_cpu);
> +		/* Run process for long enough to gain 100% usage*/
> +		sleep(2);
> +	}
> +
> +	clock_gettime(CLOCK_REALTIME, &ipi_wakeup.time_start);
> +

If we capture the time_start here, it would also include the time
required to create a thread and run it on src_cpu.

Shouldn't we move clock_gettime(time_start) inside the writer, just
before the write(pipe) and another clock_gettime(time_end) in the
reader soon after the read(pipe) returns ?



> +	tid = start_thread_on(writer, NULL, src_cpu);
> +	pthread_join(tid, NULL);
> +	tid1 = start_thread_on(reader, NULL, dest_cpu);
> +	pthread_join(tid1, NULL);
> +
> +	clock_gettime(CLOCK_REALTIME, &ipi_wakeup.time_end);
> +	ipi_wakeup.latency_ns = (ipi_wakeup.time_end.tv_sec -
> +		ipi_wakeup.time_start.tv_sec) * 1000000000ULL +
> +		ipi_wakeup.time_end.tv_nsec - ipi_wakeup.time_start.tv_nsec;
> +
> +	if (baseline)
> +		pthread_cancel(baseline_tid);
> +}
> +
> +static void ipi_test(int src_cpu)
> +{
> +	uint64_t avg_arr[cpus], avg_latency;
> +	int cpu, state;
> +	FILE *fptr;
> +
> +	assert(cpuidle_disable_all_states() == 0);
> +
> +	if (pipe(pipe_fd))
> +		exit(1);
> +
> +	fptr = fopen(log_file, "a");
> +	fprintf(fptr, "----IPI TEST----\n");
> +
> +	fprintf(fptr, "----Baseline IPI Latency----\n");
> +	fprintf(fptr, "%s %10s %18s\n", "SRC_CPU", "DEST_CPU",
> +		"Baseline_latency(ns)");
> +	/* Run the test as dummy once to stablize */
> +	ipi_test_once(1, src_cpu, cpu_list[0]);
> +	for (cpu = 0; cpu < cpus; cpu++) {
> +		ipi_test_once(1, src_cpu, cpu_list[cpu]);
> +		fprintf(fptr, "%3d %10d %12ld\n", ipi_wakeup.src_cpu,
> +			ipi_wakeup.dest_cpu,
> +			ipi_wakeup.latency_ns);
> +		avg_arr[cpu] = ipi_wakeup.latency_ns;
> +	}
> +	avg_latency = average(avg_arr, cpus);
> +	fprintf(fptr, "Baseline average IPI latency(ns): %ld\n\n", avg_latency);
> +
> +	for (state = 0; state < idle_states; state++) {
> +		fprintf(fptr, "--Enabling state: %d--\n", state);
> +		assert(cpuidle_enable_state(state) == 0);
> +		fprintf(fptr, "%s %10s %18s\n", "SRC_CPU", "DEST_CPU",
> +			"IPI_Latency(ns)");
> +		for (cpu = 0; cpu < cpus; cpu++) {
> +			/* Allow sufficient cycles to go idle */
> +			sleep(1);
> +			ipi_test_once(0, src_cpu, cpu_list[cpu]);
> +			fprintf(fptr, "%3d %10d %18ld\n", ipi_wakeup.src_cpu,
> +				ipi_wakeup.dest_cpu,
> +				ipi_wakeup.latency_ns);
> +			avg_arr[cpu] = ipi_wakeup.latency_ns;
> +		}
> +		fprintf(fptr, "Expected Latency(ns): %ld\n",
> +			us_to_ns(latency_list[state]));
> +		avg_latency = average(avg_arr, cpus);
> +		fprintf(fptr, "Observed Average IPI latency(ns): %ld\n\n",
> +			avg_latency);
> +		assert(cpuidle_disable_state(state) == 0);
> +	}
> +
> +	assert(cpuidle_enable_all_states() == 0);
> +	fclose(fptr);
> +}
> +
> +/* Timer based wakeup latencies */
> +static int soak_done;
> +struct timer_data {
> +	unsigned int src_cpu;
> +	uint64_t timeout;
> +	struct timespec time_start;
> +	struct timespec time_end;
> +	uint64_t timeout_diff_ns;
> +} timer_wakeup;
> +
> +void catch_alarm(int sig)
> +{
> +	soak_done = 1;
> +}
> +
> +static void setup_timer(void)
> +{
> +	struct itimerval timer_settings = {};
> +	int err;
> +
> +	timer_settings.it_value.tv_usec = timer_wakeup.timeout;
> +	err = setitimer(ITIMER_REAL, &timer_settings, NULL);
> +	if (err < 0) {
> +		perror("failed to arm interval timer");
> +		exit(1);
> +	}
> +	signal(SIGALRM, catch_alarm);
> +	while (!soak_done)
> +		sleep(1);
> +}
> +
> +static void *queue_timer(void *timeout)
> +{
> +	timer_wakeup.src_cpu = sched_getcpu();
> +	timer_wakeup.timeout = (uint64_t)timeout;
> +	setup_timer();
> +
> +	return NULL;
> +}
> +
> +static void timeout_test_once(int baseline, uint64_t timeout, int dest_cpu)
> +{
> +	pthread_t tid, baseline_tid;
> +
> +	if (baseline) {
> +		baseline_tid = start_thread_on(util_full_cpu, NULL, dest_cpu);
> +		/* Run process for long enough to gain 100% usage */
> +		sleep(2);
> +	}
> +
> +	clock_gettime(CLOCK_REALTIME, &timer_wakeup.time_start);
> +

Here too, should we move the clock_gettime(time_start) in setup_timer()
just before calling setittimer() and clockgettime(time_end) in
catch_alarm() ?




> +	tid = start_thread_on(queue_timer, (void *)timeout, dest_cpu);
> +	pthread_join(tid, NULL);
> +
> +	clock_gettime(CLOCK_REALTIME, &timer_wakeup.time_end);
> +	timer_wakeup.timeout_diff_ns = (timer_wakeup.time_end.tv_sec -
> +		timer_wakeup.time_start.tv_sec) * 1000000000ULL +
> +		(timer_wakeup.time_end.tv_nsec - timer_wakeup.time_start.tv_nsec);
> +	if (baseline)
> +		pthread_cancel(baseline_tid);
> +}
> +
> +static void timeout_test(unsigned long timeout)
> +{
> +	uint64_t avg_arr[cpus], avg_timeout_diff;
> +	int state, cpu;
> +	FILE *fptr;
> +
> +	assert(cpuidle_disable_all_states() == 0);
> +	fptr = fopen(log_file, "a");
> +	fprintf(fptr, "----TIMEOUT TEST----\n");
> +
> +	fprintf(fptr, "----Baseline Timeout Latency diff----\n");
> +	fprintf(fptr, "%s %10s\n", "SRC_CPU", "Baseline_Latency(ns)");
> +	/* Run the test as dummy once to stablize */
> +	timeout_test_once(1, timeout, cpu_list[0]);
> +	for (cpu = 0; cpu < cpus; cpu++) {
> +		timeout_test_once(1, timeout, cpu_list[cpu]);
> +		fprintf(fptr, "%3d %11ld\n", timer_wakeup.src_cpu,
> +			timer_wakeup.timeout_diff_ns);
> +		avg_arr[cpu] = timer_wakeup.timeout_diff_ns;
> +	}
> +	avg_timeout_diff = average(avg_arr, cpus);
> +	fprintf(fptr, "Baseline Average Timeout diff(ns): %ld\n\n",
> +		avg_timeout_diff);
> +
> +	for (state = 0; state < idle_states; state++) {
> +		fprintf(fptr, "--Enabling state: %d--\n", state);
> +		assert(cpuidle_enable_state(state) == 0);
> +		fprintf(fptr, "%s %11s\n", "SRC_CPU", "Timeout_Latency(ns)");
> +		for (cpu = 0; cpu < cpus; cpu++) {
> +			/* Allow sufficient cycles to go idle */
> +			sleep(1);
> +			timeout_test_once(0, timeout,
> +					  cpu_list[cpu]);
> +			fprintf(fptr, "%3d %15ld\n", timer_wakeup.src_cpu,
> +				timer_wakeup.timeout_diff_ns);
> +			avg_arr[cpu] = timer_wakeup.timeout_diff_ns;
> +		}
> +		fprintf(fptr, "Expected Residency(ns): %ld\n",
> +			us_to_ns(residency_list[state]));
> +		avg_timeout_diff = average(avg_arr, cpus);
> +		fprintf(fptr, "Observed Average Timeout diff(ns): %ld\n\n",
> +			avg_timeout_diff);
> +		assert(cpuidle_disable_state(state) == 0);
> +	}
> +	assert(cpuidle_enable_all_states() == 0);
> +	fclose(fptr);
> +}
> +
> +static struct option options[] = {
> +	{"output", required_argument, 0, 'o'},
> +	{"mode", required_argument, 0, 'm'},
> +};
> +
> +static void usage(void)
> +{
> +	fprintf(stderr, "Usage: cpuidle <options>\n\n");
> +	fprintf(stderr, "\t\t--mode=X\t(quick / full / <num_cpus>)\n");
> +	fprintf(stderr, "\t\t--output=X\tOutput loc (def: cpuidle.log)\n\n");
> +}
> +
> +void get_n_random_cpus(int *shf_list, int shf_size, int *list, int n)
> +{
> +	/* Shuffle the Online CPU list */
> +	int i;
> +	int shuffle_index_list[shf_size];
> +
> +	for (i = 0; i < shf_size; i++)
> +		shuffle_index_list[i] = i;
> +	for (i = 0; i < shf_size; i++) {
> +		int idx = i + rand() % (shf_size - i);
> +		int temp = shuffle_index_list[i];
> +
> +		shuffle_index_list[i] = shuffle_index_list[idx];
> +		shuffle_index_list[idx] = temp;
> +	}
> +
> +	/* Pick the first n from the shf_list elements */
> +	for (i = 0; i < n; i++)
> +		list[i] = shf_list[shuffle_index_list[i]];
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int total_cpus, online_cpus, option_index = 0;
> +	int *online_cpu_list;
> +	signed char c;
> +	FILE *fptr;
> +
> +	if (getuid()) {
> +		fprintf(stderr, "cpuidle latency test must run as root\n");
> +		exit(1);
> +	}
> +
> +	total_cpus = sysconf(_SC_NPROCESSORS_ONLN);
> +	online_cpu_list = malloc(total_cpus * sizeof(int));
> +	if (!online_cpu_list) {
> +		perror("Malloc failure");
> +		exit(1);
> +	}
> +
> +	online_cpus = get_online_cpus(&online_cpu_list[0], total_cpus);
> +	if (!online_cpus) {
> +		perror("Unable to get online CPUS");
> +		exit(1);
> +	}
> +
> +	/* Get one CPU for a quick test */
> +	cpus = 1;
> +	cpu_list = malloc(1 * sizeof(int));
> +	srand(time(NULL));
> +	cpu_list[0] = online_cpu_list[(rand() % online_cpus) + 1];
> +
> +	while (1) {
> +		c = getopt_long(argc, argv, "", options, &option_index);
> +		if (c == -1)
> +			break;
> +
> +		switch (c) {
> +		case 'o':
> +			log_file = optarg;
> +			break;
> +		case 'm':
> +			if (!strcmp(optarg, "full")) {
> +				cpu_list = realloc(cpu_list,
> +						   online_cpus * sizeof(int));
> +				memcpy(cpu_list, online_cpu_list,
> +				       online_cpus * sizeof(int));
> +				cpus = online_cpus;
> +			} else if (strcmp(optarg, "quick")) {
> +				int opt_cpus;
> +
> +				opt_cpus = atoi(optarg);
> +				if (!opt_cpus) {
> +					fprintf(stderr, "Error parsing mode\n");
> +					usage();
> +					exit(1);
> +				}
> +				if (opt_cpus > online_cpus) {
> +					fprintf(stderr, "Number of CPUS > Online CPUs\n");
> +					usage();
> +					exit(1);
> +				}
> +				cpu_list = realloc(cpu_list,
> +						   opt_cpus * sizeof(int));
> +				get_n_random_cpus(online_cpu_list, online_cpus,
> +						  &cpu_list[0], opt_cpus);
> +				cpus = opt_cpus;
> +			}
> +			break;
> +		default:
> +			usage();
> +			exit(1);
> +		}
> +	}
> +
> +	idle_states = get_idle_state_count(online_cpu_list[0]);
> +	if (idle_states == -1) {
> +		perror("Unable to get idle states");
> +		exit(1);
> +	}
> +
> +	fptr = fopen(log_file, "w+");
> +	fprintf(fptr, "cpuidle latency selftests. IPI & Timers\n");
> +	fprintf(fptr, "Number of CPUS: %d\n", total_cpus);
> +	fprintf(fptr, "Number of idle states: %d\n", idle_states);
> +	fclose(fptr);
> +
> +	latency_list = malloc(idle_states * sizeof(uint64_t));
> +	residency_list = malloc(idle_states * sizeof(uint64_t));
> +	if (!latency_list || !residency_list) {
> +		perror("Malloc failure");
> +		exit(1);
> +	}
> +
> +	get_latency(online_cpu_list[0]);
> +	get_residency(online_cpu_list[0]);
> +
> +	ipi_test(online_cpu_list[0]);
> +	printf("IPI test done\n");
> +	fflush(stdout);
> +
> +	timeout_test(TIMEOUT_US);
> +	printf("Timeout test done\n");
> +	fflush(stdout);
> +
> +	printf("Output logged at: %s\n", log_file);
> +
> +	free(latency_list);
> +	free(residency_list);
> +	free(cpu_list);
> +	free(online_cpu_list);
> +	return 0;
> +}
> diff --git a/tools/testing/selftests/cpuidle/settings b/tools/testing/selftests/cpuidle/settings
> new file mode 100644
> index 000000000000..e7b9417537fb
> --- /dev/null
> +++ b/tools/testing/selftests/cpuidle/settings
> @@ -0,0 +1 @@
> +timeout=0
> -- 
> 2.26.2
>
diff mbox series

Patch

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 9018f45d631d..2bb0e87f76fd 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -8,6 +8,7 @@  TARGETS += cgroup
 TARGETS += clone3
 TARGETS += core
 TARGETS += cpufreq
+TARGETS += cpuidle
 TARGETS += cpu-hotplug
 TARGETS += drivers/dma-buf
 TARGETS += efivarfs
diff --git a/tools/testing/selftests/cpuidle/Makefile b/tools/testing/selftests/cpuidle/Makefile
new file mode 100644
index 000000000000..d332485e1bc5
--- /dev/null
+++ b/tools/testing/selftests/cpuidle/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0
+TEST_GEN_PROGS := cpuidle
+
+CFLAGS += -O2
+LDLIBS += -lpthread
+
+include ../lib.mk
diff --git a/tools/testing/selftests/cpuidle/cpuidle.c b/tools/testing/selftests/cpuidle/cpuidle.c
new file mode 100644
index 000000000000..4b1e7a91f75c
--- /dev/null
+++ b/tools/testing/selftests/cpuidle/cpuidle.c
@@ -0,0 +1,616 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Cpuidle latency measurement microbenchmark
+ *
+ * A mechanism to measure wakeup latency for IPI and Timer based interrupts
+ * Results of this microbenchmark can be used to check and validate against the
+ * advertised latencies for each cpuidle state
+ *
+ * IPIs (using pipes) and Timers are used to wake the CPU up and measure the
+ * time difference
+ *
+ * Usage:
+ *	./cpuidle --mode <full / quick / num_cpus> --output <output location>
+ *
+ * Copyright (C) 2020 Pratik Rajesh Sampat <psampat@linux.ibm.com>, IBM
+ */
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define READ		0
+#define WRITE		1
+#define TIMEOUT_US	500000
+
+static int pipe_fd[2];
+static int *cpu_list;
+static int cpus;
+static int idle_states;
+static uint64_t *latency_list;
+static uint64_t *residency_list;
+
+static char *log_file = "cpuidle.log";
+
+static int get_online_cpus(int *online_cpu_list, int total_cpus)
+{
+	char filename[80];
+	int i, index = 0;
+	FILE *fptr;
+
+	for (i = 0; i < total_cpus; i++) {
+		char status;
+
+		sprintf(filename, "/sys/devices/system/cpu/cpu");
+		sprintf(filename + strlen(filename), "%d%s", i, "/online");
+		fptr = fopen(filename, "r");
+		if (!fptr)
+			continue;
+		assert(fscanf(fptr, "%c", &status) != EOF);
+		if (status == '1')
+			online_cpu_list[index++] = i;
+		fclose(fptr);
+	}
+	return index;
+}
+
+static uint64_t us_to_ns(uint64_t val)
+{
+	return val * 1000;
+}
+
+static void get_latency(int cpu)
+{
+	char filename[80];
+	uint64_t latency;
+	FILE *fptr;
+	int state;
+
+	for (state = 0; state < idle_states; state++) {
+		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
+			cpu, "/cpuidle/state",
+			state, "/latency");
+		fptr = fopen(filename, "r");
+		assert(fptr);
+
+		assert(fscanf(fptr, "%ld", &latency) != EOF);
+		latency_list[state] = latency;
+		fclose(fptr);
+	}
+}
+
+static void get_residency(int cpu)
+{
+	uint64_t residency;
+	char filename[80];
+	FILE *fptr;
+	int state;
+
+	for (state = 0; state < idle_states; state++) {
+		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
+			cpu, "/cpuidle/state",
+			state, "/residency");
+		fptr = fopen(filename, "r");
+		assert(fptr);
+
+		assert(fscanf(fptr, "%ld", &residency) != EOF);
+		residency_list[state] = residency;
+		fclose(fptr);
+	}
+}
+
+static int get_idle_state_count(int cpu)
+{
+	struct dirent *entry;
+	int dir_count = 0;
+	char filename[80];
+	DIR *dirp;
+
+	sprintf(filename, "%s%d%s", "/sys/devices/system/cpu/cpu",
+		cpu, "/cpuidle");
+
+	dirp = opendir(filename);
+	if (!dirp)
+		return -1;
+	while (entry = readdir(dirp)) {
+		if (entry->d_type == DT_DIR) {
+			if (strcmp(entry->d_name, ".") == 0 ||
+			    strcmp(entry->d_name, "..") == 0)
+				continue;
+			dir_count++;
+		}
+	}
+	closedir(dirp);
+	return dir_count;
+}
+
+/* Enable or disable all idle states */
+static int state_all_idle(char *disable)
+{
+	char filename[80];
+	FILE *fptr;
+	int i, j;
+
+	for (i = 0; i < cpus; i++) {
+		for (j = 0; j < idle_states; j++) {
+			sprintf(filename, "%s%d%s%d%s",
+				"/sys/devices/system/cpu/cpu", cpu_list[i],
+				"/cpuidle/state", j, "/disable");
+			fptr = fopen(filename, "w");
+			assert(fptr);
+			fprintf(fptr, "%s", disable);
+			fclose(fptr);
+		}
+	}
+	return 0;
+}
+
+/* Disable all idle states */
+static int cpuidle_disable_all_states(void)
+{
+	return state_all_idle("1");
+}
+
+static int cpuidle_enable_all_states(void)
+{
+	return state_all_idle("0");
+}
+
+static int state_operation(char *disable, int state)
+{
+	char filename[80];
+	FILE *fptr;
+	int i;
+
+	for (i = 0; i < cpus; i++) {
+		sprintf(filename, "%s%d%s%d%s", "/sys/devices/system/cpu/cpu",
+			cpu_list[i], "/cpuidle/state", state, "/disable");
+		fptr = fopen(filename, "w");
+		assert(fptr);
+		fprintf(fptr, "%s", disable);
+		fclose(fptr);
+	}
+	return 0;
+}
+
+static int cpuidle_enable_state(int state)
+{
+	return state_operation("0", state);
+}
+
+static int cpuidle_disable_state(int state)
+{
+	return state_operation("1", state);
+}
+
+static uint64_t average(uint64_t *arr, int size)
+{
+	int i, sum = 0;
+
+	assert(size != 0);
+	for (i = 0; i < size; i++)
+		sum += arr[i];
+	return sum / size;
+}
+
+static pthread_t start_thread_on(void *(*fn)(void *), void *arg, uint64_t cpu)
+{
+	pthread_attr_t attr;
+	cpu_set_t cpuset;
+	pthread_t tid;
+	int rc;
+
+	CPU_ZERO(&cpuset);
+	CPU_SET(cpu, &cpuset);
+
+	rc = pthread_attr_init(&attr);
+	if (rc) {
+		perror("pthread_attr_init");
+		exit(1);
+	}
+
+	rc = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
+	if (rc) {
+		perror("pthread_attr_setaffinity_np");
+		exit(1);
+	}
+
+	rc = pthread_create(&tid, &attr, fn, arg);
+	if (rc) {
+		perror("pthread_create");
+		exit(1);
+	}
+	return tid;
+}
+
+void *util_full_cpu(void *unused)
+{
+	FILE *fptr;
+
+	fptr = fopen("/dev/null", "w");
+	assert(fptr);
+	while (1) {
+		fprintf(fptr, "0");
+		nanosleep((const struct timespec[]){{0, 10L}}, NULL);
+	}
+	fclose(fptr);
+}
+
+/* IPI based wakeup latencies */
+struct latency {
+	unsigned int src_cpu;
+	unsigned int dest_cpu;
+	struct timespec time_start;
+	struct timespec time_end;
+	uint64_t latency_ns;
+} ipi_wakeup;
+
+static void *writer(void *unused)
+{
+	signed char c = 'P';
+
+	assert(write(pipe_fd[WRITE], &c, 1) == 1);
+	ipi_wakeup.src_cpu = sched_getcpu();
+
+	return NULL;
+}
+
+static void *reader(void *unused)
+{
+	signed char c;
+
+	assert(read(pipe_fd[READ], &c, 1) ==  1);
+	ipi_wakeup.dest_cpu = sched_getcpu();
+
+	return NULL;
+}
+
+static void ipi_test_once(int baseline, int src_cpu, int dest_cpu)
+{
+	pthread_t tid, tid1, baseline_tid;
+
+	if (baseline) {
+		baseline_tid = start_thread_on(util_full_cpu, NULL, dest_cpu);
+		/* Run process for long enough to gain 100% usage*/
+		sleep(2);
+	}
+
+	clock_gettime(CLOCK_REALTIME, &ipi_wakeup.time_start);
+
+	tid = start_thread_on(writer, NULL, src_cpu);
+	pthread_join(tid, NULL);
+	tid1 = start_thread_on(reader, NULL, dest_cpu);
+	pthread_join(tid1, NULL);
+
+	clock_gettime(CLOCK_REALTIME, &ipi_wakeup.time_end);
+	ipi_wakeup.latency_ns = (ipi_wakeup.time_end.tv_sec -
+		ipi_wakeup.time_start.tv_sec) * 1000000000ULL +
+		ipi_wakeup.time_end.tv_nsec - ipi_wakeup.time_start.tv_nsec;
+
+	if (baseline)
+		pthread_cancel(baseline_tid);
+}
+
+static void ipi_test(int src_cpu)
+{
+	uint64_t avg_arr[cpus], avg_latency;
+	int cpu, state;
+	FILE *fptr;
+
+	assert(cpuidle_disable_all_states() == 0);
+
+	if (pipe(pipe_fd))
+		exit(1);
+
+	fptr = fopen(log_file, "a");
+	fprintf(fptr, "----IPI TEST----\n");
+
+	fprintf(fptr, "----Baseline IPI Latency----\n");
+	fprintf(fptr, "%s %10s %18s\n", "SRC_CPU", "DEST_CPU",
+		"Baseline_latency(ns)");
+	/* Run the test as dummy once to stablize */
+	ipi_test_once(1, src_cpu, cpu_list[0]);
+	for (cpu = 0; cpu < cpus; cpu++) {
+		ipi_test_once(1, src_cpu, cpu_list[cpu]);
+		fprintf(fptr, "%3d %10d %12ld\n", ipi_wakeup.src_cpu,
+			ipi_wakeup.dest_cpu,
+			ipi_wakeup.latency_ns);
+		avg_arr[cpu] = ipi_wakeup.latency_ns;
+	}
+	avg_latency = average(avg_arr, cpus);
+	fprintf(fptr, "Baseline average IPI latency(ns): %ld\n\n", avg_latency);
+
+	for (state = 0; state < idle_states; state++) {
+		fprintf(fptr, "--Enabling state: %d--\n", state);
+		assert(cpuidle_enable_state(state) == 0);
+		fprintf(fptr, "%s %10s %18s\n", "SRC_CPU", "DEST_CPU",
+			"IPI_Latency(ns)");
+		for (cpu = 0; cpu < cpus; cpu++) {
+			/* Allow sufficient cycles to go idle */
+			sleep(1);
+			ipi_test_once(0, src_cpu, cpu_list[cpu]);
+			fprintf(fptr, "%3d %10d %18ld\n", ipi_wakeup.src_cpu,
+				ipi_wakeup.dest_cpu,
+				ipi_wakeup.latency_ns);
+			avg_arr[cpu] = ipi_wakeup.latency_ns;
+		}
+		fprintf(fptr, "Expected Latency(ns): %ld\n",
+			us_to_ns(latency_list[state]));
+		avg_latency = average(avg_arr, cpus);
+		fprintf(fptr, "Observed Average IPI latency(ns): %ld\n\n",
+			avg_latency);
+		assert(cpuidle_disable_state(state) == 0);
+	}
+
+	assert(cpuidle_enable_all_states() == 0);
+	fclose(fptr);
+}
+
+/* Timer based wakeup latencies */
+static int soak_done;
+struct timer_data {
+	unsigned int src_cpu;
+	uint64_t timeout;
+	struct timespec time_start;
+	struct timespec time_end;
+	uint64_t timeout_diff_ns;
+} timer_wakeup;
+
+void catch_alarm(int sig)
+{
+	soak_done = 1;
+}
+
+static void setup_timer(void)
+{
+	struct itimerval timer_settings = {};
+	int err;
+
+	timer_settings.it_value.tv_usec = timer_wakeup.timeout;
+	err = setitimer(ITIMER_REAL, &timer_settings, NULL);
+	if (err < 0) {
+		perror("failed to arm interval timer");
+		exit(1);
+	}
+	signal(SIGALRM, catch_alarm);
+	while (!soak_done)
+		sleep(1);
+}
+
+static void *queue_timer(void *timeout)
+{
+	timer_wakeup.src_cpu = sched_getcpu();
+	timer_wakeup.timeout = (uint64_t)timeout;
+	setup_timer();
+
+	return NULL;
+}
+
+static void timeout_test_once(int baseline, uint64_t timeout, int dest_cpu)
+{
+	pthread_t tid, baseline_tid;
+
+	if (baseline) {
+		baseline_tid = start_thread_on(util_full_cpu, NULL, dest_cpu);
+		/* Run process for long enough to gain 100% usage */
+		sleep(2);
+	}
+
+	clock_gettime(CLOCK_REALTIME, &timer_wakeup.time_start);
+
+	tid = start_thread_on(queue_timer, (void *)timeout, dest_cpu);
+	pthread_join(tid, NULL);
+
+	clock_gettime(CLOCK_REALTIME, &timer_wakeup.time_end);
+	timer_wakeup.timeout_diff_ns = (timer_wakeup.time_end.tv_sec -
+		timer_wakeup.time_start.tv_sec) * 1000000000ULL +
+		(timer_wakeup.time_end.tv_nsec - timer_wakeup.time_start.tv_nsec);
+	if (baseline)
+		pthread_cancel(baseline_tid);
+}
+
+static void timeout_test(unsigned long timeout)
+{
+	uint64_t avg_arr[cpus], avg_timeout_diff;
+	int state, cpu;
+	FILE *fptr;
+
+	assert(cpuidle_disable_all_states() == 0);
+	fptr = fopen(log_file, "a");
+	fprintf(fptr, "----TIMEOUT TEST----\n");
+
+	fprintf(fptr, "----Baseline Timeout Latency diff----\n");
+	fprintf(fptr, "%s %10s\n", "SRC_CPU", "Baseline_Latency(ns)");
+	/* Run the test as dummy once to stablize */
+	timeout_test_once(1, timeout, cpu_list[0]);
+	for (cpu = 0; cpu < cpus; cpu++) {
+		timeout_test_once(1, timeout, cpu_list[cpu]);
+		fprintf(fptr, "%3d %11ld\n", timer_wakeup.src_cpu,
+			timer_wakeup.timeout_diff_ns);
+		avg_arr[cpu] = timer_wakeup.timeout_diff_ns;
+	}
+	avg_timeout_diff = average(avg_arr, cpus);
+	fprintf(fptr, "Baseline Average Timeout diff(ns): %ld\n\n",
+		avg_timeout_diff);
+
+	for (state = 0; state < idle_states; state++) {
+		fprintf(fptr, "--Enabling state: %d--\n", state);
+		assert(cpuidle_enable_state(state) == 0);
+		fprintf(fptr, "%s %11s\n", "SRC_CPU", "Timeout_Latency(ns)");
+		for (cpu = 0; cpu < cpus; cpu++) {
+			/* Allow sufficient cycles to go idle */
+			sleep(1);
+			timeout_test_once(0, timeout,
+					  cpu_list[cpu]);
+			fprintf(fptr, "%3d %15ld\n", timer_wakeup.src_cpu,
+				timer_wakeup.timeout_diff_ns);
+			avg_arr[cpu] = timer_wakeup.timeout_diff_ns;
+		}
+		fprintf(fptr, "Expected Residency(ns): %ld\n",
+			us_to_ns(residency_list[state]));
+		avg_timeout_diff = average(avg_arr, cpus);
+		fprintf(fptr, "Observed Average Timeout diff(ns): %ld\n\n",
+			avg_timeout_diff);
+		assert(cpuidle_disable_state(state) == 0);
+	}
+	assert(cpuidle_enable_all_states() == 0);
+	fclose(fptr);
+}
+
+static struct option options[] = {
+	{"output", required_argument, 0, 'o'},
+	{"mode", required_argument, 0, 'm'},
+};
+
+static void usage(void)
+{
+	fprintf(stderr, "Usage: cpuidle <options>\n\n");
+	fprintf(stderr, "\t\t--mode=X\t(quick / full / <num_cpus>)\n");
+	fprintf(stderr, "\t\t--output=X\tOutput loc (def: cpuidle.log)\n\n");
+}
+
+void get_n_random_cpus(int *shf_list, int shf_size, int *list, int n)
+{
+	/* Shuffle the Online CPU list */
+	int i;
+	int shuffle_index_list[shf_size];
+
+	for (i = 0; i < shf_size; i++)
+		shuffle_index_list[i] = i;
+	for (i = 0; i < shf_size; i++) {
+		int idx = i + rand() % (shf_size - i);
+		int temp = shuffle_index_list[i];
+
+		shuffle_index_list[i] = shuffle_index_list[idx];
+		shuffle_index_list[idx] = temp;
+	}
+
+	/* Pick the first n from the shf_list elements */
+	for (i = 0; i < n; i++)
+		list[i] = shf_list[shuffle_index_list[i]];
+}
+
+int main(int argc, char *argv[])
+{
+	int total_cpus, online_cpus, option_index = 0;
+	int *online_cpu_list;
+	signed char c;
+	FILE *fptr;
+
+	if (getuid()) {
+		fprintf(stderr, "cpuidle latency test must run as root\n");
+		exit(1);
+	}
+
+	total_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+	online_cpu_list = malloc(total_cpus * sizeof(int));
+	if (!online_cpu_list) {
+		perror("Malloc failure");
+		exit(1);
+	}
+
+	online_cpus = get_online_cpus(&online_cpu_list[0], total_cpus);
+	if (!online_cpus) {
+		perror("Unable to get online CPUS");
+		exit(1);
+	}
+
+	/* Get one CPU for a quick test */
+	cpus = 1;
+	cpu_list = malloc(1 * sizeof(int));
+	srand(time(NULL));
+	cpu_list[0] = online_cpu_list[(rand() % online_cpus) + 1];
+
+	while (1) {
+		c = getopt_long(argc, argv, "", options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'o':
+			log_file = optarg;
+			break;
+		case 'm':
+			if (!strcmp(optarg, "full")) {
+				cpu_list = realloc(cpu_list,
+						   online_cpus * sizeof(int));
+				memcpy(cpu_list, online_cpu_list,
+				       online_cpus * sizeof(int));
+				cpus = online_cpus;
+			} else if (strcmp(optarg, "quick")) {
+				int opt_cpus;
+
+				opt_cpus = atoi(optarg);
+				if (!opt_cpus) {
+					fprintf(stderr, "Error parsing mode\n");
+					usage();
+					exit(1);
+				}
+				if (opt_cpus > online_cpus) {
+					fprintf(stderr, "Number of CPUS > Online CPUs\n");
+					usage();
+					exit(1);
+				}
+				cpu_list = realloc(cpu_list,
+						   opt_cpus * sizeof(int));
+				get_n_random_cpus(online_cpu_list, online_cpus,
+						  &cpu_list[0], opt_cpus);
+				cpus = opt_cpus;
+			}
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	idle_states = get_idle_state_count(online_cpu_list[0]);
+	if (idle_states == -1) {
+		perror("Unable to get idle states");
+		exit(1);
+	}
+
+	fptr = fopen(log_file, "w+");
+	fprintf(fptr, "cpuidle latency selftests. IPI & Timers\n");
+	fprintf(fptr, "Number of CPUS: %d\n", total_cpus);
+	fprintf(fptr, "Number of idle states: %d\n", idle_states);
+	fclose(fptr);
+
+	latency_list = malloc(idle_states * sizeof(uint64_t));
+	residency_list = malloc(idle_states * sizeof(uint64_t));
+	if (!latency_list || !residency_list) {
+		perror("Malloc failure");
+		exit(1);
+	}
+
+	get_latency(online_cpu_list[0]);
+	get_residency(online_cpu_list[0]);
+
+	ipi_test(online_cpu_list[0]);
+	printf("IPI test done\n");
+	fflush(stdout);
+
+	timeout_test(TIMEOUT_US);
+	printf("Timeout test done\n");
+	fflush(stdout);
+
+	printf("Output logged at: %s\n", log_file);
+
+	free(latency_list);
+	free(residency_list);
+	free(cpu_list);
+	free(online_cpu_list);
+	return 0;
+}
diff --git a/tools/testing/selftests/cpuidle/settings b/tools/testing/selftests/cpuidle/settings
new file mode 100644
index 000000000000..e7b9417537fb
--- /dev/null
+++ b/tools/testing/selftests/cpuidle/settings
@@ -0,0 +1 @@ 
+timeout=0