@@ -19,6 +19,7 @@ package main
1919import (
2020 "fmt"
2121 "io"
22+ "sort"
2223 "strconv"
2324 "strings"
2425 "time"
@@ -119,6 +120,7 @@ PostgreSQL Cluster:
119120 reportWAL (fd , result )
120121 reportBGWriter (fd , result )
121122 reportBackends (fd , o .tooLongSec , result )
123+ reportLocks (fd , result )
122124 if version >= 90600 {
123125 reportVacuumProgress (fd , result )
124126 }
@@ -498,6 +500,54 @@ Backends:
498500 }
499501}
500502
503+ type lockCount struct {
504+ notGranted int
505+ total int
506+ }
507+
508+ func reportLocks (fd io.Writer , result * pgmetrics.Model ) {
509+ if len (result .Locks ) == 0 {
510+ return
511+ }
512+
513+ c := make (map [string ]* lockCount )
514+ for _ , l := range result .Locks {
515+ lc , ok := c [l .LockType ]
516+ if ! ok {
517+ lc = & lockCount {}
518+ c [l .LockType ] = lc
519+ }
520+ if ! l .Granted {
521+ lc .notGranted ++
522+ }
523+ lc .total ++
524+ }
525+ lt := make ([]string , 0 , len (c ))
526+ for k := range c {
527+ lt = append (lt , k )
528+ }
529+ sort .Strings (lt )
530+
531+ fmt .Fprint (fd , `
532+ Locks:
533+ ` )
534+ var tw tableWriter
535+ tw .add ("Lock Type" , "Not Granted" , "Total" )
536+ var tot1 , tot2 int
537+ for _ , t := range lt {
538+ lc , ok := c [t ]
539+ if ! ok || lc == nil {
540+ continue
541+ }
542+ tw .add (t , lc .notGranted , lc .total )
543+ tot1 += lc .notGranted
544+ tot2 += lc .total
545+ }
546+ tw .add ("" , tot1 , tot2 )
547+ tw .hasFooter = true
548+ tw .write (fd , " " )
549+ }
550+
501551func reportVacuumProgress (fd io.Writer , result * pgmetrics.Model ) {
502552 fmt .Fprint (fd , `
503553Vacuum Progress:` )
@@ -813,9 +863,105 @@ Database #%d:
813863 }
814864 gap = true
815865 }
866+
867+ if ls := filterLocksByDB (result , d .Name ); len (ls ) > 0 {
868+ if gap {
869+ fmt .Fprintln (fd )
870+ }
871+ fmt .Fprintf (fd , ` Blocked Queries:
872+ ` )
873+ count := 0
874+ for _ , l := range ls {
875+ if l .Granted {
876+ continue
877+ }
878+ be := getBE (result , l .PID )
879+ if be == nil {
880+ continue
881+ }
882+ count ++
883+ fmt .Fprintf (fd , ` Blocked Query #%d:
884+ Query: %s
885+ Started By: %s
886+ Waiting Since: %s
887+ ` ,
888+ count ,
889+ prepQ (be .Query ),
890+ getBEClient (be ),
891+ fmtTimeAndSince (be .StateChange ))
892+ if result .BlockingPIDs == nil {
893+ continue
894+ }
895+ pids , ok := result .BlockingPIDs [l .PID ]
896+ if ! ok || len (pids ) == 0 {
897+ continue
898+ }
899+ for _ , b := range pids {
900+ bbe := getBE (result , b )
901+ if bbe == nil {
902+ continue
903+ }
904+ fmt .Fprintf (fd , ` Waiting For:
905+ Query: %s
906+ Lock: %s
907+ Started By: %s
908+ ` ,
909+ prepQ (bbe .Query ),
910+ getLockDesc (l , result ),
911+ getBEClient (bbe ),
912+ )
913+ }
914+ }
915+ gap = true
916+ }
816917 }
817918}
818919
920+ func getBE (result * pgmetrics.Model , pid int ) * pgmetrics.Backend {
921+ for i , be := range result .Backends {
922+ if be .PID == pid {
923+ return & result .Backends [i ]
924+ }
925+ }
926+ return nil
927+ }
928+
929+ func getBEClient (be * pgmetrics.Backend ) string {
930+ // role@client:db (PID pid)
931+ // appname role@client:db (PID pid)
932+ var out string
933+ if len (be .ApplicationName ) > 0 {
934+ out = be .ApplicationName + " "
935+ }
936+ out += be .RoleName
937+ if len (be .ClientAddr ) > 0 {
938+ c := strings .TrimSuffix (be .ClientAddr , "/128" )
939+ c = strings .TrimSuffix (c , "/32" )
940+ out += "@" + c
941+ }
942+ out += fmt .Sprintf ("/%s (PID %d)" , be .DBName , be .PID )
943+ return out
944+ }
945+
946+ func getLockDesc (l * pgmetrics.Lock , result * pgmetrics.Model ) (out string ) {
947+ out = l .LockType + ", " + l .Mode
948+ if l .LockType == "relation" {
949+ // search tables
950+ if t := result .TableByOID (l .RelationOID ); t != nil {
951+ out += ", table " + t .SchemaName + "." + t .Name
952+ } else {
953+ // else search indexes
954+ for _ , idx := range result .Indexes {
955+ if idx .OID == l .RelationOID {
956+ out += ", index " + idx .SchemaName + "." + idx .Name
957+ break
958+ }
959+ }
960+ }
961+ }
962+ return
963+ }
964+
819965const stmtSQLDisplayLength = 50
820966
821967func prepQ (s string ) string {
@@ -908,6 +1054,15 @@ func filterSubscriptionsByDB(result *pgmetrics.Model, db string) (out []*pgmetri
9081054 return
9091055}
9101056
1057+ func filterLocksByDB (result * pgmetrics.Model , db string ) (out []* pgmetrics.Lock ) {
1058+ for i := range result .Locks {
1059+ if l := & result .Locks [i ]; l .DBName == db {
1060+ out = append (out , l )
1061+ }
1062+ }
1063+ return
1064+ }
1065+
9111066// Duh. Did anyone say generics?
9121067
9131068func fmtPct (a , b int64 ) string {
@@ -1313,7 +1468,8 @@ func getMaxWalSize(result *pgmetrics.Model) (string, string) {
13131468//------------------------------------------------------------------------------
13141469
13151470type tableWriter struct {
1316- data [][]string
1471+ data [][]string
1472+ hasFooter bool
13171473}
13181474
13191475func (t * tableWriter ) add (cols ... interface {}) {
@@ -1367,7 +1523,7 @@ func (t *tableWriter) write(fd io.Writer, pfx string) {
13671523 }
13681524 line ()
13691525 for i , row := range t .data {
1370- if i == 1 {
1526+ if i == 1 || ( t . hasFooter && i == len ( t . data ) - 1 ) {
13711527 line ()
13721528 }
13731529 fmt .Fprintf (fd , "%s|" , pfx )
0 commit comments