我正在参与「启航方案」
介绍
本来呢最近是在学Rust,趁便看看Tauri相关的内容.然后刷评论区忽然看到有人说到go生态中也有类似的框架—Wails,所以下午花了点时间来着手玩一下.
首要看一下最终的运转作用,前端样式懒得调整所以界面很丑仅仅完结一下功能
开端
这次的目标便是做一个功能类似于nvidia-smi
的桌面东西,别的整合一下cpu的运用情况.阐明一下所运用到的相关库,关于CPU资源运用gopsutil来得到,GPU相关信息则是运用go-nvml(现在仅仅支持linux,原本想运用wails跨平台编译一个windows exe成果不支持就此作罢);前端界面除了wails自身的vue模板,可视化运用vue3-apexcharts.
大致思路:
- 完结后端逻辑,得到相关数据
- 完结前端界面
- 前后端交互完结动态数据更新
1 后端逻辑
根据相关库的介绍,咱们能够简略完结相应数据的获取,这儿拿GPU中计算运用率为例
首要咱们需求一个全局的nvml device,因为主机只要一张显卡所以没有考虑运用count循环而是直接指定index(0)作为device.
ret = nvml.Init()
if ret != nvml.SUCCESS {
log.Fatalf("init failed %v", nvml.ErrorString(ret))
}
device, ret = nvml.DeviceGetHandleByIndex(0)
这儿没有defer nvml.shutdown()
因为是一向更新,所以当函数退出可是goroutine依旧在运转,后期就无法得到device犯错.
再来考虑数据方面,因为我想调查usage的变化,因而得规划一个类数组容器来记载每次得到的值,一起为了保证不会无限增加所以得设置最大长度.一开端考虑用数组或者切片,可是这无可避免会导致后期继续更新数据的时分每次都需求copy,无端消耗性能与内存空间.所以想到用循环行列,这样只需求更新队首与队尾,而且占用内存并不会产生增加.
type CircularQueue struct {
slice []uint32
front int
size int
count int
}
func newCircularQueue(maxSize int) *CircularQueue {
return &CircularQueue{
slice: make([]uint32, maxSize),
front: 0,
size: maxSize,
count: 0,
}
}
func (cq *CircularQueue) enqueue(element uint32) {
if cq.count == cq.size {
cq.front = (cq.front + 1) % cq.size
cq.count--
}
rear := (cq.front + cq.count) % cq.size
cq.slice[rear] = element
cq.count++
}
func (cq *CircularQueue) getSlice() []uint32 {
if cq.count == 0 {
return nil
}
slice := make([]uint32, cq.count)
for i := 0; i < cq.count; i++ {
slice[i] = cq.slice[(cq.front+i)%cq.size]
}
return slice
}
经过循环行列,咱们每次请求元素并将元素入队,最终回来只需求运用getSlice()
方法回来行列中的数据即可.具体得到GPU Usage数据的代码如下
const MAXSIZE = 10
var GpuUsagecq = newCircularQueue(MAXSIZE)
func (a *App) GetGpuUsage() []uint32 {
rwmutex.RLock()
utilization, ret := device.GetUtilizationRates()
rwmutex.RUnlock()
if ret != nvml.SUCCESS {
log.Fatalf("Unable to get utilization of device at index %d: %v", 0, nvml.ErrorString(ret))
}
GpuUsagecq.enqueue(utilization.Gpu)
return GpuUsagecq.getSlice()
}
这儿为了保证并发安全还是用了一下读锁,不过感觉读操作的话用不用应该问题不大.这儿运用device.XXX()
就能够得到GPU当前的各种信息,具体能够去看api文档.依照相似的逻辑,就能够得到咱们所需求的一切信息数据.
2 前端界面
这儿咱们运用vue3-apexcharts,不过因为我的前端技能很菜,对于vue3也仅仅大致了解过,因而这部分我也不太好具体简介,更多是对着官网中的demo修正.在frontend/src/components
下创立一个组件Monitor,然后写一下template
<template>
<div>
<div class="chart-row">
<apexchart
v-for="(chartOptions, index) in areaChartOptions"
:key="index"
type="area"
:options="chartOptions"
:series="areaChartSeries[getChartId(index)]"
class="chart-column"
/>
</div>
<div class="chart-row">
<apexchart type="donut" :options="DonutOptions" :series="donutSeries" />
<apexchart type="radialBar" :options="radialOptions" :series="radialSeries" />
</div>
</div>
</template>
在下面data()中设置好options和series初始值,运转wails dev
就能看到一个静态的页面.
3 前后端交互
这部分我感觉wails文档中写的并不好,没有任何很具体的例子指出运转时的前后端数据怎么交互.参阅官网介绍中唯一能够参阅的Events事情配合怎么工作,自己渐渐体会写出来.
首要来看后端部分,第一部分中咱们完结了一切数据的获取,这儿咱们只需求让数据获取继续运转(改写距离自定义)而且将数据传给前端.
go func() {
for {
runtime.EventsEmit(a.ctx, "GetCpuUsage", a.GetCpuUsage())
runtime.EventsEmit(a.ctx, "GetGpuMem", a.GetGpuMem())
runtime.EventsEmit(a.ctx, "GetGpuUsage", a.GetGpuUsage())
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
for {
runtime.EventsEmit(a.ctx, "GetFans", a.GetFans())
runtime.EventsEmit(a.ctx, "GetTemperature", a.GetTemperature())
time.Sleep(3 * time.Second)
}
}()
我期望关于usage以及memory的信息改写更及时,而温度和风扇转速这些貌似不太重要的信息能够慢一点.然后经过EventsEmit
将数据与事情传递给前端,而前端设置一下EventsOn
事情监听,完结数据接收并完结前端界面数据更新.
EventsOn("GetGpuUsage",GetGpuUsage=>{
if(GetGpuUsage){
this.areaChartSeries['area-chart-1'][0].data=GetGpuUsage
}
})
这样就最终完结了咱们一开端的界面内容.
最后
这一次尝试算是Tauri之前的一次小玩具,花了两个个小时从0学习wails以及一些前端库,最终也算拼凑出了最初规划的功能.wails最终打包出来的可执行文件巨细仅仅只要2.8M,比起一些Electron打包出来的东西来说小了不止一点.
别的咱们能够在main.go
中添加webview运用硬件加速,这样就能够让GPU usage不再一向是0%
Linux: &linux.Options{
WebviewGpuPolicy: linux.WebviewGpuPolicyAlways,
},
最后放一张与watch -n0.1 nvidia-smi
比照的图片作为结束