1// Copyright (C) MongoDB, Inc. 2014-present. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7package stat_consumer 8 9import ( 10 "bytes" 11 "fmt" 12 "sort" 13 "strings" 14 15 "github.com/mongodb/mongo-tools-common/text" 16 "github.com/mongodb/mongo-tools/mongostat/stat_consumer/line" 17) 18 19// GridLineFormatter uses a text.GridWriter to format the StatLines as a grid 20type GridLineFormatter struct { 21 *limitableFormatter 22 *text.GridWriter 23 24 // If true, enables printing of headers to output 25 includeHeader bool 26 27 // Counter for periodic headers 28 index int 29 30 // Tracks number of hosts so we can reprint headers when it changes 31 prevLineCount int 32} 33 34func NewGridLineFormatter(maxRows int64, includeHeader bool) LineFormatter { 35 return &GridLineFormatter{ 36 limitableFormatter: &limitableFormatter{maxRows: maxRows}, 37 includeHeader: includeHeader, 38 GridWriter: &text.GridWriter{ColumnPadding: 1}, 39 } 40} 41 42func init() { 43 FormatterConstructors[""] = NewGridLineFormatter 44} 45 46// headerInterval is the number of chunks before the header is re-printed in GridLineFormatter 47const headerInterval = 10 48 49func (glf *GridLineFormatter) Finish() { 50} 51 52// FormatLines formats the StatLines as a grid 53func (glf *GridLineFormatter) FormatLines(lines []*line.StatLine, headerKeys []string, keyNames map[string]string) string { 54 buf := &bytes.Buffer{} 55 56 // Sort the stat lines by hostname, so that we see the output 57 // in the same order for each snapshot 58 sort.Sort(line.StatLines(lines)) 59 60 // Print the columns that are enabled 61 for _, key := range headerKeys { 62 header := keyNames[key] 63 glf.WriteCell(header) 64 } 65 glf.EndRow() 66 67 for _, l := range lines { 68 if l.Printed && l.Error == nil { 69 l.Error = fmt.Errorf("no data received") 70 } 71 l.Printed = true 72 73 if l.Error != nil { 74 glf.WriteCell(l.Fields["host"]) 75 glf.Feed(l.Error.Error()) 76 continue 77 } 78 79 for _, key := range headerKeys { 80 glf.WriteCell(l.Fields[key]) 81 } 82 glf.EndRow() 83 } 84 glf.Flush(buf) 85 86 // clear the flushed data 87 glf.Reset() 88 89 gridLine := buf.String() 90 91 if glf.prevLineCount != len(lines) { 92 glf.index = 0 93 } 94 glf.prevLineCount = len(lines) 95 96 if !glf.includeHeader || glf.index != 0 { 97 // Strip out the first line of the formatted output, 98 // which contains the headers. They've been left in up until this point 99 // in order to force the formatting of the columns to be wide enough. 100 firstNewLinePos := strings.Index(gridLine, "\n") 101 if firstNewLinePos >= 0 { 102 gridLine = gridLine[firstNewLinePos+1:] 103 } 104 } 105 glf.index++ 106 if glf.index == headerInterval { 107 glf.index = 0 108 } 109 110 if len(lines) > 1 { 111 // For multi-node stats, add an extra newline to tell each block apart 112 gridLine = fmt.Sprintf("\n%s", gridLine) 113 } 114 glf.increment() 115 return gridLine 116} 117