emdbg.debug.px4.perf
1# Copyright (c) 2023, Auterion AG 2# SPDX-License-Identifier: BSD-3-Clause 3 4from __future__ import annotations 5from .base import Base 6from .utils import format_units 7from typing import Callable, Any 8import rich.box, rich.markup 9from rich.table import Table 10from functools import cached_property 11import math 12 13 14class PerfCounter(Base): 15 """ 16 Pretty Printing Perf Counters 17 """ 18 19 def __init__(self, gdb, perf_ptr: "gdb.Value"): 20 super().__init__(gdb) 21 self._perf = perf_ptr.cast(gdb.lookup_type("struct perf_ctr_count").pointer()) 22 if self.type == "PC_ELAPSED": 23 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_elapsed").pointer()) 24 elif self.type == "PC_INTERVAL": 25 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_interval").pointer()) 26 # print(self._perf, self.short_type, self.events, self.name) 27 28 @cached_property 29 def name(self) -> str: 30 """Name of the counter""" 31 try: 32 return self._perf["name"].string() 33 except: 34 return "?" 35 36 @cached_property 37 def events(self) -> int: 38 """How many events were counted""" 39 return int(self._perf["event_count"]) 40 41 @cached_property 42 def _types(self): 43 return self._gdb.types.make_enum_dict(self._gdb.lookup_type("enum perf_counter_type")) 44 45 @cached_property 46 def type(self) -> str: 47 """Counter type name""" 48 for name, value in self._types.items(): 49 if value == self._perf["type"]: 50 return name 51 return "UNKNOWN" 52 53 @cached_property 54 def short_type(self) -> str: 55 """The short name of the type""" 56 return self.type.replace("PC_", "").capitalize() 57 58 @cached_property 59 def elapsed(self) -> int | None: 60 """ 61 How much time has elapsed in microseconds. 62 Only applies to Elapsed counters. 63 """ 64 if self.type == "PC_ELAPSED": 65 return int(self._perf["time_total"]) 66 return None 67 68 @cached_property 69 def first(self) -> int | None: 70 """ 71 The first time in microseconds. 72 Only applies to Interval counters. 73 """ 74 if self.type == "PC_INTERVAL": 75 return int(self._perf["time_first"]) 76 return None 77 78 @cached_property 79 def last(self) -> int | None: 80 """ 81 The last time in microseconds. 82 Only applies to Interval counters. 83 """ 84 if self.type == "PC_INTERVAL": 85 return int(self._perf["time_last"]) 86 return None 87 88 @cached_property 89 def interval(self) -> int | None: 90 """ 91 The interval time in microseconds. 92 Only applies to Interval counters. 93 """ 94 if self.type == "PC_INTERVAL": 95 return self.last - self.first 96 return None 97 98 @cached_property 99 def average(self) -> int | None: 100 """ 101 The average time in microseconds. 102 Only applies to Elapsed and Interval counters. 103 """ 104 if self.type == "PC_ELAPSED": 105 return self.elapsed / self.events if self.events else 0 106 elif self.type == "PC_INTERVAL": 107 return (self.last - self.first) / self.events if self.events else 0 108 return None 109 110 @cached_property 111 def least(self) -> int | None: 112 """ 113 The least time in microseconds. 114 Only applies to Elapsed and Interval counters. 115 """ 116 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 117 return int(self._perf["time_least"]) 118 return None 119 120 @cached_property 121 def most(self) -> int | None: 122 """ 123 The most time in microseconds. 124 Only applies to Elapsed and Interval counters. 125 """ 126 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 127 return int(self._perf["time_most"]) 128 return None 129 130 @cached_property 131 def rms(self) -> int | None: 132 """ 133 The root mean square in microseconds. 134 Only applies to Elapsed and Interval counters. 135 """ 136 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 137 return 1e6 * math.sqrt(float(self._perf["M2"]) / (self.events - 1)) if self.events > 1 else 0 138 return None 139 140 141_PREVIOUS_COUNTERS = {} 142def all_perf_counters_as_table(gdb, filter_: Callable[[PerfCounter], bool] = None, 143 sort_key: Callable[[PerfCounter], Any] = None) -> Table | None: 144 """ 145 Pretty print all perf counters as a table. Counters that did not change 146 since the last call are dimmed. 147 148 :param filter_: A function to filter the perf counters. 149 :param sort_key: A function to sort the perf counters by key. 150 :returns: A rich table with all perf counters or `None` if no counters found. 151 """ 152 if (queue := gdb.lookup_static_symbol("perf_counters")) is None: 153 return None 154 queue = queue.value() 155 item, tail = queue["head"], queue["tail"] 156 counters = [] 157 loop_count = 0 158 while item and item != tail: 159 pc = PerfCounter(gdb, item) 160 counters.append(pc) 161 item = item["flink"] 162 loop_count += 1 163 if loop_count > 1000: break 164 # Filter may result in no matches 165 if not counters: 166 return None 167 168 global _PREVIOUS_COUNTERS 169 changed = {c for c in counters 170 if (events := _PREVIOUS_COUNTERS.get(int(c._perf))) is not None 171 and events != c.events} 172 _PREVIOUS_COUNTERS |= {int(c._perf): c.events for c in counters} 173 174 # Filter out the counters 175 if filter_ is not None: 176 counters = [c for c in counters if filter_(c)] 177 if not counters: 178 return None 179 180 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 181 table.add_column("perf_ctr_count*", justify="right", no_wrap=True) 182 table.add_column("Name") 183 table.add_column("Events", justify="right") 184 table.add_column("Elapsed", justify="right") 185 table.add_column("Average", justify="right") 186 table.add_column("Least", justify="right") 187 table.add_column("Most", justify="right") 188 table.add_column("RMS", justify="right") 189 table.add_column("Interval", justify="right") 190 table.add_column("First", justify="right") 191 table.add_column("Last", justify="right") 192 193 # Sort the rows by name by default and format the table 194 for counter in sorted(counters, key=sort_key or (lambda p: p.name)): 195 table.add_row(hex(counter._perf), rich.markup.escape(counter.name), str(counter.events), 196 format_units(counter.elapsed, "t:µs", fmt=".1f", if_zero="-"), 197 format_units(counter.average, "t:µs", fmt=".1f", if_zero="-"), 198 format_units(counter.least, "t:µs", fmt=".1f", if_zero="-"), 199 format_units(counter.most, "t:µs", fmt=".1f", if_zero="-"), 200 format_units(counter.rms, "t:µs", fmt=".3f", if_zero="-"), 201 format_units(counter.interval, "t:µs", fmt=".1f", if_zero="-"), 202 format_units(counter.first, "t:µs", fmt=".1f", if_zero="-"), 203 format_units(counter.last, "t:µs", fmt=".1f", if_zero="-"), 204 style="dim" if changed and counter not in changed else None) 205 return table
15class PerfCounter(Base): 16 """ 17 Pretty Printing Perf Counters 18 """ 19 20 def __init__(self, gdb, perf_ptr: "gdb.Value"): 21 super().__init__(gdb) 22 self._perf = perf_ptr.cast(gdb.lookup_type("struct perf_ctr_count").pointer()) 23 if self.type == "PC_ELAPSED": 24 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_elapsed").pointer()) 25 elif self.type == "PC_INTERVAL": 26 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_interval").pointer()) 27 # print(self._perf, self.short_type, self.events, self.name) 28 29 @cached_property 30 def name(self) -> str: 31 """Name of the counter""" 32 try: 33 return self._perf["name"].string() 34 except: 35 return "?" 36 37 @cached_property 38 def events(self) -> int: 39 """How many events were counted""" 40 return int(self._perf["event_count"]) 41 42 @cached_property 43 def _types(self): 44 return self._gdb.types.make_enum_dict(self._gdb.lookup_type("enum perf_counter_type")) 45 46 @cached_property 47 def type(self) -> str: 48 """Counter type name""" 49 for name, value in self._types.items(): 50 if value == self._perf["type"]: 51 return name 52 return "UNKNOWN" 53 54 @cached_property 55 def short_type(self) -> str: 56 """The short name of the type""" 57 return self.type.replace("PC_", "").capitalize() 58 59 @cached_property 60 def elapsed(self) -> int | None: 61 """ 62 How much time has elapsed in microseconds. 63 Only applies to Elapsed counters. 64 """ 65 if self.type == "PC_ELAPSED": 66 return int(self._perf["time_total"]) 67 return None 68 69 @cached_property 70 def first(self) -> int | None: 71 """ 72 The first time in microseconds. 73 Only applies to Interval counters. 74 """ 75 if self.type == "PC_INTERVAL": 76 return int(self._perf["time_first"]) 77 return None 78 79 @cached_property 80 def last(self) -> int | None: 81 """ 82 The last time in microseconds. 83 Only applies to Interval counters. 84 """ 85 if self.type == "PC_INTERVAL": 86 return int(self._perf["time_last"]) 87 return None 88 89 @cached_property 90 def interval(self) -> int | None: 91 """ 92 The interval time in microseconds. 93 Only applies to Interval counters. 94 """ 95 if self.type == "PC_INTERVAL": 96 return self.last - self.first 97 return None 98 99 @cached_property 100 def average(self) -> int | None: 101 """ 102 The average time in microseconds. 103 Only applies to Elapsed and Interval counters. 104 """ 105 if self.type == "PC_ELAPSED": 106 return self.elapsed / self.events if self.events else 0 107 elif self.type == "PC_INTERVAL": 108 return (self.last - self.first) / self.events if self.events else 0 109 return None 110 111 @cached_property 112 def least(self) -> int | None: 113 """ 114 The least time in microseconds. 115 Only applies to Elapsed and Interval counters. 116 """ 117 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 118 return int(self._perf["time_least"]) 119 return None 120 121 @cached_property 122 def most(self) -> int | None: 123 """ 124 The most time in microseconds. 125 Only applies to Elapsed and Interval counters. 126 """ 127 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 128 return int(self._perf["time_most"]) 129 return None 130 131 @cached_property 132 def rms(self) -> int | None: 133 """ 134 The root mean square in microseconds. 135 Only applies to Elapsed and Interval counters. 136 """ 137 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 138 return 1e6 * math.sqrt(float(self._perf["M2"]) / (self.events - 1)) if self.events > 1 else 0 139 return None
Pretty Printing Perf Counters
20 def __init__(self, gdb, perf_ptr: "gdb.Value"): 21 super().__init__(gdb) 22 self._perf = perf_ptr.cast(gdb.lookup_type("struct perf_ctr_count").pointer()) 23 if self.type == "PC_ELAPSED": 24 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_elapsed").pointer()) 25 elif self.type == "PC_INTERVAL": 26 self._perf = self._perf.cast(gdb.lookup_type("struct perf_ctr_interval").pointer()) 27 # print(self._perf, self.short_type, self.events, self.name)
29 @cached_property 30 def name(self) -> str: 31 """Name of the counter""" 32 try: 33 return self._perf["name"].string() 34 except: 35 return "?"
Name of the counter
37 @cached_property 38 def events(self) -> int: 39 """How many events were counted""" 40 return int(self._perf["event_count"])
How many events were counted
46 @cached_property 47 def type(self) -> str: 48 """Counter type name""" 49 for name, value in self._types.items(): 50 if value == self._perf["type"]: 51 return name 52 return "UNKNOWN"
Counter type name
54 @cached_property 55 def short_type(self) -> str: 56 """The short name of the type""" 57 return self.type.replace("PC_", "").capitalize()
The short name of the type
59 @cached_property 60 def elapsed(self) -> int | None: 61 """ 62 How much time has elapsed in microseconds. 63 Only applies to Elapsed counters. 64 """ 65 if self.type == "PC_ELAPSED": 66 return int(self._perf["time_total"]) 67 return None
How much time has elapsed in microseconds. Only applies to Elapsed counters.
69 @cached_property 70 def first(self) -> int | None: 71 """ 72 The first time in microseconds. 73 Only applies to Interval counters. 74 """ 75 if self.type == "PC_INTERVAL": 76 return int(self._perf["time_first"]) 77 return None
The first time in microseconds. Only applies to Interval counters.
79 @cached_property 80 def last(self) -> int | None: 81 """ 82 The last time in microseconds. 83 Only applies to Interval counters. 84 """ 85 if self.type == "PC_INTERVAL": 86 return int(self._perf["time_last"]) 87 return None
The last time in microseconds. Only applies to Interval counters.
89 @cached_property 90 def interval(self) -> int | None: 91 """ 92 The interval time in microseconds. 93 Only applies to Interval counters. 94 """ 95 if self.type == "PC_INTERVAL": 96 return self.last - self.first 97 return None
The interval time in microseconds. Only applies to Interval counters.
99 @cached_property 100 def average(self) -> int | None: 101 """ 102 The average time in microseconds. 103 Only applies to Elapsed and Interval counters. 104 """ 105 if self.type == "PC_ELAPSED": 106 return self.elapsed / self.events if self.events else 0 107 elif self.type == "PC_INTERVAL": 108 return (self.last - self.first) / self.events if self.events else 0 109 return None
The average time in microseconds. Only applies to Elapsed and Interval counters.
111 @cached_property 112 def least(self) -> int | None: 113 """ 114 The least time in microseconds. 115 Only applies to Elapsed and Interval counters. 116 """ 117 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 118 return int(self._perf["time_least"]) 119 return None
The least time in microseconds. Only applies to Elapsed and Interval counters.
121 @cached_property 122 def most(self) -> int | None: 123 """ 124 The most time in microseconds. 125 Only applies to Elapsed and Interval counters. 126 """ 127 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 128 return int(self._perf["time_most"]) 129 return None
The most time in microseconds. Only applies to Elapsed and Interval counters.
131 @cached_property 132 def rms(self) -> int | None: 133 """ 134 The root mean square in microseconds. 135 Only applies to Elapsed and Interval counters. 136 """ 137 if self.type in ["PC_ELAPSED", "PC_INTERVAL"]: 138 return 1e6 * math.sqrt(float(self._perf["M2"]) / (self.events - 1)) if self.events > 1 else 0 139 return None
The root mean square in microseconds. Only applies to Elapsed and Interval counters.
Inherited Members
- emdbg.debug.px4.base.Base
- register_names
- registers
- read_register
- write_register
- write_registers
- fix_nuttx_sp
- lookup_static_symbol_in_function
- lookup_static_symbol_ptr
- lookup_global_symbol_ptr
- lookup_static_symbol_in_function_ptr
- value_ptr
- addr_ptr
- read_memory
- write_memory
- read_uint
- read_int
- read_string
- symtab_line
- block
- description_at
- integer_type
- uint32
- int32
143def all_perf_counters_as_table(gdb, filter_: Callable[[PerfCounter], bool] = None, 144 sort_key: Callable[[PerfCounter], Any] = None) -> Table | None: 145 """ 146 Pretty print all perf counters as a table. Counters that did not change 147 since the last call are dimmed. 148 149 :param filter_: A function to filter the perf counters. 150 :param sort_key: A function to sort the perf counters by key. 151 :returns: A rich table with all perf counters or `None` if no counters found. 152 """ 153 if (queue := gdb.lookup_static_symbol("perf_counters")) is None: 154 return None 155 queue = queue.value() 156 item, tail = queue["head"], queue["tail"] 157 counters = [] 158 loop_count = 0 159 while item and item != tail: 160 pc = PerfCounter(gdb, item) 161 counters.append(pc) 162 item = item["flink"] 163 loop_count += 1 164 if loop_count > 1000: break 165 # Filter may result in no matches 166 if not counters: 167 return None 168 169 global _PREVIOUS_COUNTERS 170 changed = {c for c in counters 171 if (events := _PREVIOUS_COUNTERS.get(int(c._perf))) is not None 172 and events != c.events} 173 _PREVIOUS_COUNTERS |= {int(c._perf): c.events for c in counters} 174 175 # Filter out the counters 176 if filter_ is not None: 177 counters = [c for c in counters if filter_(c)] 178 if not counters: 179 return None 180 181 table = Table(box=rich.box.MINIMAL_DOUBLE_HEAD) 182 table.add_column("perf_ctr_count*", justify="right", no_wrap=True) 183 table.add_column("Name") 184 table.add_column("Events", justify="right") 185 table.add_column("Elapsed", justify="right") 186 table.add_column("Average", justify="right") 187 table.add_column("Least", justify="right") 188 table.add_column("Most", justify="right") 189 table.add_column("RMS", justify="right") 190 table.add_column("Interval", justify="right") 191 table.add_column("First", justify="right") 192 table.add_column("Last", justify="right") 193 194 # Sort the rows by name by default and format the table 195 for counter in sorted(counters, key=sort_key or (lambda p: p.name)): 196 table.add_row(hex(counter._perf), rich.markup.escape(counter.name), str(counter.events), 197 format_units(counter.elapsed, "t:µs", fmt=".1f", if_zero="-"), 198 format_units(counter.average, "t:µs", fmt=".1f", if_zero="-"), 199 format_units(counter.least, "t:µs", fmt=".1f", if_zero="-"), 200 format_units(counter.most, "t:µs", fmt=".1f", if_zero="-"), 201 format_units(counter.rms, "t:µs", fmt=".3f", if_zero="-"), 202 format_units(counter.interval, "t:µs", fmt=".1f", if_zero="-"), 203 format_units(counter.first, "t:µs", fmt=".1f", if_zero="-"), 204 format_units(counter.last, "t:µs", fmt=".1f", if_zero="-"), 205 style="dim" if changed and counter not in changed else None) 206 return table
Pretty print all perf counters as a table. Counters that did not change since the last call are dimmed.
Parameters
- filter_: A function to filter the perf counters.
- sort_key: A function to sort the perf counters by key.
:returns: A rich table with all perf counters or
None
if no counters found.