sysload.go 12.9 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"
liming6's avatar
liming6 committed
7
	"strings"
liming6's avatar
liming6 committed
8
	"sync"
9
	"sync/atomic"
liming6's avatar
liming6 committed
10
11
12
13
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
liming6's avatar
liming6 committed
14
	linkedlist "github.com/emirpasic/gods/v2/lists/doublylinkedlist"
15
16
	"github.com/lucasb-eyer/go-colorful"
	"github.com/muesli/gamut"
liming6's avatar
liming6 committed
17
18
19
20
21
)

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

25
26
27
28
29
30
31
32
var (
	SysLoadBorder = LowLeightStyle.Render(`│



│`)
)

liming6's avatar
liming6 committed
33
34
// ModelSysLoad 系统负载组件
type ModelSysLoad struct {
35
36
37
38
	SysMem              *MyTimeChart
	SysCPU              *MyTimeChart
	DCU                 *MyTimeChart
	DCUMem              *MyTimeChart
liming6's avatar
liming6 committed
39
40
41
42
	dataMaplock         sync.RWMutex                               // 保护下面两个map的锁,防止并发读写
	dcuMemData          map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的内存使用信息点
	dcuUsgData          map[int]*linkedlist.List[tchart.TimePoint] // 记录dcu的使用率信息点
	current             *SysLoadInfo                               // 当前的全量信息
43
44
45
	topLine, topL       string
	bottomLine, bottomL string
	style               lipgloss.Style
liming6's avatar
liming6 committed
46
47
48
49
50
51
52
53
	width               int              // 组件总宽度
	colors              []lipgloss.Color // 时序图的颜色表
	colorsLow           []lipgloss.Color // 低亮度模式的颜色表
	actionMsg           *ActionMsg       // 动作消息
	modelMsg            *ModelMsg        // 模型周期性消息
	DCUToShow           atomic.Int32     // 要显示的DCU信息的索引,如果为-1,表示显示dcu的平均数据

	dataNumToStore int // 维持DCU和DCUMem图表需要的最多数据点的数量
liming6's avatar
liming6 committed
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
}

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
71
	result.width = width
liming6's avatar
liming6 committed
72
73
74
75
	result.dcuMemData = make(map[int]*linkedlist.List[tchart.TimePoint])
	result.dcuUsgData = make(map[int]*linkedlist.List[tchart.TimePoint])
	result.colors = ConvertColor(gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight))
	result.colorsLow = ConvertColor(gamut.Blends(gamut.Darker(lipgloss.Color("#ff0000"), 0.8), gamut.Darker(lipgloss.Color("#00ff00ff"), 0.8), SysLoadHeight))
76

liming6's avatar
liming6 committed
77
	subLine := width - StaticWidth - 1
liming6's avatar
liming6 committed
78
	result.dataNumToStore = subLine*2 + 1
liming6's avatar
liming6 committed
79
80
81
82
	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
83
84
	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
85

86
87
88
89
90
	result.topL = LowLeightStyle.Render("├" + genXAxisNoStyle(SysLoadWidth) + "┼" + genXAxisNoStyle(subLine) + "┤")

	result.bottomL = LowLeightStyle.Render(result.bottomLine)
	result.style = lipgloss.NewStyle()
	result.DCUToShow.Store(-1)
liming6's avatar
liming6 committed
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
	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
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
	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
156
157
158
159
160
}

func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case *ModelMsg:
liming6's avatar
liming6 committed
161
		m.modelMsg = msg
162
		m.updateInfo(msg)
liming6's avatar
liming6 committed
163
164
165
		return m, nil
	case *ActionMsg:
		m.actionMsg = msg
166
		m.handleAction()
liming6's avatar
liming6 committed
167
168
169
170
171
		return m, nil
	}
	return m, nil
}

172
173
// handleAction 处理ActionMsg
func (m *ModelSysLoad) handleAction() {
174
	if m.actionMsg == nil || m.actionMsg.VM != VMMain {
175
176
		return
	}
177
	targetIndex := int32(m.actionMsg.TargetDCUIndex)
178
179
180
181
182
183
184
185
	oldIndex := m.DCUToShow.Swap(targetIndex)
	if oldIndex == targetIndex {
		// 没有变
		return
	}
	wg := sync.WaitGroup{}
	wg.Add(1)
	index := int(targetIndex)
liming6's avatar
liming6 committed
186
187
188
189
190
	rl := m.dataMaplock.RLocker()
	rl.Lock()
	mem := m.dcuMemData[index].Values()
	usage := m.dcuUsgData[index].Values()
	rl.Unlock()
191
192
193
194
195
196
197
198
199
200
	go func() {
		defer wg.Done()
		m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
		m.DCU = m3.(*MyTimeChart)
	}()
	m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
	m.DCUMem = m4.(*MyTimeChart)
	wg.Wait()
}

liming6's avatar
liming6 committed
201
func (m *ModelSysLoad) View() string {
202
	darkMode := false
liming6's avatar
liming6 committed
203
	if m.actionMsg != nil && m.actionMsg.Action != nil && m.actionMsg.VM == VMMain {
204
205
		darkMode = true
	}
liming6's avatar
liming6 committed
206
207
	memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte}

208
	load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent)
209
	mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
210
211
212
213
214
	if darkMode {
		load = LowLeightStyle.Render(load)
		mem = LowLeightStyle.Render(mem)
	}
	var dcuMem, dcu string
215
	if m.actionMsg == nil  {
216
217
218
		dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
		dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
	} else {
219
		index := m.actionMsg.TargetDCUIndex
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
		if index == -1 {
			dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
			dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
		} else {
			dcuMem = fmt.Sprintf(" DCU  %2d MEM: %.1f%%", index, m.current.DCUMemUsage[index])
			dcu = fmt.Sprintf(" DCU  %2d UTL: %.1f%%", index, m.current.DCUUsage[index])
		}
	}
	if darkMode {
		dcuMem = LowLeightStyle.Render(dcuMem)
		dcu = LowLeightStyle.Render(dcu)
	}
	if darkMode {
		load = StackPosition(load, m.SysCPU.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
		mem = StackPosition(mem, m.SysMem.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
		dcuMem = StackPosition(dcuMem, m.DCUMem.ViewWithColor(m.colorsLow), lipgloss.Top, lipgloss.Left)
		dcu = StackPosition(dcu, m.DCU.ViewWithColor(m.colorsLow), lipgloss.Bottom, lipgloss.Left)
		load = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, load, SysLoadBorder)
		mem = lipgloss.JoinHorizontal(lipgloss.Top, SysLoadBorder, mem, SysLoadBorder)
		dcuMem = lipgloss.JoinHorizontal(lipgloss.Top, dcuMem, SysLoadBorder)
		dcu = lipgloss.JoinHorizontal(lipgloss.Top, dcu, SysLoadBorder)
	} else {
		load = StackPosition(load, m.SysCPU.View(), lipgloss.Top, lipgloss.Left)
		mem = StackPosition(mem, m.SysMem.View(), lipgloss.Bottom, lipgloss.Left)
		dcuMem = StackPosition(dcuMem, m.DCUMem.View(), lipgloss.Top, lipgloss.Left)
		dcu = StackPosition(dcu, m.DCU.View(), lipgloss.Bottom, lipgloss.Left)
liming6's avatar
liming6 committed
246

247
248
249
250
251
		load = m.style.Border(myBorder, false, true, false).Render(load)
		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
252
253
	up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
	down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
254
255
256
	if darkMode {
		return lipgloss.JoinVertical(lipgloss.Left, up, m.topL, down, m.bottomL) + "\n"
	}
257
	return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
liming6's avatar
liming6 committed
258
259
}

260
// updateInfo 向ModelSysLoad中添加数据
261
func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
liming6's avatar
liming6 committed
262
263
264
265
266
267
	var sysInfo *utils.SysInfo
	if t.systemInfo != nil {
		sysInfo = t.systemInfo
	} else {
		sysInfo, _ = utils.GetSysInfo()
	}
liming6's avatar
liming6 committed
268
269
270
271
272
273
274
275
276
277
	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
278
	s.T = t.t
liming6's avatar
liming6 committed
279
280
281
282
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsage = make(map[int]float32)
	s.DCUMemUsage = make(map[int]float32)
	s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0
283
284

	qinfo, lock := t.DCUInfo.GetQuitInfo()
liming6's avatar
liming6 committed
285
	m.dataMaplock.Lock()
286
	for k, v := range qinfo {
liming6's avatar
liming6 committed
287
288
		s.DCUMemUsageAvg += v.MemUsedPerent
		s.DCUMemUsage[k] = v.MemUsedPerent
liming6's avatar
liming6 committed
289
		s.DCUUsageAvg += v.DCUUTil
liming6's avatar
liming6 committed
290
		s.DCUUsage[k] = v.DCUUTil
liming6's avatar
liming6 committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304

		l1, have := m.dcuMemData[k]
		if !have {
			l1 = linkedlist.New[tchart.TimePoint]()
			m.dcuMemData[k] = l1
		}
		l1.Add(tchart.TimePoint{Time: t.t, Value: float64(v.MemUsedPerent)})

		l2, have := m.dcuUsgData[k]
		if !have {
			l2 = linkedlist.New[tchart.TimePoint]()
			m.dcuUsgData[k] = l2
		}
		l2.Add(tchart.TimePoint{Time: t.t, Value: float64(v.DCUUTil)})
liming6's avatar
liming6 committed
305
	}
306
	l := len(qinfo)
liming6's avatar
liming6 committed
307
	lock.Unlock()
liming6's avatar
liming6 committed
308
309
	s.DCUMemUsageAvg /= float32(l)
	s.DCUUsageAvg /= float32(l)
310
311
	s.DCUMemUsage[-1] = s.DCUMemUsageAvg
	s.DCUUsage[-1] = s.DCUUsageAvg
liming6's avatar
liming6 committed
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337

	dcu, have := m.dcuUsgData[-1]
	if !have {
		dcu = linkedlist.New[tchart.TimePoint]()
		m.dcuUsgData[-1] = dcu
	}
	dcu.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUUsageAvg)})

	mem, have := m.dcuMemData[-1]
	if !have {
		mem = linkedlist.New[tchart.TimePoint]()
		m.dcuMemData[-1] = mem
	}
	mem.Add(tchart.TimePoint{Time: t.t, Value: float64(s.DCUMemUsageAvg)})

	// 判断是否要丢弃多余的点
	if dcu.Size() > m.dataNumToStore {
		for _, v := range m.dcuMemData {
			v.Remove(0)
		}
		for _, v := range m.dcuUsgData {
			v.Remove(0)
		}
	}

	m.dataMaplock.Unlock()
liming6's avatar
liming6 committed
338
	m.current = &s
339
340

	needUpdate, index := false, -1
341
342
	if m.actionMsg != nil  {
		index = m.actionMsg.TargetDCUIndex
343
344
		// 需要进入特定DCU的图表
		oldIndex := m.DCUToShow.Swap(int32(index))
345
		if oldIndex != int32(m.actionMsg.TargetDCUIndex) {
346
347
348
349
350
351
352
353
354
355
			// 需要更新
			needUpdate = true
		}
	} else {
		oldIndex := m.DCUToShow.Swap(-1)
		if oldIndex != -1 {
			// 需要更新
			needUpdate = true
		}
	}
liming6's avatar
liming6 committed
356
357
358
359
	wg := sync.WaitGroup{}
	wg.Add(4)
	go func() {
		defer wg.Done()
360
		m1, _ := m.SysMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}, false))
liming6's avatar
liming6 committed
361
362
363
364
		m.SysMem = m1.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
365
		m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false))
liming6's avatar
liming6 committed
366
367
		m.SysCPU = m2.(*MyTimeChart)
	}()
368
369
370

	if needUpdate {
		// 准备全量数据
liming6's avatar
liming6 committed
371
372
373
374
375
		rl := m.dataMaplock.RLocker()
		rl.Lock()
		mem := m.dcuMemData[index].Values()
		usage := m.dcuUsgData[index].Values()
		rl.Unlock()
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
		go func() {
			defer wg.Done()
			m3, _ := m.DCU.Update(NewTimeCharMsg(usage, true))
			m.DCU = m3.(*MyTimeChart)
		}()
		go func() {
			defer wg.Done()
			m4, _ := m.DCUMem.Update(NewTimeCharMsg(mem, true))
			m.DCUMem = m4.(*MyTimeChart)
		}()
	} else {
		// 无需更新,仅追加数据
		var valM, valU float32
		vU, haveU := s.DCUUsage[index]
		vM, haveM := s.DCUMemUsage[index]
		if haveU {
			valU = vU
		} else {
			valU = 0
		}
		if haveM {
			valM = vM
		} else {
			valM = 0
liming6's avatar
liming6 committed
400
		}
401
402
403
404
405
406
407
408
409
410
		go func() {
			defer wg.Done()
			m3, _ := m.DCU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valU)}}, false))
			m.DCU = m3.(*MyTimeChart)
		}()
		go func() {
			defer wg.Done()
			m4, _ := m.DCUMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: float64(valM)}}, false))
			m.DCUMem = m4.(*MyTimeChart)
		}()
liming6's avatar
liming6 committed
411
	}
liming6's avatar
liming6 committed
412
413
	wg.Wait()
}
414
415
416
417
418
419
420
421
422
423

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
}