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