Commit | Line | Data |
---|---|---|
29a81195 MS |
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 | ||
6008d526 BJ |
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) | |
29a81195 MS |
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 | uds_log_info("%s dump triggered via %s", UDS_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 | uds_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 | ||
0eea6b6e | 82 | vdo_report_memory_usage(); |
29a81195 MS |
83 | uds_log_info("end of %s dump", UDS_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 | uds_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 | /* | |
d6e260cc | 149 | * Dump out the data_vio waiters on a waitq. |
29a81195 MS |
150 | * wait_on should be the label to print for queue (e.g. logical or physical) |
151 | */ | |
d6e260cc | 152 | static void dump_vio_waiters(struct vdo_wait_queue *waitq, char *wait_on) |
29a81195 | 153 | { |
d6e260cc | 154 | struct vdo_waiter *waiter, *first = vdo_waitq_get_first_waiter(waitq); |
29a81195 MS |
155 | struct data_vio *data_vio; |
156 | ||
157 | if (first == NULL) | |
158 | return; | |
159 | ||
d6e260cc | 160 | data_vio = vdo_waiter_as_data_vio(first); |
29a81195 MS |
161 | |
162 | uds_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) { | |
d6e260cc | 167 | data_vio = vdo_waiter_as_data_vio(waiter); |
29a81195 MS |
168 | uds_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 | |
d6e260cc | 180 | * W => vio is on a waitq |
29a81195 MS |
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]; | |
29a81195 MS |
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 | uds_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 | } |