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"
7
	"image/color"
liming6's avatar
liming6 committed
8
	"strings"
liming6's avatar
liming6 committed
9
	"sync"
10
	"sync/atomic"
liming6's avatar
liming6 committed
11
12
13
14
	"time"

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

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

27
28
29
30
31
32
33
34
var (
	SysLoadBorder = LowLeightStyle.Render(`│



│`)
)

liming6's avatar
liming6 committed
35
36
// ModelSysLoad 系统负载组件
type ModelSysLoad struct {
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	SysMem              *MyTimeChart
	SysCPU              *MyTimeChart
	DCU                 *MyTimeChart
	DCUMem              *MyTimeChart
	sysInfoLock         sync.Mutex // sysinfo和keys的保护锁,防止并发写
	sysInfo             *treemap.Map[time.Time, SysLoadInfo]
	keys                *binaryheap.Heap[time.Time]
	current             *SysLoadInfo
	topLine, topL       string
	bottomLine, bottomL string
	style               lipgloss.Style
	width               int           // 组件总宽度
	colors              []color.Color // 时序图的颜色表
	colorsLow           []color.Color // 低亮度模式的颜色表
	actionMsg           *ActionMsg    // 动作消息
	modelMsg            *ModelMsg     // 模型周期性消息
	DCUToShow           atomic.Int32  // 要显示的DCU信息的索引,如果为-1,表示显示dcu的平均数据
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
72
73
74
75
76
77
78
79
80
81
	result.sysInfo = treemap.NewWith[time.Time, SysLoadInfo](func(x, y time.Time) int {
		if x.Before(y) {
			return -1
		}
		if x.After(y) {
			return 1
		}
		return 0
	})
	result.keys = binaryheap.NewWith(func(a, b time.Time) int {
liming6's avatar
liming6 committed
82
83
84
85
86
87
88
89
		if a.After(b) {
			return 1
		}
		if a.Before(b) {
			return -1
		}
		return 0
	})
liming6's avatar
liming6 committed
90
	result.colors = gamut.Blends(lipgloss.Color("#ff0000"), lipgloss.Color("#00ff00ff"), SysLoadHeight)
91
92
	result.colorsLow = gamut.Blends(gamut.Darker(lipgloss.Color("#ff0000"), 0.8), gamut.Darker(lipgloss.Color("#00ff00ff"), 0.8), SysLoadHeight)

liming6's avatar
liming6 committed
93
	subLine := width - StaticWidth - 1
liming6's avatar
liming6 committed
94
95
96
97
	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
98
99
	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
100

101
102
103
104
105
	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
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
	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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
	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
171
172
173
174
175
}

func (m *ModelSysLoad) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := inputMsg.(type) {
	case *ModelMsg:
liming6's avatar
liming6 committed
176
		m.modelMsg = msg
177
		m.updateInfo(msg)
liming6's avatar
liming6 committed
178
179
180
		return m, nil
	case *ActionMsg:
		m.actionMsg = msg
181
		m.handleAction()
liming6's avatar
liming6 committed
182
183
184
185
186
		return m, nil
	}
	return m, nil
}

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// handleAction 处理ActionMsg
func (m *ModelSysLoad) handleAction() {
	if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil {
		return
	}
	targetIndex := int32(*m.actionMsg.TargetDCUIndex)
	oldIndex := m.DCUToShow.Swap(targetIndex)
	if oldIndex == targetIndex {
		// 没有变
		return
	}
	wg := sync.WaitGroup{}
	wg.Add(1)
	index := int(targetIndex)
	i := m.sysInfo.Iterator()

	mem := make([]tchart.TimePoint, 0, m.sysInfo.Size())
	usage := make([]tchart.TimePoint, 0, m.sysInfo.Size())
	for {
		if !i.Next() {
			break
		}
		key := i.Key()
		var valM, valU float32
		vU, haveU := i.Value().DCUUsage[index]
		vM, haveM := i.Value().DCUMemUsage[index]
		if haveU {
			valU = vU
		} else {
			valU = 0
		}
		if haveM {
			valM = vM
		} else {
			valM = 0
		}
		mem = append(mem, tchart.TimePoint{Time: key, Value: float64(valM)})
		usage = append(usage, tchart.TimePoint{Time: key, Value: float64(valU)})
	}
	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
236
func (m *ModelSysLoad) View() string {
237
	darkMode := false
liming6's avatar
liming6 committed
238
	if m.actionMsg != nil && m.actionMsg.Action != nil && m.actionMsg.VM == VMMain {
239
240
		darkMode = true
	}
liming6's avatar
liming6 committed
241
242
	memUsed := utils.MemorySize{Num: m.current.MemUsed, Unit: utils.Byte}

243
	load := fmt.Sprintf(" Load Average: %.2f %.2f %.2f\n CPU: %.1f%%", m.current.Load1, m.current.Load5, m.current.Load15, m.current.CPUPercent)
244
	mem := fmt.Sprintf(" MEM: %s (%.1f%%)", memUsed.HumanReadStr(1), m.current.MemUsedPercent)
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
	if darkMode {
		load = LowLeightStyle.Render(load)
		mem = LowLeightStyle.Render(mem)
	}
	var dcuMem, dcu string
	if m.actionMsg == nil || m.actionMsg.TargetDCUIndex == nil {
		dcuMem = fmt.Sprintf(" DCU AVG MEM: %.1f%%", m.current.DCUMemUsageAvg)
		dcu = fmt.Sprintf(" DCU AVG UTL: %.1f%%", m.current.DCUUsageAvg)
	} else {
		index := int(*m.actionMsg.TargetDCUIndex)
		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
281

282
283
284
285
286
		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
287
288
	up := lipgloss.JoinHorizontal(lipgloss.Top, load, dcuMem)
	down := lipgloss.JoinHorizontal(lipgloss.Top, mem, dcu)
289
290
291
	if darkMode {
		return lipgloss.JoinVertical(lipgloss.Left, up, m.topL, down, m.bottomL) + "\n"
	}
292
	return lipgloss.JoinVertical(lipgloss.Left, up, m.topLine, down, m.bottomLine) + "\n"
liming6's avatar
liming6 committed
293
294
}

295
// updateInfo 向ModelSysLoad中添加数据
296
func (m *ModelSysLoad) updateInfo(t *ModelMsg) {
liming6's avatar
liming6 committed
297
298
299
300
301
302
303
304
305
306
307
	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
308
	s.T = t.t
liming6's avatar
liming6 committed
309
310
311
312
	s.CPUPercent = sysInfo.CPUPercent
	s.DCUUsage = make(map[int]float32)
	s.DCUMemUsage = make(map[int]float32)
	s.DCUMemUsageAvg, s.DCUUsageAvg = 0, 0
313
314
315

	qinfo, lock := t.DCUInfo.GetQuitInfo()
	for k, v := range qinfo {
liming6's avatar
liming6 committed
316
317
		s.DCUMemUsageAvg += v.MemUsedPerent
		s.DCUMemUsage[k] = v.MemUsedPerent
liming6's avatar
liming6 committed
318
		s.DCUUsageAvg += v.DCUUTil
liming6's avatar
liming6 committed
319
320
		s.DCUUsage[k] = v.DCUUTil
	}
321
	l := len(qinfo)
liming6's avatar
liming6 committed
322
	lock.Unlock()
liming6's avatar
liming6 committed
323
324
	s.DCUMemUsageAvg /= float32(l)
	s.DCUUsageAvg /= float32(l)
325
326
	s.DCUMemUsage[-1] = s.DCUMemUsageAvg
	s.DCUUsage[-1] = s.DCUUsageAvg
liming6's avatar
liming6 committed
327
	m.current = &s
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344

	needUpdate, index := false, -1
	if m.actionMsg != nil && m.actionMsg.TargetDCUIndex != nil {
		index = *m.actionMsg.TargetDCUIndex
		// 需要进入特定DCU的图表
		oldIndex := m.DCUToShow.Swap(int32(index))
		if oldIndex != int32(*m.actionMsg.TargetDCUIndex) {
			// 需要更新
			needUpdate = true
		}
	} else {
		oldIndex := m.DCUToShow.Swap(-1)
		if oldIndex != -1 {
			// 需要更新
			needUpdate = true
		}
	}
liming6's avatar
liming6 committed
345
346
347
348
	wg := sync.WaitGroup{}
	wg.Add(4)
	go func() {
		defer wg.Done()
349
		m1, _ := m.SysMem.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.MemUsedPercent}}, false))
liming6's avatar
liming6 committed
350
351
352
353
		m.SysMem = m1.(*MyTimeChart)
	}()
	go func() {
		defer wg.Done()
354
		m2, _ := m.SysCPU.Update(NewTimeCharMsg([]tchart.TimePoint{{Time: t.t, Value: s.CPUPercent}}, false))
liming6's avatar
liming6 committed
355
356
		m.SysCPU = m2.(*MyTimeChart)
	}()
liming6's avatar
liming6 committed
357
	// 存放数据
358
359
360
361
362
	m.sysInfoLock.Lock()
	m.sysInfo.Put(t.t, s)
	m.keys.Push(t.t)
	if m.keys.Size() > SysLoadCap {
		delKey, have := m.keys.Pop()
liming6's avatar
liming6 committed
363
		if have {
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
			m.sysInfo.Remove(delKey)
		}
	}
	m.sysInfoLock.Unlock()

	if needUpdate {
		// 准备全量数据
		i := m.sysInfo.Iterator()
		mem := make([]tchart.TimePoint, 0, m.sysInfo.Size())
		usage := make([]tchart.TimePoint, 0, m.sysInfo.Size())
		for {
			if !i.Next() {
				break
			}
			key := i.Key()
			var valM, valU float32
			vU, haveU := i.Value().DCUUsage[index]
			vM, haveM := i.Value().DCUMemUsage[index]
			if haveU {
				valU = vU
			} else {
				valU = 0
			}
			if haveM {
				valM = vM
			} else {
				valM = 0
			}
			mem = append(mem, tchart.TimePoint{Time: key, Value: float64(valM)})
			usage = append(usage, tchart.TimePoint{Time: key, Value: float64(valU)})
		}
		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
419
		}
420
421
422
423
424
425
426
427
428
429
		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
430
	}
liming6's avatar
liming6 committed
431
432
	wg.Wait()
}
433
434
435
436
437
438
439
440
441
442

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
}