Commit | Line | Data |
---|---|---|
e3027339 MD |
1 | /* |
2 | * test-ssd-write.c | |
3 | * | |
4 | * Hard disk write workload: write random (or zeroed) data at random | |
5 | * locations in a file, and optionally validate by re-reading some | |
6 | * blocks after a while. | |
7 | * | |
8 | * Compile with: | |
9 | * gcc -O2 -Wall -o test-ssd-write test-ssd-write.c | |
10 | * | |
11 | * Hard disks known to stop responding with this program (requires a | |
12 | * warm machine reboot to get the drive to respond, as soft reset is not | |
13 | * enough): | |
14 | * | |
15 | * Vendor Model Firmware Controller # drives tested | |
16 | * Intel (Lenovo) SSDSC2BW180A3L LE1i SandForce 2281 1 | |
17 | * Intel (Lenovo) SSDSC2BW180A3L LF1i SandForce 2281 2 | |
18 | * | |
19 | * We ensured that the problem is not coming from other parts by running | |
20 | * this test on other SSD drives, which don't show this problem: | |
21 | * | |
22 | * Vendor Model Firmware Controller # drives tested | |
23 | * Intel SSDSA2M160G2GC 2CV102HD Intel 1 | |
24 | * | |
25 | * Under Linux (Debian, Ubuntu, various kernels), after about 5 minutes, | |
26 | * we get this result: | |
27 | * | |
28 | * ata1.00: exception Emask 0x0 SAct 0x1 SErr 0x0 action 0x6 frozen | |
29 | * ata1.00: failed command: WRITE FPDMA QUEUED | |
30 | * ata1.00: cmd 61/28:00:a8:a9:7f/00:00:02:00:00/40 tag 0 ncq 20480 out | |
31 | * res 40/00:00:00:4f:c2/00:00:00:00:00/00 Emask 0x4 (timeout) | |
32 | * ata1.00: status: { DRDY } | |
33 | * ata1.00: COMRESET failed (errno=-16) | |
34 | * ata1.00: COMRESET failed (errno=-16) | |
35 | * | |
36 | * This happens with random data, zeroed data (-z), and has been tested | |
37 | * with file sizes of 1MB, 200MB, 3.1GB and 21GB. The error happens with | |
38 | * and without the validation (-v) option. | |
39 | * | |
40 | * Copyright 2013 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com> | |
41 | * | |
42 | * Permission is hereby granted, free of charge, to any person obtaining | |
43 | * a copy of this software and associated documentation files (the | |
44 | * "Software"), to deal in the Software without restriction, including | |
45 | * without limitation the rights to use, copy, modify, merge, publish, | |
46 | * distribute, sublicense, and/or sell copies of the Software, and to | |
47 | * permit persons to whom the Software is furnished to do so, subject to | |
48 | * the following conditions: | |
49 | * | |
50 | * The above copyright notice and this permission notice shall be | |
51 | * included in all copies or substantial portions of the Software. | |
52 | * | |
53 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
54 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
55 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
56 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
57 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
58 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
59 | * SOFTWARE. | |
60 | */ | |
61 | ||
62 | #include <stdio.h> | |
63 | #include <sys/types.h> | |
64 | #include <sys/stat.h> | |
65 | #include <fcntl.h> | |
66 | #include <assert.h> | |
67 | #include <stdlib.h> | |
68 | #include <unistd.h> | |
69 | #include <stdint.h> | |
70 | #include <inttypes.h> | |
71 | #include <string.h> | |
72 | ||
73 | enum write_mode { | |
8b048fcd | 74 | WRITE_RANDOM = 0, |
e3027339 MD |
75 | WRITE_ZEROES, |
76 | }; | |
77 | ||
78 | static enum write_mode write_mode; | |
79 | static int verify_mode; | |
80 | ||
81 | #define PRINT_FREQ 100000 | |
82 | #define VALIDATE_FREQ 10000 | |
83 | #define BUFLEN 4096 | |
84 | ||
85 | static void rand_buf(char *buf, size_t buflen) | |
86 | { | |
87 | size_t i; | |
88 | ||
89 | for (i = 0; i < buflen; i += sizeof(int)) { | |
90 | union { | |
91 | int i; | |
92 | char c[sizeof(int)]; | |
93 | } u; | |
94 | u.i = rand(); | |
95 | memcpy(&buf[i], u.c, sizeof(int)); | |
96 | } | |
97 | } | |
98 | ||
99 | static uint64_t validate(int fd, const char *validate_buf, off_t offset) | |
100 | { | |
101 | char buf[BUFLEN]; | |
102 | ssize_t rret; | |
103 | off_t pos; | |
104 | int i; | |
105 | uint64_t diffcnt = 0; | |
106 | ||
107 | if (!verify_mode) | |
108 | return 0; | |
109 | ||
110 | pos = lseek(fd, offset, SEEK_SET); | |
111 | if (pos < 0) { | |
112 | perror("lseek"); | |
113 | exit(EXIT_FAILURE); | |
114 | } | |
115 | rret = read(fd, buf, BUFLEN); | |
116 | if (rret != BUFLEN) { | |
117 | fprintf(stderr, "Error at read from offset: %zu\n", | |
118 | pos); | |
119 | perror("read"); | |
120 | exit(EXIT_FAILURE); | |
121 | } | |
122 | for (i = 0; i < BUFLEN; i++) { | |
123 | if (buf[i] != validate_buf[i]) { | |
124 | diffcnt++; | |
125 | } | |
126 | } | |
127 | return diffcnt; | |
128 | } | |
129 | ||
130 | static int rand_write(int fd, size_t len) | |
131 | { | |
132 | off_t pos, offset; | |
133 | uint64_t write_nr = 0; | |
134 | ssize_t wret; | |
135 | char buf[BUFLEN]; | |
136 | char validate_buf[BUFLEN]; | |
137 | off_t validate_offset = 0; | |
138 | uint64_t valcount; | |
139 | int ret; | |
140 | ||
141 | memset(buf, 0, BUFLEN); | |
142 | ||
143 | for (;;) { | |
144 | if (len > UINT32_MAX) { | |
145 | offset = (((size_t) rand() << 32) + (size_t) rand()) % len; | |
146 | } else { | |
147 | offset = rand() % len; | |
148 | } | |
149 | ||
150 | if ((offset >= validate_offset && | |
151 | offset < validate_offset + BUFLEN) | |
152 | || (validate_offset >= offset && | |
153 | validate_offset < offset + BUFLEN)) { | |
154 | /* Don't overwrite the range we want to validate. */ | |
155 | continue; | |
156 | } | |
157 | if (write_mode == WRITE_RANDOM) | |
158 | rand_buf(buf, BUFLEN); | |
159 | /* Save validation buffer and position */ | |
160 | if (write_nr % VALIDATE_FREQ == 0 && verify_mode) { | |
161 | memcpy(validate_buf, buf, BUFLEN); | |
162 | validate_offset = offset; | |
163 | } | |
164 | pos = lseek(fd, offset, SEEK_SET); | |
165 | if (pos < 0) { | |
166 | perror("lseek"); | |
167 | exit(EXIT_FAILURE); | |
168 | } | |
169 | wret = write(fd, buf, BUFLEN); | |
170 | if (wret != BUFLEN) { | |
171 | fprintf(stderr, "Error at write to offset: %zu\n", | |
172 | pos); | |
173 | perror("write"); | |
174 | exit(EXIT_FAILURE); | |
175 | } | |
176 | ||
177 | /* | |
178 | * Advise that we won't be re-reading the blocks. This | |
179 | * will ask the kernel to drop pages related to this | |
180 | * file quickly from its page cache, thus forcing a read | |
181 | * from disk. | |
182 | */ | |
183 | ret = fdatasync(fd); | |
184 | if (ret) { | |
185 | perror("fdatasync"); | |
186 | exit(EXIT_FAILURE); | |
187 | } | |
188 | ret = posix_fadvise(fd, offset, BUFLEN, POSIX_FADV_DONTNEED); | |
189 | if (ret) { | |
190 | perror("posix_fadvise"); | |
191 | exit(EXIT_FAILURE); | |
192 | } | |
193 | ||
194 | write_nr++; | |
195 | if (write_nr % PRINT_FREQ == 0) { | |
196 | printf("Status: %" PRIu64 " writes.\n", write_nr); | |
197 | } | |
198 | ||
199 | /* | |
200 | * Use the validation buffer and position saved | |
201 | * VALIDATE_FREQ operations earlier. | |
202 | */ | |
203 | if (write_nr % VALIDATE_FREQ == 0 && verify_mode) { | |
204 | valcount = validate(fd, validate_buf, validate_offset); | |
205 | if (valcount) { | |
206 | printf("VALIDATION ERROR at offset %zu, %" PRIu64 " bytes differ\n", | |
207 | validate_offset, valcount); | |
208 | } | |
209 | } | |
210 | } | |
211 | return 0; | |
212 | } | |
213 | ||
214 | int main(int argc, char **argv) | |
215 | { | |
216 | int fd, ret, i, seed; | |
217 | size_t len; | |
218 | off_t pos; | |
219 | ssize_t wret; | |
220 | ||
221 | if (argc < 4) { | |
222 | printf("Usage: %s <output file> <len (64-bit)> <seed (32-bit)> <-z to write zeroes> <-v to verify written data>\n", argv[0]); | |
223 | exit(EXIT_FAILURE); | |
224 | } | |
225 | ||
226 | fd = open(argv[1], O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); | |
227 | if (fd < 0) { | |
228 | perror("open"); | |
229 | exit(EXIT_FAILURE); | |
230 | } | |
231 | ||
232 | len = atoll(argv[2]); | |
233 | seed = atoi(argv[3]); | |
234 | srand(seed); | |
235 | ||
236 | printf("Creating file %s of length %zu, random seed %u\n", argv[1], len, | |
237 | seed); | |
238 | ||
e3027339 MD |
239 | for (i = 4; i < argc; i++) { |
240 | if (strcmp(argv[i], "-z") == 0) { | |
241 | write_mode = WRITE_ZEROES; | |
242 | } else if (strcmp(argv[i], "-v") == 0) { | |
243 | verify_mode = 1; | |
244 | } | |
245 | } | |
246 | ||
e3027339 MD |
247 | switch (write_mode) { |
248 | case WRITE_RANDOM: | |
8b048fcd MD |
249 | printf("Generating random data\n"); |
250 | break; | |
e3027339 | 251 | case WRITE_ZEROES: |
8b048fcd | 252 | printf("Filling with zeroes (compressible pattern)\n"); |
e3027339 MD |
253 | break; |
254 | default: | |
255 | printf("Unsupported write-mode\n"); | |
256 | exit(EXIT_FAILURE); | |
257 | } | |
258 | ||
8b048fcd MD |
259 | if (verify_mode) { |
260 | printf("Verification mode activated.\n"); | |
261 | } | |
262 | ||
e3027339 MD |
263 | /* Grow file */ |
264 | pos = lseek(fd, len - 1, SEEK_SET); | |
265 | if (pos < 0) { | |
266 | perror("lseek"); | |
267 | exit(EXIT_FAILURE); | |
268 | } | |
269 | wret = write(fd, "", 1); | |
270 | if (wret < 0) { | |
271 | perror("write"); | |
272 | exit(EXIT_FAILURE); | |
273 | } | |
274 | ||
275 | /* Advise the OS that we are performing random accesses */ | |
276 | ret = posix_fadvise(fd, 0, len, POSIX_FADV_RANDOM); | |
277 | if (ret) { | |
278 | perror("posix_fadvise"); | |
279 | exit(EXIT_FAILURE); | |
280 | } | |
281 | ||
282 | ret = rand_write(fd, len); | |
283 | if (ret) { | |
284 | exit(EXIT_FAILURE); | |
285 | } | |
286 | ||
287 | ret = close(fd); | |
288 | assert(!ret); | |
289 | exit(EXIT_SUCCESS); | |
290 | } |