Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 1 | /*! \file talloc_ctx_vty.c |
| 2 | * Osmocom talloc context introspection via VTY. */ |
| 3 | /* |
| 4 | * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com> |
| 5 | * |
| 6 | * All Rights Reserved |
| 7 | * |
Harald Welte | e08da97 | 2017-11-13 01:00:26 +0900 | [diff] [blame] | 8 | * SPDX-License-Identifier: GPL-2.0+ |
| 9 | * |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 10 | * This program is free software; you can redistribute it and/or modify |
| 11 | * it under the terms of the GNU General Public License as published by |
| 12 | * the Free Software Foundation; either version 2 of the License, or |
| 13 | * (at your option) any later version. |
| 14 | * |
| 15 | * This program is distributed in the hope that it will be useful, |
| 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | * GNU General Public License for more details. |
| 19 | * |
| 20 | * You should have received a copy of the GNU General Public License along |
| 21 | * with this program; if not, write to the Free Software Foundation, Inc., |
| 22 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| 23 | * |
| 24 | */ |
| 25 | |
| 26 | #include <stdio.h> |
| 27 | #include <regex.h> |
| 28 | #include <string.h> |
| 29 | #include <talloc.h> |
| 30 | |
| 31 | #include <osmocom/vty/command.h> |
| 32 | #include <osmocom/vty/vty.h> |
| 33 | |
| 34 | extern void *tall_vty_ctx; |
| 35 | extern struct host host; |
| 36 | |
| 37 | enum walk_filter_type { |
| 38 | WALK_FILTER_NONE = 0, |
| 39 | WALK_FILTER_REGEXP, |
| 40 | WALK_FILTER_TREE, |
| 41 | }; |
| 42 | |
| 43 | struct walk_cb_params { |
| 44 | enum walk_filter_type filter; |
| 45 | unsigned int depth_pass; |
| 46 | const void *chunk_ptr; |
| 47 | struct vty *vty; |
| 48 | regex_t regexp; |
| 49 | }; |
| 50 | |
| 51 | /*! |
| 52 | * Print a talloc memory hierarchy to the given VTY. |
| 53 | * To be called by the talloc_report_depth_cb(). |
| 54 | * If one of supported filters is specified, then |
| 55 | * only satisfying memory trees would be printed. |
| 56 | * |
| 57 | * @param chunk The talloc chunk to be printed |
| 58 | * @param depth Current depth value |
| 59 | * @param max_depth Maximal depth of report (negative means full) |
| 60 | * @param is_ref Is this chunk a reference? |
| 61 | * @param data The walk_cb_params struct instance |
| 62 | */ |
| 63 | static void talloc_ctx_walk_cb(const void *chunk, int depth, |
| 64 | int max_depth, int is_ref, void *data) |
| 65 | { |
| 66 | struct walk_cb_params *p = (struct walk_cb_params *) data; |
| 67 | const char *chunk_name = talloc_get_name(chunk); |
| 68 | struct vty *vty = p->vty; |
| 69 | size_t chunk_blocks; |
| 70 | size_t chunk_size; |
| 71 | int rc; |
| 72 | |
| 73 | if (depth > 0 && p->filter) { |
| 74 | /** |
| 75 | * A filter is being bypassed while current depth value |
| 76 | * is higher than the 'depth_pass', i.e. the callback does |
| 77 | * processing the child memory chunks. As soon as this |
| 78 | * condition becomes false, we need to 'enable' a filter, |
| 79 | * and resume the processing other chunks. |
| 80 | */ |
| 81 | if (p->depth_pass && depth > p->depth_pass) |
| 82 | goto filter_bypass; |
| 83 | else |
| 84 | p->depth_pass = 0; |
| 85 | |
| 86 | switch (p->filter) { |
| 87 | case WALK_FILTER_REGEXP: |
| 88 | /* Filter chunks using a regular expression */ |
| 89 | rc = regexec(&p->regexp, chunk_name, 0, NULL, 0); |
| 90 | if (rc) |
| 91 | return; |
| 92 | break; |
| 93 | case WALK_FILTER_TREE: |
| 94 | /* Print a specific memory tree only */ |
| 95 | if (chunk != p->chunk_ptr) |
| 96 | return; |
| 97 | break; |
| 98 | default: |
| 99 | /* Unsupported filter or incorrect value */ |
| 100 | return; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * As soon as a filter passes any chunk, all the memory |
| 105 | * tree starting from one would be printed. To do that, |
| 106 | * we need to temporary 'disable' a filter for child |
| 107 | * chunks (current_depth > depth_pass). |
| 108 | */ |
| 109 | p->depth_pass = depth; |
| 110 | } |
| 111 | |
| 112 | filter_bypass: |
| 113 | |
| 114 | if (is_ref) { |
| 115 | vty_out(vty, "%*sreference to: %s%s", |
| 116 | depth * 2, "", chunk_name, VTY_NEWLINE); |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | chunk_blocks = talloc_total_blocks(chunk); |
| 121 | chunk_size = talloc_total_size(chunk); |
| 122 | |
| 123 | if (depth == 0) { |
| 124 | vty_out(vty, "%stalloc report on '%s' " |
| 125 | "(total %6zu bytes in %3zu blocks)%s", |
| 126 | (max_depth < 0 ? "full " : ""), chunk_name, |
| 127 | chunk_size, chunk_blocks, VTY_NEWLINE); |
| 128 | return; |
| 129 | } |
| 130 | |
| 131 | vty_out(vty, "%*s%-30s contains %6zu bytes " |
| 132 | "in %3zu blocks (ref %zu) %p%s", depth * 2, "", |
| 133 | chunk_name, chunk_size, chunk_blocks, |
| 134 | talloc_reference_count(chunk), |
| 135 | chunk, VTY_NEWLINE); |
| 136 | } |
| 137 | |
| 138 | /*! |
| 139 | * Parse talloc context and depth values from a VTY command. |
| 140 | * |
| 141 | * @param ctx The context to be printed (a string from argv) |
| 142 | * @param depth The report depth (a string from argv) |
| 143 | * @param params The walk_cb_params struct instance |
| 144 | */ |
| 145 | static void talloc_ctx_walk(const char *ctx, const char *depth, |
| 146 | struct walk_cb_params *params) |
| 147 | { |
| 148 | const void *talloc_ctx = NULL; |
| 149 | int max_depth; |
| 150 | |
| 151 | /* Determine a context for report */ |
| 152 | if (!strncmp(ctx, "app", 3)) |
| 153 | talloc_ctx = host.app_info->tall_ctx; |
| 154 | else if (!strncmp(ctx, "all", 3)) |
| 155 | talloc_ctx = NULL; |
| 156 | |
| 157 | /* Determine report depth */ |
| 158 | if (depth[0] == 'f') |
| 159 | max_depth = -1; |
| 160 | else if (depth[0] == 'b') |
| 161 | max_depth = 1; |
| 162 | else |
| 163 | max_depth = atoi(depth); |
| 164 | |
| 165 | talloc_report_depth_cb(talloc_ctx, 0, max_depth, |
| 166 | &talloc_ctx_walk_cb, params); |
| 167 | } |
| 168 | |
| 169 | #define BASE_CMD_STR \ |
| 170 | "show talloc-context (application|all) (full|brief|DEPTH)" |
| 171 | |
| 172 | #define BASE_CMD_DESCR \ |
| 173 | SHOW_STR "Show talloc memory hierarchy\n" \ |
| 174 | "Application's context\n" \ |
| 175 | "All contexts, if NULL-context tracking is enabled\n" \ |
| 176 | "Display a full talloc memory hierarchy\n" \ |
| 177 | "Display a brief talloc memory hierarchy\n" \ |
Harald Welte | 408a900 | 2018-02-14 01:29:19 +0100 | [diff] [blame] | 178 | "Specify required maximal depth value\n" |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 179 | |
| 180 | DEFUN(show_talloc_ctx, show_talloc_ctx_cmd, |
| 181 | BASE_CMD_STR, BASE_CMD_DESCR) |
| 182 | { |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 183 | struct walk_cb_params params = { 0 }; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 184 | |
| 185 | /* Set up callback parameters */ |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 186 | params.filter = WALK_FILTER_NONE; |
| 187 | params.vty = vty; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 188 | |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 189 | talloc_ctx_walk(argv[0], argv[1], ¶ms); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 190 | return CMD_SUCCESS; |
| 191 | } |
| 192 | |
| 193 | DEFUN(show_talloc_ctx_filter, show_talloc_ctx_filter_cmd, |
| 194 | BASE_CMD_STR " filter REGEXP", BASE_CMD_DESCR |
| 195 | "Filter chunks using regular expression\n" |
Harald Welte | 408a900 | 2018-02-14 01:29:19 +0100 | [diff] [blame] | 196 | "Regular expression\n") |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 197 | { |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 198 | struct walk_cb_params params = { 0 }; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 199 | int rc; |
| 200 | |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 201 | /* Attempt to compile a regular expression */ |
Vadim Yanitskiy | 3d81147 | 2019-04-06 20:17:57 +0700 | [diff] [blame] | 202 | rc = regcomp(¶ms.regexp, argv[2], REG_NOSUB); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 203 | if (rc) { |
| 204 | vty_out(vty, "Invalid expression%s", VTY_NEWLINE); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 205 | return CMD_WARNING; |
| 206 | } |
| 207 | |
| 208 | /* Set up callback parameters */ |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 209 | params.filter = WALK_FILTER_REGEXP; |
| 210 | params.vty = vty; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 211 | |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 212 | talloc_ctx_walk(argv[0], argv[1], ¶ms); |
| 213 | regfree(¶ms.regexp); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 214 | |
| 215 | return CMD_SUCCESS; |
| 216 | } |
| 217 | |
| 218 | DEFUN(show_talloc_ctx_tree, show_talloc_ctx_tree_cmd, |
| 219 | BASE_CMD_STR " tree ADDRESS", BASE_CMD_DESCR |
| 220 | "Display only a specific memory chunk\n" |
Harald Welte | 408a900 | 2018-02-14 01:29:19 +0100 | [diff] [blame] | 221 | "Chunk address (e.g. 0xdeadbeef)\n") |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 222 | { |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 223 | struct walk_cb_params params = { 0 }; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 224 | int rc; |
| 225 | |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 226 | /* Attempt to parse an address */ |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 227 | rc = sscanf(argv[2], "%p", ¶ms.chunk_ptr); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 228 | if (rc != 1) { |
| 229 | vty_out(vty, "Invalid chunk address%s", VTY_NEWLINE); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 230 | return CMD_WARNING; |
| 231 | } |
| 232 | |
| 233 | /* Set up callback parameters */ |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 234 | params.filter = WALK_FILTER_TREE; |
| 235 | params.vty = vty; |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 236 | |
Vadim Yanitskiy | bd6968a | 2019-04-06 19:16:59 +0700 | [diff] [blame] | 237 | talloc_ctx_walk(argv[0], argv[1], ¶ms); |
Vadim Yanitskiy | 463deef | 2017-09-23 19:30:07 +0330 | [diff] [blame] | 238 | return CMD_SUCCESS; |
| 239 | } |
| 240 | |
| 241 | /*! |
| 242 | * Install VTY commands for talloc context introspection. |
| 243 | * |
| 244 | * This installs a set of VTY commands for introspection of |
| 245 | * a talloc context. Call this once from your application |
| 246 | * if you want to support those commands. |
| 247 | */ |
| 248 | void osmo_talloc_vty_add_cmds(void) |
| 249 | { |
| 250 | install_element_ve(&show_talloc_ctx_cmd); |
| 251 | install_element_ve(&show_talloc_ctx_tree_cmd); |
| 252 | install_element_ve(&show_talloc_ctx_filter_cmd); |
| 253 | } |