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