-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.go
155 lines (147 loc) · 3.54 KB
/
app.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package mkrg
import (
"os"
"sync"
"time"
"github.com/mackerelio/mackerel-client-go"
"golang.org/x/sync/errgroup"
"golang.org/x/term"
)
// App ...
type App struct {
client *mackerel.Client
hostID string
fetcher *fetcher
}
// NewApp creates a new app.
func NewApp(client *mackerel.Client, hostID string) *App {
return &App{
client: client,
hostID: hostID,
fetcher: newFetcher(client),
}
}
// Run the app.
func (app *App) Run() error {
metricNamesMap, err := app.getMetricNamesMap()
if err != nil {
return err
}
termWidth, _, err := term.GetSize(0)
if err != nil {
return err
}
var maxColumn int
if termWidth > 160 {
maxColumn = 3
} else if termWidth > 80 {
maxColumn = 2
} else {
maxColumn = 1
}
width := (termWidth+4)/maxColumn - 4
height := width / 8 * 3
until := time.Now().Round(time.Minute)
from := until.Add(-time.Duration(width*3) * time.Minute)
var ui ui
if os.Getenv("TERM_PROGRAM") == "iTerm.app" && os.Getenv("MKRG_VIEWER") == "" ||
os.Getenv("MKRG_VIEWER") == "iTerm2" {
ui = newIterm2UI(height, width, maxColumn, from, until)
} else if os.Getenv("MKRG_VIEWER") == "Sixel" {
ui = newSixel(height, width, maxColumn, from, until)
} else {
from = until.Add(-time.Duration(width) * time.Minute)
ui = newTui(height, width, maxColumn, until)
}
eg, mu := errgroup.Group{}, new(sync.Mutex)
orderChs := make([]chan struct{}, len(systemGraphs))
for i := range orderChs {
orderChs[i] = make(chan struct{})
}
var j int
for _, graph := range systemGraphs {
graph := graph
var metricNames []string
for _, metric := range graph.metrics {
metricNames = append(metricNames, filterMetricNames(metricNamesMap, metric.name)...)
}
if len(metricNames) == 0 {
continue
}
k := j
j++
eg.Go(func() error {
ms, err := app.fetchMetrics(graph, metricNames, from, until)
if err != nil {
return err
}
if k > 0 {
<-orderChs[k-1]
}
mu.Lock()
defer func() {
mu.Unlock()
close(orderChs[k])
}()
return ui.output(graph, ms)
})
}
if err := eg.Wait(); err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
return ui.cleanup()
}
func (app *App) fetchMetrics(graph graph, metricNames []string, from, until time.Time) (metricsByName, error) {
eg, mu := errgroup.Group{}, new(sync.Mutex)
ms := make(metricsByName, len(metricNames))
for _, metricName := range metricNames {
metricName := metricName
eg.Go(func() error {
metrics, err := app.fetcher.fetchMetric(app.hostID, metricName, from, until)
if err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
ms.Add(metricName, metrics)
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
}
ms.AddMemorySwapUsed()
ms.Stack(graph)
return ms, nil
}
func (app *App) getMetricNamesMap() (map[string]bool, error) {
metricNames, err := app.client.ListHostMetricNames(app.hostID)
if err != nil {
return nil, err
}
metricNamesMap := make(map[string]bool, len(metricNames))
for _, metricName := range metricNames {
metricNamesMap[metricName] = true
}
return metricNamesMap, nil
}
func filterMetricNames(metricNamesMap map[string]bool, name string) []string {
if metricNamesMap[name] {
return []string{name}
}
if name == "memory.swap_used" {
if metricNamesMap["memory.swap_total"] && metricNamesMap["memory.swap_free"] {
return []string{"memory.swap_free"}
}
}
namePattern := metricNamePattern(name)
var metricNames []string
for metricName := range metricNamesMap {
if namePattern.MatchString(metricName) {
metricNames = append(metricNames, metricName)
}
}
return metricNames
}