1 /*-
2 * Public Domain 2014-2018 MongoDB, Inc.
3 * Public Domain 2008-2014 WiredTiger, Inc.
4 *
5 * This is free and unencumbered software released into the public domain.
6 *
7 * Anyone is free to copy, modify, publish, use, compile, sell, or
8 * distribute this software, either in source code form or as a compiled
9 * binary, for any purpose, commercial or non-commercial, and by any
10 * means.
11 *
12 * In jurisdictions that recognize copyright laws, the author or authors
13 * of this software dedicate any and all copyright interest in the
14 * software to the public domain. We make this dedication for the benefit
15 * of the public at large and to the detriment of our heirs and
16 * successors. We intend this dedication to be an overt act of
17 * relinquishment in perpetuity of all present and future rights to this
18 * software under copyright law.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
24 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 * OTHER DEALINGS IN THE SOFTWARE.
27 */
28
29 #include "format.h"
30
31 #ifndef MAX
32 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
33 #endif
34
35 void
key_init(void)36 key_init(void)
37 {
38 size_t i;
39 uint32_t max;
40
41 /*
42 * The key is a variable length item with a leading 10-digit value.
43 * Since we have to be able re-construct it from the record number
44 * (when doing row lookups), we pre-load a set of random lengths in
45 * a lookup table, and then use the record number to choose one of
46 * the pre-loaded lengths.
47 *
48 * Fill in the random key lengths.
49 *
50 * Focus on relatively small items, admitting the possibility of larger
51 * items. Pick a size close to the minimum most of the time, only create
52 * a larger item 1 in 20 times.
53 */
54 for (i = 0;
55 i < sizeof(g.key_rand_len) / sizeof(g.key_rand_len[0]); ++i) {
56 max = g.c_key_max;
57 if (i % 20 != 0 && max > g.c_key_min + 20)
58 max = g.c_key_min + 20;
59 g.key_rand_len[i] = mmrand(NULL, g.c_key_min, max);
60 }
61 }
62
63 void
key_gen_init(WT_ITEM * key)64 key_gen_init(WT_ITEM *key)
65 {
66 size_t i, len;
67 char *p;
68
69 len = MAX(KILOBYTE(100), g.c_key_max);
70 p = dmalloc(len);
71 for (i = 0; i < len; ++i)
72 p[i] = "abcdefghijklmnopqrstuvwxyz"[i % 26];
73
74 key->mem = p;
75 key->memsize = len;
76 key->data = key->mem;
77 key->size = 0;
78 }
79
80 void
key_gen_teardown(WT_ITEM * key)81 key_gen_teardown(WT_ITEM *key)
82 {
83 free(key->mem);
84 memset(key, 0, sizeof(*key));
85 }
86
87 static void
key_gen_common(WT_ITEM * key,uint64_t keyno,const char * const suffix)88 key_gen_common(WT_ITEM *key, uint64_t keyno, const char * const suffix)
89 {
90 int len;
91 char *p;
92
93 p = key->mem;
94
95 /*
96 * The key always starts with a 10-digit string (the specified row)
97 * followed by two digits, a random number between 1 and 15 if it's
98 * an insert, otherwise 00.
99 */
100 u64_to_string_zf(keyno, key->mem, 11);
101 p[10] = '.';
102 p[11] = suffix[0];
103 p[12] = suffix[1];
104 len = 13;
105
106 /*
107 * In a column-store, the key is only used for Berkeley DB inserts,
108 * and so it doesn't need a random length.
109 */
110 if (g.type == ROW) {
111 p[len] = '/';
112
113 /*
114 * Because we're doing table lookup for key sizes, we weren't
115 * able to set really big keys sizes in the table, the table
116 * isn't big enough to keep our hash from selecting too many
117 * big keys and blowing out the cache. Handle that here, use a
118 * really big key 1 in 2500 times.
119 */
120 len = keyno % 2500 == 0 && g.c_key_max < KILOBYTE(80) ?
121 KILOBYTE(80) :
122 (int)g.key_rand_len[keyno % WT_ELEMENTS(g.key_rand_len)];
123 }
124
125 key->data = key->mem;
126 key->size = (size_t)len;
127 }
128
129 void
key_gen(WT_ITEM * key,uint64_t keyno)130 key_gen(WT_ITEM *key, uint64_t keyno)
131 {
132 key_gen_common(key, keyno, "00");
133 }
134
135 void
key_gen_insert(WT_RAND_STATE * rnd,WT_ITEM * key,uint64_t keyno)136 key_gen_insert(WT_RAND_STATE *rnd, WT_ITEM *key, uint64_t keyno)
137 {
138 static const char * const suffix[15] = {
139 "01", "02", "03", "04", "05",
140 "06", "07", "08", "09", "10",
141 "11", "12", "13", "14", "15"
142 };
143
144 key_gen_common(key, keyno, suffix[mmrand(rnd, 0, 14)]);
145 }
146
147 static char *val_base; /* Base/original value */
148 static uint32_t val_dup_data_len; /* Length of duplicate data items */
149 static uint32_t val_len; /* Length of data items */
150
151 static inline uint32_t
value_len(WT_RAND_STATE * rnd,uint64_t keyno,uint32_t min,uint32_t max)152 value_len(WT_RAND_STATE *rnd, uint64_t keyno, uint32_t min, uint32_t max)
153 {
154 /*
155 * Focus on relatively small items, admitting the possibility of larger
156 * items. Pick a size close to the minimum most of the time, only create
157 * a larger item 1 in 20 times, and a really big item 1 in somewhere
158 * around 2500 items.
159 */
160 if (keyno % 2500 == 0 && max < KILOBYTE(80)) {
161 min = KILOBYTE(80);
162 max = KILOBYTE(100);
163 } else if (keyno % 20 != 0 && max > min + 20)
164 max = min + 20;
165 return (mmrand(rnd, min, max));
166 }
167
168 void
val_init(void)169 val_init(void)
170 {
171 size_t i;
172
173 /*
174 * Set initial buffer contents to recognizable text.
175 *
176 * Add a few extra bytes in order to guarantee we can always offset
177 * into the buffer by a few extra bytes, used to generate different
178 * data for column-store run-length encoded files.
179 */
180 val_len = MAX(KILOBYTE(100), g.c_value_max) + 20;
181 val_base = dmalloc(val_len);
182 for (i = 0; i < val_len; ++i)
183 val_base[i] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % 26];
184
185 val_dup_data_len = value_len(NULL,
186 (uint64_t)mmrand(NULL, 1, 20), g.c_value_min, g.c_value_max);
187 }
188
189 void
val_teardown(void)190 val_teardown(void)
191 {
192 free(val_base);
193 val_base = NULL;
194 val_dup_data_len = val_len = 0;
195 }
196
197 void
val_gen_init(WT_ITEM * value)198 val_gen_init(WT_ITEM *value)
199 {
200 value->mem = dmalloc(val_len);
201 value->memsize = val_len;
202 value->data = value->mem;
203 value->size = 0;
204 }
205
206 void
val_gen_teardown(WT_ITEM * value)207 val_gen_teardown(WT_ITEM *value)
208 {
209 free(value->mem);
210 memset(value, 0, sizeof(*value));
211 }
212
213 void
val_gen(WT_RAND_STATE * rnd,WT_ITEM * value,uint64_t keyno)214 val_gen(WT_RAND_STATE *rnd, WT_ITEM *value, uint64_t keyno)
215 {
216 char *p;
217
218 p = value->mem;
219 value->data = value->mem;
220
221 /*
222 * Fixed-length records: take the low N bits from the last digit of
223 * the record number.
224 */
225 if (g.type == FIX) {
226 switch (g.c_bitcnt) {
227 case 8: p[0] = (char)mmrand(rnd, 1, 0xff); break;
228 case 7: p[0] = (char)mmrand(rnd, 1, 0x7f); break;
229 case 6: p[0] = (char)mmrand(rnd, 1, 0x3f); break;
230 case 5: p[0] = (char)mmrand(rnd, 1, 0x1f); break;
231 case 4: p[0] = (char)mmrand(rnd, 1, 0x0f); break;
232 case 3: p[0] = (char)mmrand(rnd, 1, 0x07); break;
233 case 2: p[0] = (char)mmrand(rnd, 1, 0x03); break;
234 case 1: p[0] = 1; break;
235 }
236 value->size = 1;
237 return;
238 }
239
240 /*
241 * WiredTiger doesn't store zero-length data items in row-store files,
242 * test that by inserting a zero-length data item every so often.
243 */
244 if (keyno % 63 == 0) {
245 p[0] = '\0';
246 value->size = 0;
247 return;
248 }
249
250 /*
251 * Data items have unique leading numbers by default and random lengths;
252 * variable-length column-stores use a duplicate data value to test RLE.
253 */
254 if (g.type == VAR && mmrand(rnd, 1, 100) < g.c_repeat_data_pct) {
255 value->size = val_dup_data_len;
256 memcpy(p, val_base, value->size);
257 (void)strcpy(p, "DUPLICATEV");
258 p[10] = '/';
259 } else {
260 value->size =
261 value_len(rnd, keyno, g.c_value_min, g.c_value_max);
262 memcpy(p, val_base, value->size);
263 u64_to_string_zf(keyno, p, 11);
264 p[10] = '/';
265 }
266 }
267
268 void
track(const char * tag,uint64_t cnt,TINFO * tinfo)269 track(const char *tag, uint64_t cnt, TINFO *tinfo)
270 {
271 static size_t lastlen = 0;
272 size_t len;
273 char msg[128];
274
275 if (g.c_quiet || tag == NULL)
276 return;
277
278 if (tinfo == NULL && cnt == 0)
279 testutil_check(__wt_snprintf_len_set(
280 msg, sizeof(msg), &len, "%4d: %s", g.run_cnt, tag));
281 else if (tinfo == NULL)
282 testutil_check(__wt_snprintf_len_set(
283 msg, sizeof(msg), &len,
284 "%4d: %s: %" PRIu64, g.run_cnt, tag, cnt));
285 else
286 testutil_check(__wt_snprintf_len_set(
287 msg, sizeof(msg), &len,
288 "%4d: %s: "
289 "search %" PRIu64 "%s, "
290 "insert %" PRIu64 "%s, "
291 "update %" PRIu64 "%s, "
292 "remove %" PRIu64 "%s",
293 g.run_cnt, tag,
294 tinfo->search > M(9) ? tinfo->search / M(1) : tinfo->search,
295 tinfo->search > M(9) ? "M" : "",
296 tinfo->insert > M(9) ? tinfo->insert / M(1) : tinfo->insert,
297 tinfo->insert > M(9) ? "M" : "",
298 tinfo->update > M(9) ? tinfo->update / M(1) : tinfo->update,
299 tinfo->update > M(9) ? "M" : "",
300 tinfo->remove > M(9) ? tinfo->remove / M(1) : tinfo->remove,
301 tinfo->remove > M(9) ? "M" : ""));
302
303 if (lastlen > len) {
304 memset(msg + len, ' ', (size_t)(lastlen - len));
305 msg[lastlen] = '\0';
306 }
307 lastlen = len;
308
309 if (printf("%s\r", msg) < 0)
310 testutil_die(EIO, "printf");
311 if (fflush(stdout) == EOF)
312 testutil_die(errno, "fflush");
313 }
314
315 /*
316 * path_setup --
317 * Build the standard paths and shell commands we use.
318 */
319 void
path_setup(const char * home)320 path_setup(const char *home)
321 {
322 size_t len;
323
324 /* Home directory. */
325 g.home = dstrdup(home == NULL ? "RUNDIR" : home);
326
327 /* Log file. */
328 len = strlen(g.home) + strlen("log") + 2;
329 g.home_log = dmalloc(len);
330 testutil_check(__wt_snprintf(g.home_log, len, "%s/%s", g.home, "log"));
331
332 /* RNG log file. */
333 len = strlen(g.home) + strlen("rand") + 2;
334 g.home_rand = dmalloc(len);
335 testutil_check(__wt_snprintf(
336 g.home_rand, len, "%s/%s", g.home, "rand"));
337
338 /* Run file. */
339 len = strlen(g.home) + strlen("CONFIG") + 2;
340 g.home_config = dmalloc(len);
341 testutil_check(__wt_snprintf(
342 g.home_config, len, "%s/%s", g.home, "CONFIG"));
343
344 /* Statistics file. */
345 len = strlen(g.home) + strlen("stats") + 2;
346 g.home_stats = dmalloc(len);
347 testutil_check(__wt_snprintf(
348 g.home_stats, len, "%s/%s", g.home, "stats"));
349
350 /* BDB directory. */
351 len = strlen(g.home) + strlen("bdb") + 2;
352 g.home_bdb = dmalloc(len);
353 testutil_check(__wt_snprintf(g.home_bdb, len, "%s/%s", g.home, "bdb"));
354
355 /*
356 * Home directory initialize command: create the directory if it doesn't
357 * exist, else remove everything except the RNG log file, create the KVS
358 * subdirectory.
359 *
360 * Redirect the "cd" command to /dev/null so chatty cd implementations
361 * don't add the new working directory to our output.
362 */
363 #undef CMD
364 #ifdef _WIN32
365 #define CMD "del /q rand.copy & " \
366 "(IF EXIST %s\\rand copy /y %s\\rand rand.copy) & " \
367 "(IF EXIST %s rd /s /q %s) & mkdir %s & " \
368 "(IF EXIST rand.copy copy rand.copy %s\\rand) & " \
369 "cd %s & mkdir KVS"
370 len = strlen(g.home) * 7 + strlen(CMD) + 1;
371 g.home_init = dmalloc(len);
372 testutil_check(__wt_snprintf(g.home_init, len, CMD,
373 g.home, g.home, g.home, g.home, g.home, g.home, g.home));
374 #else
375 #define CMD "test -e %s || mkdir %s; " \
376 "cd %s > /dev/null && rm -rf `ls | sed /rand/d`; " \
377 "mkdir KVS"
378 len = strlen(g.home) * 3 + strlen(CMD) + 1;
379 g.home_init = dmalloc(len);
380 testutil_check(__wt_snprintf(
381 g.home_init, len, CMD, g.home, g.home, g.home));
382 #endif
383
384 /* Primary backup directory. */
385 len = strlen(g.home) + strlen("BACKUP") + 2;
386 g.home_backup = dmalloc(len);
387 testutil_check(__wt_snprintf(
388 g.home_backup, len, "%s/%s", g.home, "BACKUP"));
389
390 /*
391 * Backup directory initialize command, remove and re-create the primary
392 * backup directory, plus a copy we maintain for recovery testing.
393 */
394 #undef CMD
395 #ifdef _WIN32
396 #define CMD "rd /s /q %s\\%s %s\\%s & mkdir %s\\%s %s\\%s"
397 #else
398 #define CMD "rm -rf %s/%s %s/%s && mkdir %s/%s %s/%s"
399 #endif
400 len = strlen(g.home) * 4 +
401 strlen("BACKUP") * 2 + strlen("BACKUP_COPY") * 2 + strlen(CMD) + 1;
402 g.home_backup_init = dmalloc(len);
403 testutil_check(__wt_snprintf(g.home_backup_init, len, CMD,
404 g.home, "BACKUP", g.home, "BACKUP_COPY",
405 g.home, "BACKUP", g.home, "BACKUP_COPY"));
406
407 /*
408 * Salvage command, save the interesting files so we can replay the
409 * salvage command as necessary.
410 *
411 * Redirect the "cd" command to /dev/null so chatty cd implementations
412 * don't add the new working directory to our output.
413 */
414 #undef CMD
415 #ifdef _WIN32
416 #define CMD \
417 "cd %s && " \
418 "rd /q /s slvg.copy & mkdir slvg.copy && " \
419 "copy WiredTiger* slvg.copy\\ >:nul && copy wt* slvg.copy\\ >:nul"
420 #else
421 #define CMD \
422 "cd %s > /dev/null && " \
423 "rm -rf slvg.copy && mkdir slvg.copy && " \
424 "cp WiredTiger* wt* slvg.copy/"
425 #endif
426 len = strlen(g.home) + strlen(CMD) + 1;
427 g.home_salvage_copy = dmalloc(len);
428 testutil_check(__wt_snprintf(g.home_salvage_copy, len, CMD, g.home));
429 }
430
431 /*
432 * rng --
433 * Return a random number.
434 */
435 uint32_t
rng(WT_RAND_STATE * rnd)436 rng(WT_RAND_STATE *rnd)
437 {
438 u_long ulv;
439 uint32_t v;
440 char *endptr, buf[64];
441
442 /*
443 * Threaded operations have their own RNG information, otherwise we
444 * use the default.
445 */
446 if (rnd == NULL)
447 rnd = &g.rnd;
448
449 /*
450 * We can reproduce a single-threaded run based on the random numbers
451 * used in the initial run, plus the configuration files.
452 *
453 * Check g.replay and g.rand_log_stop: multithreaded runs log/replay
454 * until they get to the operations phase, then turn off log/replay,
455 * threaded operation order can't be replayed.
456 */
457 if (g.rand_log_stop)
458 return (__wt_random(rnd));
459
460 if (g.replay) {
461 if (fgets(buf, sizeof(buf), g.randfp) == NULL) {
462 if (feof(g.randfp)) {
463 fprintf(stderr,
464 "\n" "end of random number log reached\n");
465 exit(EXIT_SUCCESS);
466 }
467 testutil_die(errno, "random number log");
468 }
469
470 errno = 0;
471 ulv = strtoul(buf, &endptr, 10);
472 testutil_assert(errno == 0 && endptr[0] == '\n');
473 testutil_assert(ulv <= UINT32_MAX);
474 return ((uint32_t)ulv);
475 }
476
477 v = __wt_random(rnd);
478
479 /* Save and flush the random number so we're up-to-date on error. */
480 (void)fprintf(g.randfp, "%" PRIu32 "\n", v);
481 (void)fflush(g.randfp);
482
483 return (v);
484 }
485
486 /*
487 * fclose_and_clear --
488 * Close a file and clear the handle so we don't close twice.
489 */
490 void
fclose_and_clear(FILE ** fpp)491 fclose_and_clear(FILE **fpp)
492 {
493 FILE *fp;
494
495 if ((fp = *fpp) == NULL)
496 return;
497 *fpp = NULL;
498 if (fclose(fp) != 0)
499 testutil_die(errno, "fclose");
500 return;
501 }
502
503 /*
504 * checkpoint --
505 * Periodically take a checkpoint
506 */
507 WT_THREAD_RET
checkpoint(void * arg)508 checkpoint(void *arg)
509 {
510 WT_CONNECTION *conn;
511 WT_DECL_RET;
512 WT_SESSION *session;
513 u_int secs;
514 const char *ckpt_config;
515 char config_buf[64];
516 bool backup_locked;
517
518 (void)arg;
519 conn = g.wts_conn;
520 testutil_check(conn->open_session(conn, NULL, NULL, &session));
521
522 for (secs = mmrand(NULL, 1, 10); !g.workers_finished;) {
523 if (secs > 0) {
524 __wt_sleep(1, 0);
525 --secs;
526 continue;
527 }
528
529 /*
530 * LSM and data-sources don't support named checkpoints. Also,
531 * don't attempt named checkpoints during a hot backup. It's
532 * OK to create named checkpoints during a hot backup, but we
533 * can't delete them, so repeating an already existing named
534 * checkpoint will fail when we can't drop the previous one.
535 */
536 ckpt_config = NULL;
537 backup_locked = false;
538 if (!DATASOURCE("helium") && !DATASOURCE("kvsbdb") &&
539 !DATASOURCE("lsm"))
540 switch (mmrand(NULL, 1, 20)) {
541 case 1:
542 /*
543 * 5% create a named snapshot. Rotate between a
544 * few names to test multiple named snapshots in
545 * the system.
546 */
547 ret = pthread_rwlock_trywrlock(&g.backup_lock);
548 if (ret == 0) {
549 backup_locked = true;
550 testutil_check(__wt_snprintf(
551 config_buf, sizeof(config_buf),
552 "name=mine.%" PRIu32,
553 mmrand(NULL, 1, 4)));
554 ckpt_config = config_buf;
555 } else if (ret != EBUSY)
556 testutil_check(ret);
557 break;
558 case 2:
559 /*
560 * 5% drop all named snapshots.
561 */
562 ret = pthread_rwlock_trywrlock(&g.backup_lock);
563 if (ret == 0) {
564 backup_locked = true;
565 ckpt_config = "drop=(all)";
566 } else if (ret != EBUSY)
567 testutil_check(ret);
568 break;
569 }
570
571 testutil_check(session->checkpoint(session, ckpt_config));
572
573 if (backup_locked)
574 testutil_check(pthread_rwlock_unlock(&g.backup_lock));
575
576 secs = mmrand(NULL, 5, 40);
577 }
578
579 testutil_check(session->close(session, NULL));
580 return (WT_THREAD_RET_VALUE);
581 }
582
583 /*
584 * timestamp --
585 * Periodically update the oldest timestamp.
586 */
587 WT_THREAD_RET
timestamp(void * arg)588 timestamp(void *arg)
589 {
590 WT_CONNECTION *conn;
591 WT_DECL_RET;
592 WT_SESSION *session;
593 char buf[64];
594 bool done;
595
596 (void)(arg);
597 conn = g.wts_conn;
598
599 testutil_check(conn->open_session(conn, NULL, NULL, &session));
600
601 testutil_check(
602 __wt_snprintf(buf, sizeof(buf), "%s", "oldest_timestamp="));
603
604 /* Update the oldest timestamp at least once every 15 seconds. */
605 done = false;
606 do {
607 /*
608 * Do a final bump of the oldest timestamp as part of shutting
609 * down the worker threads, otherwise recent operations can
610 * prevent verify from running.
611 */
612 if (g.workers_finished)
613 done = true;
614 else
615 random_sleep(&g.rnd, 15);
616
617 /*
618 * Lock out transaction timestamp operations. The lock acts as a
619 * barrier ensuring we've checked if the workers have finished,
620 * we don't want that line reordered.
621 */
622 testutil_check(pthread_rwlock_wrlock(&g.ts_lock));
623
624 ret = conn->query_timestamp(conn,
625 buf + strlen("oldest_timestamp="), "get=all_committed");
626 testutil_assert(ret == 0 || ret == WT_NOTFOUND);
627 if (ret == 0)
628 testutil_check(conn->set_timestamp(conn, buf));
629
630 testutil_check(pthread_rwlock_unlock(&g.ts_lock));
631 } while (!done);
632
633 testutil_check(session->close(session, NULL));
634 return (WT_THREAD_RET_VALUE);
635 }
636
637 /*
638 * alter --
639 * Periodically alter a table's metadata.
640 */
641 WT_THREAD_RET
alter(void * arg)642 alter(void *arg)
643 {
644 WT_CONNECTION *conn;
645 WT_DECL_RET;
646 WT_SESSION *session;
647 u_int period;
648 char buf[32];
649 bool access_value;
650
651 (void)(arg);
652 conn = g.wts_conn;
653
654 /*
655 * Only alter the access pattern hint. If we alter the cache resident
656 * setting we may end up with a setting that fills cache and doesn't
657 * allow it to be evicted.
658 */
659 access_value = false;
660
661 /* Open a session */
662 testutil_check(conn->open_session(conn, NULL, NULL, &session));
663
664 while (!g.workers_finished) {
665 period = mmrand(NULL, 1, 10);
666
667 testutil_check(__wt_snprintf(buf, sizeof(buf),
668 "access_pattern_hint=%s",
669 access_value ? "random" : "none"));
670 access_value = !access_value;
671 /*
672 * Alter can return EBUSY if concurrent with other operations.
673 */
674 while ((ret = session->alter(session, g.uri, buf)) != 0 &&
675 ret != EBUSY)
676 testutil_die(ret, "session.alter");
677 while (period > 0 && !g.workers_finished) {
678 --period;
679 __wt_sleep(1, 0);
680 }
681 }
682
683 testutil_check(session->close(session, NULL));
684 return (WT_THREAD_RET_VALUE);
685 }
686
687 /*
688 * print_item_data --
689 * Display a single data/size pair, with a tag.
690 */
691 void
print_item_data(const char * tag,const uint8_t * data,size_t size)692 print_item_data(const char *tag, const uint8_t *data, size_t size)
693 {
694 static const char hex[] = "0123456789abcdef";
695 u_char ch;
696
697 fprintf(stderr, "\t%s {", tag);
698 if (g.type == FIX)
699 fprintf(stderr, "0x%02x", data[0]);
700 else
701 for (; size > 0; --size, ++data) {
702 ch = data[0];
703 if (__wt_isprint(ch))
704 fprintf(stderr, "%c", (int)ch);
705 else
706 fprintf(stderr, "%x%x",
707 (u_int)hex[(data[0] & 0xf0) >> 4],
708 (u_int)hex[data[0] & 0x0f]);
709 }
710 fprintf(stderr, "}\n");
711 }
712
713 /*
714 * print_item --
715 * Display a single data/size pair, with a tag.
716 */
717 void
print_item(const char * tag,WT_ITEM * item)718 print_item(const char *tag, WT_ITEM *item)
719 {
720 print_item_data(tag, item->data, item->size);
721 }
722