简介

这是一个利用 Flutter/Dart 编写的,用于显示 Linux 系统状态信息的APP,可以运行在包括Android、iOS、Linux、MacOS、Windows等平台上,兼容从小屏手机到大屏幕电视等各种尺寸的展示,支持局域网自动发现服务,也支持通过网络远程使用。

开发动机

  1. 组了台洋垃圾E5主机,配的是一颗10核20线程的CPU,所以想做一个可以随时获得数框框快乐的工具
  2. 钟爱的 deepin 系统升级v23版本后系统环境改动很大,原本使用的dock栏网速插件和悬浮窗插件失效了,所以打算做一个替代品
  3. 除了日常使用 Linux 桌面,手里还有一些运行 Linux 系统的云服务器、虚拟机和机顶盒等设备,所以想要做一个无关发行版和桌面环境,通用的系统监视工具
  4. 家里一堆淘汰的旧手机想要利用起来,并且用旧手机做系统状态监控可以在不占用一丁点的电脑屏幕空间情况下显示足够丰富的信息

特性

  • 支持Android直接下载安装APP,iOS、Linux、MacOS、Windows等可以无修改直接支持
  • 多CPU架构支持(X64、ARM64)
  • 适配各种屏幕尺寸的显示、支持横竖屏切换
  • 适配多核心的自适应布局显示,最多可同时显示105个核心的状态
  • 安装脚本支持通过网络一键下载安装配置
  • 支持局域网自动发现服务
  • 支持通过网络远程使用
  • 支持设备上线自动连接
  • 优化网络数据传输量
  • 低内存占用及CPU资源占用
  • 进程合并显示优化、wine应用\玲珑应用进程信息优化
  • 支持桌面应用进程显示窗口标题
  • 可以显示的信息有:
    • CPU频率、总占用率、以及每个核心的频率、占用率
    • 内存信息及虚拟内存信息
    • 当前网络上行、下行速度及总发送、总接收
    • CPU占用最高的进程列表及占用率
    • 内存占用最高的进程列表及内存使用量

客户端使用说明

启动

客户端打开后界面如下:
startup
左侧列表显示了在局域网中自动发现的服务器,并显示了其用户名、主机名、地址端口和开机时间信息。
点击服务器条目可直接进入监控页面,点击“记住并连接”按钮,则会先记住该服务器,再进入监控页面。

添加服务器

如果由于各种原因无法自动发现局域网内的服务器,或者想要通过互联网连接远程服务器,则可以点击右下角的添加按钮。
add_server
在弹出的对话框中输入服务器的地址、端口及名称(可选),如果能够连接成功,则会添加该服务器并关闭对话框;否则将显示错误信息,请确认服务器正确运行、可以通过网络访问且输入的信息无误。

编辑已保存的服务器

edit_server
点击右上角的“编辑”按钮即可修改已保存的服务器。
对于局域网内自动发现的服务器,只能对其进行删除操作;
对于手动添加的远程服务器,可以点击修改,在弹出的对话框对其配置信息进行修改。

监控页面(横屏)

monitor_landscape
进入监控页面后,应用自动进入全屏模式,并且屏幕将保持常亮

## 监控页面(竖屏)

monitor_portrait
横竖屏切换时,监控页面将自动修改布局以适应屏幕尺寸的变化。
点击监控页面任意位置将显示如上右图所示的按钮,点击即可退出监控页面回到首页。

提示:可以在首页的已保存服务器中将常用服务器的“自动连接”选项打开,这样当应用检测到服务器在线时会自动进入监控页面。
当服务器关机或离线时,应用会自动退出监控模式,结合上面的自动连接功能,可以放一台旧手机在电脑旁,设置不自动锁屏并一直连接电源适配器(注意电池鼓包风险),即可实现电脑开机自动监控,电脑关机自动退出,完全无需手动操作~

安装方法

请在 Linux 系统的桌面系统或服务器上,以 非root 权限执行如下命令:

1
bash -c "$(curl -fsSL https://www.debuggerx.com/raw_assets/scripts/rsm_install.sh)"

根据提示依次输入:

  1. sudo密码
  2. 服务监听的端口号(1024-65535)
  3. 服务的安装模式(1.桌面模式 2.服务器模式)

完整截图如下:
rsm_install

注意:

  1. 安装完成后会在当前终端中运行服务端程序,此时就可以直接使用客户端进行连接;手动关闭程序或退出终端后服务端不会自动重启,只会在下次系统启动或进入桌面时自动运行
  2. UOS系统中安装过程中可能会弹窗询问是否允许添加启动项,请务必选择允许

开发过程分享

感谢勇哥的技术分享

在确定了需求和大致设计思路之后,我首先尝试搜索了Flutter/Dart的开发社区中已有的“轮子”。得益于这些年Flutter社区生态的蓬勃发展,其实现在也已经有不少用于在Linux环境获取系统信息的库,例如:

但在试验后,发现这些库都还不能完全满足我的需要,又查了一些资料和分享,也没能找到质量特别高且适合我的。
这时我忽然回忆起,好像曾看到过deepin的前CTO勇哥分享过几篇有关deepin开发的技术分享,其中似乎有一篇就是讲如何实现deepin里那个酷炫的系统监视器的,于是赶紧找来学习,一看之下大喜过望:

深度系统监视器原理剖析

勇哥的这篇文章清晰易懂,娓娓道来,不仅介绍了在Linux系统中获取系统信息的方式,也详细解释了每个文件和数据的含义和解析计算方法,关键地方甚至还贴心地准备了示例代码,同时在实现deepin的系统监视器过程中碰到的问题和总结的技巧也毫无保留的分享了出来。早先也拜读过这篇文章,但当时是没有目的性的泛读,所以还不能完全体会到其价值;如今在有明确的目标和问题的情况下阅读,结合了与其他搜索到的文章资料的对比,才深刻感受到勇哥作为技术人的追求和务实、作为社区负责人的认真与严谨、以及作为开源导师的无私和热情。

再次勉励自己,向勇哥学习!
贴上勇哥的blog地址,希望看到本文的,对技术有追求的朋友可以看看:

ManateeLazyCat - 「生活可以更简单, 欢迎来到我的开源世界」

关于进程信息的合并

deepin的系统监视器有一个“问题”,用户经常会看到很多重复的应用或进程,尤其是使用浏览器的时候:

其实这也算不上真正的问题,只是因为很多程序都是多进程设计,尤其是浏览器,本身就有多个进程,运行插件又要多几个进程,然后每多开标签页又都是新开进程,所以就会看到很多的“重复”。

但是或许这对于很多人来说并不是预期要想的结果,很多时候我们并不关系程序的每个子进程的信息,而是希望知道整个应用占用了多少的内存或CPU。所以我专门对进程信息做了合并处理,具体思路是:

  1. 我们可以通过 ps -e -o ppid,pid,command 命令,获取所有进程的父进程ID、进程ID、已经完整命令行
  2. 观察输出的信息,我们可以发现大致两种情况
    1. 一种是真正的主程序进程和子进程关系,例如chrome,所有子进程的占用可以合并到进程7055上:
      chrome
    2. 另一种则类似init进程及所有子进程,Linux下所有的进程都会是init进程的直接或间接子进程,这些子进程显然不能合并在一起:
      init
  3. 观察上面两种情况,总结规律,我得出的判断依据是:在某个进程的所有直接子进程中,是否存在子进程和父进程的命令相同的情况(排除参数),如果符合这个条件,就将子进程的内存及CPU占用累加后加到父进程身上,并依此递归处理,直到处理完系统中的所有进程,最终得到的就是一个相对合理的、重复较少的进程信息列表
  4. 实际操作发现,还有一些情况是上面的判断覆盖不到的,比如原生的飞书这个应用,它的子进程中存在 '/proc/self/exe' 这种cmd:
    feishu
    参考 Which process is /proc/self/ for?/proc/self/exe 指的就是当前进程的命令行的意思,所以这种情况也应该算进去。
  5. 最后一种我碰到的特殊情况是,有些wine应用的cmd中包含空格,从而导致判断出错,也需要特殊处理

关于服务发现

为了实现手机自动发现和连接上服务程序,我使用了 Avahi 这个用于服务注册和发现的工具。一个小知识是,树莓派的系统默认安装并启用了这个服务,所以可以通过访问 [树莓派的主机名].local 这个地址来访问连接局域网内的树莓派,而不用手动输入IP。
另外,如果安装了 avahi-utils 工具,可以尝试运行 avahi-browse -a ,这个命令,然后就可以看到局域网里各种注册的服务,你可能会发现Windows的共享网络、可能会发现路由器的信息,甚至是手机和其他家里连接了网络的物联网设备注册的服务,很有意思~

前段时间有个国外的软件很火,叫做 localsend: 苹果手机和安卓手机互传文件,LocalSend实测好用!。因为它也是用Flutter写的跨平台工具,而且也实现了局域网内的互相发现,所以好奇看了下它的实现代码,结果找到这里:/lib/provider/network/nearby_devices_provider.dart#L78-L90

1
2
3
4
5
6
7
8
9
10
11
12
13
Stream<Device> _getStream(String networkInterface, int port, bool https, String fingerprint) {
final ipList = List.generate(256, (i) => '${networkInterface.split('.').take(3).join('.')}.$i').where((ip) => ip != networkInterface).toList();
_runners[networkInterface]?.stop();
_runners[networkInterface] = TaskRunner<Device?>(
initialTasks: List.generate(
ipList.length,
(index) => () async => _doRequest(ipList[index], port, https, fingerprint),
),
concurrency: 50,
);

return _runners[networkInterface]!.stream.where((device) => device != null).cast<Device>();
}

简单来说,这个工具发现局域网内的方法是,利用当前设备的ip的前三位,暴力尝试连接从xxx.xxx.xxx.0xxx.xxx.xxx.255整个网段的所有IP,返回响应的就是可连接设备😹
据很多人测试,这个方法出奇的好用,尤其是某些网络下管理员会限制设备注册发现服务,用这个方法就可以了。
所以说有的时候,优雅的做法可能还不如简单粗暴的方式来得效果好,虽然这个方法并不适合我(因为我的服务端并不固定端口号),但是思路也是值得学习吧~

参考了deepin的UI设计

最后感谢deepin的设计和资源。
本人资深伪全栈,从需求设计到前后端开发,再到项目发布部署运维,多多少少也都能做个像那么回事,唯独美术水平一直不忍直视。所以本工具在很多地方借鉴参考了deepin的UI设计,包括不限于设计指南(UI视觉规范、设计基础知识),主题配色,组件样式,甚至app的应用图标也是直接拿系统监视器的图标来用的,可以说虽然这是个通用为目的的工具,但是一切还是优先以deepin为主,希望大家喜欢。