├── LICENSE ├── README.md └── gdbpg.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, Tomas Vondra (tomas@pgaddict.com). All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY TOMAS VONDRA ''AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOMAS VONDRA OR 16 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | The views and conclusions contained in the software and documentation are those of the 24 | authors and should not be interpreted as representing official policies, either expressed 25 | or implied, of Tomas Vondra. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Postgres and GPDB GDB commands 2 | ======================= 3 | 4 | GDB commands making debugging Postgres and Greenplum internals easier. 5 | Currently this provides a single command 'pgprint' that understand the 'Node' 6 | structures used internally, can can print them semi-intelligently. 7 | 8 | Forked from PostgreSQL debugging tool found here: 9 | 10 | https://github.com/tvondra/gdbpg 11 | 12 | 13 | Usage 14 | ----- 15 | 16 | Copy the `gdbpg.py` script somewhere, and load it from `gdb`: 17 | 18 | $ gdb ... 19 | (gdb) source /...path.../gdbpg.py 20 | 21 | and then just use `pgprint` command to print variables from gdb console. 22 | E.g. if `plan` is pointing to `(PlannedStmt *)`, you may do this: 23 | 24 | (gdb) pgprint plan 25 | 26 | and you'll get something like this: 27 | 28 | ``` 29 | PlannedStmt [commandType=CMD_SELECT planGen=PLANGEN_PLANNER queryId=0 hasReturning=false hasModifyingCTE=false canSetTag=true transientPlan=false oneoffPlan=false 30 | simplyUpdatable=false dependsOnRole=false parallelModeNeeded=false numSlices=2 slices=0x56059fb60a08 subplan_sliceIds=0x56059fb61458 rewindPlanIDs=0x0 31 | nParamExec=0 query_mem=0 metricsQueryType=0 '\000'] 32 | [planTree] 33 | -> Motion [startup_cost=0 total_cost=2989 plan_rows=96300 plan_width=4 parallel_aware=false plan_node_id=0] 34 | [motionType=MOTIONTYPE_GATHER sendSorted=false motionID=1 collations=0x0] 35 | [targetlist] 36 | TargetEntry [resno=1 resname="a" resorigtbl=16392 resorigcol=1] 37 | Var [varno=OUTER_VAR varattno=1 vartype=23 varnoold=1 varoattno=1] 38 | [flow] Flow [flotype=FLOW_SINGLETON locustype=CdbLocusType_Entry segindex=-1 numsegments=3] 39 | [lefttree] 40 | -> SeqScan [startup_cost=0 total_cost=1063 plan_rows=96300 plan_width=4 parallel_aware=false plan_node_id=1] 41 | [scanrelid=1] 42 | [targetlist] 43 | TargetEntry [resno=1 resname="a" resorigtbl=16392 resorigcol=1] 44 | Var [varno=1 varattno=1 vartype=23 varnoold=1 varoattno=1] 45 | [flow] Flow [flotype=FLOW_PARTITIONED locustype=CdbLocusType_Hashed segindex=0 numsegments=3] 46 | [rtable] 47 | RangeTblEntry [rtekind=RTE_RELATION relid=16392 relkind='r' jointype=JOIN_INNER funcordinality=false ctelevelsup=0 self_reference=false forceDistRandom=false 48 | lateral=false inFromCl=true requiredPerms=2 selectedCols=0x00000400] 49 | [relationOids] OidList: [16392] 50 | ``` 51 | -------------------------------------------------------------------------------- /gdbpg.py: -------------------------------------------------------------------------------- 1 | import gdb 2 | import string 3 | 4 | # Visibility options 5 | NOT_NULL = "not_null" 6 | HIDE_INVALID = "hide_invalid" 7 | NEVER_SHOW = "never_show" 8 | ALWAYS_SHOW = "always_show" 9 | 10 | # TODO: Put these fields in a config file 11 | PlanNodes = ['Result', 'Repeat', 'ModifyTable','Append', 'Sequence', 'Motion', 12 | 'AOCSScan', 'BitmapAnd', 'BitmapOr', 'Scan', 'SeqScan', 'DynamicSeqScan', 13 | 'TableScan', 'IndexScan', 'DynamicIndexScan', 'BitmapIndexScan', 14 | 'BitmapHeapScan', 'BitmapAppendOnlyScan', 'BitmapTableScan', 15 | 'DynamicTableScan', 'TidScan', 'SubqueryScan', 'FunctionScan', 16 | 'TableFunctionScan', 'ValuesScan', 'ExternalScan', 'AppendOnlyScan', 17 | 'Join', 'NestLoop', 'MergeJoin', 'HashJoin', 'ShareInputScan', 18 | 'Material', 'Sort', 'Agg', 'Window', 'Unique', 'Hash', 'SetOp', 19 | 'Limit', 'DML', 'SplitUpdate', 'AssertOp', 'RowTrigger', 20 | 'PartitionSelector' ] 21 | 22 | # TODO: Put these fields in a config file 23 | PathNodes = ['Path', 'AppendOnlyPath', 'AOCSPath', 'ExternalPath', 'PartitionSelectorPath', 24 | 'IndexPath', 'BitmapHeapPath', 'BitmapAndPath', 'BitmapOrPath', 'TidPath', 25 | 'CdbMotionPath', 'ForeignPath', 'AppendPath', 'MergeAppendPath', 'ResultPath', 26 | 'HashPath', 'MergePath', 'MaterialPath', 'NestPath', 'JoinPath', 'UniquePath'] 27 | 28 | # TODO: Put these defaults config file 29 | DEFAULT_DISPLAY_METHODS = { 30 | 'regular_fields': 'format_regular_field', 31 | 'node_fields': 'format_optional_node_field', 32 | 'list_fields': 'format_optional_node_list', 33 | 'tree_fields': 'format_optional_node_field', 34 | 'datatype_methods': { 35 | 'char *': 'format_string_pointer_field', 36 | 'const char *': 'format_string_pointer_field', 37 | 'Bitmapset *': 'format_bitmapset_field', 38 | 'struct timeval': 'format_timeval_field', 39 | 'NameData': 'format_namedata_field', 40 | 'struct nameData': 'format_namedata_field', 41 | 'struct ItemPointerData': 'format_item_pointer_data_field', 42 | }, 43 | 'show_hidden': False, 44 | 'max_recursion_depth': 30 45 | } 46 | 47 | # TODO: generate these overrides in a yaml config file 48 | FORMATTER_OVERRIDES = { 49 | 'A_Expr': { 50 | 'fields':{ 51 | 'location': {'visibility': "never_show"}, 52 | }, 53 | }, 54 | 'Aggref': { 55 | 'fields':{ 56 | 'aggcollid': {'visibility': "not_null"}, 57 | 'inputcollid': {'visibility': "not_null"}, 58 | 'aggtranstype': {'visibility': "not_null"}, 59 | 'location': {'visibility': "never_show"}, 60 | }, 61 | }, 62 | 'BoolExpr': { 63 | 'fields':{ 64 | 'args': {'skip_tag': True}, 65 | 'location': {'visibility': "never_show"}, 66 | }, 67 | }, 68 | 'CaseWhen': { 69 | 'fields':{ 70 | 'location': {'visibility': "never_show"}, 71 | }, 72 | }, 73 | 'ColumnDef': { 74 | 'fields': { 75 | 'identity': { 76 | 'visibility': "not_null", 77 | 'formatter': 'format_generated_when', 78 | }, 79 | 'generated': { 80 | 'visibility': "not_null", 81 | 'formatter': 'format_generated_when', 82 | }, 83 | 'collOid': {'visibility': "not_null"}, 84 | 'location': {'visibility': "never_show"}, 85 | } 86 | }, 87 | 'Const': { 88 | 'fields': { 89 | 'consttypmod': {'visibility': "hide_invalid"}, 90 | 'constcollid': {'visibility': "not_null"}, 91 | 'location': {'visibility': "never_show"}, 92 | }, 93 | }, 94 | 'Constraint': { 95 | 'fields': { 96 | 'location': {'visibility': "never_show"}, 97 | 'conname': {'visibility': "not_null"}, 98 | 'cooked_expr': {'visibility': "not_null"}, 99 | 'generated_when': { 100 | 'visibility': "not_null", 101 | 'formatter': 'format_generated_when', 102 | }, 103 | 'indexname': {'visibility': "not_null"}, 104 | 'indexspace': {'visibility': "not_null"}, 105 | 'access_method': {'visibility': "not_null"}, 106 | 'fk_matchtype': { 107 | 'visibility': "not_null", 108 | 'formatter': 'format_foreign_key_matchtype', 109 | }, 110 | 'fk_upd_action': { 111 | 'visibility': "not_null", 112 | 'formatter': 'format_foreign_key_actions', 113 | }, 114 | 'fk_del_action': { 115 | 'visibility': "not_null", 116 | 'formatter': 'format_foreign_key_actions', 117 | }, 118 | 'old_pktable_oid': {'visibility': "not_null"}, 119 | 'trig1Oid': {'visibility': "not_null"}, 120 | 'trig2Oid': {'visibility': "not_null"}, 121 | 'trig3Oid': {'visibility': "not_null"}, 122 | 'trig4Oid': {'visibility': "not_null"}, 123 | }, 124 | 'datatype_methods': { 125 | } 126 | }, 127 | 'CreateStmt': { 128 | 'fields': { 129 | 'inhRelations': {'formatter': "format_optional_oid_list"}, 130 | } 131 | }, 132 | 'DefElem': { 133 | 'fields': { 134 | 'defnamespace': {'visibility': "not_null"}, 135 | }, 136 | }, 137 | 'DistinctExpr': { 138 | 'fields': { 139 | 'args': {'skip_tag': True}, 140 | 'opcollid': {'visibility': "not_null"}, 141 | 'inputcollid': {'visibility': "not_null"}, 142 | }, 143 | }, 144 | 'EquivalenceClass': { 145 | 'fields': { 146 | # TODO: These fields are nice to dump recursively, but 147 | # they potentially have backwards references to their parents. 148 | # Need a way to detect this condition and stop dumping 149 | 'ec_sources': {'formatter': 'minimal_format_node_field', }, 150 | 'ec_derives': {'formatter': 'minimal_format_node_field', }, 151 | }, 152 | }, 153 | 'EState': { 154 | 'fields':{ 155 | 'es_plannedstmt': {'visibility': "never_show"}, 156 | # TODO: These fields crash gdbpg.py 157 | 'es_sharenode': {'visibility': "never_show"}, 158 | }, 159 | }, 160 | 'ExprContext': { 161 | 'fields':{ 162 | 'ecxt_estate': {'visibility': "never_show"}, 163 | }, 164 | }, 165 | 'IndexOptInfo': { 166 | 'fields': { 167 | 'rel': {'formatter': 'minimal_format_node_field', } 168 | }, 169 | }, 170 | 'FuncExpr': { 171 | 'fields':{ 172 | 'args': {'skip_tag': True}, 173 | 'funccollid': {'visibility': "not_null"}, 174 | 'inputcollid': {'visibility': "not_null"}, 175 | 'location': {'visibility': "never_show"}, 176 | }, 177 | }, 178 | 'FuncExprState': { 179 | 'fields':{ 180 | # TODO: These fields crash gdbpg.py 181 | 'fp_arg': {'visibility': "never_show"}, 182 | 'fp_datum': {'visibility': "never_show"}, 183 | 'fp_null': {'visibility': "never_show"}, 184 | }, 185 | }, 186 | 'GenericExprState': { 187 | 'fields':{ 188 | 'arg': {'skip_tag': True}, 189 | }, 190 | }, 191 | 'IntoClause': { 192 | 'fields':{ 193 | 'tableSpaceName': {'visibility': 'not_null'}, 194 | }, 195 | }, 196 | # TODO: It would be nice to be able to recurse into memory contexts and 197 | # print the tree, but need to make its own NodeFormatter in order 198 | # to make its output look like a tree 199 | 'MemoryContextData': { 200 | 'fields':{ 201 | 'methods': { 202 | 'formatter': "format_pseudo_node_field", 203 | 'field_type': "node_field", 204 | }, 205 | 'parent': {'formatter': "minimal_format_memory_context_data_field"}, 206 | 'prevchild': {'formatter': "minimal_format_memory_context_data_field"}, 207 | 'firstchild': {'formatter': "minimal_format_memory_context_data_field"}, 208 | 'nextchild': {'formatter': "minimal_format_memory_context_data_field"}, 209 | }, 210 | }, 211 | 'NullIfExpr': { 212 | 'fields': { 213 | 'args': {'skip_tag': True}, 214 | 'opcollid': {'visibility': "not_null"}, 215 | 'inputcollid': {'visibility': "not_null"}, 216 | }, 217 | }, 218 | 'OpExpr': { 219 | 'fields':{ 220 | 'args': {'skip_tag': True}, 221 | 'opcollid': {'visibility': "not_null"}, 222 | 'inputcollid': {'visibility': "not_null"}, 223 | 'location': {'visibility': 'never_show'}, 224 | }, 225 | }, 226 | 'Param': { 227 | 'fields':{ 228 | 'paramtypmod': {'visibility': "hide_invalid"}, 229 | 'paramcollid': {'visibility': "not_null"}, 230 | 'location': {'visibility': "never_show"}, 231 | }, 232 | }, 233 | 'PartitionBoundSpec': { 234 | 'fields': { 235 | 'everyGenList': {'formatter': 'format_everyGenList_node'}, 236 | 'location': {'visibility': "never_show"}, 237 | }, 238 | }, 239 | 'PartitionElem': { 240 | 'fields': { 241 | 'location': {'visibility': "never_show"}, 242 | }, 243 | }, 244 | 'PartitionSpec': { 245 | 'fields': { 246 | 'location': {'visibility': "never_show"}, 247 | }, 248 | }, 249 | 'Path': { 250 | 'fields':{ 251 | 'parent': {'formatter': 'minimal_format_node_field'}, 252 | }, 253 | }, 254 | 'PlannerGlobal': { 255 | 'fields':{ 256 | 'subroots': {'formatter': 'minimal_format_node_list'}, 257 | }, 258 | }, 259 | 'PlannerInfo': { 260 | 'fields':{ 261 | 'parent_root': {'formatter': 'minimal_format_node_field'}, 262 | 'subroots': {'formatter': 'minimal_format_node_list'}, 263 | # TODO: Broken. Need to make dumping these fields possible 264 | 'simple_rel_array': {'visibility': 'never_show'}, 265 | 'simple_rte_array': {'visibility': 'never_show'}, 266 | 'upper_rels': {'formatter': 'minimal_format_node_field'}, 267 | 'upper_targets': {'formatter': 'minimal_format_node_field'}, 268 | }, 269 | }, 270 | 'Query': { 271 | 'fields':{ 272 | 'result_relation': {'visibility': 'not_null'}, 273 | 'hasAggs': {'visibility': 'not_null'}, 274 | 'hasWindowFuncs': {'visibility': 'not_null'}, 275 | 'hasSubLinks': {'visibility': 'not_null'}, 276 | 'hasDynamicFunctions': {'visibility': 'not_null'}, 277 | 'hasFuncsWithExecRestrictions': {'visibility': 'not_null'}, 278 | 'hasDistinctOn': {'visibility': 'not_null'}, 279 | 'hasRecursive': {'visibility': 'not_null'}, 280 | 'hasModifyingCTE': {'visibility': 'not_null'}, 281 | 'hasForUpdate': {'visibility': 'not_null'}, 282 | 'hasRowSecurity': {'visibility': 'not_null'}, 283 | 'canOptSelectLockingClause': {'visibility': 'not_null'}, 284 | 'isTableValueSelect': {'visibility': 'not_null'}, 285 | }, 286 | }, 287 | 'RangeTblEntry': { 288 | 'fields': { 289 | 'relid': {'visibility': "not_null"}, 290 | 'relkind': { 291 | 'visibility': "not_null", 292 | 'formatter': 'format_char_field', 293 | }, 294 | 'rellockmode': {'visibility': "not_null"}, 295 | 'tablesample': {'visibility': "not_null"}, 296 | 'subquery': {'visibility': "not_null"}, 297 | 'security_barrier': {'visibility': "not_null"}, 298 | 'tablefunc': {'visibility': "not_null"}, 299 | 'ctename': {'visibility': "not_null"}, 300 | 'tcelevelsup': {'visibility': "not_null"}, 301 | 'enrname': {'visibility': "not_null"}, 302 | 'enrtuples': {'visibility': "not_null"}, 303 | 'inh': {'visibility': "not_null"}, 304 | 'requiredPerms': {'visibility': "not_null"}, 305 | 'checkAsUser': {'visibility': "not_null"}, 306 | 'selectedCols': {'visibility': "not_null"}, 307 | 'insertedCols': {'visibility': "not_null"}, 308 | 'updatedCols': {'visibility': "not_null"}, 309 | 'extraUpdatedCols': {'visibility': "not_null"}, 310 | 'eref': {'visibility': "never_show"}, 311 | }, 312 | }, 313 | 'RangeVar': { 314 | 'fields': { 315 | 'catalogname': {'visibility': "not_null"}, 316 | 'schemaname': {'visibility': "not_null"}, 317 | 'relpersistence': {'formatter': "format_char_field"}, 318 | 'location': {'visibility': "never_show"}, 319 | }, 320 | }, 321 | 'RelabelType': { 322 | 'fields': { 323 | 'arg': {'skip_tag': True}, 324 | 'resultcollid': {'visibility': "not_null"}, 325 | 'resulttypmod': {'visibility': "hide_invalid"}, 326 | 'location': {'visibility': "never_show"}, 327 | }, 328 | }, 329 | 'RestrictInfo': { 330 | 'fields': { 331 | 'parent_ec': {'formatter': 'minimal_format_node_field', }, 332 | 'scansel_cache': {'formatter': 'minimal_format_node_field', } 333 | }, 334 | }, 335 | 'SubLink': { 336 | 'fields': { 337 | 'location': {'visibility': "never_show"}, 338 | }, 339 | }, 340 | 'TargetEntry': { 341 | 'fields':{ 342 | 'expr': {'skip_tag': True}, 343 | 'resname': {'visibility': "not_null"}, 344 | 'ressortgroupref': {'visibility': "not_null"}, 345 | 'resorigtbl': {'visibility': "not_null"}, 346 | 'resorigcol': {'visibility': "not_null"}, 347 | 'resjunk': {'visibility': "not_null"}, 348 | }, 349 | }, 350 | 'TupleTableSlot': { 351 | 'fields': { 352 | 'tts_tupleDescriptor': { 353 | 'field_type': 'node_field', 354 | 'formatter': 'format_tuple_descriptor', 355 | }, 356 | 'PRIVATE_tts_isnull': { 357 | 'field_type': 'node_field', 358 | 'formatter': 'format_tts_isnulls', 359 | }, 360 | 'PRIVATE_tts_values': { 361 | 'field_type': 'node_field', 362 | 'formatter': 'format_tts_values', 363 | }, 364 | }, 365 | }, 366 | 'TypeName': { 367 | 'fields': { 368 | 'typeOid': {'visibility': "not_null"}, 369 | 'typemod': {'visibility': "hide_invalid"}, 370 | 'location': {'visibility': "never_show"}, 371 | }, 372 | }, 373 | 'Var': { 374 | 'fields':{ 375 | 'varno': {'formatter': "format_varno_field"}, 376 | 'vartypmod': {'visibility': "hide_invalid"}, 377 | 'varcollid': {'visibility': "not_null"}, 378 | 'varlevelsup': {'visibility': "not_null"}, 379 | 'varoattno': {'visibility': "hide_invalid"}, 380 | 'location': {'visibility': "never_show"}, 381 | }, 382 | }, 383 | # Plan Nodes 384 | 'Plan': { 385 | 'fields': { 386 | 'extParam': {'visibility': "not_null"}, 387 | 'allParam': {'visibility': "not_null"}, 388 | 'operatorMemKB': {'visibility': "not_null"}, 389 | 'lefttree': { 390 | 'field_type': 'tree_field', 391 | 'visibility': 'not_null', 392 | }, 393 | 'righttree': { 394 | 'field_type': 'tree_field', 395 | 'visibility': 'not_null', 396 | }, 397 | # GPDB Only: 398 | 'motionNode': {'formatter': "minimal_format_node_field"}, 399 | 'gpmon_pkt': {'visibility': "never_show"}, 400 | }, 401 | }, 402 | 403 | # GPDB Specific Plan nodes 404 | 'Motion': { 405 | 'fields': { 406 | 'hashFuncs': {'visibility': "not_null"}, 407 | 'segidColIdx': {'visibility': "not_null"}, 408 | 'numSortCols': {'visibility': "not_null"}, 409 | 'sortColIdx': {'visibility': "not_null"}, 410 | 'sortOperators': {'visibility': "not_null"}, 411 | 'nullsFirst': {'visibility': "not_null"}, 412 | 'senderSliceInfo': {'visibility': "not_null"}, 413 | }, 414 | }, 415 | 416 | # State Nodes 417 | 'PlanState': { 418 | 'fields': { 419 | 'lefttree': { 420 | 'field_type': 'tree_field', 421 | 'visibility': 'not_null', 422 | 'skip_tag': True 423 | }, 424 | 'righttree': { 425 | 'field_type': 'tree_field', 426 | 'visibility': 'not_null', 427 | 'skip_tag': True 428 | }, 429 | 'plan': { 'formatter': 'minimal_format_node_field', }, 430 | 'state': { 'formatter': 'minimal_format_node_field', }, 431 | 'instrument': { 432 | 'field_type': 'node_field', 433 | 'formatter': 'format_pseudo_node_field', 434 | }, 435 | # GPDB only: 436 | 'gpmon_pkt': {'visibility': 'never_show'}, 437 | }, 438 | }, 439 | 'AggState': { 440 | 'fields': { 441 | 'hashiter': { 442 | 'field_type': 'node_field', 443 | 'formatter': 'format_pseudo_node_field', 444 | }, 445 | }, 446 | }, 447 | 448 | # GPDB Specific Partition related nodes 449 | 'PartitionBy': { 450 | 'fields': { 451 | 'location': {'visibility': "never_show"}, 452 | }, 453 | }, 454 | 'PartitionRangeItem': { 455 | 'fields': { 456 | 'location': {'visibility': "never_show"}, 457 | }, 458 | }, 459 | 460 | # Pseudo nodes 461 | 'tupleDesc': { 462 | 'fields': { 463 | 'tdtypmod': {'visibility': "hide_invalid"}, 464 | 'tdrefcount': {'visibility': "hide_invalid"}, 465 | }, 466 | }, 467 | 'FormData_pg_attribute': { 468 | 'fields': { 469 | 'attstattarget': {'visibility': "hide_invalid"}, 470 | 'attndims': {'visibility': "not_null"}, 471 | 'attcacheoff': {'visibility': "hide_invalid"}, 472 | 'atttypmod': {'visibility': "hide_invalid"}, 473 | 'attstorage': {'formatter': "format_char_field"}, 474 | 'attalign': {'formatter': "format_char_field"}, 475 | 'attinhcount': {'visibility': "not_null"}, 476 | 'attcollation': {'visibility': "not_null"}, 477 | }, 478 | }, 479 | 'MotionConn': { 480 | 'fields': { 481 | 'sndQueue': {'visibility': "never_show"}, 482 | 'unackQueue': {'visibility': "never_show"}, 483 | 'conn_info': { 484 | 'field_type': 'node_field', 485 | 'formatter': 'format_pseudo_node_field', 486 | }, 487 | 'ackWaitBeginTime': { 488 | 'field_type': 'node_field', 489 | 'formatter': 'format_pseudo_node_field', 490 | }, 491 | 'activeXmitTime': { 492 | 'field_type': 'node_field', 493 | 'formatter': 'format_pseudo_node_field', 494 | }, 495 | 'remoteHostAndPort': {'formatter': "format_string_pointer_field"}, 496 | 'localHostAndPort': {'formatter': "format_string_pointer_field"}, 497 | # TODO: need a sockaddr_storage dumping function 498 | 'peer': { 499 | 'field_type': 'node_field', 500 | 'formatter': 'format_pseudo_node_field', 501 | 'visibility': 'never_show', 502 | }, 503 | }, 504 | }, 505 | } 506 | 507 | 508 | StateNodes = [] 509 | for node in PlanNodes: 510 | StateNodes.append(node + "State") 511 | 512 | JoinNodes = ['NestLoop', 'MergeJoin', 'HashJoin', 'Join', 'NestLoopState', 513 | 'MergeJoinState', 'HashJoinState', 'JoinState'] 514 | 515 | recursion_depth = 0 516 | 517 | def format_type(t, indent=0): 518 | 'strip the leading T_ from the node type tag' 519 | 520 | t = str(t) 521 | 522 | if t.startswith('T_'): 523 | t = t[2:] 524 | 525 | return add_indent(t, indent) 526 | 527 | def get_base_datatype(l): 528 | if isinstance(l, gdb.Type): 529 | stripped_type = l.strip_typedefs() 530 | else: 531 | stripped_type = l.type.strip_typedefs() 532 | while stripped_type.code in [gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY]: 533 | # from: https://sourceware.org/gdb/current/onlinedocs/gdb/Types-In-Python.html 534 | # 535 | # 'For a pointer type, the target type is the type of the 536 | # pointed-to object. For an array type (meaning C-like 537 | # arrays), the target type is the type of the elements of 538 | # the array. For a function or method type, the target type 539 | # is the type of the return value. For a complex type, the 540 | # target type is the type of the elements. For a typedef, 541 | # the target type is the aliased type.' 542 | stripped_type = stripped_type.target() 543 | 544 | return stripped_type 545 | 546 | def get_base_datatype_string(l): 547 | basetype = get_base_datatype(l) 548 | if basetype.tag == None: 549 | return str(basetype) 550 | return basetype.tag 551 | 552 | def is_old_style_list(l): 553 | try: 554 | x = l['head'] 555 | return True 556 | except: 557 | return False 558 | 559 | def format_oid_list(lst, indent=0): 560 | 'format list containing Oid values directly (not warapped in Node)' 561 | 562 | # handle NULL pointer (for List we return NIL) 563 | if (str(lst) == '0x0'): 564 | return '(NIL)' 565 | 566 | # we'll collect the formatted items into a Python list 567 | tlist = [] 568 | if is_old_style_list(lst): 569 | item = lst['head'] 570 | 571 | # walk the list until we reach the last item 572 | while str(item) != '0x0': 573 | 574 | # get item from the list and just grab 'oid_value as int' 575 | tlist.append(int(item['data']['oid_value'])) 576 | 577 | # next item 578 | item = item['next'] 579 | else: 580 | for col in range(0, lst['length']): 581 | element = lst['elements'][col] 582 | tlist.append(int(element['oid_value'])) 583 | 584 | return add_indent(str(tlist), indent) 585 | 586 | 587 | def format_node_list(lst, indent=0, newline=False): 588 | 'format list containing Node values' 589 | 590 | # handle NULL pointer (for List we return NIL) 591 | if (str(lst) == '0x0'): 592 | return add_indent('(NULL)', indent) 593 | 594 | # we'll collect the formatted items into a Python list 595 | tlist = [] 596 | 597 | if is_old_style_list(lst): 598 | item = lst['head'] 599 | 600 | # walk the list until we reach the last item 601 | while str(item) != '0x0': 602 | 603 | # we assume the list contains Node instances, so grab a reference 604 | # and cast it to (Node*) 605 | node = cast(item['data']['ptr_value'], 'Node') 606 | 607 | # append the formatted Node to the result list 608 | tlist.append(format_node(node)) 609 | 610 | # next item 611 | item = item['next'] 612 | else: 613 | for col in range(0, lst['length']): 614 | element = lst['elements'][col] 615 | node = cast(element['ptr_value'], 'Node') 616 | tlist.append(format_node(node)) 617 | 618 | retval = str(tlist) 619 | if newline: 620 | retval = "\n".join([str(t) for t in tlist]) 621 | 622 | return add_indent(retval, indent) 623 | 624 | 625 | def format_char(value): 626 | '''convert the 'value' into a single-character string (ugly, maybe there's a better way''' 627 | 628 | str_val = str(value.cast(gdb.lookup_type('char'))) 629 | 630 | # remove the quotes (start/end) 631 | return str_val.split(' ')[1][1:-1] 632 | 633 | 634 | def format_bitmapset(bitmapset): 635 | if (str(bitmapset) == '0x0'): 636 | return '0x0' 637 | 638 | num_words = int(bitmapset['nwords']) 639 | retval = '0x' 640 | for word in reversed(range(num_words)): 641 | retval += '%08x' % int(bitmapset['words'][word]) 642 | return retval 643 | 644 | 645 | def format_node_array(array, start_idx, length, indent=0): 646 | 647 | items = [] 648 | for i in range(start_idx, start_idx + length - 1): 649 | items.append(str(i) + " => " + format_node(array[i])) 650 | 651 | return add_indent(("\n".join(items)), indent) 652 | 653 | def max_depth_exceeded(): 654 | global recursion_depth 655 | if recursion_depth >= DEFAULT_DISPLAY_METHODS['max_recursion_depth']: 656 | return True 657 | return False 658 | 659 | def format_node(node, indent=0): 660 | 'format a single Node instance (only selected Node types supported)' 661 | 662 | # Check the recursion depth 663 | global recursion_depth 664 | if max_depth_exceeded(): 665 | if is_node(node): 666 | node = cast(node, 'Node') 667 | return "%s %s " % (format_type(node['type']), str(node)) 668 | else: 669 | return "%s " % str(node) 670 | 671 | recursion_depth += 1 672 | 673 | if str(node) == '0x0': 674 | return add_indent('(NULL)', indent) 675 | 676 | retval = '' 677 | 678 | if is_a(node, 'A_Const'): 679 | node = cast(node, 'A_Const') 680 | 681 | retval = format_a_const(node) 682 | 683 | elif is_a(node, 'List'): 684 | node = cast(node, 'List') 685 | 686 | retval = format_node_list(node, 0, True) 687 | 688 | elif is_a(node, 'String'): 689 | node = cast(node, 'Value') 690 | 691 | retval = 'String [%s]' % getchars(node['val']['str']) 692 | 693 | elif is_a(node, 'Integer'): 694 | node = cast(node, 'Value') 695 | 696 | retval = 'Integer [%s]' % node['val']['ival'] 697 | 698 | elif is_a(node, 'OidList'): 699 | retval = 'OidList: %s' % format_oid_list(node) 700 | 701 | elif is_a(node, 'IntList'): 702 | retval = 'IntList: %s' % format_oid_list(node) 703 | 704 | elif is_pathnode(node): 705 | node_formatter = PlanStateFormatter(node) 706 | retval += node_formatter.format() 707 | 708 | elif is_plannode(node): 709 | node_formatter = PlanStateFormatter(node) 710 | retval += node_formatter.format() 711 | 712 | elif is_statenode(node): 713 | node_formatter = PlanStateFormatter(node) 714 | retval += node_formatter.format() 715 | 716 | else: 717 | node_formatter = NodeFormatter(node) 718 | retval += node_formatter.format() 719 | 720 | recursion_depth -= 1 721 | return add_indent(str(retval), indent) 722 | 723 | def is_pathnode(node): 724 | for nodestring in PathNodes: 725 | if is_a(node, nodestring): 726 | return True 727 | 728 | return False 729 | 730 | def is_plannode(node): 731 | for nodestring in PlanNodes: 732 | if is_a(node, nodestring): 733 | return True 734 | 735 | return False 736 | 737 | def is_statenode(node): 738 | for nodestring in StateNodes: 739 | if is_a(node, nodestring): 740 | return True 741 | 742 | return False 743 | 744 | def is_joinnode(node): 745 | for nodestring in JoinNodes: 746 | if is_a(node, nodestring): 747 | return True 748 | 749 | return False 750 | 751 | def format_a_const(node, indent=0): 752 | retval = "A_Const [%(val)s]" % { 753 | 'val': format_node(node['val'].address), 754 | } 755 | 756 | return add_indent(retval, indent) 757 | 758 | def is_a(n, t): 759 | '''checks that the node has type 't' (just like IsA() macro)''' 760 | 761 | if not is_node(n): 762 | return False 763 | n = cast(n, 'Node') 764 | 765 | return (str(n['type']) == ('T_' + t)) 766 | 767 | def is_xpr(l): 768 | try: 769 | x = l['xpr'] 770 | return True 771 | except: 772 | return False 773 | 774 | def is_node(l): 775 | '''return True if the value looks like a Node (has 'type' field)''' 776 | if is_xpr(l): 777 | return True 778 | 779 | try: 780 | x = l['type'] 781 | return True 782 | except: 783 | return False 784 | 785 | def is_type(value, type_name, is_pointer): 786 | t = gdb.lookup_type(type_name) 787 | if(is_pointer): 788 | t = t.pointer() 789 | return (str(value.type) == str(t)) 790 | # This doesn't work for list types for some reason... 791 | # return (gdb.types.get_basic_type(value.type) == gdb.types.get_basic_type(t)) 792 | 793 | def cast(node, type_name): 794 | '''wrap the gdb cast to proper node type''' 795 | 796 | # lookup the type with name 'type_name' and cast the node to it 797 | t = gdb.lookup_type(type_name) 798 | return node.cast(t.pointer()) 799 | 800 | def get_base_node_type(node): 801 | if is_node(node): 802 | node = cast(node, "Node") 803 | return format_type(node['type']) 804 | 805 | return None 806 | 807 | def add_indent(val, indent, add_newline=False): 808 | retval = '' 809 | if add_newline == True: 810 | retval += '\n' 811 | 812 | retval += "\n".join([(("\t" * indent) + l) for l in val.split("\n")]) 813 | return retval 814 | 815 | def getchars(arg): 816 | if (str(arg) == '0x0'): 817 | return str(arg) 818 | 819 | retval = '"' 820 | 821 | i=0 822 | while arg[i] != ord("\0"): 823 | character = int(arg[i].cast(gdb.lookup_type("char"))) 824 | if chr(character) in string.printable: 825 | retval += "%c" % chr(character) 826 | else: 827 | retval += "\\x%x" % character 828 | i += 1 829 | 830 | retval += '"' 831 | 832 | return retval 833 | 834 | def get_node_fields(node): 835 | nodefields = [("Node", True), ("Expr", True)] 836 | type_name = str(node['type']).replace("T_", "") 837 | 838 | t = gdb.lookup_type(type_name) 839 | fields = [] 840 | for v in t.values(): 841 | for field, is_pointer in nodefields: 842 | if is_type(v, field, is_pointer): 843 | fields.append(v.name) 844 | 845 | return fields 846 | 847 | def get_list_fields(node): 848 | listfields = [("List", True)] 849 | type_name = str(node['type']).replace("T_", "") 850 | 851 | t = gdb.lookup_type(type_name) 852 | fields = [] 853 | for v in t.values(): 854 | for field, is_pointer in listfields: 855 | if is_type(v, field, is_pointer): 856 | fields.append(v.name) 857 | return fields 858 | 859 | def format_regular_field(node, field): 860 | return node[field] 861 | 862 | def format_string_pointer_field(node, field): 863 | return getchars(node[field]) 864 | 865 | def format_char_field(node, field): 866 | return "'%s'" % format_char(node[field]) 867 | 868 | def format_bitmapset_field(node, field): 869 | return format_bitmapset(node[field]) 870 | 871 | def format_generated_when(node, field): 872 | generated_when = { 873 | 'a': 'ATTRIBUTE_IDENTITY_ALWAYS', 874 | 'd': 'ATTRIBUTE_IDENTITY_BY_DEFAULT', 875 | 's': 'ATTRIBUTE_GENERATED_STORED', 876 | } 877 | 878 | fk_char = format_char(node[field]) 879 | 880 | if generated_when.get(fk_char) != None: 881 | return generated_when.get(fk_char) 882 | 883 | return fk_char 884 | 885 | def format_foreign_key_matchtype(node, field): 886 | foreign_key_matchtypes = { 887 | 'f': 'FKCONSTR_MATCH_FULL', 888 | 'p': 'FKCONSTR_MATCH_PARTIAL', 889 | 's': 'FKCONSTR_MATCH_SIMPLE', 890 | } 891 | 892 | fk_char = format_char(node[field]) 893 | 894 | if foreign_key_matchtypes.get(fk_char) != None: 895 | return foreign_key_matchtypes.get(fk_char) 896 | 897 | return fk_char 898 | 899 | 900 | def format_foreign_key_actions(node, field): 901 | foreign_key_actions = { 902 | 'a': 'FKONSTR_ACTION_NOACTION', 903 | 'r': 'FKCONSTR_ACTION_RESTRICT', 904 | 'c': 'FKCONSTR_ACTION_CASCADE', 905 | 'n': 'FKONSTR_ACTION_SETNULL', 906 | 'd': 'FKONSTR_ACTION_SETDEFAULT', 907 | } 908 | 909 | fk_char = format_char(node[field]) 910 | 911 | if foreign_key_actions.get(fk_char) != None: 912 | return foreign_key_actions.get(fk_char) 913 | 914 | return fk_char 915 | 916 | def format_varno_field(node, field): 917 | varno_type = { 918 | 65000: "INNER_VAR", 919 | 65001: "OUTER_VAR", 920 | 65002: "INDEX_VAR", 921 | } 922 | 923 | varno = int(node[field]) 924 | if varno_type.get(varno) != None: 925 | return varno_type.get(varno) 926 | 927 | return node[field] 928 | 929 | def format_timeval_field(node, field): 930 | return "%s.%s" %(node[field]['tv_sec'], node[field]['tv_usec']) 931 | 932 | def format_namedata_field(node, field): 933 | return getchars(node[field]['data']) 934 | 935 | def format_item_pointer_data_field(node, field): 936 | ip_blkid = node[field]['ip_blkid'] 937 | block_hi = int(ip_blkid['bi_hi']) 938 | block_low = int(ip_blkid['bi_lo']) 939 | 940 | block_id = block_hi << 16 941 | block_id += block_low 942 | 943 | return "(%s,%s)" % (block_id, node[field]['ip_posid']) 944 | 945 | def format_optional_node_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 946 | if cast_to != None: 947 | node = cast(node, cast_to) 948 | 949 | if str(node[fieldname]) != '0x0': 950 | node_output = format_node(node[fieldname]) 951 | 952 | if skip_tag == True: 953 | return add_indent('%s' % node_output, indent, True) 954 | else: 955 | retval = '[%s] ' % fieldname 956 | if node_output.count('\n') > 0: 957 | node_output = add_indent(node_output, 1, True) 958 | retval += node_output 959 | return add_indent(retval , indent, True) 960 | elif print_null == True: 961 | return add_indent("[%s] (NULL)" % fieldname, indent, True) 962 | 963 | return '' 964 | 965 | def format_optional_node_list(node, fieldname, cast_to=None, skip_tag=False, newLine=True, print_null=False, indent=1): 966 | if cast_to != None: 967 | node = cast(node, cast_to) 968 | 969 | retval = '' 970 | indent_add = 0 971 | if str(node[fieldname]) != '0x0': 972 | if is_a(node[fieldname], 'OidList') or is_a(node[fieldname], 'IntList'): 973 | return format_optional_oid_list(node, fieldname, skip_tag, newLine, print_null, indent) 974 | 975 | if skip_tag == False: 976 | retval += add_indent('[%s]' % fieldname, indent, True) 977 | indent_add = 1 978 | 979 | if newLine == True: 980 | retval += '\n' 981 | retval += '%s' % format_node_list(node[fieldname], indent + indent_add, newLine) 982 | else: 983 | retval += ' %s' % format_node_list(node[fieldname], 0, newLine) 984 | elif print_null == True: 985 | return add_indent("[%s] (NIL)" % fieldname, indent, True) 986 | 987 | return retval 988 | 989 | def format_optional_oid_list(node, fieldname, skip_tag=False, newLine=False, print_null=False, indent=1): 990 | retval = '' 991 | if str(node[fieldname]) != '0x0': 992 | node_type = format_type(node[fieldname]['type']) 993 | if skip_tag == False: 994 | retval += '[%s] %s' % (fieldname, format_node(node[fieldname])) 995 | else: 996 | retval += format_node(node[fieldname]) 997 | 998 | retval = add_indent(retval, indent, True) 999 | elif print_null == True: 1000 | retval += add_indent("[%s] (NIL)" % fieldname, indent, True) 1001 | 1002 | return retval 1003 | 1004 | def format_everyGenList_node(node, fieldname, skip_tag=False, newLine=False, print_null=False, indent=1): 1005 | retval = '' 1006 | if str(node[fieldname]) != '0x0': 1007 | genlist_strings = [] 1008 | if is_old_style_list(node[fieldname]): 1009 | item = node[fieldname]['head'] 1010 | 1011 | while str(item) != '0x0': 1012 | 1013 | listnode = cast(item['data']['ptr_value'], 'List') 1014 | 1015 | genlist_item = listnode['head'] 1016 | val = '[' 1017 | while str(genlist_item) != '0x0': 1018 | genlist_string = getchars(cast(genlist_item['data']['ptr_value'], 'char')) 1019 | val += genlist_string 1020 | # next item 1021 | genlist_item = genlist_item['next'] 1022 | if str(genlist_item) != "0x0": 1023 | val += ' ' 1024 | val += ']' 1025 | 1026 | genlist_strings.append(val) 1027 | 1028 | # next item 1029 | item = item['next'] 1030 | else: 1031 | raise Exception("Tried to dump everyGenList using new style lists") 1032 | 1033 | if skip_tag == False: 1034 | retval += '[%s]' % fieldname 1035 | 1036 | for item in genlist_strings: 1037 | retval += add_indent(item, 1, True) 1038 | 1039 | retval = add_indent(retval, indent, True) 1040 | elif print_null == True: 1041 | retval += add_indent("[%s] (NIL)" % fieldname, indent, True) 1042 | 1043 | return retval 1044 | 1045 | def minimal_format_node_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1046 | retval = '' 1047 | if str(node[fieldname]) != '0x0': 1048 | retval = add_indent("[%s] (%s)%s" % (fieldname, node[fieldname].type, node[fieldname]), indent, True) 1049 | elif print_null == True: 1050 | retval = add_indent("[%s] (NIL)" % fieldname, indent, True) 1051 | 1052 | return retval 1053 | 1054 | def minimal_format_node_list(node, fieldname, cast_to=None, skip_tag=False, newLine=True, print_null=False, indent=1): 1055 | retval = '' 1056 | indent_add = 0 1057 | if str(node[fieldname]) != '0x0': 1058 | if skip_tag == False: 1059 | retval += add_indent('[%s]' % fieldname, indent, True) 1060 | indent_add = 1 1061 | 1062 | if newLine == True: 1063 | retval += '\n' 1064 | retval += '%s' % minimal_format_node_list_field(node, fieldname, cast_to, skip_tag, newLine, print_null, indent + indent_add) 1065 | else: 1066 | retval += ' %s' % minimal_format_node_list_field(node, fieldname, cast_to, skip_tag, newLine, print_null, 0) 1067 | elif print_null == True: 1068 | return add_indent("[%s] (NIL)" % fieldname, indent, True) 1069 | 1070 | return retval 1071 | 1072 | def minimal_format_node_list_field(node, fieldname, cast_to=None, skip_tag=False, newLine=True, print_null=False, indent=1): 1073 | 'minimal format list containing Node values' 1074 | if cast_to != None: 1075 | lst = cast(node[fieldname], cast_to) 1076 | else: 1077 | lst = node[fieldname] 1078 | 1079 | # we'll collect the formatted items into a Python list 1080 | tlist = [] 1081 | 1082 | if is_old_style_list(lst): 1083 | item = lst['head'] 1084 | 1085 | # walk the list until we reach the last item 1086 | while str(item) != '0x0': 1087 | lstnode = cast(item['data']['ptr_value'], 'Node') 1088 | nodetype = get_base_node_type(lstnode) 1089 | lstnode = cast(lstnode, nodetype) 1090 | 1091 | val = "(%s)%s" % (lstnode.type, node[fieldname]) 1092 | # append the formatted Node to the result list 1093 | tlist.append(val) 1094 | 1095 | # next item 1096 | item = item['next'] 1097 | else: 1098 | for col in range(0, lst['length']): 1099 | element = lst['elements'][col] 1100 | lstnode = cast(item['data']['ptr_value'], 'Node') 1101 | nodetype = get_base_node_type(lstnode) 1102 | lstnode = cast(lstnode, nodetype) 1103 | 1104 | val = "(%s)%s" % (lstnode.type, node[fieldname]) 1105 | 1106 | tlist.append(val) 1107 | 1108 | if newLine: 1109 | retval = "\n".join([str(t) for t in tlist]) 1110 | else: 1111 | retval = str(tlist) 1112 | 1113 | return add_indent(retval, indent) 1114 | 1115 | 1116 | def minimal_format_memory_context_data_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1117 | retval = '' 1118 | if str(node[fieldname]) != '0x0': 1119 | retval = add_indent("[%s] (%s)%s [name=%s]" % (fieldname, node[fieldname].type, node[fieldname], format_string_pointer_field(node[fieldname], 'name')), indent, True) 1120 | elif print_null == True: 1121 | retval = add_indent("[%s] (NIL)" % fieldname, indent, True) 1122 | 1123 | return retval 1124 | 1125 | def format_pseudo_node_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1126 | if str(node[fieldname]) != '0x0': 1127 | node_type = node[fieldname].type.strip_typedefs() 1128 | formatter = NodeFormatter(node[fieldname], pseudo_node=True) 1129 | node_output = formatter.format() 1130 | 1131 | if skip_tag == True: 1132 | return add_indent('%s' % node_output, indent, True) 1133 | else: 1134 | retval = '[%s] ' % fieldname 1135 | if node_output.count('\n') > 0: 1136 | node_output = add_indent(node_output, 1, True) 1137 | retval += node_output 1138 | return add_indent(retval , indent, True) 1139 | elif print_null == True: 1140 | return add_indent("[%s] (NULL)" % fieldname, indent, True) 1141 | 1142 | return '' 1143 | 1144 | # --- 1145 | # TupleTableSlot related dumpers 1146 | def format_tuple_descriptor(node, field, cast_to=None, skip_tag=False, print_null=False, indent=1): 1147 | if str(node[field]) == '0x0': 1148 | return '[%s] (NULL)' % field 1149 | 1150 | natts = node[field]['natts'] 1151 | retval = format_pseudo_node_field(node, field, 'tupleDesc' , skip_tag, print_null, 0) 1152 | for col in range(0, natts): 1153 | attr = node[field]['attrs'][col] 1154 | formatter = LabelNodeFormatter(attr, pseudo_node=True, label='[%s] ' % (col+1)) 1155 | retval += add_indent(formatter.format(), 1, True) 1156 | 1157 | return add_indent(retval, indent, False) 1158 | 1159 | def format_tts_isnulls(node, field, cast_to=None, skip_tag=False, print_null=False, indent=1): 1160 | if str(node['tts_tupleDescriptor']) == '0x0' or str(node[field]) == '0x0': 1161 | if print_null: 1162 | return '[%s] (NULL)' % field 1163 | else: 1164 | return '' 1165 | 1166 | descr = node['tts_tupleDescriptor'].dereference() 1167 | natts = descr['natts'] 1168 | 1169 | nullmap, nullmap_bytes = get_tts_nullmap(node, field, natts) 1170 | retlines = [] 1171 | col = 1 1172 | for isnull, byte in zip(nullmap, nullmap_bytes): 1173 | if isnull == None: 1174 | retlines.append('[%s] %s ' % (col, byte)) 1175 | else: 1176 | retlines.append('[%s] %s' % (col, isnull)) 1177 | col+=1 1178 | 1179 | retval = '\n'.join([line for line in retlines]) 1180 | 1181 | if skip_tag: 1182 | return add_indent('%s' % retval, indent, True) 1183 | else: 1184 | retval = '[%s]' % field + add_indent(retval, 1, True) 1185 | return add_indent(retval, indent, True) 1186 | 1187 | def get_tts_nullmap(node, nullmap_field, natts): 1188 | if str(node[nullmap_field]) == '0x0': 1189 | return None 1190 | 1191 | nullmap = [] 1192 | nullmap_byte = [] 1193 | for col in range(0, natts): 1194 | if node[nullmap_field][col] not in [0,1]: 1195 | nullmap.append(None) 1196 | else: 1197 | nullmap.append((int(node[nullmap_field][col]) == 1)) 1198 | 1199 | nullmap_byte.append('0x%x' % node[nullmap_field][col]) 1200 | 1201 | return nullmap, nullmap_byte 1202 | 1203 | def format_tts_values(node, field, cast_to=None, skip_tag=False, print_null=False, indent=1): 1204 | if str(node['tts_tupleDescriptor']) == '0x0' or str(node[field]) == '0x0': 1205 | if print_null: 1206 | return '[%s] (NULL)' % field 1207 | else: 1208 | return '' 1209 | 1210 | descr = node['tts_tupleDescriptor'].dereference() 1211 | natts = descr['natts'] 1212 | attrs = descr['attrs'] 1213 | 1214 | nullmap, nullmap_bytes = get_tts_nullmap(node, 'PRIVATE_tts_isnull', natts) 1215 | 1216 | tts_values_list = [] 1217 | 1218 | values = node[field] 1219 | tts_values_list = [] 1220 | for col in range(0, natts): 1221 | attr = attrs[col].dereference() 1222 | tts_values_list.append("[%d] 0x%08x" % (col + 1, values[col])) 1223 | 1224 | values_retval = '\n'.join([line for line in tts_values_list]) 1225 | 1226 | formatted_tuple_list= [] 1227 | for col in range(0, natts): 1228 | attr = attrs[col].dereference() 1229 | formatted_tuple_list.append("[%d] %s" % (col + 1, format_tuple_value(values[col], nullmap[col], attr))) 1230 | 1231 | formatted_tuples_retval = '\n'.join([line for line in formatted_tuple_list]) 1232 | 1233 | retval = '[%s]' % field + add_indent(values_retval, 1, True) 1234 | retval += '\n[values_formatted_tuple]' + add_indent(formatted_tuples_retval, 1, True) 1235 | return add_indent(retval, indent, True) 1236 | 1237 | def format_tuple_value(value, is_null, attr): 1238 | typid = attr['atttypid'] 1239 | 1240 | if is_null: 1241 | return 'NULL' 1242 | elif typid == 20: 1243 | return "[int8] %d" % value 1244 | elif typid == 701: 1245 | return "[float8] %s" % value.cast(gdb.lookup_type('float8')) 1246 | else: 1247 | return "" % typid 1248 | #--- 1249 | 1250 | def debug_format_regular_field(node, field): 1251 | node_type = get_base_node_type(node) 1252 | if node_type == None: 1253 | node_type = get_base_datatype_string(node) 1254 | base_field_type = get_base_datatype_string(node[field]) 1255 | gdb_basic_type = gdb.types.get_basic_type(node[field].type) 1256 | field_type = str(node[field].type) 1257 | print("debug_format_regular_field: (field_type: %s gdb.types.get_basic_type: %s, base_field_type: %s) %s[%s]: %s" 1258 | % (field_type, gdb_basic_type, base_field_type, node_type, field, node[field])) 1259 | return node[field] 1260 | 1261 | def debug_format_string_pointer_field(node, field): 1262 | print("debug_format_string_pointer_field : %s[%s]: " % (get_base_node_type(node), field), end='') 1263 | ret = format_string_pointer_field(node,field) 1264 | print(ret) 1265 | return ret 1266 | 1267 | def debug_format_char_field(node, field): 1268 | print("debug_format_char_field: %s[%s]: " % (get_base_node_type(node), field), end='') 1269 | ret = format_char_field(node,field) 1270 | print(ret) 1271 | return ret 1272 | 1273 | def debug_format_bitmapset_field(node, field): 1274 | print("debug_format_bitmapset_field: %s[%s]: " % (get_base_node_type(node), field), end='') 1275 | ret = format_bitmapset_field(node,field) 1276 | print(ret) 1277 | return ret 1278 | 1279 | def debug_format_varno_field(node, field): 1280 | print("debug_format_varno_field: %s[%s]: " % (get_base_node_type(node), field), end='') 1281 | ret = format_varno_field(node,field) 1282 | print(ret) 1283 | return ret 1284 | 1285 | def debug_format_optional_node_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1286 | print("debug_format_optional_node_field: %s[%s]: cast_to=%s skip_tag=%s print_null=%s, indent=%s" % 1287 | (get_base_node_type(node), fieldname, cast_to, skip_tag, print_null, indent), end='') 1288 | ret = format_optional_node_field(node, fieldname, cast_to, skip_tag, True, indent) 1289 | print(ret) 1290 | return ret 1291 | 1292 | def debug_format_optional_node_list(node, fieldname, cast_to=None, skip_tag=False, newLine=True, print_null=False, indent=1): 1293 | print("debug_format_optional_node_list: %s[%s]: cast_to=%s skip_tag=%s print_null=%s, indent=%s" % 1294 | (get_base_node_type(node), fieldname, cast_to, skip_tag, print_null, indent)) 1295 | ret = format_optional_node_list(node, fieldname, cast_to, skip_tag, newLine, True, indent) 1296 | print(ret) 1297 | return ret 1298 | 1299 | def debug_format_optional_oid_list(node, fieldname, skip_tag=False, newLine=False, print_null=False, indent=1): 1300 | print("debug_format_optional_oid_list: %s[%s]: cast_to=%s skip_tag=%s print_null=%s, indent=%s" % 1301 | (get_base_node_type(node), fieldname, cast_to, skip_tag, print_null, indent)) 1302 | ret = format_optional_oid_list(node, fieldname, newLine, skip_tag, print_null, indent) 1303 | print(ret) 1304 | return ret 1305 | 1306 | def debug_minimal_format_node_list(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1307 | print("debug_minimal_format_node_list: %s[%s]: cast_to=%s skip_tag=%s print_null=%s, indent=%s" % (get_base_node_type(node), fieldname, 1308 | cast_to, skip_tag, print_null, indent)) 1309 | ret = minimal_format_node_list(node, fieldname, cast_to, skip_tag, True, indent) 1310 | print(ret) 1311 | return ret 1312 | 1313 | def debug_format_pseudo_node_field(node, fieldname, cast_to=None, skip_tag=False, print_null=False, indent=1): 1314 | print("debug_minimal_format_node_list: (%s) %s[%s]: cast_to=%s skip_tag=%s print_null=%s, indent=%s" % (get_base_datatype_string(node[fieldname]), get_base_datatype_string(node), fieldname, 1315 | cast_to, skip_tag, print_null, indent)) 1316 | ret = format_pseudo_node_field(node, fieldname, cast_to, skip_tag, True, indent) 1317 | print(ret) 1318 | return ret 1319 | 1320 | 1321 | class NodeFormatter(object): 1322 | # Basic node information 1323 | _node = None 1324 | _parent_node = None 1325 | 1326 | _type_string = None 1327 | _base_type = None 1328 | _node_type = None 1329 | 1330 | # String representations of individual fields in node 1331 | _all_fields = None 1332 | _regular_fields = None 1333 | _node_fields = None 1334 | _list_fields = None 1335 | _tree_fields = None 1336 | _ignore_field_types = None 1337 | 1338 | # Some node types are sub-types of other fields, for example a JoinState inherits a PlanState 1339 | __nested_nodes = None 1340 | 1341 | _default_display_methods = None 1342 | _default_regular_visibility = None 1343 | _default_list_visibility = None 1344 | _default_node_visibility = None 1345 | _default_skip_tag = None 1346 | _formatter_overrides = None 1347 | 1348 | # String representation of the types to match to generate the above lists 1349 | _list_types = None 1350 | _node_types = None 1351 | def __init__(self, node, typecast=None, pseudo_node=False): 1352 | 1353 | # TODO: get node and list types from yaml config OR check each field 1354 | # for a node 'signature' 1355 | # TODO: this should be done in a class method 1356 | self._list_types = ["List"] 1357 | self._node_types = ["Node"] 1358 | 1359 | self._pseudo_node = pseudo_node 1360 | if typecast == None: 1361 | if self._pseudo_node: 1362 | pseudo_type = node.type.strip_typedefs() 1363 | stripped_type = pseudo_type 1364 | 1365 | self._type_string = get_base_datatype_string(node) 1366 | self._base_type = get_base_datatype(node) 1367 | self._node_type = pseudo_type 1368 | # TODO: Why does this work? 1369 | #if self._node_type.code != gdb.TYPE_CODE_PTR: 1370 | # raise Exception("Must use a pointer for pseudo node types") 1371 | else: 1372 | self._type_string = get_base_node_type(node) 1373 | self._base_type = gdb.lookup_type(self._type_string) 1374 | self._node_type = self._base_type.pointer() 1375 | else: 1376 | self._type_string = typecast 1377 | self._base_type = gdb.lookup_type(self.type_string) 1378 | self._node_type = self._base_type.pointer() 1379 | 1380 | self._node = node.cast(self._node_type) 1381 | 1382 | 1383 | # Get methods for display 1384 | self._default_display_methods = DEFAULT_DISPLAY_METHODS 1385 | self._default_regular_visibility = ALWAYS_SHOW 1386 | self._default_list_visibility = NOT_NULL 1387 | self._default_node_visibility = NOT_NULL 1388 | self._default_skip_tag = False 1389 | self._formatter_overrides = FORMATTER_OVERRIDES.get(self.type_string) 1390 | #print("NodeFormatter:", self.type) 1391 | 1392 | def is_child_node(self): 1393 | if self._pseudo_node: 1394 | return False 1395 | 1396 | t = gdb.lookup_type(self.type_string) 1397 | first_field = t.values()[0] 1398 | 1399 | if first_field.name == "type": 1400 | return False 1401 | elif first_field.name == "xpr": 1402 | return False 1403 | elif first_field.name == "xprstate": 1404 | return False 1405 | 1406 | return True 1407 | 1408 | @property 1409 | def parent_node(self): 1410 | if self._parent_node == None: 1411 | if self.is_child_node(): 1412 | t = gdb.lookup_type(self.type_string) 1413 | first_field = t.values()[0] 1414 | 1415 | self._parent_node = NodeFormatter(self._node, str(first_field.type)) 1416 | return self._parent_node 1417 | 1418 | 1419 | def get_datatype_override(self, field): 1420 | if self._formatter_overrides != None: 1421 | datatype_overrides = self._formatter_overrides.get('datatype_methods') 1422 | if datatype_overrides != None: 1423 | return datatype_overrides.get(str(self.field_datatype(field))) 1424 | return None 1425 | 1426 | 1427 | def get_field_override(self, field, override_type): 1428 | if self._formatter_overrides != None: 1429 | field_overrides = self._formatter_overrides.get('fields') 1430 | if field_overrides != None: 1431 | field_override = field_overrides.get(field) 1432 | if field_override != None: 1433 | return field_override.get(override_type) 1434 | return None 1435 | 1436 | def get_display_method(self, field): 1437 | # Individual field overrides are a higher priority than type 1438 | # overrides so print them first 1439 | field_override_method_name = self.get_field_override(field, 'formatter') 1440 | if field_override_method_name != None: 1441 | return globals()[field_override_method_name] 1442 | 1443 | # Datatype methods are only for regular fields 1444 | datatype_override_method = self.get_datatype_override(field) 1445 | if datatype_override_method != None: 1446 | return globals()[datatype_override_method] 1447 | 1448 | # Check if this datatype has a generic dumping method 1449 | default_type_method = self._default_display_methods['datatype_methods'].get(str(self.field_datatype(field))) 1450 | if default_type_method != None: 1451 | return globals()[default_type_method] 1452 | 1453 | if field in self.regular_fields: 1454 | return globals()[self._default_display_methods['regular_fields']] 1455 | elif field in self.node_fields: 1456 | return globals()[self._default_display_methods['node_fields']] 1457 | elif field in self.list_fields: 1458 | return globals()[self._default_display_methods['list_fields']] 1459 | elif field in self.tree_fields: 1460 | return globals()[self._default_display_methods['tree_fields']] 1461 | 1462 | raise Exception("Did not find a display method for %s[%s]" % (self.type_string, field)) 1463 | 1464 | 1465 | def get_display_mode(self, field): 1466 | # If the global 'show_hidden' is set, then this command shal always 1467 | # return ALWAYS_SHOW 1468 | if self._default_display_methods['show_hidden'] == True: 1469 | return ALWAYS_SHOW 1470 | 1471 | override_string = self.get_field_override(field, 'visibility') 1472 | if override_string != None: 1473 | return override_string 1474 | 1475 | if field in self.regular_fields: 1476 | return self._default_regular_visibility 1477 | if field in self.list_fields: 1478 | return self._default_list_visibility 1479 | if field in self.node_fields: 1480 | return self._default_node_visibility 1481 | 1482 | return ALWAYS_SHOW 1483 | 1484 | def is_skip_tag(self, field): 1485 | # If the global 'show_hidden' is set, always show tag 1486 | if self._default_display_methods['show_hidden'] == True: 1487 | return False 1488 | 1489 | skip_tag = self.get_field_override(field, 'skip_tag') 1490 | if skip_tag != None: 1491 | return skip_tag 1492 | 1493 | return self._default_skip_tag 1494 | 1495 | @property 1496 | def type_string(self): 1497 | return self._type_string 1498 | 1499 | @property 1500 | def fields(self): 1501 | if self._all_fields == None: 1502 | self._all_fields = [] 1503 | t = self._base_type 1504 | 1505 | for index in range(0, len(t.values())): 1506 | skip = False 1507 | 1508 | field = t.values()[index] 1509 | # TODO: should the ability to ignore fields entirely exist at all? 1510 | # This seems to conflict with the visibility settings 1511 | # Fields that are to be ignored 1512 | if self._ignore_field_types is not None: 1513 | for tag in self._ignore_field_types: 1514 | if self.is_type(field, tag): 1515 | skip = True 1516 | 1517 | if index == 0: 1518 | # The node['type'] field is just a tag that we already know 1519 | if field.name == "type": 1520 | skip = True 1521 | # The node['xpr'] field is just a wrapper around node['type'] 1522 | elif field.name == "xpr": 1523 | skip = True 1524 | elif field.name == "xprstate": 1525 | skip = True 1526 | elif self.is_child_node(): 1527 | skip = True 1528 | if skip: 1529 | continue 1530 | 1531 | self._all_fields.append(field.name) 1532 | 1533 | return self._all_fields 1534 | 1535 | @property 1536 | def list_fields(self): 1537 | if self._list_fields == None: 1538 | self._list_fields = [] 1539 | 1540 | for f in self.fields: 1541 | # Honor overrides before all else 1542 | override_string = self.get_field_override(f, 'field_type') 1543 | if override_string != None: 1544 | if override_string == 'list_field': 1545 | self._list_fields.append(f) 1546 | else: 1547 | continue 1548 | 1549 | v = self._node[f] 1550 | for field in self._list_types: 1551 | if self.is_type(v, field): 1552 | self._list_fields.append(f) 1553 | 1554 | return self._list_fields 1555 | 1556 | @property 1557 | def node_fields(self): 1558 | if self._node_fields == None: 1559 | self._node_fields = [] 1560 | 1561 | for f in self.fields: 1562 | override_string = self.get_field_override(f, 'field_type') 1563 | if override_string != None: 1564 | if override_string == 'node_field': 1565 | self._node_fields.append(f) 1566 | else: 1567 | continue 1568 | 1569 | v = self._node[f] 1570 | for field in self._node_types: 1571 | if self.is_type(v, field): 1572 | self._node_fields.append(f) 1573 | 1574 | if is_node(v): 1575 | if f not in self.list_fields: 1576 | self._node_fields.append(f) 1577 | 1578 | return self._node_fields 1579 | 1580 | @property 1581 | def tree_fields(self): 1582 | if self._tree_fields == None: 1583 | self._tree_fields = [] 1584 | for f in self.fields: 1585 | override_string = self.get_field_override(f, 'field_type') 1586 | if override_string != None: 1587 | if override_string == 'tree_field': 1588 | self._tree_fields.append(f) 1589 | else: 1590 | continue 1591 | 1592 | return self._tree_fields 1593 | 1594 | @property 1595 | def regular_fields(self): 1596 | if self._regular_fields == None: 1597 | self._regular_fields = [] 1598 | 1599 | self._regular_fields = [field for field in self.fields if field not in self.list_fields + self.node_fields + self.tree_fields] 1600 | 1601 | return self._regular_fields 1602 | 1603 | def is_type(self, value, type_name): 1604 | t = gdb.lookup_type(type_name) 1605 | return (get_base_datatype(value) == get_base_datatype(t)) 1606 | 1607 | def field_datatype(self, field): 1608 | return gdb.types.get_basic_type(self._node[field].type) 1609 | 1610 | def format(self, prefix=None): 1611 | retval = '' 1612 | if prefix != None: 1613 | retval = prefix 1614 | retval += self.type_string + ' ' 1615 | newline_padding_chars = len(retval) 1616 | formatted_fields = self.format_all_regular_fields(newline_padding_chars+1) 1617 | 1618 | fieldno = 1 1619 | for field in formatted_fields: 1620 | retval += field 1621 | if fieldno < len(formatted_fields): 1622 | retval += '\n' + ' ' * newline_padding_chars 1623 | fieldno += 1 1624 | 1625 | retval += self.format_complex_fields() 1626 | 1627 | retval += self.format_tree_nodes() 1628 | 1629 | return retval 1630 | 1631 | def format_regular_fields(self, newline_padding_chars): 1632 | # TODO: get this value from config file 1633 | max_regular_field_chars = 140 1634 | retval = "[" 1635 | 1636 | fieldcount = 0 1637 | retline = "" 1638 | for field in self.regular_fields: 1639 | fieldcount +=1 1640 | display_mode = self.get_display_mode(field) 1641 | if display_mode == NEVER_SHOW: 1642 | continue 1643 | 1644 | # Some fields don't have a meaning if they aren't given a value 1645 | if display_mode == NOT_NULL: 1646 | field_datatype = self.field_datatype(field) 1647 | empty_value = gdb.Value(0).cast(field_datatype) 1648 | if self._node[field] == empty_value: 1649 | continue 1650 | 1651 | # Some fields are initialized to -1 if they are not used 1652 | if display_mode == HIDE_INVALID: 1653 | field_datatype = self.field_datatype(field) 1654 | empty_value = gdb.Value(-1).cast(field_datatype) 1655 | if self._node[field] == empty_value: 1656 | continue 1657 | 1658 | value = self.format_regular_field(field) 1659 | 1660 | 1661 | # TODO: display method did not return a value- fallback to the 1662 | # default display method 1663 | #if value == None: 1664 | # continue 1665 | 1666 | if fieldcount > 1: 1667 | # TODO: track current indentation level 1668 | if len(retline) > max_regular_field_chars: 1669 | retval += retline + '\n' + (' ' * newline_padding_chars) 1670 | retline = '' 1671 | elif len(retline) > 0: 1672 | retline += ' ' 1673 | 1674 | retline += "%(field)s=%(value)s" % { 1675 | 'field': field, 1676 | 'value': value 1677 | } 1678 | 1679 | retval += retline 1680 | retval += ']' 1681 | 1682 | return retval 1683 | 1684 | def format_regular_field(self, field): 1685 | display_method = self.get_display_method(field) 1686 | return display_method(self._node, field) 1687 | 1688 | def format_complex_fields(self): 1689 | retval = "" 1690 | if self.is_child_node(): 1691 | retval += self.parent_node.format_complex_fields() 1692 | 1693 | for field in self.fields: 1694 | if field in self.regular_fields: 1695 | continue 1696 | if field in self.tree_fields: 1697 | continue 1698 | retval += self.format_complex_field(field) 1699 | 1700 | return retval 1701 | 1702 | def format_complex_field(self, field): 1703 | display_mode = self.get_display_mode(field) 1704 | print_null = False 1705 | if display_mode == NEVER_SHOW: 1706 | return "" 1707 | elif display_mode == ALWAYS_SHOW: 1708 | print_null = True 1709 | 1710 | skip_tag = self.is_skip_tag(field) 1711 | 1712 | display_method = self.get_display_method(field) 1713 | return display_method(self._node, field, skip_tag=skip_tag, print_null=print_null) 1714 | 1715 | def format_all_regular_fields(self, offset): 1716 | formatted_fields = [] 1717 | if self.parent_node != None: 1718 | formatted_fields += self.parent_node.format_all_regular_fields(offset) 1719 | 1720 | formatted_fields.append(self.format_regular_fields(offset)) 1721 | return formatted_fields 1722 | 1723 | 1724 | def ignore_type(self, field): 1725 | if self._ignore_field_types is None: 1726 | self._ignore_field_types = [] 1727 | self._ignore_field_types.append(field) 1728 | 1729 | # reset list of all fields 1730 | self._list_fields = None 1731 | self._regular_fields = None 1732 | self._all_fields = None 1733 | 1734 | def format_tree_nodes(self): 1735 | retval = "" 1736 | for field in self.tree_fields: 1737 | retval += self.format_complex_field(field) 1738 | 1739 | if retval == "" and self.is_child_node(): 1740 | retval += self.parent_node.format_tree_nodes() 1741 | 1742 | return retval 1743 | 1744 | class LabelNodeFormatter(NodeFormatter): 1745 | def __init__(self, node, typecast=None, pseudo_node=False, label=''): 1746 | self._label = label 1747 | super().__init__(node, typecast, pseudo_node) 1748 | def format(self): 1749 | return super().format(prefix=self._label) 1750 | 1751 | class PlanStateFormatter(NodeFormatter): 1752 | def format(self): 1753 | return super().format(prefix='-> ') 1754 | 1755 | class PgPrintCommand(gdb.Command): 1756 | "print PostgreSQL structures" 1757 | 1758 | def __init__(self): 1759 | super(PgPrintCommand, self).__init__("pgprint", gdb.COMMAND_SUPPORT, 1760 | gdb.COMPLETE_NONE, False) 1761 | 1762 | def invoke(self, arg, from_tty): 1763 | global recursion_depth 1764 | 1765 | arg_list = gdb.string_to_argv(arg) 1766 | if len(arg_list) != 1: 1767 | print("usage: pgprint var") 1768 | return 1769 | recursion_depth = 0 1770 | 1771 | l = gdb.parse_and_eval(arg_list[0]) 1772 | 1773 | if not is_node(l): 1774 | print("not a node type") 1775 | print("running experimental dump...") 1776 | formatter = NodeFormatter(l, pseudo_node=True) 1777 | print(formatter.format()) 1778 | else: 1779 | print(format_node(l)) 1780 | 1781 | PgPrintCommand() 1782 | --------------------------------------------------------------------------------