/*- * Copyright (c) 2014-2020 MongoDB, Inc. * Copyright (c) 2008-2014 WiredTiger, Inc. * All rights reserved. * * See the file LICENSE for redistribution information. */ #include "wt_internal.h" #ifdef HAVE_DIAGNOSTIC /* * We pass around a session handle and output information, group it together. */ typedef struct __wt_dbg WT_DBG; struct __wt_dbg { WT_SESSION_IMPL *session; /* Enclosing session */ /* * When using the standard event handlers, the debugging output has to do its own message * handling because its output isn't line-oriented. */ FILE *fp; /* Optional file handle */ WT_ITEM *msg; /* Buffered message */ int (*f)(WT_DBG *, const char *, ...) /* Function to write */ WT_GCC_FUNC_DECL_ATTRIBUTE((format(printf, 2, 3))); const char *key_format; const char *value_format; WT_ITEM *t1, *t2; /* Temporary space */ }; static const /* Output separator */ char *const sep = "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; static int __debug_cell(WT_DBG *, const WT_PAGE_HEADER *, WT_CELL_UNPACK *); static int __debug_cell_data(WT_DBG *, WT_PAGE *, int, const char *, WT_CELL_UNPACK *); static int __debug_col_skip(WT_DBG *, WT_INSERT_HEAD *, const char *, bool); static int __debug_config(WT_SESSION_IMPL *, WT_DBG *, const char *); static int __debug_dsk_cell(WT_DBG *, const WT_PAGE_HEADER *); static int __debug_dsk_col_fix(WT_DBG *, const WT_PAGE_HEADER *); static int __debug_page(WT_DBG *, WT_REF *, uint32_t); static int __debug_page_col_fix(WT_DBG *, WT_REF *); static int __debug_page_col_int(WT_DBG *, WT_PAGE *, uint32_t); static int __debug_page_col_var(WT_DBG *, WT_REF *); static int __debug_page_metadata(WT_DBG *, WT_REF *); static int __debug_page_row_int(WT_DBG *, WT_PAGE *, uint32_t); static int __debug_page_row_leaf(WT_DBG *, WT_PAGE *); static int __debug_ref(WT_DBG *, WT_REF *); static int __debug_row_skip(WT_DBG *, WT_INSERT_HEAD *); static int __debug_tree(WT_SESSION_IMPL *, WT_REF *, const char *, uint32_t); static int __debug_update(WT_DBG *, WT_UPDATE *, bool); static int __debug_wrapup(WT_DBG *); /* * __wt_debug_set_verbose -- * Set verbose flags from the debugger. */ int __wt_debug_set_verbose(WT_SESSION_IMPL *session, const char *v) { char buf[256]; const char *cfg[2] = {NULL, NULL}; WT_RET(__wt_snprintf(buf, sizeof(buf), "verbose=[%s]", v)); cfg[0] = buf; return (__wt_verbose_config(session, cfg)); } /* * __debug_hex_byte -- * Output a single byte in hex. */ static inline int __debug_hex_byte(WT_DBG *ds, uint8_t v) { return (ds->f(ds, "#%c%c", __wt_hex((v & 0xf0) >> 4), __wt_hex(v & 0x0f))); } /* * __debug_bytes -- * Dump a single set of bytes. */ static int __debug_bytes(WT_DBG *ds, const void *data_arg, size_t size) { size_t i; const uint8_t *data; u_char ch; for (data = data_arg, i = 0; i < size; ++i, ++data) { ch = data[0]; if (__wt_isprint(ch)) WT_RET(ds->f(ds, "%c", (int)ch)); else WT_RET(__debug_hex_byte(ds, data[0])); } return (0); } /* * __debug_item -- * Dump a single data/size item, with an optional tag. */ static int __debug_item(WT_DBG *ds, const char *tag, const void *data_arg, size_t size) { WT_RET(ds->f(ds, "\t%s%s{", tag == NULL ? "" : tag, tag == NULL ? "" : " ")); WT_RET(__debug_bytes(ds, data_arg, size)); WT_RET(ds->f(ds, "}\n")); return (0); } /* * __debug_item_key -- * Dump a single data/size key item, with an optional tag. */ static int __debug_item_key(WT_DBG *ds, const char *tag, const void *data_arg, size_t size) { WT_SESSION_IMPL *session; session = ds->session; /* * If the format is 'S', it's a string and our version of it may not yet be nul-terminated. */ if (WT_STREQ(ds->key_format, "S") && ((char *)data_arg)[size - 1] != '\0') { WT_RET(__wt_buf_fmt(session, ds->t2, "%.*s", (int)size, (char *)data_arg)); data_arg = ds->t2->data; size = ds->t2->size + 1; } return (ds->f(ds, "\t%s%s{%s}\n", tag == NULL ? "" : tag, tag == NULL ? "" : " ", __wt_buf_set_printable_format(session, data_arg, size, ds->key_format, ds->t1))); } /* * __debug_item_value -- * Dump a single data/size value item, with an optional tag. */ static int __debug_item_value(WT_DBG *ds, const char *tag, const void *data_arg, size_t size) { WT_SESSION_IMPL *session; session = ds->session; if (size == 0) return (ds->f(ds, "\t%s%s{}\n", tag == NULL ? "" : tag, tag == NULL ? "" : " ")); /* * If the format is 'S', it's a string and our version of it may not yet be nul-terminated. */ if (WT_STREQ(ds->value_format, "S") && ((char *)data_arg)[size - 1] != '\0') { WT_RET(__wt_buf_fmt(session, ds->t2, "%.*s", (int)size, (char *)data_arg)); data_arg = ds->t2->data; size = ds->t2->size + 1; } return (ds->f(ds, "\t%s%s{%s}\n", tag == NULL ? "" : tag, tag == NULL ? "" : " ", __wt_buf_set_printable_format(session, data_arg, size, ds->value_format, ds->t1))); } /* * __dmsg_event -- * Send a debug message to the event handler. */ static int __dmsg_event(WT_DBG *ds, const char *fmt, ...) { WT_DECL_RET; WT_ITEM *msg; WT_SESSION_IMPL *session; size_t len, space; char *p; va_list ap; session = ds->session; /* * Debug output chunks are not necessarily terminated with a newline character. It's easy if * we're dumping to a stream, but if we're dumping to an event handler, which is line-oriented, * we must buffer the output chunk, and pass it to the event handler once we see a terminating * newline. */ msg = ds->msg; for (;;) { p = (char *)msg->mem + msg->size; space = msg->memsize - msg->size; va_start(ap, fmt); ret = __wt_vsnprintf_len_set(p, space, &len, fmt, ap); va_end(ap); WT_RET(ret); /* Check if there was enough space. */ if (len < space) { msg->size += len; break; } /* * There's not much to do on error without checking for an error return on every single * printf. Anyway, it's pretty unlikely and this is debugging output, I'm not going to worry * about it. */ WT_RET(__wt_buf_grow(session, msg, msg->memsize + len + 128)); } if (((uint8_t *)msg->mem)[msg->size - 1] == '\n') { ((uint8_t *)msg->mem)[msg->size - 1] = '\0'; WT_RET(__wt_msg(session, "%s", (char *)msg->mem)); msg->size = 0; } return (0); } /* * __dmsg_file -- * Send a debug message to a file. */ static int __dmsg_file(WT_DBG *ds, const char *fmt, ...) { WT_DECL_RET; va_list ap; va_start(ap, fmt); ret = vfprintf(ds->fp, fmt, ap) < 0 ? EIO : 0; va_end(ap); return (ret); } /* * __debug_config -- * Configure debugging output. */ static int __debug_config(WT_SESSION_IMPL *session, WT_DBG *ds, const char *ofile) { WT_BTREE *btree; WT_DECL_RET; memset(ds, 0, sizeof(WT_DBG)); ds->session = session; WT_ERR(__wt_scr_alloc(session, 512, &ds->t1)); WT_ERR(__wt_scr_alloc(session, 512, &ds->t2)); /* * If we weren't given a file, we use the default event handler, and we'll have to buffer * messages. */ if (ofile == NULL) { WT_ERR(__wt_scr_alloc(session, 512, &ds->msg)); ds->f = __dmsg_event; } else { if ((ds->fp = fopen(ofile, "w")) == NULL) WT_ERR(__wt_set_return(session, EIO)); __wt_stream_set_line_buffer(ds->fp); ds->f = __dmsg_file; } btree = S2BT(session); ds->key_format = btree->key_format; ds->value_format = btree->value_format; return (0); err: WT_TRET(__debug_wrapup(ds)); return (ret); } /* * __debug_wrapup -- * Flush any remaining output, release resources. */ static int __debug_wrapup(WT_DBG *ds) { WT_DECL_RET; WT_ITEM *msg; WT_SESSION_IMPL *session; session = ds->session; msg = ds->msg; __wt_scr_free(session, &ds->t1); __wt_scr_free(session, &ds->t2); /* * Discard the buffer -- it shouldn't have anything in it, but might as well be cautious. */ if (msg != NULL) { if (msg->size != 0) ret = __wt_msg(session, "%s", (char *)msg->mem); __wt_scr_free(session, &ds->msg); } /* Close any file we opened. */ if (ds->fp != NULL) (void)fclose(ds->fp); return (ret); } /* * __wt_debug_addr_print -- * Print out an address. */ int __wt_debug_addr_print(WT_SESSION_IMPL *session, const uint8_t *addr, size_t addr_size) { WT_DECL_ITEM(buf); WT_DECL_RET; WT_RET(__wt_scr_alloc(session, 128, &buf)); ret = __wt_fprintf( session, WT_STDERR(session), "%s\n", __wt_addr_string(session, addr, addr_size, buf)); __wt_scr_free(session, &buf); return (ret); } /* * __wt_debug_addr -- * Read and dump a disk page in debugging mode, using an addr/size pair. */ int __wt_debug_addr(WT_SESSION_IMPL *session, const uint8_t *addr, size_t addr_size, const char *ofile) { WT_BM *bm; WT_DECL_ITEM(buf); WT_DECL_RET; WT_ASSERT(session, S2BT_SAFE(session) != NULL); bm = S2BT(session)->bm; WT_RET(__wt_scr_alloc(session, 1024, &buf)); WT_ERR(bm->read(bm, session, buf, addr, addr_size)); ret = __wt_debug_disk(session, buf->mem, ofile); err: __wt_scr_free(session, &buf); return (ret); } /* * __wt_debug_offset_blind -- * Read and dump a disk page in debugging mode, using a file offset. */ int __wt_debug_offset_blind(WT_SESSION_IMPL *session, wt_off_t offset, const char *ofile) { uint32_t checksum, size; WT_ASSERT(session, S2BT_SAFE(session) != NULL); /* * This routine depends on the default block manager's view of files, where an address consists * of a file offset, length, and checksum. This is for debugging only. */ WT_RET(__wt_block_read_off_blind(session, S2BT(session)->bm->block, offset, &size, &checksum)); return (__wt_debug_offset(session, offset, size, checksum, ofile)); } /* * __wt_debug_offset -- * Read and dump a disk page in debugging mode, using a file offset/size/checksum triplet. */ int __wt_debug_offset( WT_SESSION_IMPL *session, wt_off_t offset, uint32_t size, uint32_t checksum, const char *ofile) { WT_DECL_ITEM(buf); WT_DECL_RET; uint8_t addr[WT_BTREE_MAX_ADDR_COOKIE], *endp; WT_ASSERT(session, S2BT_SAFE(session) != NULL); /* * This routine depends on the default block manager's view of files, where an address consists * of a file offset, length, and checksum. This is for debugging only: other block managers * might not see a file or address the same way, that's why there's no block manager method. * * Convert the triplet into an address structure. */ endp = addr; WT_RET(__wt_block_addr_to_buffer(S2BT(session)->bm->block, &endp, offset, size, checksum)); /* * Read the address through the btree I/O functions (so the block is decompressed as necessary). */ WT_RET(__wt_scr_alloc(session, 0, &buf)); WT_ERR(__wt_bt_read(session, buf, addr, WT_PTRDIFF(endp, addr))); ret = __wt_debug_disk(session, buf->mem, ofile); err: __wt_scr_free(session, &buf); return (ret); } /* * __wt_debug_disk -- * Dump a disk page in debugging mode. */ int __wt_debug_disk(WT_SESSION_IMPL *session, const WT_PAGE_HEADER *dsk, const char *ofile) { WT_DBG *ds, _ds; WT_DECL_RET; ds = &_ds; WT_RET(__debug_config(session, ds, ofile)); WT_ERR(ds->f(ds, "%s page", __wt_page_type_string(dsk->type))); switch (dsk->type) { case WT_PAGE_BLOCK_MANAGER: break; case WT_PAGE_COL_FIX: case WT_PAGE_COL_INT: case WT_PAGE_COL_VAR: WT_ERR(ds->f(ds, ", recno %" PRIu64, dsk->recno)); /* FALLTHROUGH */ case WT_PAGE_ROW_INT: case WT_PAGE_ROW_LEAF: WT_ERR(ds->f(ds, ", entries %" PRIu32, dsk->u.entries)); break; case WT_PAGE_OVFL: WT_ERR(ds->f(ds, ", datalen %" PRIu32, dsk->u.datalen)); break; default: WT_ERR(__wt_illegal_value(session, dsk->type)); } if (F_ISSET(dsk, WT_PAGE_COMPRESSED)) WT_ERR(ds->f(ds, ", compressed")); if (F_ISSET(dsk, WT_PAGE_ENCRYPTED)) WT_ERR(ds->f(ds, ", encrypted")); if (F_ISSET(dsk, WT_PAGE_EMPTY_V_ALL)) WT_ERR(ds->f(ds, ", empty-all")); if (F_ISSET(dsk, WT_PAGE_EMPTY_V_NONE)) WT_ERR(ds->f(ds, ", empty-none")); if (F_ISSET(dsk, WT_PAGE_LAS_UPDATE)) WT_ERR(ds->f(ds, ", LAS-update")); WT_ERR(ds->f(ds, ", generation %" PRIu64 "\n", dsk->write_gen)); switch (dsk->type) { case WT_PAGE_BLOCK_MANAGER: break; case WT_PAGE_COL_FIX: WT_ERR(__debug_dsk_col_fix(ds, dsk)); break; case WT_PAGE_COL_INT: case WT_PAGE_COL_VAR: case WT_PAGE_ROW_INT: case WT_PAGE_ROW_LEAF: WT_ERR(__debug_dsk_cell(ds, dsk)); break; default: break; } err: WT_TRET(__debug_wrapup(ds)); return (ret); } /* * __debug_dsk_col_fix -- * Dump a WT_PAGE_COL_FIX page. */ static int __debug_dsk_col_fix(WT_DBG *ds, const WT_PAGE_HEADER *dsk) { WT_BTREE *btree; uint32_t i; uint8_t v; WT_ASSERT(ds->session, S2BT_SAFE(ds->session) != NULL); btree = S2BT(ds->session); WT_FIX_FOREACH (btree, dsk, v, i) { WT_RET(ds->f(ds, "\t{")); WT_RET(__debug_hex_byte(ds, v)); WT_RET(ds->f(ds, "}\n")); } return (0); } /* * __debug_dsk_cell -- * Dump a page of WT_CELL's. */ static int __debug_dsk_cell(WT_DBG *ds, const WT_PAGE_HEADER *dsk) { WT_BTREE *btree; WT_CELL_UNPACK unpack; WT_ASSERT(ds->session, S2BT_SAFE(ds->session) != NULL); btree = S2BT(ds->session); WT_CELL_FOREACH_BEGIN (ds->session, btree, dsk, unpack) { WT_RET(__debug_cell(ds, dsk, &unpack)); } WT_CELL_FOREACH_END; return (0); } /* * __debug_tree_shape_info -- * Pretty-print information about a page. */ static char * __debug_tree_shape_info(WT_REF *ref, char *buf, size_t len) { WT_PAGE *page; uint64_t v; const char *unit; page = ref->page; v = page->memory_footprint; if (v > WT_GIGABYTE) { v /= WT_GIGABYTE; unit = "G"; } else if (v > WT_MEGABYTE) { v /= WT_MEGABYTE; unit = "M"; } else if (v > WT_KILOBYTE) { v /= WT_KILOBYTE; unit = "K"; } else { unit = "B"; } WT_IGNORE_RET( __wt_snprintf(buf, len, "(%p, %" PRIu64 "%s, evict gen %" PRIu64 ", create gen %" PRIu64 ")", (void *)ref, v, unit, page->evict_pass_gen, page->cache_create_gen)); return (buf); } /* * __debug_tree_shape_worker -- * Dump information about the current page and descend. */ static int __debug_tree_shape_worker(WT_DBG *ds, WT_REF *ref, int level) { WT_REF *walk; WT_SESSION_IMPL *session; char buf[128]; session = ds->session; if (WT_PAGE_IS_INTERNAL(ref->page)) { WT_RET(ds->f(ds, "%*s" "I" "%d %s\n", level * 3, " ", level, __debug_tree_shape_info(ref, buf, sizeof(buf)))); WT_INTL_FOREACH_BEGIN (session, ref->page, walk) { if (walk->state == WT_REF_MEM) WT_RET(__debug_tree_shape_worker(ds, walk, level + 1)); } WT_INTL_FOREACH_END; } else WT_RET(ds->f(ds, "%*s" "L" " %s\n", level * 3, " ", __debug_tree_shape_info(ref, buf, sizeof(buf)))); return (0); } /* * __wt_debug_tree_shape -- * Dump the shape of the in-memory tree. */ int __wt_debug_tree_shape(WT_SESSION_IMPL *session, WT_REF *ref, const char *ofile) { WT_DBG *ds, _ds; WT_DECL_RET; WT_ASSERT(session, S2BT_SAFE(session) != NULL); ds = &_ds; WT_RET(__debug_config(session, ds, ofile)); /* A NULL WT_REF starts at the top of the tree -- it's a convenience. */ if (ref == NULL) ref = &S2BT(session)->root; WT_WITH_PAGE_INDEX(session, ret = __debug_tree_shape_worker(ds, ref, 1)); WT_TRET(__debug_wrapup(ds)); return (ret); } /* AUTOMATIC FLAG VALUE GENERATION START */ #define WT_DEBUG_TREE_LEAF 0x1u /* Debug leaf pages */ #define WT_DEBUG_TREE_WALK 0x2u /* Descend the tree */ /* AUTOMATIC FLAG VALUE GENERATION STOP */ /* * __wt_debug_tree_all -- * Dump the in-memory information for a tree, including leaf pages. */ int __wt_debug_tree_all(void *session_arg, WT_BTREE *btree, WT_REF *ref, const char *ofile) { WT_DECL_RET; WT_SESSION_IMPL *session; /* * Allow an explicit btree as an argument, as one may not yet be set on the session. */ session = (WT_SESSION_IMPL *)session_arg; if (btree == NULL) btree = S2BT(session); WT_WITH_BTREE(session, btree, ret = __debug_tree(session, ref, ofile, WT_DEBUG_TREE_LEAF | WT_DEBUG_TREE_WALK)); return (ret); } /* * __wt_debug_tree -- * Dump the in-memory information for a tree, not including leaf pages. */ int __wt_debug_tree(void *session_arg, WT_BTREE *btree, WT_REF *ref, const char *ofile) { WT_DECL_RET; WT_SESSION_IMPL *session; /* * Allow an explicit btree as an argument, as one may not yet be set on the session. */ session = (WT_SESSION_IMPL *)session_arg; if (btree == NULL) btree = S2BT(session); WT_WITH_BTREE(session, btree, ret = __debug_tree(session, ref, ofile, WT_DEBUG_TREE_WALK)); return (ret); } /* * __wt_debug_page -- * Dump the in-memory information for a page. */ int __wt_debug_page(void *session_arg, WT_BTREE *btree, WT_REF *ref, const char *ofile) { WT_DBG *ds, _ds; WT_DECL_RET; WT_SESSION_IMPL *session; /* * Allow an explicit btree as an argument, as one may not yet be set on the session. */ session = (WT_SESSION_IMPL *)session_arg; if (btree == NULL) btree = S2BT(session); ds = &_ds; WT_WITH_BTREE(session, btree, ret = __debug_config(session, ds, ofile)); WT_RET(ret); WT_WITH_BTREE(session, btree, ret = __debug_page(ds, ref, WT_DEBUG_TREE_LEAF)); WT_TRET(__debug_wrapup(ds)); return (ret); } /* * __wt_debug_cursor_page -- * Dump the in-memory information for a cursor-referenced page. */ int __wt_debug_cursor_page(void *cursor_arg, const char *ofile) WT_GCC_FUNC_ATTRIBUTE((visibility("default"))) { WT_CURSOR *cursor; WT_CURSOR_BTREE *cbt; cursor = cursor_arg; cbt = cursor_arg; return (__wt_debug_page(cursor->session, cbt->btree, cbt->ref, ofile)); } /* * __wt_debug_cursor_las -- * Dump the LAS tree given a user cursor. */ int __wt_debug_cursor_las(void *cursor_arg, const char *ofile) WT_GCC_FUNC_ATTRIBUTE((visibility("default"))) { WT_CONNECTION_IMPL *conn; WT_CURSOR *cursor; WT_CURSOR_BTREE *cbt; WT_SESSION_IMPL *las_session; cursor = cursor_arg; conn = S2C((WT_SESSION_IMPL *)cursor->session); las_session = conn->cache->las_session[0]; if (las_session == NULL) return (0); cbt = (WT_CURSOR_BTREE *)las_session->las_cursor; return (__wt_debug_tree_all(las_session, cbt->btree, NULL, ofile)); } /* * __debug_tree -- * Dump the in-memory information for a tree. */ static int __debug_tree(WT_SESSION_IMPL *session, WT_REF *ref, const char *ofile, uint32_t flags) { WT_DBG *ds, _ds; WT_DECL_RET; ds = &_ds; WT_RET(__debug_config(session, ds, ofile)); /* A NULL page starts at the top of the tree -- it's a convenience. */ if (ref == NULL) ref = &S2BT(session)->root; ret = __debug_page(ds, ref, flags); WT_TRET(__debug_wrapup(ds)); return (ret); } /* * __debug_page -- * Dump the in-memory information for an in-memory page. */ static int __debug_page(WT_DBG *ds, WT_REF *ref, uint32_t flags) { WT_DECL_RET; WT_SESSION_IMPL *session; session = ds->session; /* Dump the page metadata. */ WT_WITH_PAGE_INDEX(session, ret = __debug_page_metadata(ds, ref)); WT_RET(ret); /* Dump the page. */ switch (ref->page->type) { case WT_PAGE_COL_FIX: if (LF_ISSET(WT_DEBUG_TREE_LEAF)) WT_RET(__debug_page_col_fix(ds, ref)); break; case WT_PAGE_COL_INT: WT_WITH_PAGE_INDEX(session, ret = __debug_page_col_int(ds, ref->page, flags)); WT_RET(ret); break; case WT_PAGE_COL_VAR: if (LF_ISSET(WT_DEBUG_TREE_LEAF)) WT_RET(__debug_page_col_var(ds, ref)); break; case WT_PAGE_ROW_INT: WT_WITH_PAGE_INDEX(session, ret = __debug_page_row_int(ds, ref->page, flags)); WT_RET(ret); break; case WT_PAGE_ROW_LEAF: if (LF_ISSET(WT_DEBUG_TREE_LEAF)) WT_RET(__debug_page_row_leaf(ds, ref->page)); break; default: return (__wt_illegal_value(session, ref->page->type)); } return (0); } /* * __debug_page_metadata -- * Dump an in-memory page's metadata. */ static int __debug_page_metadata(WT_DBG *ds, WT_REF *ref) { WT_PAGE *page; WT_PAGE_INDEX *pindex; WT_PAGE_MODIFY *mod; WT_SESSION_IMPL *session; uint64_t split_gen; uint32_t entries; session = ds->session; page = ref->page; mod = page->modify; split_gen = 0; WT_RET(ds->f(ds, "%p", (void *)ref)); switch (page->type) { case WT_PAGE_COL_INT: WT_RET(ds->f(ds, " recno %" PRIu64, ref->ref_recno)); WT_INTL_INDEX_GET(session, page, pindex); entries = pindex->entries; split_gen = page->pg_intl_split_gen; break; case WT_PAGE_COL_FIX: WT_RET(ds->f(ds, " recno %" PRIu64, ref->ref_recno)); entries = page->entries; break; case WT_PAGE_COL_VAR: WT_RET(ds->f(ds, " recno %" PRIu64, ref->ref_recno)); entries = page->entries; break; case WT_PAGE_ROW_INT: WT_INTL_INDEX_GET(session, page, pindex); entries = pindex->entries; split_gen = page->pg_intl_split_gen; break; case WT_PAGE_ROW_LEAF: entries = page->entries; break; default: return (__wt_illegal_value(session, page->type)); } WT_RET(ds->f(ds, ": %s\n", __wt_page_type_string(page->type))); WT_RET(ds->f(ds, "\t" "disk %p", (void *)page->dsk)); if (page->dsk != NULL) WT_RET(ds->f(ds, ", dsk_mem_size %" PRIu32, page->dsk->mem_size)); WT_RET(ds->f(ds, ", entries %" PRIu32, entries)); WT_RET(ds->f(ds, ", %s", __wt_page_is_modified(page) ? "dirty" : "clean")); if (F_ISSET_ATOMIC(page, WT_PAGE_BUILD_KEYS)) WT_RET(ds->f(ds, ", keys-built")); if (F_ISSET_ATOMIC(page, WT_PAGE_DISK_ALLOC)) WT_RET(ds->f(ds, ", disk-alloc")); if (F_ISSET_ATOMIC(page, WT_PAGE_DISK_MAPPED)) WT_RET(ds->f(ds, ", disk-mapped")); if (F_ISSET_ATOMIC(page, WT_PAGE_EVICT_LRU)) WT_RET(ds->f(ds, ", evict-lru")); if (F_ISSET_ATOMIC(page, WT_PAGE_OVERFLOW_KEYS)) WT_RET(ds->f(ds, ", overflow-keys")); if (F_ISSET_ATOMIC(page, WT_PAGE_SPLIT_INSERT)) WT_RET(ds->f(ds, ", split-insert")); if (F_ISSET_ATOMIC(page, WT_PAGE_UPDATE_IGNORE)) WT_RET(ds->f(ds, ", update-ignore")); if (mod != NULL) switch (mod->rec_result) { case WT_PM_REC_EMPTY: WT_RET(ds->f(ds, ", empty")); break; case WT_PM_REC_MULTIBLOCK: WT_RET(ds->f(ds, ", multiblock")); break; case WT_PM_REC_REPLACE: WT_RET(ds->f(ds, ", replaced")); break; case 0: break; default: return (__wt_illegal_value(session, mod->rec_result)); } if (split_gen != 0) WT_RET(ds->f(ds, ", split-gen=%" PRIu64, split_gen)); if (mod != NULL) WT_RET(ds->f(ds, ", page-state=%" PRIu32, mod->page_state)); WT_RET(ds->f(ds, ", memory-size %" WT_SIZET_FMT, page->memory_footprint)); WT_RET(ds->f(ds, "\n")); return (0); } /* * __debug_page_col_fix -- * Dump an in-memory WT_PAGE_COL_FIX page. */ static int __debug_page_col_fix(WT_DBG *ds, WT_REF *ref) { WT_BTREE *btree; WT_INSERT *ins; WT_PAGE *page; const WT_PAGE_HEADER *dsk; WT_SESSION_IMPL *session; uint64_t recno; uint32_t i; uint8_t v; WT_ASSERT(ds->session, S2BT_SAFE(ds->session) != NULL); session = ds->session; btree = S2BT(session); page = ref->page; dsk = page->dsk; recno = ref->ref_recno; if (dsk != NULL) { ins = WT_SKIP_FIRST(WT_COL_UPDATE_SINGLE(page)); WT_FIX_FOREACH (btree, dsk, v, i) { WT_RET(ds->f(ds, "\t%" PRIu64 "\t{", recno)); WT_RET(__debug_hex_byte(ds, v)); WT_RET(ds->f(ds, "}\n")); /* Check for a match on the update list. */ if (ins != NULL && WT_INSERT_RECNO(ins) == recno) { WT_RET(ds->f(ds, "\tupdate %" PRIu64 "\n", WT_INSERT_RECNO(ins))); WT_RET(__debug_update(ds, ins->upd, true)); ins = WT_SKIP_NEXT(ins); } ++recno; } } if (WT_COL_UPDATE_SINGLE(page) != NULL) { WT_RET(ds->f(ds, "%s", sep)); WT_RET(__debug_col_skip(ds, WT_COL_UPDATE_SINGLE(page), "update", true)); } if (WT_COL_APPEND(page) != NULL) { WT_RET(ds->f(ds, "%s", sep)); WT_RET(__debug_col_skip(ds, WT_COL_APPEND(page), "append", true)); } return (0); } /* * __debug_page_col_int -- * Dump an in-memory WT_PAGE_COL_INT page. */ static int __debug_page_col_int(WT_DBG *ds, WT_PAGE *page, uint32_t flags) { WT_REF *ref; WT_SESSION_IMPL *session; session = ds->session; WT_INTL_FOREACH_BEGIN (session, page, ref) { WT_RET(ds->f(ds, "\trecno %" PRIu64 "\n", ref->ref_recno)); WT_RET(__debug_ref(ds, ref)); } WT_INTL_FOREACH_END; if (LF_ISSET(WT_DEBUG_TREE_WALK)) { WT_INTL_FOREACH_BEGIN (session, page, ref) { if (ref->state == WT_REF_MEM) { WT_RET(ds->f(ds, "\n")); WT_RET(__debug_page(ds, ref, flags)); } } WT_INTL_FOREACH_END; } return (0); } /* * __debug_page_col_var -- * Dump an in-memory WT_PAGE_COL_VAR page. */ static int __debug_page_col_var(WT_DBG *ds, WT_REF *ref) { WT_CELL *cell; WT_CELL_UNPACK *unpack, _unpack; WT_COL *cip; WT_INSERT_HEAD *update; WT_PAGE *page; uint64_t recno, rle; uint32_t i; char tag[64]; unpack = &_unpack; page = ref->page; recno = ref->ref_recno; WT_COL_FOREACH (page, cip, i) { cell = WT_COL_PTR(page, cip); __wt_cell_unpack(ds->session, page, cell, unpack); rle = __wt_cell_rle(unpack); WT_RET(__wt_snprintf(tag, sizeof(tag), "%" PRIu64 " %" PRIu64, recno, rle)); WT_RET(__debug_cell_data(ds, page, WT_PAGE_COL_VAR, tag, unpack)); if ((update = WT_COL_UPDATE(page, cip)) != NULL) WT_RET(__debug_col_skip(ds, update, "update", false)); recno += rle; } if (WT_COL_APPEND(page) != NULL) { WT_RET(ds->f(ds, "%s", sep)); WT_RET(__debug_col_skip(ds, WT_COL_APPEND(page), "append", false)); } return (0); } /* * __debug_page_row_int -- * Dump an in-memory WT_PAGE_ROW_INT page. */ static int __debug_page_row_int(WT_DBG *ds, WT_PAGE *page, uint32_t flags) { WT_REF *ref; WT_SESSION_IMPL *session; size_t len; void *p; session = ds->session; WT_INTL_FOREACH_BEGIN (session, page, ref) { __wt_ref_key(page, ref, &p, &len); WT_RET(__debug_item_key(ds, "K", p, len)); WT_RET(__debug_ref(ds, ref)); } WT_INTL_FOREACH_END; if (LF_ISSET(WT_DEBUG_TREE_WALK)) { WT_INTL_FOREACH_BEGIN (session, page, ref) { if (ref->state == WT_REF_MEM) { WT_RET(ds->f(ds, "\n")); WT_RET(__debug_page(ds, ref, flags)); } } WT_INTL_FOREACH_END; } return (0); } /* * __debug_page_row_leaf -- * Dump an in-memory WT_PAGE_ROW_LEAF page. */ static int __debug_page_row_leaf(WT_DBG *ds, WT_PAGE *page) { WT_CELL_UNPACK *unpack, _unpack; WT_DECL_ITEM(key); WT_DECL_RET; WT_INSERT_HEAD *insert; WT_ROW *rip; WT_SESSION_IMPL *session; WT_UPDATE *upd; uint32_t i; session = ds->session; unpack = &_unpack; WT_RET(__wt_scr_alloc(session, 256, &key)); /* * Dump any K/V pairs inserted into the page before the first from-disk key on the page. */ if ((insert = WT_ROW_INSERT_SMALLEST(page)) != NULL) WT_ERR(__debug_row_skip(ds, insert)); /* Dump the page's K/V pairs. */ WT_ROW_FOREACH (page, rip, i) { WT_ERR(__wt_row_leaf_key(session, page, rip, key, false)); WT_ERR(__debug_item_key(ds, "K", key->data, key->size)); __wt_row_leaf_value_cell(session, page, rip, NULL, unpack); WT_ERR(__debug_cell_data(ds, page, WT_PAGE_ROW_LEAF, "V", unpack)); if ((upd = WT_ROW_UPDATE(page, rip)) != NULL) WT_ERR(__debug_update(ds, upd, false)); if ((insert = WT_ROW_INSERT(page, rip)) != NULL) WT_ERR(__debug_row_skip(ds, insert)); } err: __wt_scr_free(session, &key); return (ret); } /* * __debug_col_skip -- * Dump a column-store skiplist. */ static int __debug_col_skip(WT_DBG *ds, WT_INSERT_HEAD *head, const char *tag, bool hexbyte) { WT_INSERT *ins; WT_SKIP_FOREACH (ins, head) { WT_RET(ds->f(ds, "\t%s %" PRIu64 "\n", tag, WT_INSERT_RECNO(ins))); WT_RET(__debug_update(ds, ins->upd, hexbyte)); } return (0); } /* * __debug_row_skip -- * Dump an insert list. */ static int __debug_row_skip(WT_DBG *ds, WT_INSERT_HEAD *head) { WT_INSERT *ins; WT_SKIP_FOREACH (ins, head) { WT_RET(__debug_item_key(ds, "insert", WT_INSERT_KEY(ins), WT_INSERT_KEY_SIZE(ins))); WT_RET(__debug_update(ds, ins->upd, false)); } return (0); } /* * __debug_modify -- * Dump a modify update. */ static int __debug_modify(WT_DBG *ds, WT_UPDATE *upd) { size_t nentries, data_size, offset, size; const size_t *p; const uint8_t *data; p = (size_t *)upd->data; memcpy(&nentries, p++, sizeof(size_t)); data = upd->data + sizeof(size_t) + (nentries * 3 * sizeof(size_t)); WT_RET(ds->f(ds, "%" WT_SIZET_FMT ": ", nentries)); for (; nentries-- > 0; data += data_size) { memcpy(&data_size, p++, sizeof(size_t)); memcpy(&offset, p++, sizeof(size_t)); memcpy(&size, p++, sizeof(size_t)); WT_RET(ds->f(ds, "{%" WT_SIZET_FMT ", %" WT_SIZET_FMT ", %" WT_SIZET_FMT ", ", data_size, offset, size)); WT_RET(__debug_bytes(ds, data, data_size)); WT_RET(ds->f(ds, "}%s", nentries == 0 ? "" : ", ")); } return (0); } /* * __debug_update -- * Dump an update list. */ static int __debug_update(WT_DBG *ds, WT_UPDATE *upd, bool hexbyte) { char ts_string[WT_TS_INT_STRING_SIZE]; const char *prepare_state; for (; upd != NULL; upd = upd->next) { switch (upd->type) { case WT_UPDATE_INVALID: WT_RET(ds->f(ds, "\tvalue {invalid}\n")); break; case WT_UPDATE_BIRTHMARK: WT_RET(ds->f(ds, "\tvalue {birthmark}\n")); break; case WT_UPDATE_MODIFY: WT_RET(ds->f(ds, "\tvalue {modify: ")); WT_RET(__debug_modify(ds, upd)); WT_RET(ds->f(ds, "}\n")); break; case WT_UPDATE_RESERVE: WT_RET(ds->f(ds, "\tvalue {reserve}\n")); break; case WT_UPDATE_STANDARD: if (hexbyte) { WT_RET(ds->f(ds, "\t{")); WT_RET(__debug_hex_byte(ds, *upd->data)); WT_RET(ds->f(ds, "}\n")); } else WT_RET(__debug_item_value(ds, "value", upd->data, upd->size)); break; case WT_UPDATE_TOMBSTONE: WT_RET(ds->f(ds, "\tvalue {tombstone}\n")); break; } if (upd->txnid == WT_TXN_ABORTED) WT_RET(ds->f(ds, "\t" "txn id aborted")); else WT_RET(ds->f(ds, "\t" "txn id %" PRIu64, upd->txnid)); WT_RET(ds->f(ds, ", start_ts %s", __wt_timestamp_to_string(upd->start_ts, ts_string))); if (upd->durable_ts != WT_TS_NONE) WT_RET( ds->f(ds, ", durable_ts %s", __wt_timestamp_to_string(upd->durable_ts, ts_string))); prepare_state = NULL; switch (upd->prepare_state) { case WT_PREPARE_INIT: break; case WT_PREPARE_INPROGRESS: prepare_state = "in-progress"; break; case WT_PREPARE_LOCKED: prepare_state = "locked"; break; case WT_PREPARE_RESOLVED: prepare_state = "resolved"; break; } if (prepare_state != NULL) WT_RET(ds->f(ds, ", prepare %s", prepare_state)); WT_RET(ds->f(ds, "\n")); } return (0); } /* * __debug_ref -- * Dump a WT_REF structure. */ static int __debug_ref(WT_DBG *ds, WT_REF *ref) { WT_SESSION_IMPL *session; size_t addr_size; const uint8_t *addr; const char *state; session = ds->session; switch (ref->state) { case WT_REF_DISK: state = "disk"; break; case WT_REF_DELETED: state = "deleted"; break; case WT_REF_LOCKED: state = "locked"; break; case WT_REF_LOOKASIDE: state = "lookaside"; break; case WT_REF_MEM: state = "memory"; break; case WT_REF_READING: state = "reading"; break; case WT_REF_SPLIT: state = "split"; break; default: state = "INVALID"; break; } __wt_ref_info(session, ref, &addr, &addr_size, NULL); return (ds->f(ds, "\t" "%p %s %s\n", (void *)ref, state, __wt_addr_string(session, addr, addr_size, ds->t1))); } /* * __debug_cell -- * Dump a single unpacked WT_CELL. */ static int __debug_cell(WT_DBG *ds, const WT_PAGE_HEADER *dsk, WT_CELL_UNPACK *unpack) { WT_DECL_ITEM(buf); WT_DECL_RET; WT_SESSION_IMPL *session; char ts_string[3][WT_TS_INT_STRING_SIZE]; session = ds->session; WT_RET(ds->f(ds, "\t%s: len %" PRIu32, __wt_cell_type_string(unpack->raw), unpack->size)); /* Dump cell's per-disk page type information. */ switch (dsk->type) { case WT_PAGE_COL_INT: switch (unpack->type) { case WT_CELL_VALUE: WT_RET(ds->f(ds, ", recno: %" PRIu64, unpack->v)); break; } break; case WT_PAGE_COL_VAR: switch (unpack->type) { case WT_CELL_DEL: case WT_CELL_KEY_OVFL_RM: case WT_CELL_VALUE: case WT_CELL_VALUE_OVFL: case WT_CELL_VALUE_OVFL_RM: WT_RET(ds->f(ds, ", rle: %" PRIu64, __wt_cell_rle(unpack))); break; } break; case WT_PAGE_ROW_INT: case WT_PAGE_ROW_LEAF: switch (unpack->type) { case WT_CELL_KEY: WT_RET(ds->f(ds, ", pfx: %" PRIu8, unpack->prefix)); break; } break; } /* Dump timestamps. */ switch (unpack->raw) { case WT_CELL_ADDR_DEL: case WT_CELL_ADDR_INT: case WT_CELL_ADDR_LEAF: case WT_CELL_ADDR_LEAF_NO: WT_RET(ds->f(ds, ", ts/txn %s,%s/%" PRIu64 ",%s/%" PRIu64, __wt_timestamp_to_string(unpack->newest_durable_ts, ts_string[0]), __wt_timestamp_to_string(unpack->oldest_start_ts, ts_string[1]), unpack->oldest_start_txn, __wt_timestamp_to_string(unpack->newest_stop_ts, ts_string[2]), unpack->newest_stop_txn)); break; case WT_CELL_DEL: case WT_CELL_VALUE: case WT_CELL_VALUE_COPY: case WT_CELL_VALUE_OVFL: case WT_CELL_VALUE_OVFL_RM: case WT_CELL_VALUE_SHORT: WT_RET(ds->f(ds, ", ts/txn %s/%" PRIu64 ",%s/%" PRIu64, __wt_timestamp_to_string(unpack->start_ts, ts_string[0]), unpack->start_txn, __wt_timestamp_to_string(unpack->stop_ts, ts_string[1]), unpack->stop_txn)); break; } /* Dump addresses. */ switch (unpack->raw) { case WT_CELL_ADDR_DEL: case WT_CELL_ADDR_INT: case WT_CELL_ADDR_LEAF: case WT_CELL_ADDR_LEAF_NO: case WT_CELL_KEY_OVFL: case WT_CELL_KEY_OVFL_RM: case WT_CELL_VALUE_OVFL: case WT_CELL_VALUE_OVFL_RM: WT_RET(__wt_scr_alloc(session, 128, &buf)); ret = ds->f(ds, ", %s", __wt_addr_string(session, unpack->data, unpack->size, buf)); __wt_scr_free(session, &buf); WT_RET(ret); break; } WT_RET(ds->f(ds, "\n")); return (__debug_cell_data(ds, NULL, dsk->type, NULL, unpack)); } /* * __debug_cell_data -- * Dump a single cell's data in debugging mode. */ static int __debug_cell_data(WT_DBG *ds, WT_PAGE *page, int page_type, const char *tag, WT_CELL_UNPACK *unpack) { WT_DECL_ITEM(buf); WT_DECL_RET; WT_SESSION_IMPL *session; const char *p; session = ds->session; /* * Column-store references to deleted cells return a NULL cell reference. */ if (unpack == NULL) return (__debug_item(ds, tag, "deleted", strlen("deleted"))); /* * Row-store references to empty cells return a NULL on-page reference. */ if (unpack->cell == NULL) return (__debug_item(ds, tag, "", 0)); switch (unpack->raw) { case WT_CELL_ADDR_DEL: case WT_CELL_ADDR_INT: case WT_CELL_ADDR_LEAF: case WT_CELL_ADDR_LEAF_NO: case WT_CELL_DEL: case WT_CELL_KEY_OVFL_RM: case WT_CELL_VALUE_OVFL_RM: p = __wt_cell_type_string(unpack->raw); return (__debug_item(ds, tag, p, strlen(p))); } WT_RET(__wt_scr_alloc(session, 256, &buf)); WT_ERR(page == NULL ? __wt_dsk_cell_data_ref(session, page_type, unpack, buf) : __wt_page_cell_data_ref(session, page, unpack, buf)); switch (unpack->raw) { case WT_CELL_KEY: case WT_CELL_KEY_OVFL: case WT_CELL_KEY_PFX: case WT_CELL_KEY_SHORT: case WT_CELL_KEY_SHORT_PFX: WT_ERR(__debug_item_key(ds, tag, buf->data, buf->size)); break; case WT_CELL_VALUE: case WT_CELL_VALUE_COPY: case WT_CELL_VALUE_OVFL: case WT_CELL_VALUE_SHORT: WT_ERR(__debug_item_value(ds, tag, buf->data, buf->size)); break; default: WT_ERR(__wt_illegal_value(session, unpack->raw)); } err: __wt_scr_free(session, &buf); return (ret); } #endif