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 }