1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/* A simple test of emmc random read and write performance.  When testing write
18 * performance, try it twice, once with O_SYNC compiled in, and once with it commented
19 * out.  Without O_SYNC, the close(2) blocks until all the dirty buffers are written
20 * out, but the numbers tend to be higher.
21 */
22
23#define _LARGEFILE64_SOURCE
24#include <string.h>
25#include <stdio.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <fcntl.h>
29#include <sys/time.h>
30#include <stdlib.h>
31#include <unistd.h>
32#include <math.h>
33
34#define TST_BLK_SIZE 4096
35/* Number of seconds to run the test */
36#define TEST_LEN 10
37
38struct stats {
39    struct timeval start;
40    struct timeval end;
41    off64_t offset;
42};
43
44static void usage(void) {
45        fprintf(stderr, "Usage: rand_emmc_perf [ -r | -w ] [-o] [-s count] [-f full_stats_filename] <size_in_mb> <block_dev>\n");
46        exit(1);
47}
48
49static void print_stats(struct stats *stats_buf, int stats_count,
50                        char * full_stats_file)
51{
52    int i;
53    struct timeval t;
54    struct timeval sum = { 0, 0 };
55    struct timeval max = { 0, 0 };
56    long long total_usecs;
57    long long avg_usecs;
58    long long max_usecs;
59    long long variance = 0;;
60    long long x;
61    double sdev;
62    FILE *full_stats = NULL;
63
64    if (full_stats_file) {
65        full_stats = fopen(full_stats_file, "w");
66        if (full_stats == NULL) {
67            fprintf(stderr, "Cannot open full stats output file %s, ignoring\n",
68                    full_stats_file);
69        }
70    }
71
72    for (i = 0; i < stats_count; i++) {
73        timersub(&stats_buf[i].end, &stats_buf[i].start, &t);
74        if (timercmp(&t, &max, >)) {
75            max = t;
76        }
77        if (full_stats) {
78            fprintf(full_stats, "%lld\n", (t.tv_sec * 1000000LL) + t.tv_usec);
79        }
80        timeradd(&sum, &t, &sum);
81    }
82
83    if (full_stats) {
84        fclose(full_stats);
85    }
86
87    max_usecs = (max.tv_sec * 1000000LL) + max.tv_usec;
88    total_usecs = (sum.tv_sec * 1000000LL) + sum.tv_usec;
89    avg_usecs = total_usecs / stats_count;
90    printf("average random %d byte iop time = %lld usecs\n",
91           TST_BLK_SIZE, avg_usecs);
92    printf("maximum random %d byte iop time = %lld usecs\n",
93           TST_BLK_SIZE, max_usecs);
94
95    /* Now that we have the average (aka mean) go through the data
96     * again and compute the standard deviation.
97     * The formula is sqrt(sum_1_to_n((Xi - avg)^2)/n)
98     */
99    for (i = 0; i < stats_count; i++) {
100        timersub(&stats_buf[i].end, &stats_buf[i].start, &t);  /* Xi */
101        x = (t.tv_sec * 1000000LL) + t.tv_usec;                /* Convert to long long */
102        x = x - avg_usecs;                                     /* Xi - avg */
103        x = x * x;                                             /* (Xi - avg) ^ 2 */
104        variance += x;                                         /* Summation */
105    }
106    sdev = sqrt((double)variance/(double)stats_count);
107    printf("standard deviation of iops is %.2f\n", sdev);
108}
109
110static void stats_test(int fd, int write_mode, off64_t max_blocks, int stats_count,
111                       char *full_stats_file)
112{
113    struct stats *stats_buf;
114    char buf[TST_BLK_SIZE] = { 0 };
115    int i;
116
117    stats_buf = malloc(stats_count * sizeof(struct stats));
118    if (stats_buf == NULL) {
119        fprintf(stderr, "Cannot allocate stats_buf\n");
120        exit(1);
121    }
122
123    for (i = 0; i < stats_count; i++) {
124        gettimeofday(&stats_buf[i].start, NULL);
125
126        if (lseek64(fd, (rand() % max_blocks) * TST_BLK_SIZE, SEEK_SET) < 0) {
127            fprintf(stderr, "lseek64 failed\n");
128        }
129
130        if (write_mode) {
131            if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
132                fprintf(stderr, "Short write\n");
133            }
134        } else {
135            if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
136                fprintf(stderr, "Short read\n");
137            }
138        }
139
140        gettimeofday(&stats_buf[i].end, NULL);
141    }
142
143    print_stats(stats_buf, stats_count, full_stats_file);
144}
145
146static void perf_test(int fd, int write_mode, off64_t max_blocks)
147{
148    struct timeval start, end, res;
149    char buf[TST_BLK_SIZE] = { 0 };
150    long long iops = 0;
151    int msecs;
152
153    res.tv_sec = 0;
154    gettimeofday(&start, NULL);
155    while (res.tv_sec < TEST_LEN) {
156        if (lseek64(fd, (rand() % max_blocks) * TST_BLK_SIZE, SEEK_SET) < 0) {
157            fprintf(stderr, "lseek64 failed\n");
158        }
159        if (write_mode) {
160            if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
161                fprintf(stderr, "Short write\n");
162            }
163        } else {
164            if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
165                fprintf(stderr, "Short read\n");
166            }
167        }
168        iops++;
169        gettimeofday(&end, NULL);
170        timersub(&end, &start, &res);
171    }
172    close(fd);
173
174    /* The close can take a while when in write_mode as buffers are flushed.
175     * So get the time again. */
176    gettimeofday(&end, NULL);
177    timersub(&end, &start, &res);
178
179    msecs = (res.tv_sec * 1000) + (res.tv_usec / 1000);
180    printf("%.0f %dbyte iops/sec\n", (float)iops * 1000 / msecs, TST_BLK_SIZE);
181}
182
183int main(int argc, char *argv[])
184{
185    int fd, fd2;
186    int write_mode = 0;
187    int o_sync = 0;
188    int stats_mode = 0;
189    int stats_count;
190    char *full_stats_file = NULL;
191    off64_t max_blocks;
192    unsigned int seed;
193    int c;
194
195    while ((c = getopt(argc, argv, "+rwos:f:")) != -1) {
196        switch (c) {
197          case '?':
198          default:
199            usage();
200            break;
201
202          case 'r':
203            /* Do nothing, read mode is the default */
204            break;
205
206          case 'w':
207            write_mode = 1;
208            break;
209
210          case 'o':
211            o_sync = O_SYNC;
212            break;
213
214          case 's':
215            stats_mode = 1;
216            stats_count = atoi(optarg);
217            break;
218
219          case 'f':
220            free(full_stats_file);
221            full_stats_file = strdup(optarg);
222            if (full_stats_file == NULL) {
223                fprintf(stderr, "Cannot get full stats filename\n");
224            }
225            break;
226        }
227    }
228
229    if (o_sync && !write_mode) {
230        /* Can only specify o_sync in write mode.  Probably doesn't matter,
231         * but clear o_sync if in read mode */
232        o_sync = 0;
233    }
234
235    if ((argc - optind) != 2) {
236        usage();
237    }
238
239    /* Size is given in megabytes, so compute the number of TST_BLK_SIZE blocks. */
240    max_blocks = atoll(argv[optind]) * ((1024*1024) / TST_BLK_SIZE);
241
242    if ((fd = open(argv[optind + 1], O_RDWR | o_sync)) < 0) {
243        fprintf(stderr, "Cannot open block device %s\n", argv[optind + 1]);
244        exit(1);
245    }
246
247    fd2 = open("/dev/urandom", O_RDONLY);
248    if (fd2 < 0) {
249        fprintf(stderr, "Cannot open /dev/urandom\n");
250    }
251    if (read(fd2, &seed, sizeof(seed)) != sizeof(seed)) {
252        fprintf(stderr, "Cannot read /dev/urandom\n");
253    }
254    close(fd2);
255    srand(seed);
256
257    if (stats_mode) {
258        stats_test(fd, write_mode, max_blocks, stats_count, full_stats_file);
259    } else {
260        perf_test(fd, write_mode, max_blocks);
261    }
262    free(full_stats_file);
263
264    exit(0);
265}
266
267