Skip to content

Commit e526c83

Browse files
committed
Show locks and waiting queries in text report.
1 parent 2dc1da1 commit e526c83

1 file changed

Lines changed: 158 additions & 2 deletions

File tree

cmd/pgmetrics/report.go

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
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+
501551
func reportVacuumProgress(fd io.Writer, result *pgmetrics.Model) {
502552
fmt.Fprint(fd, `
503553
Vacuum 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+
819965
const stmtSQLDisplayLength = 50
820966

821967
func 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

9131068
func fmtPct(a, b int64) string {
@@ -1313,7 +1468,8 @@ func getMaxWalSize(result *pgmetrics.Model) (string, string) {
13131468
//------------------------------------------------------------------------------
13141469

13151470
type tableWriter struct {
1316-
data [][]string
1471+
data [][]string
1472+
hasFooter bool
13171473
}
13181474

13191475
func (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

Comments
 (0)