github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/hyperledger/fabric/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  	"fmt"
    11  	"io"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/hellobchain/third_party/hyperledger/fabric/common/metrics"
    16  	"github.com/hellobchain/third_party/hyperledger/fabric/common/metrics/internal/namer"
    17  )
    18  
    19  // A Field represents data that is included in the reference table for metrics.
    20  type Field uint8
    21  
    22  const (
    23  	Name        Field = iota // Name is the meter name.
    24  	Type                     // Type is the type of meter option.
    25  	Description              // Description is the help text from the meter option.
    26  	Labels                   // Labels is the meter's label information.
    27  	Bucket                   // Bucket is the statsd bucket format
    28  )
    29  
    30  // A Column represents a column of data in the reference table.
    31  type Column struct {
    32  	Field Field
    33  	Name  string
    34  	Width int
    35  }
    36  
    37  // NewPrometheusTable creates a table that can be used to document Prometheus
    38  // metrics maintained by Fabric.
    39  func NewPrometheusTable(cells Cells) Table {
    40  	return Table{
    41  		Cells: cells,
    42  		Columns: []Column{
    43  			{Field: Name, Name: "Name", Width: max(cells.MaxLen(Name)+2, 20)},
    44  			{Field: Type, Name: "Type", Width: 11},
    45  			{Field: Description, Name: "Description", Width: 60},
    46  			{Field: Labels, Name: "Labels", Width: 20},
    47  		},
    48  	}
    49  }
    50  
    51  // NewStatsdTable creates a table that can be used to document StatsD metrics
    52  // maintained by Fabric.
    53  func NewStatsdTable(cells Cells) Table {
    54  	return Table{
    55  		Cells: cells,
    56  		Columns: []Column{
    57  			{Field: Bucket, Name: "Bucket", Width: max(cells.MaxLen(Bucket)+2, 20)},
    58  			{Field: Type, Name: "Type", Width: 11},
    59  			{Field: Description, Name: "Description", Width: 60},
    60  		},
    61  	}
    62  }
    63  
    64  // A Table maintains the cells and columns used to generate the restructured text
    65  // formatted reference documentation.
    66  type Table struct {
    67  	Columns []Column
    68  	Cells   Cells
    69  }
    70  
    71  // Generate generates a restructured text formatted table from the cells and
    72  // columns contained in the table.
    73  func (t Table) Generate(w io.Writer) {
    74  	fmt.Fprint(w, t.header())
    75  	for _, c := range t.Cells {
    76  		fmt.Fprint(w, t.formatCell(c))
    77  		fmt.Fprint(w, t.rowSeparator())
    78  	}
    79  }
    80  
    81  func (t Table) rowSeparator() string    { return t.separator("-") }
    82  func (t Table) headerSeparator() string { return t.separator("=") }
    83  
    84  func (t Table) separator(delim string) string {
    85  	var s string
    86  	for _, c := range t.Columns {
    87  		s += "+" + strings.Repeat(delim, c.Width)
    88  	}
    89  	return s + "+\n"
    90  }
    91  
    92  func (t Table) header() string {
    93  	var h string
    94  	h += t.rowSeparator()
    95  	for _, c := range t.Columns {
    96  		h += "| " + printWidth(c.Name, c.Width-2) + " "
    97  	}
    98  	h += "|\n"
    99  	h += t.headerSeparator()
   100  	return h
   101  }
   102  
   103  func (t Table) formatCell(cell Cell) string {
   104  	contents := map[Field][]string{}
   105  	lineCount := 0
   106  
   107  	// wrap lines
   108  	for _, c := range t.Columns {
   109  		lines := wrapWidths(cell.Field(c.Field), c.Width-2)
   110  		if l := len(lines); l > lineCount {
   111  			lineCount = l
   112  		}
   113  		contents[c.Field] = lines
   114  	}
   115  
   116  	// add extra lines
   117  	for _, col := range t.Columns {
   118  		lines := contents[col.Field]
   119  		contents[col.Field] = padLines(lines, col.Width-2, lineCount)
   120  	}
   121  
   122  	var c string
   123  	for i := 0; i < lineCount; i++ {
   124  		for _, col := range t.Columns {
   125  			c += "| " + contents[col.Field][i] + " "
   126  		}
   127  		c += "|\n"
   128  	}
   129  
   130  	return c
   131  }
   132  
   133  func wrapWidths(s string, width int) []string {
   134  	var result []string
   135  	for _, s := range strings.Split(s, "\n") {
   136  		result = append(result, wrapWidth(s, width)...)
   137  	}
   138  	return result
   139  }
   140  
   141  func wrapWidth(s string, width int) []string {
   142  	words := strings.Fields(strings.TrimSpace(s))
   143  	if len(words) == 0 { // only white space
   144  		return []string{s}
   145  	}
   146  
   147  	result := words[0]
   148  	remaining := width - len(words[0])
   149  	for _, w := range words[1:] {
   150  		if len(w)+1 > remaining {
   151  			result += "\n" + w
   152  			remaining = width - len(w) - 1
   153  		} else {
   154  			result += " " + w
   155  			remaining -= len(w) + 1
   156  		}
   157  	}
   158  
   159  	return strings.Split(result, "\n")
   160  }
   161  
   162  func padLines(lines []string, w, h int) []string {
   163  	for len(lines) < h {
   164  		lines = append(lines, "")
   165  	}
   166  	for idx, line := range lines {
   167  		lines[idx] = printWidth(line, w)
   168  	}
   169  
   170  	return lines
   171  }
   172  
   173  func printWidth(s string, w int) string {
   174  	s = strings.TrimSpace(s)
   175  	if len(s) < w {
   176  		return s + strings.Repeat(" ", w-len(s))
   177  	}
   178  	return s
   179  }
   180  
   181  func max(x, y int) int {
   182  	if x > y {
   183  		return x
   184  	}
   185  	return y
   186  }
   187  
   188  type Cell struct {
   189  	meterType   string
   190  	namer       *namer.Namer
   191  	description string
   192  	labels      []string
   193  }
   194  
   195  func (c Cell) Field(f Field) string {
   196  	switch f {
   197  	case Name:
   198  		return c.Name()
   199  	case Type:
   200  		return c.Type()
   201  	case Description:
   202  		return c.Description()
   203  	case Labels:
   204  		return c.Labels()
   205  	case Bucket:
   206  		return c.BucketFormat()
   207  	default:
   208  		panic(fmt.Sprintf("unknown field type: %d", f))
   209  	}
   210  }
   211  
   212  func (c Cell) Name() string        { return strings.Replace(c.namer.FullyQualifiedName(), ".", "_", -1) }
   213  func (c Cell) Type() string        { return c.meterType }
   214  func (c Cell) Description() string { return c.description }
   215  func (c Cell) Labels() string      { return strings.Join(c.labels, "\n") }
   216  
   217  func (c Cell) BucketFormat() string {
   218  	var lvs []string
   219  	for _, label := range c.labels {
   220  		lvs = append(lvs, label, asBucketVar(label))
   221  	}
   222  	return c.namer.Format(lvs...)
   223  }
   224  
   225  func asBucketVar(s string) string { return "%{" + s + "}" }
   226  
   227  type Cells []Cell
   228  
   229  func (c Cells) Len() int           { return len(c) }
   230  func (c Cells) Less(i, j int) bool { return c[i].Name() < c[j].Name() }
   231  func (c Cells) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
   232  
   233  func (c Cells) MaxLen(f Field) int {
   234  	var maxlen int
   235  	for _, c := range c {
   236  		if l := len(c.Field(f)); l > maxlen {
   237  			maxlen = l
   238  		}
   239  	}
   240  	return maxlen
   241  }
   242  
   243  // NewCells transforms metrics options to cells that can be used for doc
   244  // generation.
   245  func NewCells(options []interface{}) (Cells, error) {
   246  	var cells Cells
   247  	for _, o := range options {
   248  		switch m := o.(type) {
   249  		case metrics.CounterOpts:
   250  			cells = append(cells, counterCell(m))
   251  		case metrics.GaugeOpts:
   252  			cells = append(cells, gaugeCell(m))
   253  		case metrics.HistogramOpts:
   254  			cells = append(cells, histogramCell(m))
   255  		default:
   256  			return nil, fmt.Errorf("unknown option type: %t", o)
   257  		}
   258  	}
   259  	sort.Sort(cells)
   260  	return cells, nil
   261  }
   262  
   263  func counterCell(c metrics.CounterOpts) Cell {
   264  	if c.StatsdFormat == "" {
   265  		c.StatsdFormat = "%{#fqname}"
   266  	}
   267  	return Cell{
   268  		namer:       namer.NewCounterNamer(c),
   269  		meterType:   "counter",
   270  		description: c.Help,
   271  		labels:      c.LabelNames,
   272  	}
   273  }
   274  
   275  func gaugeCell(g metrics.GaugeOpts) Cell {
   276  	if g.StatsdFormat == "" {
   277  		g.StatsdFormat = "%{#fqname}"
   278  	}
   279  	return Cell{
   280  		namer:       namer.NewGaugeNamer(g),
   281  		meterType:   "gauge",
   282  		description: g.Help,
   283  		labels:      g.LabelNames,
   284  	}
   285  }
   286  
   287  func histogramCell(h metrics.HistogramOpts) Cell {
   288  	if h.StatsdFormat == "" {
   289  		h.StatsdFormat = "%{#fqname}"
   290  	}
   291  	return Cell{
   292  		namer:       namer.NewHistogramNamer(h),
   293  		meterType:   "histogram",
   294  		description: h.Help,
   295  		labels:      h.LabelNames,
   296  	}
   297  }