@@ -41,13 +41,31 @@ typedef struct XLogDumpConfig
4141 int stop_after_records ;
4242 int already_displayed_records ;
4343 bool follow ;
44+ bool stats ;
45+ bool stats_per_record ;
4446
4547 /* filter options */
4648 int filter_by_rmgr ;
4749 TransactionId filter_by_xid ;
4850 bool filter_by_xid_enabled ;
4951} XLogDumpConfig ;
5052
53+ typedef struct Stats
54+ {
55+ uint64 count ;
56+ uint64 rec_len ;
57+ uint64 fpi_len ;
58+ } Stats ;
59+
60+ #define MAX_XLINFO_TYPES 16
61+
62+ typedef struct XLogDumpStats
63+ {
64+ uint64 count ;
65+ Stats rmgr_stats [RM_NEXT_ID ];
66+ Stats record_stats [RM_NEXT_ID ][MAX_XLINFO_TYPES ];
67+ } XLogDumpStats ;
68+
5169static void
5270fatal_error (const char * fmt ,...)
5371__attribute__((format (PG_PRINTF_ATTRIBUTE , 1 , 2 )));
@@ -322,22 +340,49 @@ XLogDumpReadPage(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
322340}
323341
324342/*
325- * Print a record to stdout
343+ * Store per-rmgr and per- record statistics for a given record.
326344 */
327345static void
328- XLogDumpDisplayRecord (XLogDumpConfig * config , XLogRecPtr ReadRecPtr , XLogRecord * record )
346+ XLogDumpCountRecord (XLogDumpConfig * config , XLogDumpStats * stats , XLogRecPtr ReadRecPtr , XLogRecord * record )
329347{
330- const RmgrDescData * desc = & RmgrDescTable [record -> xl_rmid ];
348+ RmgrId rmid ;
349+ uint8 recid ;
350+
351+ stats -> count ++ ;
331352
332- if (config -> filter_by_rmgr != -1 &&
333- config -> filter_by_rmgr != record -> xl_rmid )
334- return ;
353+ /* Update per-rmgr statistics */
335354
336- if (config -> filter_by_xid_enabled &&
337- config -> filter_by_xid != record -> xl_xid )
338- return ;
355+ rmid = record -> xl_rmid ;
339356
340- config -> already_displayed_records ++ ;
357+ stats -> rmgr_stats [rmid ].count ++ ;
358+ stats -> rmgr_stats [rmid ].rec_len +=
359+ record -> xl_len + SizeOfXLogRecord ;
360+ stats -> rmgr_stats [rmid ].fpi_len +=
361+ record -> xl_tot_len - (record -> xl_len + SizeOfXLogRecord );
362+
363+ /*
364+ * Update per-record statistics, where the record is identified by a
365+ * combination of the RmgrId and the four bits of the xl_info field
366+ * that are the rmgr's domain (resulting in sixteen possible entries
367+ * per RmgrId).
368+ */
369+
370+ recid = record -> xl_info >> 4 ;
371+
372+ stats -> record_stats [rmid ][recid ].count ++ ;
373+ stats -> record_stats [rmid ][recid ].rec_len +=
374+ record -> xl_len + SizeOfXLogRecord ;
375+ stats -> record_stats [rmid ][recid ].fpi_len +=
376+ record -> xl_tot_len - (record -> xl_len + SizeOfXLogRecord );
377+ }
378+
379+ /*
380+ * Print a record to stdout
381+ */
382+ static void
383+ XLogDumpDisplayRecord (XLogDumpConfig * config , XLogRecPtr ReadRecPtr , XLogRecord * record )
384+ {
385+ const RmgrDescData * desc = & RmgrDescTable [record -> xl_rmid ];
341386
342387 printf ("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: %s " ,
343388 desc -> rm_name ,
@@ -381,6 +426,134 @@ XLogDumpDisplayRecord(XLogDumpConfig *config, XLogRecPtr ReadRecPtr, XLogRecord
381426 }
382427}
383428
429+ /*
430+ * Display a single row of record counts and sizes for an rmgr or record.
431+ */
432+ static void
433+ XLogDumpStatsRow (const char * name ,
434+ uint64 n , double n_pct ,
435+ uint64 rec_len , double rec_len_pct ,
436+ uint64 fpi_len , double fpi_len_pct ,
437+ uint64 total_len , double total_len_pct )
438+ {
439+ printf ("%-27s "
440+ "%20" INT64_MODIFIER "u (%6.02f) "
441+ "%20" INT64_MODIFIER "u (%6.02f) "
442+ "%20" INT64_MODIFIER "u (%6.02f) "
443+ "%20" INT64_MODIFIER "u (%6.02f)\n" ,
444+ name , n , n_pct , rec_len , rec_len_pct , fpi_len , fpi_len_pct ,
445+ total_len , total_len_pct );
446+ }
447+
448+
449+ /*
450+ * Display summary statistics about the records seen so far.
451+ */
452+ static void
453+ XLogDumpDisplayStats (XLogDumpConfig * config , XLogDumpStats * stats )
454+ {
455+ int ri , rj ;
456+ uint64 total_count = 0 ;
457+ uint64 total_rec_len = 0 ;
458+ uint64 total_fpi_len = 0 ;
459+ uint64 total_len = 0 ;
460+
461+ /* ---
462+ * Make a first pass to calculate column totals:
463+ * count(*),
464+ * sum(xl_len+SizeOfXLogRecord),
465+ * sum(xl_tot_len-xl_len-SizeOfXLogRecord), and
466+ * sum(xl_tot_len).
467+ * These are used to calculate percentages for each record type.
468+ * ---
469+ */
470+
471+ for (ri = 0 ; ri < RM_NEXT_ID ; ri ++ )
472+ {
473+ total_count += stats -> rmgr_stats [ri ].count ;
474+ total_rec_len += stats -> rmgr_stats [ri ].rec_len ;
475+ total_fpi_len += stats -> rmgr_stats [ri ].fpi_len ;
476+ }
477+ total_len = total_rec_len + total_fpi_len ;
478+
479+ /*
480+ * 27 is strlen("Transaction/COMMIT_PREPARED"),
481+ * 20 is strlen(2^64), 8 is strlen("(100.00%)")
482+ */
483+
484+ printf ("%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n"
485+ "%-27s %20s %8s %20s %8s %20s %8s %20s %8s\n" ,
486+ "Type" , "N" , "(%)" , "Record size" , "(%)" , "FPI size" , "(%)" , "Combined size" , "(%)" ,
487+ "----" , "-" , "---" , "-----------" , "---" , "--------" , "---" , "-------------" , "---" );
488+
489+ for (ri = 0 ; ri < RM_NEXT_ID ; ri ++ )
490+ {
491+ uint64 count , rec_len , fpi_len , tot_len ;
492+ const RmgrDescData * desc = & RmgrDescTable [ri ];
493+
494+ if (!config -> stats_per_record )
495+ {
496+ count = stats -> rmgr_stats [ri ].count ;
497+ rec_len = stats -> rmgr_stats [ri ].rec_len ;
498+ fpi_len = stats -> rmgr_stats [ri ].fpi_len ;
499+ tot_len = rec_len + fpi_len ;
500+
501+ XLogDumpStatsRow (desc -> rm_name ,
502+ count , 100 * (double ) count / total_count ,
503+ rec_len , 100 * (double ) rec_len / total_rec_len ,
504+ fpi_len , 100 * (double ) fpi_len / total_fpi_len ,
505+ tot_len , 100 * (double ) tot_len / total_len );
506+ }
507+ else
508+ {
509+ for (rj = 0 ; rj < MAX_XLINFO_TYPES ; rj ++ )
510+ {
511+ const char * id ;
512+
513+ count = stats -> record_stats [ri ][rj ].count ;
514+ rec_len = stats -> record_stats [ri ][rj ].rec_len ;
515+ fpi_len = stats -> record_stats [ri ][rj ].fpi_len ;
516+ tot_len = rec_len + fpi_len ;
517+
518+ /* Skip undefined combinations and ones that didn't occur */
519+ if (count == 0 )
520+ continue ;
521+
522+ /* the upper four bits in xl_info are the rmgr's */
523+ id = desc -> rm_identify (rj << 4 );
524+ if (id == NULL )
525+ id = psprintf ("UNKNOWN (%x)" , rj << 4 );
526+
527+ XLogDumpStatsRow (psprintf ("%s/%s" , desc -> rm_name , id ),
528+ count , 100 * (double ) count / total_count ,
529+ rec_len , 100 * (double ) rec_len / total_rec_len ,
530+ fpi_len , 100 * (double ) fpi_len / total_fpi_len ,
531+ tot_len , 100 * (double ) tot_len / total_len );
532+ }
533+ }
534+ }
535+
536+ printf ("%-27s %20s %8s %20s %8s %20s %8s %20s\n" ,
537+ "" , "--------" , "" , "--------" , "" , "--------" , "" , "--------" );
538+
539+ /*
540+ * The percentages in earlier rows were calculated against the
541+ * column total, but the ones that follow are against the row total.
542+ * Note that these are displayed with a % symbol to differentiate
543+ * them from the earlier ones, and are thus up to 9 characters long.
544+ */
545+
546+ printf ("%-27s "
547+ "%20" INT64_MODIFIER "u %-9s"
548+ "%20" INT64_MODIFIER "u %-9s"
549+ "%20" INT64_MODIFIER "u %-9s"
550+ "%20" INT64_MODIFIER "u %-6s\n" ,
551+ "Total" , stats -> count , "" ,
552+ total_rec_len , psprintf ("[%.02f%%]" , 100 * (double )total_rec_len / total_len ),
553+ total_fpi_len , psprintf ("[%.02f%%]" , 100 * (double )total_fpi_len / total_len ),
554+ total_len , "[100%]" );
555+ }
556+
384557static void
385558usage (void )
386559{
@@ -402,6 +575,8 @@ usage(void)
402575 printf (" (default: 1 or the value used in STARTSEG)\n" );
403576 printf (" -V, --version output version information, then exit\n" );
404577 printf (" -x, --xid=XID only show records with TransactionId XID\n" );
578+ printf (" -z, --stats[=record] show statistics instead of records\n" );
579+ printf (" (optionally, show per-record statistics)\n" );
405580 printf (" -?, --help show this help, then exit\n" );
406581}
407582
@@ -413,6 +588,7 @@ main(int argc, char **argv)
413588 XLogReaderState * xlogreader_state ;
414589 XLogDumpPrivate private ;
415590 XLogDumpConfig config ;
591+ XLogDumpStats stats ;
416592 XLogRecord * record ;
417593 XLogRecPtr first_record ;
418594 char * errormsg ;
@@ -429,6 +605,7 @@ main(int argc, char **argv)
429605 {"timeline" , required_argument , NULL , 't' },
430606 {"xid" , required_argument , NULL , 'x' },
431607 {"version" , no_argument , NULL , 'V' },
608+ {"stats" , optional_argument , NULL , 'z' },
432609 {NULL , 0 , NULL , 0 }
433610 };
434611
@@ -439,6 +616,7 @@ main(int argc, char **argv)
439616
440617 memset (& private , 0 , sizeof (XLogDumpPrivate ));
441618 memset (& config , 0 , sizeof (XLogDumpConfig ));
619+ memset (& stats , 0 , sizeof (XLogDumpStats ));
442620
443621 private .timeline = 1 ;
444622 private .startptr = InvalidXLogRecPtr ;
@@ -452,14 +630,16 @@ main(int argc, char **argv)
452630 config .filter_by_rmgr = -1 ;
453631 config .filter_by_xid = InvalidTransactionId ;
454632 config .filter_by_xid_enabled = false;
633+ config .stats = false;
634+ config .stats_per_record = false;
455635
456636 if (argc <= 1 )
457637 {
458638 fprintf (stderr , "%s: no arguments specified\n" , progname );
459639 goto bad_argument ;
460640 }
461641
462- while ((option = getopt_long (argc , argv , "be:?fn:p:r:s:t:Vx:" ,
642+ while ((option = getopt_long (argc , argv , "be:?fn:p:r:s:t:Vx:z " ,
463643 long_options , & optindex )) != -1 )
464644 {
465645 switch (option )
@@ -552,6 +732,21 @@ main(int argc, char **argv)
552732 }
553733 config .filter_by_xid_enabled = true;
554734 break ;
735+ case 'z' :
736+ config .stats = true;
737+ config .stats_per_record = false;
738+ if (optarg )
739+ {
740+ if (strcmp (optarg , "record" ) == 0 )
741+ config .stats_per_record = true;
742+ else if (strcmp (optarg , "rmgr" ) != 0 )
743+ {
744+ fprintf (stderr , "%s: unrecognised argument to --stats: %s\n" ,
745+ progname , optarg );
746+ goto bad_argument ;
747+ }
748+ }
749+ break ;
555750 default :
556751 goto bad_argument ;
557752 }
@@ -712,14 +907,32 @@ main(int argc, char **argv)
712907
713908 /* after reading the first record, continue at next one */
714909 first_record = InvalidXLogRecPtr ;
715- XLogDumpDisplayRecord (& config , xlogreader_state -> ReadRecPtr , record );
910+
911+ /* apply all specified filters */
912+ if (config .filter_by_rmgr != -1 &&
913+ config .filter_by_rmgr != record -> xl_rmid )
914+ continue ;
915+
916+ if (config .filter_by_xid_enabled &&
917+ config .filter_by_xid != record -> xl_xid )
918+ continue ;
919+
920+ /* process the record */
921+ if (config .stats == true)
922+ XLogDumpCountRecord (& config , & stats , xlogreader_state -> ReadRecPtr , record );
923+ else
924+ XLogDumpDisplayRecord (& config , xlogreader_state -> ReadRecPtr , record );
716925
717926 /* check whether we printed enough */
927+ config .already_displayed_records ++ ;
718928 if (config .stop_after_records > 0 &&
719929 config .already_displayed_records >= config .stop_after_records )
720930 break ;
721931 }
722932
933+ if (config .stats == true)
934+ XLogDumpDisplayStats (& config , & stats );
935+
723936 if (errormsg )
724937 fatal_error ("error in WAL record at %X/%X: %s\n" ,
725938 (uint32 ) (xlogreader_state -> ReadRecPtr >> 32 ),
0 commit comments