1 /*
2 * iobw.c - simple I/O bandwidth benchmark
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 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 GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 021110-1307, USA.
18 *
19 * Copyright (C) 2008 Andrea Righi <righi.andrea@gmail.com>
20 */
21
22 #define _GNU_SOURCE
23 #define __USE_GNU
24
25 #include <errno.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <fcntl.h>
29 #include <signal.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <limits.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <sys/wait.h>
37
38 #ifndef PAGE_SIZE
39 #define PAGE_SIZE sysconf(_SC_PAGE_SIZE)
40 #endif
41
42 #define align(x,a) __align_mask(x,(typeof(x))(a)-1)
43 #define __align_mask(x,mask) (((x)+(mask))&~(mask))
44 #define kb(x) ((x) >> 10)
45
46 const char usage[] = "Usage: iobw [-direct] threads chunk_size data_size\n";
47 const char child_fmt[] = "(%s) task %3d: time %4lu.%03lu bw %7lu KiB/s (%s)\n";
48 const char parent_fmt[] =
49 "(%s) parent %d: time %4lu.%03lu bw %7lu KiB/s (%s)\n";
50
51 static int directio = 0;
52 static size_t data_size = 0;
53 static size_t chunk_size = 0;
54
55 typedef enum {
56 OP_WRITE,
57 OP_READ,
58 NUM_IOPS,
59 } iops_t;
60
61 static const char *iops[] = {
62 "WRITE",
63 "READ ",
64 "TOTAL",
65 };
66
67 static int threads;
68 pid_t *children;
69
70 char *mygroup;
71
print_results(int id,iops_t op,size_t bytes,struct timeval * diff)72 static void print_results(int id, iops_t op, size_t bytes, struct timeval *diff)
73 {
74 fprintf(stdout, id ? child_fmt : parent_fmt,
75 mygroup, id, diff->tv_sec, diff->tv_usec / 1000,
76 (bytes / (diff->tv_sec * 1000000L + diff->tv_usec))
77 * 1000000L / 1024, iops[op]);
78 }
79
thread(int id)80 static void thread(int id)
81 {
82 struct timeval start, stop, diff;
83 int fd, i, ret;
84 size_t n;
85 void *buf;
86 int flags = O_CREAT | O_RDWR | O_LARGEFILE;
87 char filename[32];
88
89 ret = posix_memalign(&buf, PAGE_SIZE, chunk_size);
90 if (ret < 0) {
91 fprintf(stderr,
92 "ERROR: task %d couldn't allocate %zu bytes (%s)\n",
93 id, chunk_size, strerror(errno));
94 exit(1);
95 }
96 memset(buf, 0xaa, chunk_size);
97
98 snprintf(filename, sizeof(filename), "%s-%d-iobw.tmp", mygroup, id);
99 if (directio)
100 flags |= O_DIRECT;
101 fd = open(filename, flags, 0600);
102 if (fd < 0) {
103 fprintf(stderr, "ERROR: task %d couldn't open %s (%s)\n",
104 id, filename, strerror(errno));
105 free(buf);
106 exit(1);
107 }
108
109 /* Write */
110 lseek(fd, 0, SEEK_SET);
111 n = 0;
112 gettimeofday(&start, NULL);
113 while (n < data_size) {
114 i = write(fd, buf, chunk_size);
115 if (i < 0) {
116 fprintf(stderr, "ERROR: task %d writing to %s (%s)\n",
117 id, filename, strerror(errno));
118 ret = 1;
119 goto out;
120 }
121 n += i;
122 }
123 gettimeofday(&stop, NULL);
124 timersub(&stop, &start, &diff);
125 print_results(id + 1, OP_WRITE, data_size, &diff);
126
127 /* Read */
128 lseek(fd, 0, SEEK_SET);
129 n = 0;
130 gettimeofday(&start, NULL);
131 while (n < data_size) {
132 i = read(fd, buf, chunk_size);
133 if (i < 0) {
134 fprintf(stderr, "ERROR: task %d reading to %s (%s)\n",
135 id, filename, strerror(errno));
136 ret = 1;
137 goto out;
138 }
139 n += i;
140 }
141 gettimeofday(&stop, NULL);
142 timersub(&stop, &start, &diff);
143 print_results(id + 1, OP_READ, data_size, &diff);
144 out:
145 close(fd);
146 unlink(filename);
147 free(buf);
148 exit(ret);
149 }
150
spawn(int id)151 static void spawn(int id)
152 {
153 pid_t pid;
154
155 pid = fork();
156 switch (pid) {
157 case -1:
158 fprintf(stderr, "ERROR: couldn't fork thread %d\n", id);
159 exit(1);
160 case 0:
161 thread(id);
162 default:
163 children[id] = pid;
164 }
165 }
166
signal_handler(int sig)167 void signal_handler(int sig)
168 {
169 char filename[32];
170 int i;
171
172 for (i = 0; i < threads; i++)
173 if (children[i])
174 kill(children[i], SIGKILL);
175
176 for (i = 0; i < threads; i++) {
177 struct stat mystat;
178
179 snprintf(filename, sizeof(filename), "%s-%d-iobw.tmp",
180 mygroup, i);
181 if (stat(filename, &mystat) < 0)
182 continue;
183 unlink(filename);
184 }
185
186 fprintf(stdout, "received signal %d, exiting\n", sig);
187 exit(0);
188 }
189
memparse(char * ptr,char ** retptr)190 unsigned long long memparse(char *ptr, char **retptr)
191 {
192 unsigned long long ret = strtoull(ptr, retptr, 0);
193
194 switch (**retptr) {
195 case 'G':
196 case 'g':
197 ret <<= 10;
198 case 'M':
199 case 'm':
200 ret <<= 10;
201 case 'K':
202 case 'k':
203 ret <<= 10;
204 (*retptr)++;
205 default:
206 break;
207 }
208 return ret;
209 }
210
main(int argc,char * argv[])211 int main(int argc, char *argv[])
212 {
213 struct timeval start, stop, diff;
214 char *end;
215 int i;
216
217 if (argv[1] && strcmp(argv[1], "-direct") == 0) {
218 directio = 1;
219 argc--;
220 argv++;
221 }
222 if (argc != 4) {
223 fprintf(stderr, usage);
224 exit(1);
225 }
226 if ((threads = atoi(argv[1])) == 0) {
227 fprintf(stderr, usage);
228 exit(1);
229 }
230 chunk_size = align(memparse(argv[2], &end), PAGE_SIZE);
231 if (*end) {
232 fprintf(stderr, usage);
233 exit(1);
234 }
235 data_size = align(memparse(argv[3], &end), PAGE_SIZE);
236 if (*end) {
237 fprintf(stderr, usage);
238 exit(1);
239 }
240
241 /* retrieve group name */
242 mygroup = getenv("MYGROUP");
243 if (!mygroup) {
244 fprintf(stderr,
245 "ERROR: undefined environment variable MYGROUP\n");
246 exit(1);
247 }
248
249 children = malloc(sizeof(pid_t) * threads);
250 if (!children) {
251 fprintf(stderr, "ERROR: not enough memory\n");
252 exit(1);
253 }
254
255 /* handle user interrupt */
256 signal(SIGINT, signal_handler);
257 /* handle kill from shell */
258 signal(SIGTERM, signal_handler);
259
260 fprintf(stdout, "chunk_size %zuKiB, data_size %zuKiB\n",
261 kb(chunk_size), kb(data_size));
262 fflush(stdout);
263
264 gettimeofday(&start, NULL);
265 for (i = 0; i < threads; i++)
266 spawn(i);
267 for (i = 0; i < threads; i++) {
268 int status;
269 wait(&status);
270 if (!WIFEXITED(status))
271 exit(1);
272 }
273 gettimeofday(&stop, NULL);
274
275 timersub(&stop, &start, &diff);
276 print_results(0, NUM_IOPS, data_size * threads * NUM_IOPS, &diff);
277 fflush(stdout);
278 free(children);
279
280 exit(0);
281 }
282