github.com/kaituanwang/hyperledger@v2.0.1+incompatible/common/metrics/gendoc/table.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package gendoc
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"io"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/hyperledger/fabric/common/metrics"
    17  	"github.com/hyperledger/fabric/common/metrics/internal/namer"
    18  )
    19  
    20  // A Field represents data that is included in the reference table for metrics.
    21  type Field uint8
    22  
    23  const (
    24  	Name        Field = iota // Name is the meter name.
    25  	Type                     // Type is the type of meter option.
    26  	Description              // Description is the help text from the meter option.
    27  	Labels                   // Labels is the meter's label information.
    28  	Bucket                   // Bucket is the statsd bucket format
    29  )
    30  
    31  // A Column represents a column of data in the reference table.
    32  type Column struct {
    33  	Field Field
    34  	Name  string
    35  	Split int
    36  	Width int
    37  }
    38  
    39  // NewPrometheusTable creates a table that can be used to document Prometheus
    40  // metrics maintained by Fabric.
    41  func NewPrometheusTable(cells Cells) Table {
    42  	labelSplit := 0
    43  	for _, cell := range cells {
    44  		for _, label := range cell.labels {
    45  			labelSplit = max(labelSplit, len(label)+2)
    46  		}
    47  	}
    48  	return Table{
    49  		Cells: cells,
    50  		Columns: []Column{
    51  			{Field: Name, Name: "Name", Width: max(cells.MaxLen(Name)+2, 20)},
    52  			{Field: Type, Name: "Type", Width: 11},
    53  			{Field: Description, Name: "Description", Width: 60},
    54  			{Field: Labels, Name: "Labels", Width: 80, Split: labelSplit},
    55  		},
    56  	}
    57  }
    58  
    59  // NewStatsdTable creates a table that can be used to document StatsD metrics
    60  // maintained by Fabric.
    61  func NewStatsdTable(cells Cells) Table {
    62  	return Table{
    63  		Cells: cells,
    64  		Columns: []Column{
    65  			{Field: Bucket, Name: "Bucket", Width: max(cells.MaxLen(Bucket)+2, 20)},
    66  			{Field: Type, Name: "Type", Width: 11},
    67  			{Field: Description, Name: "Description", Width: 60},
    68  		},
    69  	}
    70  }
    71  
    72  // A Table maintains the cells and columns used to generate the restructured text
    73  // formatted reference documentation.
    74  type Table struct {
    75  	Columns []Column
    76  	Cells   Cells
    77  }
    78  
    79  // Generate generates a restructured text formatted table from the cells and
    80  // columns contained in the table.
    81  func (t Table) Generate(w io.Writer) {
    82  	fmt.Fprint(w, t.header())
    83  	for _, c := range t.Cells {
    84  		fmt.Fprint(w, t.formatCell(c))
    85  		fmt.Fprint(w, t.rowSeparator())
    86  	}
    87  }
    88  
    89  func (t Table) rowSeparator() string    { return t.separator("-", false) }
    90  func (t Table) headerSeparator() string { return t.separator("=", false) }
    91  
    92  func (t Table) separator(delim string, firstLine bool) string {
    93  	var buf bytes.Buffer
    94  	for _, c := range t.Columns {
    95  		buf.WriteString("+")
    96  		if !firstLine && c.Split != 0 {
    97  			buf.WriteString(strings.Repeat(delim, c.Split))
    98  			buf.WriteString("+")
    99  			buf.WriteString(strings.Repeat(delim, c.Width-c.Split-1))
   100  		} else {
   101  			buf.WriteString(strings.Repeat(delim, c.Width))
   102  		}
   103  	}
   104  	buf.WriteString("+\n")
   105  	return buf.String()
   106  }
   107  
   108  func (t Table) header() string {
   109  	var h string
   110  	h += t.separator("-", true)
   111  	for _, c := range t.Columns {
   112  		h += "| " + printWidth(c.Name, c.Width-2, 0) + " "
   113  	}
   114  	h += "|\n"
   115  	h += t.headerSeparator()
   116  	return h
   117  }
   118  
   119  func (t Table) formatCell(cell Cell) string {
   120  	contents := map[Field][]string{}
   121  	lineCount := 0
   122  	// wrap lines
   123  	for _, c := range t.Columns {
   124  		if c.Split != 0 {
   125  			lines := formSubtableCell(cell.labels, cell.labelHelp, c.Split, c.Width)
   126  			if l := len(lines); l > lineCount {
   127  				lineCount = l
   128  			}
   129  			contents[c.Field] = lines
   130  		} else {
   131  			lines := wrapWidths(cell.Field(c.Field), c.Width-2)
   132  			if l := len(lines); l > lineCount {
   133  				lineCount = l
   134  			}
   135  			contents[c.Field] = lines
   136  		}
   137  	}
   138  
   139  	// add extra lines
   140  	for _, col := range t.Columns {
   141  		lines := contents[col.Field]
   142  		contents[col.Field] = padLines(lines, col.Width-2, lineCount, col.Split-1)
   143  	}
   144  
   145  	var c string
   146  	for i := 0; i < lineCount; i++ {
   147  		endSplit := "|"
   148  		endPadding := " "
   149  		for _, col := range t.Columns {
   150  			frontSplit := "| "
   151  			if contents[col.Field][i][0] == '+' {
   152  				frontSplit = ""
   153  				endSplit = ""
   154  				endPadding = ""
   155  			}
   156  			c += frontSplit + contents[col.Field][i] + endPadding
   157  		}
   158  		c += endSplit + "\n"
   159  	}
   160  
   161  	return c
   162  }
   163  
   164  func formSubtableCell(keys []string, m map[string]string, leftWidth, cellWidth int) []string {
   165  	var result []string
   166  	for i, key := range keys {
   167  		// build subtable separator
   168  		if i != 0 {
   169  			var buf bytes.Buffer
   170  			buf.WriteString("+")
   171  			buf.WriteString(strings.Repeat("-", leftWidth))
   172  			buf.WriteString("+")
   173  			buf.WriteString(strings.Repeat("-", cellWidth-leftWidth-1))
   174  			buf.WriteString("+")
   175  			result = append(result, buf.String())
   176  		}
   177  
   178  		// populate subtable
   179  		rightLines := wrapWidths(m[key], cellWidth-leftWidth-1)
   180  		leftLines := padLines([]string{key}, leftWidth-2, max(1, len(rightLines)), 0)
   181  		for i := 0; i < len(leftLines); i++ {
   182  			text := fmt.Sprintf("%s | %s", leftLines[i], rightLines[i])
   183  			result = append(result, text)
   184  		}
   185  	}
   186  	return result
   187  }
   188  
   189  func wrapWidths(s string, width int) []string {
   190  	var result []string
   191  	for _, s := range strings.Split(s, "\n") {
   192  		result = append(result, wrapWidth(s, width)...)
   193  	}
   194  	return result
   195  }
   196  
   197  func wrapWidth(s string, width int) []string {
   198  	words := strings.Fields(strings.TrimSpace(s))
   199  	if len(words) == 0 { // only white space
   200  		return []string{s}
   201  	}
   202  
   203  	result := words[0]
   204  	remaining := width - len(words[0])
   205  	for _, w := range words[1:] {
   206  		if len(w)+1 > remaining {
   207  			result += "\n" + w
   208  			remaining = width - len(w) - 1
   209  		} else {
   210  			result += " " + w
   211  			remaining -= len(w) + 1
   212  		}
   213  	}
   214  
   215  	return strings.Split(result, "\n")
   216  }
   217  
   218  func padLines(lines []string, w, h, split int) []string {
   219  	for len(lines) < h {
   220  		lines = append(lines, "")
   221  	}
   222  	for idx, line := range lines {
   223  		lines[idx] = printWidth(line, w, split)
   224  	}
   225  
   226  	return lines
   227  }
   228  
   229  func printWidth(s string, w, split int) string {
   230  	if len(s) < w {
   231  		var buf bytes.Buffer
   232  		buf.WriteString(s)
   233  		if split <= len(s) {
   234  			buf.WriteString(strings.Repeat(" ", w-len(s)))
   235  		} else {
   236  			buf.WriteString(strings.Repeat(" ", split-len(s)))
   237  			buf.WriteString("|")
   238  			buf.WriteString(strings.Repeat(" ", w-split-1))
   239  		}
   240  		s = buf.String()
   241  	}
   242  	return s
   243  }
   244  
   245  func max(x, y int) int {
   246  	if x > y {
   247  		return x
   248  	}
   249  	return y
   250  }
   251  
   252  type Cell struct {
   253  	meterType   string
   254  	namer       *namer.Namer
   255  	description string
   256  	labels      []string
   257  	labelHelp   map[string]string
   258  }
   259  
   260  func (c Cell) Field(f Field) string {
   261  	switch f {
   262  	case Name:
   263  		return c.Name()
   264  	case Type:
   265  		return c.Type()
   266  	case Description:
   267  		return c.Description()
   268  	case Labels:
   269  		return c.Labels()
   270  	case Bucket:
   271  		return c.BucketFormat()
   272  	default:
   273  		panic(fmt.Sprintf("unknown field type: %d", f))
   274  	}
   275  }
   276  
   277  func (c Cell) Name() string        { return strings.Replace(c.namer.FullyQualifiedName(), ".", "_", -1) }
   278  func (c Cell) Type() string        { return c.meterType }
   279  func (c Cell) Description() string { return c.description }
   280  
   281  func (c Cell) Labels() string {
   282  	buf := &strings.Builder{}
   283  	for _, label := range c.labels {
   284  		fmt.Fprintf(buf, " | %s\n", label)
   285  	}
   286  	return strings.TrimRight(buf.String(), "\n")
   287  }
   288  
   289  func (c Cell) BucketFormat() string {
   290  	var lvs []string
   291  	for _, label := range c.labels {
   292  		lvs = append(lvs, label, asBucketVar(label))
   293  	}
   294  	return c.namer.Format(lvs...)
   295  }
   296  
   297  func asBucketVar(s string) string { return "%{" + s + "}" }
   298  
   299  type Cells []Cell
   300  
   301  func (c Cells) Len() int           { return len(c) }
   302  func (c Cells) Less(i, j int) bool { return c[i].Name() < c[j].Name() }
   303  func (c Cells) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
   304  
   305  func (c Cells) MaxLen(f Field) int {
   306  	var maxlen int
   307  	for _, c := range c {
   308  		if l := len(c.Field(f)); l > maxlen {
   309  			maxlen = l
   310  		}
   311  	}
   312  	return maxlen
   313  }
   314  
   315  // NewCells transforms metrics options to cells that can be used for doc
   316  // generation.
   317  func NewCells(options []interface{}) (Cells, error) {
   318  	var cells Cells
   319  	for _, o := range options {
   320  		switch m := o.(type) {
   321  		case metrics.CounterOpts:
   322  			cells = append(cells, counterCell(m))
   323  		case metrics.GaugeOpts:
   324  			cells = append(cells, gaugeCell(m))
   325  		case metrics.HistogramOpts:
   326  			cells = append(cells, histogramCell(m))
   327  		default:
   328  			return nil, fmt.Errorf("unknown option type: %t", o)
   329  		}
   330  	}
   331  	sort.Sort(cells)
   332  	return cells, nil
   333  }
   334  
   335  func counterCell(c metrics.CounterOpts) Cell {
   336  	if c.StatsdFormat == "" {
   337  		c.StatsdFormat = "%{#fqname}"
   338  	}
   339  	return Cell{
   340  		namer:       namer.NewCounterNamer(c),
   341  		meterType:   "counter",
   342  		description: c.Help,
   343  		labels:      c.LabelNames,
   344  		labelHelp:   c.LabelHelp,
   345  	}
   346  }
   347  
   348  func gaugeCell(g metrics.GaugeOpts) Cell {
   349  	if g.StatsdFormat == "" {
   350  		g.StatsdFormat = "%{#fqname}"
   351  	}
   352  	return Cell{
   353  		namer:       namer.NewGaugeNamer(g),
   354  		meterType:   "gauge",
   355  		description: g.Help,
   356  		labels:      g.LabelNames,
   357  		labelHelp:   g.LabelHelp,
   358  	}
   359  }
   360  
   361  func histogramCell(h metrics.HistogramOpts) Cell {
   362  	if h.StatsdFormat == "" {
   363  		h.StatsdFormat = "%{#fqname}"
   364  	}
   365  	return Cell{
   366  		namer:       namer.NewHistogramNamer(h),
   367  		meterType:   "histogram",
   368  		description: h.Help,
   369  		labels:      h.LabelNames,
   370  		labelHelp:   h.LabelHelp,
   371  	}
   372  }