1 /*
2  * Copyright (C) 2016-2021 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * This code is a complete clean re-write of the stress tool by
19  * Colin Ian King <colin.king@canonical.com> and attempts to be
20  * backwardly compatible with the stress tool by Amos Waterland
21  * <apw@rossby.metr.ou.edu> but has more stress tests and more
22  * functionality.
23  *
24  */
25 #include "stress-ng.h"
26 
27 static const stress_help_t help[] = {
28 	{ NULL,	"cpu-online N",		"start N workers offlining/onlining the CPUs" },
29 	{ NULL,	"cpu-online-ops N",	"stop after N offline/online operations" },
30 	{ NULL,	NULL,			NULL }
31 };
32 
33 #define STRESS_CPU_ONLINE_MAX_CPUS	(65536)
34 
35 #if defined(__linux__)
36 
37 /*
38  *  stress_cpu_online_set()
39  *	set a specified CPUs online or offline
40  */
stress_cpu_online_set(const stress_args_t * args,const uint32_t cpu,const int setting)41 static int stress_cpu_online_set(
42 	const stress_args_t *args,
43 	const uint32_t cpu,
44 	const int setting)
45 {
46 	char filename[PATH_MAX];
47 	char data[3] = { '0' + (char)setting, '\n', 0 };
48 	ssize_t ret;
49 
50 	(void)snprintf(filename, sizeof(filename),
51 		"/sys/devices/system/cpu/cpu%" PRIu32 "/online", cpu);
52 
53 	ret = system_write(filename, data, sizeof data);
54 	if ((ret < 0) &&
55 	    ((ret != -EAGAIN) && (ret != -EINTR) &&
56 	     (ret != -EBUSY) && (ret != -EOPNOTSUPP))) {
57 		pr_fail("%s: write failed, errno=%d (%s)\n",
58 			args->name, errno, strerror(errno));
59 		return EXIT_FAILURE;
60 	}
61 	return EXIT_SUCCESS;
62 }
63 
64 /*
65  *  stress_cpu_online_supported()
66  *      check if we can run this as root
67  */
stress_cpu_online_supported(const char * name)68 static int stress_cpu_online_supported(const char *name)
69 {
70 	ssize_t ret;
71 
72 	if (geteuid() != 0) {
73 		pr_inf_skip("%s stressor will be skipped, "
74 		       "need to be running as root for this stressor\n", name);
75 		return -1;
76 	}
77 
78 	ret = system_write("/sys/devices/system/cpu/cpu1/online", "1\n", 2);
79 	if (ret < 0) {
80 		pr_inf_skip("%s stressor will be skipped, "
81 		       "cannot write to cpu1 online sysfs control file\n", name);
82 		return -1;
83 	}
84 
85 	return 0;
86 }
87 
88 /*
89  *  stress_cpu_online
90  *	stress twiddling CPUs online/offline
91  */
stress_cpu_online(const stress_args_t * args)92 static int stress_cpu_online(const stress_args_t *args)
93 {
94 	int32_t cpus = stress_get_processors_configured();
95 	int32_t i, cpu_online_count = 0;
96 	bool *cpu_online;
97 	int rc = EXIT_SUCCESS;
98 
99 	if (geteuid() != 0) {
100 		if (args->instance == 0)
101 			pr_inf("%s: need root privilege to run "
102 				"this stressor\n", args->name);
103 		/* Not strictly a test failure */
104 		return EXIT_SUCCESS;
105 	}
106 
107 	if (cpus < 1) {
108 		pr_fail("%s: too few CPUs (detected %" PRId32 ")\n",
109 			args->name, cpus);
110 		return EXIT_FAILURE;
111 	}
112 	if (cpus > STRESS_CPU_ONLINE_MAX_CPUS) {
113 		pr_inf("%s: more than %" PRId32 " CPUs detected, "
114 			"limiting to %d\n",
115 			args->name, cpus, STRESS_CPU_ONLINE_MAX_CPUS);
116 		cpus = STRESS_CPU_ONLINE_MAX_CPUS;
117 	}
118 
119 	cpu_online = calloc((size_t)cpus, sizeof(*cpu_online));
120 	if (!cpu_online) {
121 		pr_err("%s: out of memory\n", args->name);
122 		return EXIT_NO_RESOURCE;
123 	}
124 
125 	/*
126 	 *  Determine how many CPUs we can online/offline via
127 	 *  the online sys interface
128 	 */
129 	for (i = 0; i < cpus; i++) {
130 		char filename[PATH_MAX];
131 		int ret;
132 
133 		(void)snprintf(filename, sizeof(filename),
134 			"/sys/devices/system/cpu/cpu%" PRId32 "/online", i);
135 		ret = access(filename, O_RDWR);
136 		if (ret == 0) {
137 			cpu_online[i] = true;
138 			cpu_online_count++;
139 		}
140 	}
141 	if (cpu_online_count == 0) {
142 		pr_inf("%s: no CPUs can be set online/offline\n", args->name);
143 		free(cpu_online);
144 		return EXIT_FAILURE;
145 	}
146 	if ((args->num_instances > 1) &&
147 	    (g_opt_flags & OPT_FLAGS_CPU_ONLINE_ALL)) {
148 		if (args->instance == 0) {
149 			pr_inf("%s: disabling --cpu-online-all option because "
150 			       "more than 1 %s stressor is being invoked\n",
151 				args->name, args->name);
152 		}
153 		g_opt_flags &= ~OPT_FLAGS_CPU_ONLINE_ALL;
154 	}
155 
156 	if ((args->instance == 0) &&
157 	    (g_opt_flags & OPT_FLAGS_CPU_ONLINE_ALL)) {
158 		pr_inf("%s: exercising all %" PRId32 " cpus\n",
159 			args->name, cpu_online_count + 1);
160 	}
161 
162 	stress_set_proc_state(args->name, STRESS_STATE_RUN);
163 
164 	/*
165 	 *  Now randomly offline/online them all
166 	 */
167 	do {
168 		uint32_t cpu = stress_mwc32() % (uint32_t)cpus;
169 
170 		/*
171 		 * Only allow CPU 0 to be offlined if OPT_FLAGS_CPU_ONLINE_ALL
172 		 * --cpu-online-all has been enabled
173 		 */
174 		if ((cpu == 0) && !(g_opt_flags & OPT_FLAGS_CPU_ONLINE_ALL))
175 			continue;
176 		if (cpu_online[cpu]) {
177 			rc = stress_cpu_online_set(args, cpu, 0);
178 			if (rc != EXIT_SUCCESS)
179 				break;
180 			rc = stress_cpu_online_set(args, cpu, 1);
181 			if (rc != EXIT_SUCCESS)
182 				break;
183 			inc_counter(args);
184 		}
185 	} while (keep_stressing(args));
186 
187 	stress_set_proc_state(args->name, STRESS_STATE_DEINIT);
188 
189 	/*
190 	 *  Force CPUs all back online
191 	 */
192 	for (i = 0; i < cpus; i++) {
193 		if (cpu_online[i])
194 			(void)stress_cpu_online_set(args, (uint32_t)i, 1);
195 	}
196 	free(cpu_online);
197 
198 	return rc;
199 }
200 
201 stressor_info_t stress_cpu_online_info = {
202 	.stressor = stress_cpu_online,
203 	.supported = stress_cpu_online_supported,
204 	.class = CLASS_CPU | CLASS_OS | CLASS_PATHOLOGICAL,
205 	.help = help
206 };
207 #else
208 stressor_info_t stress_cpu_online_info = {
209 	.stressor = stress_not_implemented,
210 	.class = CLASS_CPU | CLASS_OS | CLASS_PATHOLOGICAL,
211 	.help = help
212 };
213 #endif
214