sysload.go 7.76 KB
Newer Older
liming6's avatar
liming6 committed
1
2
3
4
package tui

import (
	"fmt"
5
	"get-container/cmd/hytop/tchart"
liming6's avatar
liming6 committed
6
	"get-container/utils"
7
	"image/color"
liming6's avatar
liming6 committed
8
	"strings"
liming6's avatar
liming6 committed
9
10
11
12
13
14
15
	"sync"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/emirpasic/gods/v2/maps/linkedhashmap"
	"github.com/emirpasic/gods/v2/trees/binaryheap"
16
17
	"github.com/lucasb-eyer/go-colorful"
	"github.com/muesli/gamut"
liming6's avatar
liming6 committed
18
19
20
21
22
)

const (
	SysLoadHeight = 5   // 固定图表高度
	SysLoadWidth  = 77  // 固定图表宽度,不包含左右的边框
liming6's avatar
liming6 committed
23
	SysLoadCap    = 600 // 记录
liming6's avatar
liming6 committed
24
25
26
27
)

// ModelSysLoad 系统负载组件
type ModelSysLoad struct {
liming6's avatar
liming6 committed
28
29
30
31
32
33
34
35
36
37
	SysMem     *MyTimeChart
	SysCPU     *MyTimeChart
	DCU        *MyTimeChart
	DCUMem     *MyTimeChart
	SysInfo    *linkedhashmap.Map[time.Time, SysLoadInfo]
	Keys       *binaryheap.Heap[time.Time]
	current    *SysLoadInfo
	topLine    string
	bottomLine string
	style      lipgloss.Style
liming6's avatar
liming6 committed
38
39
40
41
	width      int           // 组件总宽度
	colors     []color.Color // 时序图的颜色表
	actionMsg  *ActionMsg    // 动作消息
	modelMsg   *ModelMsg     // 模型周期性消息
liming6's avatar
liming6 committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
}

type SysLoadInfo struct {
	T                               time.Time
	DCUUsage                        map[int]float32
	DCUMemUsage                     map[int]float32
	Load1, Load5, Load15            float64
	MemTotal, SwapTotal             uint64
	MemUsed, SwapUsed               uint64
	MemUsedPercent, SwapUsedPercent float64
	CPUPercent                      float64
	DCUUsageAvg                     float32
	DCUMemUsageAvg                  float32
}

func NewModelSysLoad(width int) *ModelSysLoad {
	result := ModelSysLoad{}
liming6's avatar
liming6 committed
59
	result.width = width
liming6's avatar
liming6 committed
60
61
62
63
64
65
66
67
68
69
	result.SysInfo = linkedhashmap.New[time.Time, SysLoadInfo]()
	result.Keys = binaryheap.NewWith(func(a, b time.Time) int {
		if a.After(b) {
			return 1
		}
		if a.Before(b) {
			return -1
		}
		return 0
	})
liming6's avatar
liming6 committed
70
	result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight)
liming6's avatar
liming6 committed
71
	subLine := width - StaticWidth - 1
liming6's avatar
liming6 committed
72
73
74
75
	result.SysMem = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
	result.SysCPU = NewTimeChart(SysLoadWidth, SysLoadHeight, 0, 100, result.colors)
	result.DCU = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
	result.DCUMem = NewTimeChart(subLine, SysLoadHeight, 0, 100, result.colors)
liming6's avatar
liming6 committed
76
77
	result.topLine = myBorder.MiddleLeft + genXAxis(SysLoadWidth) + myBorder.Middle + genXAxis(subLine) + myBorder.MiddleRight
	result.bottomLine = "╞" + strings.Repeat(myBorder.Bottom, SysLoadWidth) + "╧" + strings.Repeat(myBorder.Bottom, subLine) + "╡"
liming6's avatar
liming6 committed
78
	result.style = lipgloss.NewStyle()
liming6's avatar
liming6 committed
79

liming6's avatar
liming6 committed
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
	return &result
}

func (m *ModelSysLoad) Init() tea.Cmd {
	result := make([]tea.Cmd, 0)
	if c := m.DCU.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.DCUMem.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.SysMem.Init(); c != nil {
		result = append(result, c)
	}
	if c := m.SysCPU.Init(); c != nil {
		result = append(result, c)
	}
	sysInfo, _ := utils.GetSysInfo()
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
	s.T = time.Now()
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsageAvg = 0
	s.DCUMemUsageAvg = 0
	m.current = &s
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
	return tea.Batch(result...)
}

func (m *ModelSysLoad) init() [6]time.Time {
	var result [6]time.Time
	result[0] = time.Now()
	m.DCU.Init()
	result[1] = time.Now()
	m.DCUMem.Init()
	result[2] = time.Now()
	m.SysMem.Init()
	result[3] = time.Now()
	m.SysCPU.Init()
	result[4] = time.Now()
	sysInfo, _ := utils.GetSysInfo()
	result[5] = time.Now()
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
	s.T = time.Now()
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsageAvg = 0
	s.DCUMemUsageAvg = 0
	m.current = &s
	return result
liming6's avatar
liming6 committed
145
146
147
148
149
}

func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case *ModelMsg:
liming6's avatar
liming6 committed
150
		m.modelMsg = msg
151
		m.updateInfo(msg)
liming6's avatar
liming6 committed
152
153
154
155
		return m, nil
	case *ActionMsg:
		m.actionMsg = msg

liming6's avatar
liming6 committed
156
157
158
159
160
161
		return m, nil
	}
	return m, nil
}

func (m *ModelSysLoad) View() string {
liming6's avatar
liming6 committed
162
163
	memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte}

164
	load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent)
165
	mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
liming6's avatar
liming6 committed
166
167
	dcuMem := fmt.Sprintf(" AVG DCU MEM: %.1f%%", m.current.DCUMemUsageAvg)
	dcu := fmt.Sprintf(" AVG DCU UTL: %.1f%%", m.current.DCUUsageAvg)
liming6's avatar
liming6 committed
168
169
170

	load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left)
	mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left)
171
172
173
	s := m.DCUMem.View()
	s = lipgloss.NewStyle().Foreground(lipgloss.Color("rgba(158, 143, 28, 0.86)")).Render(s)
	dcuMem = StackPosition(dcuMem, s, lipgloss.Top, lipgloss.Left)
liming6's avatar
liming6 committed
174
175
176
	dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left)

	load = m.style.Border(myBorder, false, true, false).Render(load)
liming6's avatar
liming6 committed
177
178
179
	mem = m.style.Border(myBorder, false, true, false).Render(mem)
	dcuMem = m.style.Border(myBorder, false, true, false, false).Render(dcuMem)
	dcu = m.style.Border(myBorder, false, true, false, false).Render(dcu)
liming6's avatar
liming6 committed
180
181
182

	up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
	down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
183
	return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
liming6's avatar
liming6 committed
184
185
186
}

// updateInfo
187
func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
liming6's avatar
liming6 committed
188
189
190
191
192
193
194
195
196
197
198
	sysInfo, _ := utils.GetSysInfo()
	s := SysLoadInfo{}
	s.Load1 = sysInfo.LoadAverage1
	s.Load5 = sysInfo.LoadAverage5
	s.Load15 = sysInfo.LoadAverage15
	s.MemTotal = sysInfo.MemTotal
	s.MemUsed = sysInfo.MemUsage
	s.SwapTotal = sysInfo.SwapTotal
	s.SwapUsed = sysInfo.SwapUsage
	s.MemUsedPercent = sysInfo.MemUsagePercent
	s.SwapUsedPercent = sysInfo.SwapUsagePercent
199
	s.T = t.t
liming6's avatar
liming6 committed
200
201
202
203
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsage = make(map[int]float32)
	s.DCUMemUsage = make(map[int]float32)
	s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0
204
205
206

	qinfo, lock := t.DCUInfo.GetQuitInfo()
	for k, v := range qinfo {
liming6's avatar
liming6 committed
207
208
		s.DCUMemUsageAvg += v.MemUsedPerent
		s.DCUMemUsage[k] = v.MemUsedPerent
liming6's avatar
liming6 committed
209
		s.DCUUsageAvg += v.DCUUTil
liming6's avatar
liming6 committed
210
211
		s.DCUUsage[k] = v.DCUUTil
	}
212
	l := len(qinfo)
liming6's avatar
liming6 committed
213
	lock.Unlock()
liming6's avatar
liming6 committed
214
215
216
217
218
219
220
	s.DCUMemUsageAvg /= float32(l)
	s.DCUUsageAvg /= float32(l)
	m.current = &s
	wg := sync.WaitGroup{}
	wg.Add(4)
	go func() {
		defer wg.Done()
liming6's avatar
liming6 committed
221
		m1, _ := m.SysMem.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}})
liming6's avatar
liming6 committed
222
223
224
225
		m.SysMem = m1.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
liming6's avatar
liming6 committed
226
		m2, _ := m.SysCPU.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}})
liming6's avatar
liming6 committed
227
228
229
230
		m.SysCPU = m2.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
liming6's avatar
liming6 committed
231
		m3, _ := m.DCU.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: float64(s.DCUUsageAvg)}}})
liming6's avatar
liming6 committed
232
233
234
235
		m.DCU = m3.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
liming6's avatar
liming6 committed
236
		m4, _ := m.DCUMem.Update(MyTimeChartMsg{[]tchart.TimePoint{{Time: t.t, Value: float64(s.DCUMemUsageAvg)}}})
liming6's avatar
liming6 committed
237
238
		m.DCUMem = m4.(*MyTimeChart)
	}()
liming6's avatar
liming6 committed
239
	// 存放数据
240
241
	m.SysInfo.Put(t.t, s)
	m.Keys.Push(t.t)
liming6's avatar
liming6 committed
242
243
244
245
246
247
	if m.Keys.Size() > SysLoadCap {
		delKey, have := m.Keys.Pop()
		if have {
			m.SysInfo.Remove(delKey)
		}
	}
liming6's avatar
liming6 committed
248
249
	wg.Wait()
}
250
251
252
253
254
255
256
257
258
259

func (m *ModelSysLoad) RanderColor(str string) string {
	lines := strings.Split(strings.Trim(str, "\n"), "\n")
	result := ""
	for k, line := range lines {
		c, _ := colorful.MakeColor(m.colors[k])
		result += lipgloss.NewStyle().Foreground(lipgloss.Color(c.Hex())).Render(line) + "\n"
	}
	return result
}