Skip to content

Commit

Permalink
output aligning rewritten:
Browse files Browse the repository at this point in the history
- fixed aligning for 'top' command
- dynamic aligning for 'report' command
- two new hotkeys in 'top', have been introduced which allow to change
  width of active columns (up/down arrows)
- column width map moved from PGresult level to Context
- aligning is performed once when first stats displayed
- stat.Print() marked as deprecated
- fixed couple IDE's warnings (unused variables)
  • Loading branch information
lesovsky committed Oct 12, 2018
1 parent d9a5906 commit 90cad1e
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 56 deletions.
15 changes: 15 additions & 0 deletions lib/stat/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type ContextUnit struct {
OrderKey int // Number of column used for order
OrderDesc bool // Order direction: descending (true) or ascending (false)
UniqueKey int // Unique key that used on rows comparing when building diffs, by default it's zero which is OK in almost all contexts
ColsWidth map[int]int // Set width for columns and control an aligning
Aligned bool // Is aligning calculated?
Msg string // Show this text in Cmdline when switching to this unit
Filters map[int]*regexp.Regexp // Storage for filter patterns: key is the column index, value - regexp pattern
}
Expand All @@ -36,6 +38,7 @@ var (
Ncols: 17,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show databases statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -47,6 +50,7 @@ var (
Ncols: 15,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show replication statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -58,6 +62,7 @@ var (
Ncols: 19,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show tables statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -69,6 +74,7 @@ var (
Ncols: 6,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show indexes statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -80,6 +86,7 @@ var (
Ncols: 7,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show tables sizes statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -91,6 +98,7 @@ var (
Ncols: 8,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show functions statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -103,6 +111,7 @@ var (
Ncols: 14,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show vacuum statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -114,6 +123,7 @@ var (
Ncols: 14,
OrderKey: 0,
OrderDesc: true,
ColsWidth: map[int]int{},
Msg: "Show activity statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -126,6 +136,7 @@ var (
OrderKey: 0,
OrderDesc: true,
UniqueKey: 11,
ColsWidth: map[int]int{},
Msg: "Show statements timings statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -138,6 +149,7 @@ var (
OrderKey: 0,
OrderDesc: true,
UniqueKey: 6,
ColsWidth: map[int]int{},
Msg: "Show statements general statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -150,6 +162,7 @@ var (
OrderKey: 0,
OrderDesc: true,
UniqueKey: 11,
ColsWidth: map[int]int{},
Msg: "Show statements IO statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -162,6 +175,7 @@ var (
OrderKey: 0,
OrderDesc: true,
UniqueKey: 7,
ColsWidth: map[int]int{},
Msg: "Show statements temp files statistics",
Filters: map[int]*regexp.Regexp{},
}
Expand All @@ -174,6 +188,7 @@ var (
OrderKey: 0,
OrderDesc: true,
UniqueKey: 11,
ColsWidth: map[int]int{},
Msg: "Show statements temp tables statistics (local IO)",
Filters: map[int]*regexp.Regexp{},
}
Expand Down
114 changes: 74 additions & 40 deletions lib/stat/pgstat.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const (
StatementsTempView = "pg_stat_statements_temp" // fictional name, based on pg_stat_statements
StatementsLocalView = "pg_stat_statements_local" // fictional name, based on pg_stat_statements

colsTruncMinLimit = 1 // minimal allowed value for truncation
colsWidthMin = 8 // base width for columns (used by default, if column name too short)

GucMainConfFile = "config_file"
GucHbaFile = "hba_file"
GucIdentFile = "ident_file"
Expand Down Expand Up @@ -93,13 +96,12 @@ type PgActivityStat struct {

// Container for basic Postgres stats collected from pg_stat_* views
type PGresult struct {
Result [][]sql.NullString /* values */
Cols []string /* list of columns' names*/
Ncols int /* numbers of columns in Result */
Nrows int /* number of rows in Result */
Colmaxlen map[string]int /* lengths of the longest value for each column */
Valid bool /* Used for result invalidations, on context switching for example */
Err error /* Error returned by query, if any */
Result [][]sql.NullString /* values */
Cols []string /* list of columns' names*/
Ncols int /* numbers of columns in Result */
Nrows int /* number of rows in Result */
Valid bool /* Used for result invalidations, on context switching for example */
Err error /* Error returned by query, if any */
}

// Get Postgres connection status - is it alive or not?
Expand Down Expand Up @@ -295,7 +297,6 @@ func (d *PGresult) Diff(p *PGresult, c *PGresult, itv uint, interval [2]int, uke
d.Cols = c.Cols
d.Ncols = len(c.Cols)
d.Nrows = c.Nrows
d.Colmaxlen = c.Colmaxlen // use lengthes of current values

// Take every row from 'current' snapshot and check its existing in 'previous' snapshot. If row exists in both snapshots
// make diff between them. If target row is not found in 'previous' snapshot, no diff needed, hence append this row
Expand Down Expand Up @@ -385,74 +386,107 @@ func (r *PGresult) Sort(key int, desc bool) {
}

// Calculate column width used at result formatting and truncate too long values
func (r *PGresult) SetAlignCustom(truncLimit int) {
r.Colmaxlen = make(map[string]int)
func (r *PGresult) SetAlign(widthes map[int]int, truncLimit int, dynamic bool) {
// forbid to make truncation limit too small
if truncLimit < colsTruncMinLimit {
truncLimit = colsTruncMinLimit
}

/* calculate max length of columns based on the longest value of the column */
colnum := 0
for _, colname := range r.Cols { // walk per-column // don't use Collen here - it's unordered
var valuelen, colnamelen int
for colidx, colname := range r.Cols { // walk per-column
if len(r.Result) == 0 {
// no rows in result, set width using length of a column name
widthes[colidx] = len(colname)
}

for rownum := 0; rownum < len(r.Result); rownum++ { // walk through rows
//
valuelen := len(r.Result[rownum][colnum].String)
colnamelen := len(colname)
valuelen = len(r.Result[rownum][colidx].String)
colnamelen = len(colname)

// align short-length columns to default width
if colnamelen < colsWidthMin {
colnamelen = colsWidthMin
}

switch {
// if value is empty, e.g. NULL - set length based on colname length, but no longer that already set
case valuelen == 0 && colnamelen >= r.Colmaxlen[colname]:
r.Colmaxlen[colname] = colnamelen
// for non-empty values, but for those whose length less than length of colnames, use length based on length of column name, bit no longer than already set
case valuelen > 0 && valuelen <= colnamelen && valuelen >= r.Colmaxlen[colname]:
r.Colmaxlen[colname] = colnamelen
// for non-empty values, but for those whose length longer than length of colnames, use length based on length of value, bit no longer than already set
case valuelen > 0 && valuelen > colnamelen && valuelen < truncLimit && valuelen >= r.Colmaxlen[colname]:
r.Colmaxlen[colname] = valuelen
// if value is empty, e.g. NULL - set width based on colname length
case valuelen == 0 && colnamelen >= widthes[colidx]:
widthes[colidx] = colnamelen
// for non-empty values, but for those whose length less than length of colnames, use length based on length of column name, but no longer than already set
case valuelen > 0 && valuelen <= colnamelen && valuelen >= widthes[colidx]:
widthes[colidx] = colnamelen
// for non-empty values, but for those whose length longer than length of colnames, use length based on length of value, but no longer than already set
case valuelen > 0 && valuelen > colnamelen && valuelen < truncLimit && valuelen >= widthes[colidx] && colidx < r.Ncols-1:
// dynamic aligning is used in 'report' when you can't adjust width on the fly
// fixed aligning is used in 'top' because it's quite uncomfortable when width is changing constantly
if dynamic {
widthes[colidx] = valuelen
} else {
widthes[colidx] = colnamelen
}
// for last column set width using truncation limit
case colidx == r.Ncols-1:
widthes[colidx] = truncLimit
// do nothing if length of value or column is less (or equal) than already specified width
case valuelen <= widthes[colidx] && colnamelen <= widthes[colidx]:

// for very long values, truncate value and set length limited by truncLimit value,
case valuelen >= truncLimit:
r.Result[rownum][colnum].String = r.Result[rownum][colnum].String[:truncLimit]
r.Colmaxlen[colname] = truncLimit
//default: // default case is used for debug purposes for catching cases that don't met upper conditions
// fmt.Printf("*** DEBUG %s -- %s***", colname, r.Result[rownum][colnum].String)
r.Result[rownum][colidx].String = r.Result[rownum][colidx].String[:truncLimit-1] + "~"
widthes[colidx] = truncLimit
//default: // default case is used for debug purposes for catching cases that don't meet upper conditions
// fmt.Printf("*** DEBUG %s -- %s, %d:%d:%d ***", colname, r.Result[rownum][colnum].String, widthes[colidx], colnamelen, valuelen)
}
}

/* add 2 extra spaces to column's length */
r.Colmaxlen[colname] = r.Colmaxlen[colname] + 2

colnum++
}
}

// Print content of PGresult container to buffer
func (r *PGresult) Fprint(buf *bytes.Buffer) {
// do simple ad-hoc aligning for current PGresult, do align using the longest value in the column
widthMap := map[int]int{}
var valuelen int
for colnum := range r.Cols {
for rownum := 0; rownum < len(r.Result); rownum++ {
valuelen = len(r.Result[rownum][colnum].String)
if valuelen > widthMap[colnum] {
widthMap[colnum] = valuelen
}
}
}

/* print header */
for _, name := range r.Cols {
fmt.Fprintf(buf, "%-*s", r.Colmaxlen[name], name)
for colidx, colname := range r.Cols {
fmt.Fprintf(buf, "%-*s", widthMap[colidx]+2, colname)
}
fmt.Fprintf(buf, "\n\n")

/* print data to buffer */
for colnum, rownum := 0, 0; rownum < r.Nrows; rownum, colnum = rownum+1, 0 {
for _, colname := range r.Cols {
for range r.Cols {
/* m[row][column] */
fmt.Fprintf(buf, "%-*s", r.Colmaxlen[colname], r.Result[rownum][colnum].String)
fmt.Fprintf(buf, "%-*s", widthMap[colnum]+2, r.Result[rownum][colnum].String)
colnum++
}
fmt.Fprintf(buf, "\n")
}
}

// Print content of PGresult container to stdout
// DEPRECATION WARNING since v0.5.0: This function used nowhere, seems it should be removed.
func (r *PGresult) Print() {
/* print header */
for _, name := range r.Cols {
fmt.Printf("\033[%d;%dm%-*s \033[0m", 37, 1, r.Colmaxlen[name], name)
fmt.Printf("\033[%d;%dm%-*s \033[0m", 37, 1, len(name)+2, name)
}
fmt.Printf("\n")

/* print data to buffer */
for colnum, rownum := 0, 0; rownum < r.Nrows; rownum, colnum = rownum+1, 0 {
for _, colname := range r.Cols {
for range r.Cols {
/* m[row][column] */
fmt.Printf("%-*s ", r.Colmaxlen[colname], r.Result[rownum][colnum].String)
fmt.Printf("%-*s ", len(r.Result[rownum][colnum].String)+2, r.Result[rownum][colnum].String)
colnum++
}
fmt.Printf("\n")
Expand Down
24 changes: 17 additions & 7 deletions report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,18 @@ func doReport(r *tar.Reader, opts ReportOptions) error {
doSort(diffStat, opts)
}

// align values for printing
diffStat.SetAlignCustom(opts.TruncLimit)
// align values for printing, use dynamic aligning
if !opts.Context.Aligned {
diffStat.SetAlign(opts.Context.ColsWidth, opts.TruncLimit, true) // we don't want truncate lines here, so just use high limit
opts.Context.Aligned = true
}

// print report
// print header after every Nth lines
if lineCnt <= 0 {
fmt.Printf(" ")
for _, name := range prevStat.Cols {
fmt.Printf("\033[%d;%dm%-*s \033[0m", 37, 1, diffStat.Colmaxlen[name], name)
for i, name := range prevStat.Cols {
fmt.Printf("\033[%d;%dm%-*s\033[0m", 37, 1, opts.Context.ColsWidth[i]+2, name)
}
fmt.Printf("\n")
lineCnt = repeatHeaderAfter
Expand Down Expand Up @@ -183,9 +186,16 @@ func doReport(r *tar.Reader, opts ReportOptions) error {
fmt.Printf(" ")
}

for _, colname := range diffStat.Cols {
/* m[row][column] */
fmt.Printf("%-*s ", diffStat.Colmaxlen[colname], diffStat.Result[rownum][colnum].String)
for i := range diffStat.Cols {
// truncate values that longer than column width
valuelen := len(diffStat.Result[rownum][colnum].String)
if valuelen > opts.Context.ColsWidth[i] {
width := opts.Context.ColsWidth[i]
// truncate value up to column width and replace last character with '~' symbol
diffStat.Result[rownum][colnum].String = diffStat.Result[rownum][colnum].String[:width-1] + "~"
}

fmt.Printf("%-*s", opts.Context.ColsWidth[i]+2, diffStat.Result[rownum][colnum].String)
colnum++
}

Expand Down
Loading

0 comments on commit 90cad1e

Please sign in to comment.