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 }