1 /* 2 * Migration stress workload 3 * 4 * Copyright (c) 2016 Red Hat, Inc. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "qemu/osdep.h" 21 #include <getopt.h> 22 #include <sys/reboot.h> 23 #include <sys/syscall.h> 24 #include <linux/random.h> 25 #include <pthread.h> 26 #include <sys/mount.h> 27 28 const char *argv0; 29 30 #define RAM_PAGE_SIZE 4096 31 32 #ifndef CONFIG_GETTID 33 static int gettid(void) 34 { 35 return syscall(SYS_gettid); 36 } 37 #endif 38 39 static __attribute__((noreturn)) void exit_failure(void) 40 { 41 if (getpid() == 1) { 42 sync(); 43 reboot(RB_POWER_OFF); 44 fprintf(stderr, "%s (%05d): ERROR: cannot reboot: %s\n", 45 argv0, gettid(), strerror(errno)); 46 abort(); 47 } else { 48 exit(1); 49 } 50 } 51 52 static int get_command_arg_str(const char *name, 53 char **val) 54 { 55 static char line[1024]; 56 FILE *fp = fopen("/proc/cmdline", "r"); 57 char *start, *end; 58 59 if (fp == NULL) { 60 fprintf(stderr, "%s (%05d): ERROR: cannot open /proc/cmdline: %s\n", 61 argv0, gettid(), strerror(errno)); 62 return -1; 63 } 64 65 if (!fgets(line, sizeof line, fp)) { 66 fprintf(stderr, "%s (%05d): ERROR: cannot read /proc/cmdline: %s\n", 67 argv0, gettid(), strerror(errno)); 68 fclose(fp); 69 return -1; 70 } 71 fclose(fp); 72 73 start = strstr(line, name); 74 if (!start) 75 return 0; 76 77 start += strlen(name); 78 79 if (*start != '=') { 80 fprintf(stderr, "%s (%05d): ERROR: no value provided for '%s' in /proc/cmdline\n", 81 argv0, gettid(), name); 82 } 83 start++; 84 85 end = strstr(start, " "); 86 if (!end) 87 end = strstr(start, "\n"); 88 89 if (end == start) { 90 fprintf(stderr, "%s (%05d): ERROR: no value provided for '%s' in /proc/cmdline\n", 91 argv0, gettid(), name); 92 return -1; 93 } 94 95 if (end) 96 *val = g_strndup(start, end - start); 97 else 98 *val = g_strdup(start); 99 return 1; 100 } 101 102 103 static int get_command_arg_ull(const char *name, 104 unsigned long long *val) 105 { 106 char *valstr; 107 char *end; 108 109 int ret = get_command_arg_str(name, &valstr); 110 if (ret <= 0) 111 return ret; 112 113 errno = 0; 114 *val = strtoll(valstr, &end, 10); 115 if (errno || *end) { 116 fprintf(stderr, "%s (%05d): ERROR: cannot parse %s value %s\n", 117 argv0, gettid(), name, valstr); 118 g_free(valstr); 119 return -1; 120 } 121 g_free(valstr); 122 return 0; 123 } 124 125 126 static int random_bytes(char *buf, size_t len) 127 { 128 int fd; 129 130 fd = open("/dev/urandom", O_RDONLY); 131 if (fd < 0) { 132 fprintf(stderr, "%s (%05d): ERROR: cannot open /dev/urandom: %s\n", 133 argv0, gettid(), strerror(errno)); 134 return -1; 135 } 136 137 if (read(fd, buf, len) != len) { 138 fprintf(stderr, "%s (%05d): ERROR: cannot read /dev/urandom: %s\n", 139 argv0, gettid(), strerror(errno)); 140 close(fd); 141 return -1; 142 } 143 144 close(fd); 145 146 return 0; 147 } 148 149 150 static unsigned long long now(void) 151 { 152 struct timeval tv; 153 154 gettimeofday(&tv, NULL); 155 156 return (tv.tv_sec * 1000ull) + (tv.tv_usec / 1000ull); 157 } 158 159 static void stressone(unsigned long long ramsizeMB) 160 { 161 size_t pagesPerMB = 1024 * 1024 / RAM_PAGE_SIZE; 162 g_autofree char *ram = g_malloc(ramsizeMB * 1024 * 1024); 163 char *ramptr; 164 size_t i, j, k; 165 g_autofree char *data = g_malloc(RAM_PAGE_SIZE); 166 char *dataptr; 167 size_t nMB = 0; 168 unsigned long long before, after; 169 170 /* We don't care about initial state, but we do want 171 * to fault it all into RAM, otherwise the first iter 172 * of the loop below will be quite slow. We can't use 173 * 0x0 as the byte as gcc optimizes that away into a 174 * calloc instead :-) */ 175 memset(ram, 0xfe, ramsizeMB * 1024 * 1024); 176 177 if (random_bytes(data, RAM_PAGE_SIZE) < 0) { 178 return; 179 } 180 181 before = now(); 182 183 while (1) { 184 185 ramptr = ram; 186 for (i = 0; i < ramsizeMB; i++, nMB++) { 187 for (j = 0; j < pagesPerMB; j++) { 188 dataptr = data; 189 for (k = 0; k < RAM_PAGE_SIZE; k += sizeof(long long)) { 190 ramptr += sizeof(long long); 191 dataptr += sizeof(long long); 192 *(unsigned long long *)ramptr ^= *(unsigned long long *)dataptr; 193 } 194 } 195 196 if (nMB == 1024) { 197 after = now(); 198 fprintf(stderr, "%s (%05d): INFO: %06llums copied 1 GB in %05llums\n", 199 argv0, gettid(), after, after - before); 200 before = now(); 201 nMB = 0; 202 } 203 } 204 } 205 } 206 207 208 static void *stressthread(void *arg) 209 { 210 unsigned long long ramsizeMB = *(unsigned long long *)arg; 211 212 stressone(ramsizeMB); 213 214 return NULL; 215 } 216 217 static void stress(unsigned long long ramsizeGB, int ncpus) 218 { 219 size_t i; 220 unsigned long long ramsizeMB = ramsizeGB * 1024 / ncpus; 221 ncpus--; 222 223 for (i = 0; i < ncpus; i++) { 224 pthread_t thr; 225 pthread_create(&thr, NULL, 226 stressthread, &ramsizeMB); 227 } 228 229 stressone(ramsizeMB); 230 } 231 232 233 static int mount_misc(const char *fstype, const char *dir) 234 { 235 if (mkdir(dir, 0755) < 0 && errno != EEXIST) { 236 fprintf(stderr, "%s (%05d): ERROR: cannot create %s: %s\n", 237 argv0, gettid(), dir, strerror(errno)); 238 return -1; 239 } 240 241 if (mount("none", dir, fstype, 0, NULL) < 0) { 242 fprintf(stderr, "%s (%05d): ERROR: cannot mount %s: %s\n", 243 argv0, gettid(), dir, strerror(errno)); 244 return -1; 245 } 246 247 return 0; 248 } 249 250 static int mount_all(void) 251 { 252 if (mount_misc("proc", "/proc") < 0 || 253 mount_misc("sysfs", "/sys") < 0 || 254 mount_misc("tmpfs", "/dev") < 0) 255 return -1; 256 257 mknod("/dev/urandom", 0777 | S_IFCHR, makedev(1, 9)); 258 mknod("/dev/random", 0777 | S_IFCHR, makedev(1, 8)); 259 260 return 0; 261 } 262 263 int main(int argc, char **argv) 264 { 265 unsigned long long ramsizeGB = 1; 266 char *end; 267 int ch; 268 int opt_ind = 0; 269 const char *sopt = "hr:c:"; 270 struct option lopt[] = { 271 { "help", no_argument, NULL, 'h' }, 272 { "ramsize", required_argument, NULL, 'r' }, 273 { "cpus", required_argument, NULL, 'c' }, 274 { NULL, 0, NULL, 0 } 275 }; 276 int ret; 277 int ncpus = 0; 278 279 argv0 = argv[0]; 280 281 while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { 282 switch (ch) { 283 case 'r': 284 errno = 0; 285 ramsizeGB = strtoll(optarg, &end, 10); 286 if (errno != 0 || *end) { 287 fprintf(stderr, "%s (%05d): ERROR: Cannot parse RAM size %s\n", 288 argv0, gettid(), optarg); 289 exit_failure(); 290 } 291 break; 292 293 case 'c': 294 errno = 0; 295 ncpus = strtoll(optarg, &end, 10); 296 if (errno != 0 || *end) { 297 fprintf(stderr, "%s (%05d): ERROR: Cannot parse CPU count %s\n", 298 argv0, gettid(), optarg); 299 exit_failure(); 300 } 301 break; 302 303 case '?': 304 case 'h': 305 fprintf(stderr, "%s: [--help][--ramsize GB][--cpus N]\n", argv0); 306 exit_failure(); 307 } 308 } 309 310 if (getpid() == 1) { 311 if (mount_all() < 0) 312 exit_failure(); 313 314 ret = get_command_arg_ull("ramsize", &ramsizeGB); 315 if (ret < 0) 316 exit_failure(); 317 } 318 319 if (ncpus == 0) 320 ncpus = sysconf(_SC_NPROCESSORS_ONLN); 321 322 fprintf(stdout, "%s (%05d): INFO: RAM %llu GiB across %d CPUs\n", 323 argv0, gettid(), ramsizeGB, ncpus); 324 325 stress(ramsizeGB, ncpus); 326 327 exit_failure(); 328 } 329