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