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