1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright 2023 Red Hat 4 */ 5 6 #include "dump.h" 7 8 #include <linux/module.h> 9 10 #include "memory-alloc.h" 11 #include "string-utils.h" 12 13 #include "constants.h" 14 #include "data-vio.h" 15 #include "dedupe.h" 16 #include "funnel-workqueue.h" 17 #include "io-submitter.h" 18 #include "logger.h" 19 #include "types.h" 20 #include "vdo.h" 21 22 enum dump_options { 23 /* Work queues */ 24 SHOW_QUEUES, 25 /* Memory pools */ 26 SHOW_VIO_POOL, 27 /* Others */ 28 SHOW_VDO_STATUS, 29 /* This one means an option overrides the "default" choices, instead of altering them. */ 30 SKIP_DEFAULT 31 }; 32 33 enum dump_option_flags { 34 /* Work queues */ 35 FLAG_SHOW_QUEUES = (1 << SHOW_QUEUES), 36 /* Memory pools */ 37 FLAG_SHOW_VIO_POOL = (1 << SHOW_VIO_POOL), 38 /* Others */ 39 FLAG_SHOW_VDO_STATUS = (1 << SHOW_VDO_STATUS), 40 /* Special */ 41 FLAG_SKIP_DEFAULT = (1 << SKIP_DEFAULT) 42 }; 43 44 #define FLAGS_ALL_POOLS (FLAG_SHOW_VIO_POOL) 45 #define DEFAULT_DUMP_FLAGS (FLAG_SHOW_QUEUES | FLAG_SHOW_VDO_STATUS) 46 /* Another static buffer... log10(256) = 2.408+, round up: */ 47 #define DIGITS_PER_U64 (1 + sizeof(u64) * 2409 / 1000) 48 49 static inline bool is_arg_string(const char *arg, const char *this_option) 50 { 51 /* convention seems to be case-independent options */ 52 return strncasecmp(arg, this_option, strlen(this_option)) == 0; 53 } 54 55 static void do_dump(struct vdo *vdo, unsigned int dump_options_requested, 56 const char *why) 57 { 58 u32 active, maximum; 59 s64 outstanding; 60 61 vdo_log_info("%s dump triggered via %s", VDO_LOGGING_MODULE_NAME, why); 62 active = get_data_vio_pool_active_requests(vdo->data_vio_pool); 63 maximum = get_data_vio_pool_maximum_requests(vdo->data_vio_pool); 64 outstanding = (atomic64_read(&vdo->stats.bios_submitted) - 65 atomic64_read(&vdo->stats.bios_completed)); 66 vdo_log_info("%u device requests outstanding (max %u), %lld bio requests outstanding, device '%s'", 67 active, maximum, outstanding, 68 vdo_get_device_name(vdo->device_config->owning_target)); 69 if (((dump_options_requested & FLAG_SHOW_QUEUES) != 0) && (vdo->threads != NULL)) { 70 thread_id_t id; 71 72 for (id = 0; id < vdo->thread_config.thread_count; id++) 73 vdo_dump_work_queue(vdo->threads[id].queue); 74 } 75 76 vdo_dump_hash_zones(vdo->hash_zones); 77 dump_data_vio_pool(vdo->data_vio_pool, 78 (dump_options_requested & FLAG_SHOW_VIO_POOL) != 0); 79 if ((dump_options_requested & FLAG_SHOW_VDO_STATUS) != 0) 80 vdo_dump_status(vdo); 81 82 vdo_report_memory_usage(); 83 vdo_log_info("end of %s dump", VDO_LOGGING_MODULE_NAME); 84 } 85 86 static int parse_dump_options(unsigned int argc, char *const *argv, 87 unsigned int *dump_options_requested_ptr) 88 { 89 unsigned int dump_options_requested = 0; 90 91 static const struct { 92 const char *name; 93 unsigned int flags; 94 } option_names[] = { 95 { "viopool", FLAG_SKIP_DEFAULT | FLAG_SHOW_VIO_POOL }, 96 { "vdo", FLAG_SKIP_DEFAULT | FLAG_SHOW_VDO_STATUS }, 97 { "pools", FLAG_SKIP_DEFAULT | FLAGS_ALL_POOLS }, 98 { "queues", FLAG_SKIP_DEFAULT | FLAG_SHOW_QUEUES }, 99 { "threads", FLAG_SKIP_DEFAULT | FLAG_SHOW_QUEUES }, 100 { "default", FLAG_SKIP_DEFAULT | DEFAULT_DUMP_FLAGS }, 101 { "all", ~0 }, 102 }; 103 104 bool options_okay = true; 105 unsigned int i; 106 107 for (i = 1; i < argc; i++) { 108 unsigned int j; 109 110 for (j = 0; j < ARRAY_SIZE(option_names); j++) { 111 if (is_arg_string(argv[i], option_names[j].name)) { 112 dump_options_requested |= option_names[j].flags; 113 break; 114 } 115 } 116 if (j == ARRAY_SIZE(option_names)) { 117 vdo_log_warning("dump option name '%s' unknown", argv[i]); 118 options_okay = false; 119 } 120 } 121 if (!options_okay) 122 return -EINVAL; 123 if ((dump_options_requested & FLAG_SKIP_DEFAULT) == 0) 124 dump_options_requested |= DEFAULT_DUMP_FLAGS; 125 *dump_options_requested_ptr = dump_options_requested; 126 return 0; 127 } 128 129 /* Dump as specified by zero or more string arguments. */ 130 int vdo_dump(struct vdo *vdo, unsigned int argc, char *const *argv, const char *why) 131 { 132 unsigned int dump_options_requested = 0; 133 int result = parse_dump_options(argc, argv, &dump_options_requested); 134 135 if (result != 0) 136 return result; 137 138 do_dump(vdo, dump_options_requested, why); 139 return 0; 140 } 141 142 /* Dump everything we know how to dump */ 143 void vdo_dump_all(struct vdo *vdo, const char *why) 144 { 145 do_dump(vdo, ~0, why); 146 } 147 148 /* 149 * Dump out the data_vio waiters on a waitq. 150 * wait_on should be the label to print for queue (e.g. logical or physical) 151 */ 152 static void dump_vio_waiters(struct vdo_wait_queue *waitq, char *wait_on) 153 { 154 struct vdo_waiter *waiter, *first = vdo_waitq_get_first_waiter(waitq); 155 struct data_vio *data_vio; 156 157 if (first == NULL) 158 return; 159 160 data_vio = vdo_waiter_as_data_vio(first); 161 162 vdo_log_info(" %s is locked. Waited on by: vio %px pbn %llu lbn %llu d-pbn %llu lastOp %s", 163 wait_on, data_vio, data_vio->allocation.pbn, data_vio->logical.lbn, 164 data_vio->duplicate.pbn, get_data_vio_operation_name(data_vio)); 165 166 for (waiter = first->next_waiter; waiter != first; waiter = waiter->next_waiter) { 167 data_vio = vdo_waiter_as_data_vio(waiter); 168 vdo_log_info(" ... and : vio %px pbn %llu lbn %llu d-pbn %llu lastOp %s", 169 data_vio, data_vio->allocation.pbn, data_vio->logical.lbn, 170 data_vio->duplicate.pbn, 171 get_data_vio_operation_name(data_vio)); 172 } 173 } 174 175 /* 176 * Encode various attributes of a data_vio as a string of one-character flags. This encoding is for 177 * logging brevity: 178 * 179 * R => vio completion result not VDO_SUCCESS 180 * W => vio is on a waitq 181 * D => vio is a duplicate 182 * p => vio is a partial block operation 183 * z => vio is a zero block 184 * d => vio is a discard 185 * 186 * The common case of no flags set will result in an empty, null-terminated buffer. If any flags 187 * are encoded, the first character in the string will be a space character. 188 */ 189 static void encode_vio_dump_flags(struct data_vio *data_vio, char buffer[8]) 190 { 191 char *p_flag = buffer; 192 *p_flag++ = ' '; 193 if (data_vio->vio.completion.result != VDO_SUCCESS) 194 *p_flag++ = 'R'; 195 if (data_vio->waiter.next_waiter != NULL) 196 *p_flag++ = 'W'; 197 if (data_vio->is_duplicate) 198 *p_flag++ = 'D'; 199 if (data_vio->is_partial) 200 *p_flag++ = 'p'; 201 if (data_vio->is_zero) 202 *p_flag++ = 'z'; 203 if (data_vio->remaining_discard > 0) 204 *p_flag++ = 'd'; 205 if (p_flag == &buffer[1]) { 206 /* No flags, so remove the blank space. */ 207 p_flag = buffer; 208 } 209 *p_flag = '\0'; 210 } 211 212 /* Implements buffer_dump_function. */ 213 void dump_data_vio(void *data) 214 { 215 struct data_vio *data_vio = data; 216 217 /* 218 * This just needs to be big enough to hold a queue (thread) name and a function name (plus 219 * a separator character and NUL). The latter is limited only by taste. 220 * 221 * In making this static, we're assuming only one "dump" will run at a time. If more than 222 * one does run, the log output will be garbled anyway. 223 */ 224 static char vio_completion_dump_buffer[100 + MAX_VDO_WORK_QUEUE_NAME_LEN]; 225 static char vio_block_number_dump_buffer[sizeof("P L D") + 3 * DIGITS_PER_U64]; 226 static char vio_flush_generation_buffer[sizeof(" FG") + DIGITS_PER_U64]; 227 static char flags_dump_buffer[8]; 228 229 /* 230 * We're likely to be logging a couple thousand of these lines, and in some circumstances 231 * syslogd may have trouble keeping up, so keep it BRIEF rather than user-friendly. 232 */ 233 vdo_dump_completion_to_buffer(&data_vio->vio.completion, 234 vio_completion_dump_buffer, 235 sizeof(vio_completion_dump_buffer)); 236 if (data_vio->is_duplicate) { 237 snprintf(vio_block_number_dump_buffer, 238 sizeof(vio_block_number_dump_buffer), "P%llu L%llu D%llu", 239 data_vio->allocation.pbn, data_vio->logical.lbn, 240 data_vio->duplicate.pbn); 241 } else if (data_vio_has_allocation(data_vio)) { 242 snprintf(vio_block_number_dump_buffer, 243 sizeof(vio_block_number_dump_buffer), "P%llu L%llu", 244 data_vio->allocation.pbn, data_vio->logical.lbn); 245 } else { 246 snprintf(vio_block_number_dump_buffer, 247 sizeof(vio_block_number_dump_buffer), "L%llu", 248 data_vio->logical.lbn); 249 } 250 251 if (data_vio->flush_generation != 0) { 252 snprintf(vio_flush_generation_buffer, 253 sizeof(vio_flush_generation_buffer), " FG%llu", 254 data_vio->flush_generation); 255 } else { 256 vio_flush_generation_buffer[0] = 0; 257 } 258 259 encode_vio_dump_flags(data_vio, flags_dump_buffer); 260 261 vdo_log_info(" vio %px %s%s %s %s%s", data_vio, 262 vio_block_number_dump_buffer, 263 vio_flush_generation_buffer, 264 get_data_vio_operation_name(data_vio), 265 vio_completion_dump_buffer, 266 flags_dump_buffer); 267 /* 268 * might want info on: wantUDSAnswer / operation / status 269 * might want info on: bio / bios_merged 270 */ 271 272 dump_vio_waiters(&data_vio->logical.waiters, "lbn"); 273 274 /* might want to dump more info from vio here */ 275 } 276