@@ -41,13 +41,31 @@ typedef struct XLogDumpConfig
4141int stop_after_records ;
4242int already_displayed_records ;
4343bool follow ;
44+ bool stats ;
45+ bool stats_per_record ;
4446
4547/* filter options */
4648int filter_by_rmgr ;
4749TransactionId filter_by_xid ;
4850bool 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 recordto stdout
343+ *Store per-rmgr and per- recordstatistics 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
342387printf ("rmgr: %-11s len (rec/tot): %6u/%6u, tx: %10u, lsn: %X/%08X, prev %X/%08X, bkp: %u%u%u%u, desc: %s " ,
343388desc -> 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)
402575printf (" (default: 1 or the value used in STARTSEG)\n" );
403576printf (" -V, --version output version information, then exit\n" );
404577printf (" -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" );
405580printf (" -?, --help show this help, then exit\n" );
406581}
407582
@@ -413,6 +588,7 @@ main(int argc, char **argv)
413588XLogReaderState * xlogreader_state ;
414589XLogDumpPrivate private ;
415590XLogDumpConfig config ;
591+ XLogDumpStats stats ;
416592XLogRecord * record ;
417593XLogRecPtr first_record ;
418594char * 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
440617memset (& private ,0 ,sizeof (XLogDumpPrivate ));
441618memset (& config ,0 ,sizeof (XLogDumpConfig ));
619+ memset (& stats ,0 ,sizeof (XLogDumpStats ));
442620
443621private .timeline = 1 ;
444622private .startptr = InvalidXLogRecPtr ;
@@ -452,14 +630,16 @@ main(int argc, char **argv)
452630config .filter_by_rmgr = -1 ;
453631config .filter_by_xid = InvalidTransactionId ;
454632config .filter_by_xid_enabled = false;
633+ config .stats = false;
634+ config .stats_per_record = false;
455635
456636if (argc <=1 )
457637{
458638fprintf (stderr ,"%s: no arguments specified\n" ,progname );
459639gotobad_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 " ,
463643long_options ,& optindex ))!= -1 )
464644{
465645switch (option )
@@ -552,6 +732,21 @@ main(int argc, char **argv)
552732}
553733config .filter_by_xid_enabled = true;
554734break ;
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+ gotobad_argument ;
747+ }
748+ }
749+ break ;
555750default :
556751gotobad_argument ;
557752}
@@ -712,14 +907,32 @@ main(int argc, char **argv)
712907
713908/* after reading the first record, continue at next one */
714909first_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 ++ ;
718928if (config .stop_after_records > 0 &&
719929config .already_displayed_records >=config .stop_after_records )
720930break ;
721931}
722932
933+ if (config .stats == true)
934+ XLogDumpDisplayStats (& config ,& stats );
935+
723936if (errormsg )
724937fatal_error ("error in WAL record at %X/%X: %s\n" ,
725938(uint32 ) (xlogreader_state -> ReadRecPtr >>32 ),