1#include "../fio.h"
2#include "../profile.h"
3#include "../parse.h"
4
5/*
6 * 1x loads
7 */
8#define R_LOAD		2000
9#define W_LOAD		1000
10
11#define SAMPLE_SEC	3600		/* 1h checks */
12
13struct act_pass_criteria {
14	unsigned int max_usec;
15	unsigned int max_perm;
16};
17#define ACT_MAX_CRIT	3
18
19static struct act_pass_criteria act_pass[ACT_MAX_CRIT] = {
20	{
21		.max_usec =	1000,
22		.max_perm =	50,
23	},
24	{
25		.max_usec =	8000,
26		.max_perm =	10,
27	},
28	{
29		.max_usec = 	64000,
30		.max_perm =	1,
31	},
32};
33
34struct act_slice {
35	uint64_t lat_buckets[ACT_MAX_CRIT];
36	uint64_t total_ios;
37};
38
39struct act_run_data {
40	struct fio_mutex *mutex;
41	unsigned int pending;
42
43	struct act_slice *slices;
44	unsigned int nr_slices;
45};
46static struct act_run_data *act_run_data;
47
48struct act_prof_data {
49	struct timeval sample_tv;
50	struct act_slice *slices;
51	unsigned int cur_slice;
52	unsigned int nr_slices;
53};
54
55static char *device_names;
56static unsigned int load;
57static unsigned int prep;
58static unsigned int threads_per_queue;
59static unsigned int num_read_blocks;
60static unsigned int write_size;
61static unsigned long long test_duration;
62
63#define ACT_MAX_OPTS	128
64static const char *act_opts[ACT_MAX_OPTS] = {
65	"direct=1",
66	"ioengine=sync",
67	"random_generator=lfsr",
68	"group_reporting=1",
69	"thread",
70	NULL,
71};
72static unsigned int opt_idx = 5;
73static unsigned int org_idx;
74
75static int act_add_opt(const char *format, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
76
77struct act_options {
78	unsigned int pad;
79	char *device_names;
80	unsigned int load;
81	unsigned int prep;
82	unsigned int threads_per_queue;
83	unsigned int num_read_blocks;
84	unsigned int write_size;
85	unsigned long long test_duration;
86};
87
88static struct act_options act_options;
89
90static struct fio_option options[] = {
91	{
92		.name	= "device-names",
93		.lname	= "device-names",
94		.type	= FIO_OPT_STR_STORE,
95		.off1	= offsetof(struct act_options, device_names),
96		.help	= "Devices to use",
97		.category = FIO_OPT_C_PROFILE,
98		.group	= FIO_OPT_G_ACT,
99	},
100	{
101		.name	= "load",
102		.lname	= "Load multiplier",
103		.type	= FIO_OPT_INT,
104		.off1	= offsetof(struct act_options, load),
105		.help	= "ACT load multipler (default 1x)",
106		.def	= "1",
107		.category = FIO_OPT_C_PROFILE,
108		.group	= FIO_OPT_G_ACT,
109	},
110	{
111		.name	= "test-duration",
112		.lname	= "Test duration",
113		.type	= FIO_OPT_STR_VAL_TIME,
114		.off1	= offsetof(struct act_options, test_duration),
115		.help	= "How long the entire test takes to run",
116		.def	= "24h",
117		.category = FIO_OPT_C_PROFILE,
118		.group	= FIO_OPT_G_ACT,
119	},
120	{
121		.name	= "threads-per-queue",
122		.lname	= "Number of read IO threads per device",
123		.type	= FIO_OPT_INT,
124		.off1	= offsetof(struct act_options, threads_per_queue),
125		.help	= "Number of read IO threads per device",
126		.def	= "8",
127		.category = FIO_OPT_C_PROFILE,
128		.group	= FIO_OPT_G_ACT,
129	},
130	{
131		.name	= "read-req-num-512-blocks",
132		.lname	= "Number of 512b blocks to read",
133		.type	= FIO_OPT_INT,
134		.off1	= offsetof(struct act_options, num_read_blocks),
135		.help	= "Number of 512b blocks to read at the time",
136		.def	= "3",
137		.category = FIO_OPT_C_PROFILE,
138		.group	= FIO_OPT_G_ACT,
139	},
140	{
141		.name	= "large-block-op-kbytes",
142		.lname	= "Size of large block ops (writes)",
143		.type	= FIO_OPT_INT,
144		.off1	= offsetof(struct act_options, write_size),
145		.help	= "Size of large block ops (writes)",
146		.def	= "128k",
147		.category = FIO_OPT_C_PROFILE,
148		.group	= FIO_OPT_G_ACT,
149	},
150	{
151		.name	= "prep",
152		.lname	= "Run ACT prep phase",
153		.type	= FIO_OPT_STR_SET,
154		.off1	= offsetof(struct act_options, prep),
155		.help	= "Set to run ACT prep phase",
156		.category = FIO_OPT_C_PROFILE,
157		.group	= FIO_OPT_G_ACT,
158	},
159	{
160		.name	= NULL,
161	},
162};
163
164static int act_add_opt(const char *str, ...)
165{
166	char buffer[512];
167	va_list args;
168	size_t len;
169
170	if (opt_idx == ACT_MAX_OPTS) {
171		log_err("act: ACT_MAX_OPTS is too small\n");
172		return 1;
173	}
174
175	va_start(args, str);
176	len = vsnprintf(buffer, sizeof(buffer), str, args);
177	va_end(args);
178
179	if (len)
180		act_opts[opt_idx++] = strdup(buffer);
181
182	return 0;
183}
184
185static int act_add_rw(const char *dev, int reads)
186{
187	if (act_add_opt("name=act-%s-%s", reads ? "read" : "write", dev))
188		return 1;
189	if (act_add_opt("filename=%s", dev))
190		return 1;
191	if (act_add_opt("rw=%s", reads ? "randread" : "randwrite"))
192		return 1;
193	if (reads) {
194		int rload = load * R_LOAD / threads_per_queue;
195
196		if (act_add_opt("numjobs=%u", threads_per_queue))
197			return 1;
198		if (act_add_opt("rate_iops=%u", rload))
199			return 1;
200		if (act_add_opt("bs=%u", num_read_blocks * 512))
201			return 1;
202	} else {
203		const int rsize = write_size / (num_read_blocks * 512);
204		int wload = (load * W_LOAD + rsize - 1) / rsize;
205
206		if (act_add_opt("rate_iops=%u", wload))
207			return 1;
208		if (act_add_opt("bs=%u", write_size))
209			return 1;
210	}
211
212	return 0;
213}
214
215static int act_add_dev_prep(const char *dev)
216{
217	/* Add sequential zero phase */
218	if (act_add_opt("name=act-prep-zeroes-%s", dev))
219		return 1;
220	if (act_add_opt("filename=%s", dev))
221		return 1;
222	if (act_add_opt("bs=1M"))
223		return 1;
224	if (act_add_opt("zero_buffers"))
225		return 1;
226	if (act_add_opt("rw=write"))
227		return 1;
228
229	/* Randomly overwrite device */
230	if (act_add_opt("name=act-prep-salt-%s", dev))
231		return 1;
232	if (act_add_opt("stonewall"))
233		return 1;
234	if (act_add_opt("filename=%s", dev))
235		return 1;
236	if (act_add_opt("bs=4k"))
237		return 1;
238	if (act_add_opt("ioengine=libaio"))
239		return 1;
240	if (act_add_opt("iodepth=64"))
241		return 1;
242	if (act_add_opt("rw=randwrite"))
243		return 1;
244
245	return 0;
246}
247
248static int act_add_dev(const char *dev)
249{
250	if (prep)
251		return act_add_dev_prep(dev);
252
253	if (act_add_opt("runtime=%llus", test_duration))
254		return 1;
255	if (act_add_opt("time_based=1"))
256		return 1;
257
258	if (act_add_rw(dev, 1))
259		return 1;
260	if (act_add_rw(dev, 0))
261		return 1;
262
263	return 0;
264}
265
266/*
267 * Fill our private options into the command line
268 */
269static int act_prep_cmdline(void)
270{
271	if (!device_names) {
272		log_err("act: you need to set IO target(s) with the "
273			"device-names option.\n");
274		return 1;
275	}
276
277	org_idx = opt_idx;
278
279	do {
280		char *dev;
281
282		dev = strsep(&device_names, ",");
283		if (!dev)
284			break;
285
286		if (act_add_dev(dev)) {
287			log_err("act: failed adding device to the mix\n");
288			break;
289		}
290	} while (1);
291
292	return 0;
293}
294
295static int act_io_u_lat(struct thread_data *td, uint64_t usec)
296{
297	struct act_prof_data *apd = td->prof_data;
298	struct act_slice *slice;
299	int i, ret = 0;
300	double perm;
301
302	if (prep)
303		return 0;
304
305	/*
306	 * Really should not happen, but lets not let jitter at the end
307	 * ruin our day.
308	 */
309	if (apd->cur_slice >= apd->nr_slices)
310		return 0;
311
312	slice = &apd->slices[apd->cur_slice];
313	slice->total_ios++;
314
315	for (i = ACT_MAX_CRIT - 1; i >= 0; i--) {
316		if (usec > act_pass[i].max_usec) {
317			slice->lat_buckets[i]++;
318			break;
319		}
320	}
321
322	if (time_since_now(&apd->sample_tv) < SAMPLE_SEC)
323		return 0;
324
325	/* SAMPLE_SEC has passed, check criteria for pass */
326	for (i = 0; i < ACT_MAX_CRIT; i++) {
327		perm = (1000.0 * slice->lat_buckets[i]) / slice->total_ios;
328		if (perm < act_pass[i].max_perm)
329			continue;
330
331		log_err("act: %f%% exceeds pass criteria of %f%%\n", perm / 10.0, (double) act_pass[i].max_perm / 10.0);
332		ret = 1;
333		break;
334	}
335
336	fio_gettime(&apd->sample_tv, NULL);
337	apd->cur_slice++;
338	return ret;
339}
340
341static void get_act_ref(void)
342{
343	fio_mutex_down(act_run_data->mutex);
344	act_run_data->pending++;
345	fio_mutex_up(act_run_data->mutex);
346}
347
348static int show_slice(struct act_slice *slice, unsigned int slice_num)
349{
350	unsigned int i, failed = 0;
351
352	log_info("   %2u", slice_num);
353
354	for (i = 0; i < ACT_MAX_CRIT; i++) {
355		double perc = 0.0;
356
357		if (slice->total_ios)
358			perc = 100.0 * (double) slice->lat_buckets[i] / (double) slice->total_ios;
359		if ((perc * 10.0) >= act_pass[i].max_perm)
360			failed++;
361		log_info("\t%2.2f", perc);
362	}
363	for (i = 0; i < ACT_MAX_CRIT; i++) {
364		double perc = 0.0;
365
366		if (slice->total_ios)
367			perc = 100.0 * (double) slice->lat_buckets[i] / (double) slice->total_ios;
368		log_info("\t%2.2f", perc);
369	}
370	log_info("\n");
371
372	return failed;
373}
374
375static void act_show_all_stats(void)
376{
377	unsigned int i, fails = 0;
378
379	log_info("        trans                   device\n");
380	log_info("        %%>(ms)                  %%>(ms)\n");
381	log_info(" slice");
382
383	for (i = 0; i < ACT_MAX_CRIT; i++)
384		log_info("\t %2u", act_pass[i].max_usec / 1000);
385	for (i = 0; i < ACT_MAX_CRIT; i++)
386		log_info("\t %2u", act_pass[i].max_usec / 1000);
387
388	log_info("\n");
389	log_info(" -----  -----   -----  ------   -----   -----  ------\n");
390
391	for (i = 0; i < act_run_data->nr_slices; i++)
392		fails += show_slice(&act_run_data->slices[i], i + 1);
393
394	log_info("\nact: test complete, device(s): %s\n", fails ? "FAILED" : "PASSED");
395}
396
397static void put_act_ref(struct thread_data *td)
398{
399	struct act_prof_data *apd = td->prof_data;
400	unsigned int i, slice;
401
402	fio_mutex_down(act_run_data->mutex);
403
404	if (!act_run_data->slices) {
405		act_run_data->slices = calloc(apd->nr_slices, sizeof(struct act_slice));
406		act_run_data->nr_slices = apd->nr_slices;
407	}
408
409	for (slice = 0; slice < apd->nr_slices; slice++) {
410		struct act_slice *dst = &act_run_data->slices[slice];
411		struct act_slice *src = &apd->slices[slice];
412
413		dst->total_ios += src->total_ios;
414
415		for (i = 0; i < ACT_MAX_CRIT; i++)
416			dst->lat_buckets[i] += src->lat_buckets[i];
417	}
418
419	if (!--act_run_data->pending)
420		act_show_all_stats();
421
422	fio_mutex_up(act_run_data->mutex);
423}
424
425static int act_td_init(struct thread_data *td)
426{
427	struct act_prof_data *apd;
428	unsigned int nr_slices;
429
430	get_act_ref();
431
432	apd = calloc(1, sizeof(*apd));
433	nr_slices = (test_duration + SAMPLE_SEC - 1) / SAMPLE_SEC;
434	apd->slices = calloc(nr_slices, sizeof(struct act_slice));
435	apd->nr_slices = nr_slices;
436	fio_gettime(&apd->sample_tv, NULL);
437	td->prof_data = apd;
438	return 0;
439}
440
441static void act_td_exit(struct thread_data *td)
442{
443	struct act_prof_data *apd = td->prof_data;
444
445	put_act_ref(td);
446	free(apd->slices);
447	free(apd);
448	td->prof_data = NULL;
449}
450
451static struct prof_io_ops act_io_ops = {
452	.td_init	= act_td_init,
453	.td_exit	= act_td_exit,
454	.io_u_lat	= act_io_u_lat,
455};
456
457static struct profile_ops act_profile = {
458	.name		= "act",
459	.desc		= "ACT Aerospike like benchmark",
460	.options	= options,
461	.opt_data	= &act_options,
462	.prep_cmd	= act_prep_cmdline,
463	.cmdline	= act_opts,
464	.io_ops		= &act_io_ops,
465};
466
467static void fio_init act_register(void)
468{
469	act_run_data = calloc(1, sizeof(*act_run_data));
470	act_run_data->mutex = fio_mutex_init(FIO_MUTEX_UNLOCKED);
471
472	if (register_profile(&act_profile))
473		log_err("fio: failed to register profile 'act'\n");
474}
475
476static void fio_exit act_unregister(void)
477{
478	while (org_idx && org_idx < opt_idx)
479		free((void *) act_opts[++org_idx]);
480
481	unregister_profile(&act_profile);
482	fio_mutex_remove(act_run_data->mutex);
483	free(act_run_data->slices);
484	free(act_run_data);
485	act_run_data = NULL;
486}
487