Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
liming6
dcu-process-montor
Commits
0b52579d
Commit
0b52579d
authored
Feb 28, 2026
by
liming6
Browse files
fix 功耗监视器添加刻度线
parent
72702c5d
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
158 additions
and
117 deletions
+158
-117
cmd/hytop/tui/power.go
cmd/hytop/tui/power.go
+53
-20
cmd/hytop/tui/timechart.go
cmd/hytop/tui/timechart.go
+82
-91
cmd/hytop/tui/tui_test.go
cmd/hytop/tui/tui_test.go
+23
-6
No files found.
cmd/hytop/tui/power.go
View file @
0b52579d
...
...
@@ -35,6 +35,7 @@ type ModelPowerDetail struct {
DisplayAvg
bool
// 显示功率平均值
lock
sync
.
RWMutex
// 保护DataCache和Charts的锁
ScreenSplit
map
[
int
][][]
ScreenArea
// 记录屏幕尺寸
title
string
// 标题字符串
}
type
ChartAndArea
struct
{
...
...
@@ -72,9 +73,20 @@ var (
)
func
(
m
*
ModelPowerDetail
)
Init
()
tea
.
Cmd
{
m
.
height
--
for
i
:=
1
;
i
<
10
;
i
++
{
m
.
ScreenSplit
[
i
]
=
m
.
SplitScreen
(
i
)
}
m
.
height
++
m
.
title
=
"DCU Power Monitor"
s
:=
"Press <P> and <Space> to select dcu, Parse <A> to show average"
extLen
:=
m
.
width
-
len
(
m
.
title
)
-
len
(
s
)
if
extLen
>
0
{
m
.
title
+=
strings
.
Repeat
(
" "
,
extLen
)
+
s
}
m
.
title
=
HeightLightStyle
.
Render
(
m
.
title
)
+
"
\n
"
return
nil
}
...
...
@@ -177,18 +189,18 @@ func (m *ModelPowerDetail) Update(imsg tea.Msg) (tea.Model, tea.Cmd) {
}
chart
,
have
:=
m
.
Charts
[
-
1
]
if
!
have
{
c
:=
NewTimeChart
(
m
.
width
,
m
.
height
-
1
,
0
,
float64
(
pwrCap
),
[]
lipgloss
.
Color
{
lipgloss
.
Color
(
"#ff0000"
),
lipgloss
.
Color
(
"#00ff00ff"
)})
c
:=
NewTimeChart
(
m
.
width
-
1
,
m
.
height
-
2
,
0
,
float64
(
pwrCap
),
[]
lipgloss
.
Color
{
lipgloss
.
Color
(
"#ff0000"
),
lipgloss
.
Color
(
"#00ff00ff"
)})
chart
=
&
ChartAndArea
{
chart
:
c
,
area
:
m
.
ScreenSplit
[
1
][
0
][
0
],
h
:
0
,
l
:
0
}
m
.
Charts
[
-
1
]
=
chart
}
chart
.
chart
.
PutPoint
([]
tchart
.
TimePoint
{{
Time
:
msg
.
t
,
Value
:
float64
(
powerTotal
)}})
chart
,
have
=
m
.
Charts
[
-
2
]
if
!
have
{
c
:=
NewTimeChart
(
m
.
width
,
m
.
height
-
1
,
0
,
float64
(
pwrCap
),
[]
lipgloss
.
Color
{
lipgloss
.
Color
(
"#ff0000"
),
lipgloss
.
Color
(
"#00ff00ff"
)})
chart
=
&
ChartAndArea
{
chart
:
c
,
area
:
m
.
ScreenSplit
[
1
][
0
][
0
],
h
:
0
,
l
:
0
}
m
.
Charts
[
-
2
]
=
chart
if
extAvg
{
chart
,
have
=
m
.
Charts
[
-
2
]
if
have
{
chart
.
chart
.
PutPoint
([]
tchart
.
TimePoint
{{
Time
:
msg
.
t
,
Value
:
float64
(
selectedTotal
)}})
m
.
Charts
[
-
2
]
=
chart
}
}
chart
.
chart
.
PutPoint
([]
tchart
.
TimePoint
{{
Time
:
msg
.
t
,
Value
:
float64
(
selectedTotal
)}})
m
.
lock
.
Unlock
()
m
.
PowerCap
[
-
1
]
=
pwrCap
for
k
:=
range
m
.
Pids
{
...
...
@@ -396,11 +408,13 @@ func (m *ModelPowerDetail) UpdateCharts(index int, isAdd bool) {
m
.
DataCache
[
-
2
]
=
l
chart
,
have
:=
m
.
Charts
[
-
2
]
if
!
have
{
c
:=
NewTimeChart
(
m
.
width
,
m
.
height
-
1
,
0
,
float64
(
m
.
PowerCap
[
-
1
]),
[]
lipgloss
.
Color
{
lipgloss
.
Color
(
"#ff0000"
),
lipgloss
.
Color
(
"#00ff00ff"
)})
c
:=
NewTimeChart
(
m
.
width
-
1
,
m
.
height
-
2
,
0
,
float64
(
m
.
PowerCap
[
-
1
]),
[]
lipgloss
.
Color
{
lipgloss
.
Color
(
"#ff0000"
),
lipgloss
.
Color
(
"#00ff00ff"
)})
chart
=
&
ChartAndArea
{
chart
:
c
,
area
:
m
.
ScreenSplit
[
1
][
0
][
0
],
h
:
0
,
l
:
0
}
m
.
Charts
[
-
2
]
=
chart
}
chart
.
chart
.
ResetPutPoint
(
points
)
}
else
{
delete
(
m
.
Charts
,
-
2
)
}
m
.
lock
.
Unlock
()
}
...
...
@@ -415,16 +429,23 @@ func (m *ModelPowerDetail) RenderCharts() string {
}
slices
.
Sort
(
dcuSelected
)
selectNum
:=
len
(
dcuSelected
)
mstr
:=
LowLeightStyle
.
Render
(
fmt
.
Sprintf
(
"%.0fW"
,
m
.
PowerCap
[
-
1
]
/
2
))
// 显示全部平均功率
if
selectNum
==
0
||
(
selectNum
==
len
(
m
.
dcuIndex
)
&&
m
.
DisplayAvg
)
{
x
:=
genXAxis
(
m
.
width
)
chart
:=
m
.
Charts
[
-
1
]
.
chart
.
View
()
return
chart
+
"
\n
"
+
x
chart
:=
m
.
Charts
[
-
1
]
.
chart
x
:=
genXAxis
(
chart
.
width
,
B
)
str
:=
StackPosition
(
mstr
,
chart
.
ViewWithMiddleLine
(),
lipgloss
.
Center
,
lipgloss
.
Left
)
+
"
\n
"
+
x
str
=
StackPosition
(
"DCU: All Avg"
,
str
,
lipgloss
.
Top
,
lipgloss
.
Left
)
str
=
MPStyle
.
Render
(
str
)
return
str
}
else
if
m
.
DisplayAvg
{
// 显示选中dcu的平均功率图表
x
:=
genXAxis
(
m
.
width
)
chart
:=
m
.
Charts
[
-
2
]
.
chart
.
View
()
return
chart
+
"
\n
"
+
x
chart
:=
m
.
Charts
[
-
2
]
.
chart
x
:=
genXAxis
(
chart
.
width
,
B
)
str
:=
StackPosition
(
mstr
,
chart
.
ViewWithMiddleLine
(),
lipgloss
.
Center
,
lipgloss
.
Left
)
+
"
\n
"
+
x
strDCU
:=
strings
.
ReplaceAll
(
strings
.
Trim
(
fmt
.
Sprintf
(
"%v"
,
dcuSelected
),
"[]"
),
" "
,
","
)
str
=
StackPosition
(
fmt
.
Sprintf
(
"DCU: %s Avg"
,
strDCU
),
str
,
lipgloss
.
Top
,
lipgloss
.
Left
)
return
MPStyle
.
Render
(
str
)
}
else
{
// 显示选中dcu的图表
areas
:=
m
.
ScreenSplit
[
selectNum
]
...
...
@@ -435,10 +456,10 @@ func (m *ModelPowerDetail) RenderCharts() string {
strs
:=
make
([]
string
,
0
,
selectNum
)
for
_
,
v
:=
range
dcuSelected
{
chart
:=
m
.
Charts
[
v
]
.
chart
str
:=
chart
.
View
(
)
str
:=
StackPosition
(
mstr
,
chart
.
ViewWithMiddleLine
(),
lipgloss
.
Center
,
lipgloss
.
Left
)
str
=
StackPosition
(
fmt
.
Sprintf
(
"DCU: %d"
,
v
),
str
,
lipgloss
.
Top
,
lipgloss
.
Left
)
str
+=
"
\n
"
str
+=
genXAxis
(
chart
.
width
)
str
+=
genXAxis
(
chart
.
width
,
B
)
strs
=
append
(
strs
,
MPStyle
.
Render
(
str
))
}
lines
:=
make
(
map
[
int
]
string
)
...
...
@@ -452,17 +473,29 @@ func (m *ModelPowerDetail) RenderCharts() string {
lines
[
po
[
0
]]
=
lipgloss
.
JoinHorizontal
(
lipgloss
.
Top
,
l
,
v
)
}
}
return
lipgloss
.
JoinVertical
(
lipgloss
.
Left
,
lines
[
0
],
lines
[
1
],
lines
[
2
])
switch
len
(
lines
)
{
case
1
:
return
lines
[
0
]
case
2
:
return
lipgloss
.
JoinVertical
(
lipgloss
.
Left
,
lines
[
0
],
lines
[
1
])
case
3
:
return
lipgloss
.
JoinVertical
(
lipgloss
.
Left
,
lines
[
0
],
lines
[
1
],
lines
[
2
])
default
:
panic
(
"error line number"
)
}
}
}
func
(
m
*
ModelPowerDetail
)
View
()
string
{
rl
:=
m
.
lock
.
RLocker
()
rl
.
Lock
()
defer
rl
.
Unlock
()
if
m
.
DisplaySummary
{
summary
:=
m
.
SummaryInfo
()
charts
:=
m
.
RenderCharts
()
return
StackPosition
(
summary
,
charts
,
lipgloss
.
Center
,
lipgloss
.
Center
)
return
m
.
title
+
StackPosition
(
summary
,
charts
,
lipgloss
.
Center
,
lipgloss
.
Center
)
}
else
{
return
m
.
RenderCharts
()
return
m
.
title
+
m
.
RenderCharts
()
}
}
...
...
@@ -607,7 +640,7 @@ func (m *ModelPowerDetail) SplitScreen(num int) [][]ScreenArea {
result
[
h
][
l
]
.
h
++
}
}
for
h
:=
range
result
[
:
1
]
{
for
h
:=
range
result
[
:
2
]
{
for
l
:=
0
;
l
<
m
.
width
%
3
;
l
++
{
result
[
h
][
l
]
.
w
++
}
...
...
cmd/hytop/tui/timechart.go
View file @
0b52579d
...
...
@@ -19,6 +19,7 @@ import (
const
(
A
=
"├"
B
=
"└"
)
var
(
...
...
@@ -26,12 +27,16 @@ var (
)
// genXAxis 生成X轴,参数l是x轴的长度
func
genXAxis
(
l
int
)
string
{
func
genXAxis
(
l
int
,
s
...
string
)
string
{
if
s
==
nil
{
s
=
make
([]
string
,
0
,
1
)
s
=
append
(
s
,
A
)
}
t60
:=
l
/
30
t30
:=
l
>=
18
var
result
string
if
t30
{
result
=
A
+
strings
.
Repeat
(
"─"
,
14
)
result
=
s
[
0
]
+
strings
.
Repeat
(
"─"
,
14
)
result
=
axisFStyle
.
Render
(
"30s"
)
+
result
}
else
{
return
strings
.
Repeat
(
"─"
,
l
)
...
...
@@ -51,7 +56,7 @@ func genXAxis(l int) string {
break
}
// 渲染标记
result
=
timeStr
+
A
+
strings
.
Repeat
(
"─"
,
targetLen
-
lipgloss
.
Width
(
result
)
-
timeStrLen
-
1
)
+
result
result
=
timeStr
+
s
[
0
]
+
strings
.
Repeat
(
"─"
,
targetLen
-
lipgloss
.
Width
(
result
)
-
timeStrLen
-
1
)
+
result
}
return
result
}
...
...
@@ -107,6 +112,7 @@ type MyTimeChart struct {
max
,
min
float64
// y轴的最值
lockPoints
sync
.
RWMutex
// 保护dataSet的并发读写
color
[]
lipgloss
.
Color
middleLine
bool
// 是否添加中间线
}
// New 新建图表,color的长度如果为1,则图表的颜色为color[0],如果color的长度为heigh,则图表的颜色随高度变化
...
...
@@ -121,6 +127,7 @@ func NewTimeChart(width, height int, vmin, vmax float64, color []lipgloss.Color)
result
.
lockPoints
=
sync
.
RWMutex
{}
result
.
lockPoints
.
Lock
()
result
.
points
=
linkedlist
.
New
[
tchart
.
TimePoint
]()
result
.
middleLine
=
false
now
:=
time
.
Now
()
// 用最小值填充空白点
t
:=
result
.
width
*
2
+
1
...
...
@@ -284,13 +291,11 @@ func (m *MyTimeChart) Update(inputMsg tea.Msg) (tea.Model, tea.Cmd) {
return
m
,
nil
}
func
(
m
*
MyTimeChart
)
ViewWithColor
(
color
[]
lipgloss
.
Color
)
string
{
func
(
m
*
MyTimeChart
)
genLines
()
[]
string
{
str
:=
m
.
zM
.
Scan
(
m
.
chart
.
View
())
sb
:=
strings
.
Builder
{}
sb
.
WriteString
(
str
[
m
.
width
+
1
:
])
str
=
sb
.
String
()
// return str
lines
:=
strings
.
Split
(
strings
.
Trim
(
str
,
"
\n
"
),
"
\n
"
)
runes
:=
make
([][]
rune
,
len
(
lines
))
for
k
,
v
:=
range
lines
{
...
...
@@ -371,6 +376,12 @@ func (m *MyTimeChart) ViewWithColor(color []lipgloss.Color) string {
for
k
,
v
:=
range
runes
{
resultLines
[
k
]
=
string
(
v
)
}
return
resultLines
}
func
(
m
*
MyTimeChart
)
ViewWithColor
(
color
[]
lipgloss
.
Color
)
string
{
resultLines
:=
m
.
genLines
()
height
:=
len
(
resultLines
)
style
:=
lipgloss
.
NewStyle
()
if
len
(
color
)
==
height
{
for
k
,
v
:=
range
resultLines
{
...
...
@@ -381,103 +392,83 @@ func (m *MyTimeChart) ViewWithColor(color []lipgloss.Color) string {
}
func
(
m
*
MyTimeChart
)
View
()
string
{
str
:=
m
.
zM
.
Scan
(
m
.
chart
.
View
())
sb
:=
strings
.
Builder
{}
sb
.
WriteString
(
str
[
m
.
width
+
1
:
])
str
=
sb
.
String
()
// return str
lines
:=
strings
.
Split
(
strings
.
Trim
(
str
,
"
\n
"
),
"
\n
"
)
runes
:=
make
([][]
rune
,
len
(
lines
))
for
k
,
v
:=
range
lines
{
runes
[
k
]
=
[]
rune
(
v
)
}
width
:=
lipgloss
.
Width
(
str
)
height
:=
lipgloss
.
Height
(
str
)
for
col
:=
range
width
{
firstLine
:=
-
1
var
charStat
utils
.
CharType
=
utils
.
CharEmpty
for
line
:=
range
height
{
c
:=
runes
[
line
][
col
]
charType
:=
utils
.
GetCharType
(
c
)
if
charType
!=
utils
.
CharEmpty
&&
firstLine
==
-
1
{
firstLine
=
line
}
if
firstLine
==
-
1
{
continue
}
if
firstLine
==
line
{
// 遇到了一列的第一个非空字符
if
t
,
have
:=
utils
.
MM
[
c
];
have
{
runes
[
line
][
col
]
=
t
}
charStat
=
utils
.
CharTypeOr
(
charStat
,
utils
.
GetCharType
(
runes
[
line
][
col
]))
}
else
{
// 第一个非空字符下面的字符
switch
charType
{
case
utils
.
CharEmpty
:
switch
charStat
{
case
utils
.
CharLeft
:
runes
[
line
][
col
]
=
utils
.
LeftFullMW
case
utils
.
CharRight
:
runes
[
line
][
col
]
=
utils
.
RightFullMW
case
utils
.
CharFull
:
runes
[
line
][
col
]
=
utils
.
FullMW
}
if
t
,
have
:=
utils
.
MM
[
runes
[
line
][
col
]];
have
{
runes
[
line
][
col
]
=
t
}
case
utils
.
CharLeft
:
switch
utils
.
CharTypeOr
(
charStat
,
utils
.
CharLeft
)
{
case
utils
.
CharLeft
:
runes
[
line
][
col
]
=
utils
.
LeftFullMW
case
utils
.
CharRight
:
runes
[
line
][
col
]
=
utils
.
RightFullMW
case
utils
.
CharFull
:
runes
[
line
][
col
]
=
utils
.
FullMW
}
charStat
=
utils
.
CharTypeOr
(
charStat
,
utils
.
CharLeft
)
if
t
,
have
:=
utils
.
MM
[
runes
[
line
][
col
]];
have
{
runes
[
line
][
col
]
=
t
}
case
utils
.
CharRight
:
switch
utils
.
CharTypeOr
(
charStat
,
utils
.
CharRight
)
{
case
utils
.
CharLeft
:
runes
[
line
][
col
]
=
utils
.
LeftFullMW
case
utils
.
CharRight
:
runes
[
line
][
col
]
=
utils
.
RightFullMW
case
utils
.
CharFull
:
runes
[
line
][
col
]
=
utils
.
FullMW
}
charStat
=
utils
.
CharTypeOr
(
charStat
,
utils
.
CharRight
)
if
t
,
have
:=
utils
.
MM
[
runes
[
line
][
col
]];
have
{
runes
[
line
][
col
]
=
t
}
case
utils
.
CharFull
:
charStat
=
utils
.
CharFull
if
t
,
have
:=
utils
.
MM
[
runes
[
line
][
col
]];
have
{
runes
[
line
][
col
]
=
t
}
}
}
resultLines
:=
m
.
genLines
()
height
:=
len
(
resultLines
)
switch
len
(
m
.
color
)
{
case
1
:
style
:=
lipgloss
.
NewStyle
()
.
Foreground
(
m
.
color
[
0
])
return
style
.
Render
(
strings
.
Join
(
resultLines
,
"
\n
"
))
case
height
:
style
:=
lipgloss
.
NewStyle
()
for
k
,
v
:=
range
resultLines
{
resultLines
[
k
]
=
style
.
Foreground
(
m
.
color
[
k
])
.
Render
(
v
)
}
return
strings
.
Join
(
resultLines
,
"
\n
"
)
default
:
return
strings
.
Join
(
resultLines
,
"
\n
"
)
}
resultLines
:=
make
([]
string
,
height
)
for
k
,
v
:=
range
runes
{
resultLines
[
k
]
=
string
(
v
)
}
// ViewWithMiddleLine
func
(
m
*
MyTimeChart
)
ViewWithMiddleLine
()
string
{
resultLines
:=
m
.
genLines
()
height
:=
len
(
resultLines
)
targetLine
:=
0
isMiddle
:=
false
if
height
%
2
==
0
{
targetLine
=
height
/
2
-
1
}
else
{
targetLine
=
height
/
2
isMiddle
=
true
}
switch
len
(
m
.
color
)
{
case
1
:
style
:=
lipgloss
.
NewStyle
()
.
Foreground
(
m
.
color
[
0
])
return
style
.
Render
(
strings
.
Join
(
resultLines
,
"
\n
"
))
for
k
,
v
:=
range
resultLines
{
if
k
==
targetLine
{
if
isMiddle
{
resultLines
[
k
]
=
style
.
Strikethrough
(
true
)
.
Render
(
v
)
style
.
Strikethrough
(
false
)
}
else
{
resultLines
[
k
]
=
style
.
Underline
(
true
)
.
Render
(
v
)
style
.
Underline
(
false
)
}
}
else
{
resultLines
[
k
]
=
style
.
Render
(
v
)
}
}
return
strings
.
Join
(
resultLines
,
"
\n
"
)
case
height
:
style
:=
lipgloss
.
NewStyle
()
for
k
,
v
:=
range
resultLines
{
resultLines
[
k
]
=
style
.
Foreground
(
m
.
color
[
k
])
.
Render
(
v
)
if
k
==
targetLine
{
if
isMiddle
{
resultLines
[
k
]
=
style
.
Foreground
(
m
.
color
[
k
])
.
Strikethrough
(
true
)
.
Render
(
v
)
style
.
Strikethrough
(
false
)
}
else
{
resultLines
[
k
]
=
style
.
Foreground
(
m
.
color
[
k
])
.
Underline
(
true
)
.
Render
(
v
)
style
.
Underline
(
false
)
}
}
else
{
resultLines
[
k
]
=
style
.
Foreground
(
m
.
color
[
k
])
.
Render
(
v
)
}
}
return
strings
.
Join
(
resultLines
,
"
\n
"
)
default
:
style
:=
lipgloss
.
NewStyle
()
for
k
,
v
:=
range
resultLines
{
if
k
==
targetLine
{
if
isMiddle
{
resultLines
[
k
]
=
style
.
Strikethrough
(
true
)
.
Render
(
v
)
style
.
Strikethrough
(
false
)
}
else
{
resultLines
[
k
]
=
style
.
Underline
(
true
)
.
Render
(
v
)
style
.
Underline
(
false
)
}
}
else
{
resultLines
[
k
]
=
style
.
Render
(
v
)
}
}
return
strings
.
Join
(
resultLines
,
"
\n
"
)
}
}
cmd/hytop/tui/tui_test.go
View file @
0b52579d
...
...
@@ -110,10 +110,27 @@ func TestNewProcessEnv(t *testing.T) {
t
.
Log
(
pe
.
View
())
}
func
TestModelPower
(
t
*
testing
.
T
)
{
a
:=
`hello world
time is comd`
b
:=
"ok"
s
:=
StackPosition
(
b
,
a
,
lipgloss
.
Top
,
lipgloss
.
Left
)
t
.
Log
(
"
\n
"
+
s
)
func
TestTimeChartLine
(
t
*
testing
.
T
)
{
chart1
:=
NewTimeChart
(
100
,
20
,
0
,
100
,
nil
)
// chart2 := NewTimeChart(100, 20, 0, 100, nil)
now
:=
time
.
Now
()
points
:=
make
([]
tchart
.
TimePoint
,
0
,
200
)
for
i
:=
range
200
{
points
=
append
(
points
,
tchart
.
TimePoint
{
Time
:
now
.
Add
(
time
.
Duration
(
-
i
)
*
time
.
Second
),
Value
:
rand
.
Float64
()
*
100
})
}
style1
:=
lipgloss
.
NewStyle
()
.
Underline
(
true
)
style2
:=
lipgloss
.
NewStyle
()
.
Strikethrough
(
true
)
chart1
.
PutPoint
(
points
)
str
:=
chart1
.
View
()
lines
:=
strings
.
Split
(
str
,
"
\n
"
)
h
:=
lipgloss
.
Height
(
str
)
if
h
%
2
==
0
{
lines
[
h
/
2
-
1
]
=
style1
.
Render
(
lines
[
h
/
2
-
1
])
t
.
Log
(
lines
[
h
/
2
-
1
])
}
else
{
lines
[
h
/
2
]
=
style2
.
Render
(
lines
[
h
/
2
])
t
.
Log
(
lines
[
h
/
2
])
}
str
=
strings
.
Join
(
lines
,
"
\n
"
)
t
.
Logf
(
"
\n
%s
\n
"
,
str
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment