计算机性能监控体系:Quark2.0

时间:2024-11-05 17:01:32

一、背景

在过去的IT日常支持场景中,因为服务的用户、终端、系统等等因业务而异,往往会遇到以下类似这些问题或需求:

  • IT工程师定位终端问题跨越不同的平台或系统,低效繁琐
  • 用户想要获取一些个人相关的IT环境信息,只能咨询IT部门
  • 电脑终端的软硬件资源类性能消耗无法集中宏观监控、数字化管理
  • 主动预判终端问题的客观依据和快速定位能力有差异化

通过建设一套“IT用户终端信息一体化管理平台”,采集用户信息、机器信息、软件信息、权限信息、网络信息等等,实现数据入库、实时更新、可视化。具备提供一站式查询管理、缩短case定位时间、集中数字化管理、提前预警终端软硬件风险等能力。

其中针对电脑及性能数据的采集,面临着指标多样性、数据实时性存储、系统差异性等高要求,下面针对电脑终端信息采集的Quark 2.0体系进行详细的介绍。

二、架构

Quark 2.0体系是为“IT用户终端信息一体化管理平台”建立的一套计算机性能监控体系,作为IT资产管理的延伸,帮助IT人员完成日常维护、故障排查和资源统计等工作。

该体系架构示意图如下:

安装在办公电脑的Agent按单位时间一次的心跳频率,采集计算机的CPU、内存、磁盘、网络、电池、进程占用等信息上报给Master集群,并把历史数据打点存入InfluxDB。支持Windows和MacOS两种主流的办公电脑操作系统。Master集群接收到Agent上报的心跳数据,将其存入Redis,并通过Etcd进行节点注册和健康检测。Registry集群作为IT一体化自助查询平台的后端服务器集群,承担了多个数据来源的集中查询、日志记录、任务调度等功能。

Agent客户端支持采集的数据类型如下:

心跳上报:实时获取最新数据,新数据会覆盖旧数据
数据打点:存储最新数据,保留旧数据

类型

指标

采集方式

备注

系统

操作系统

心跳上报

Windows/MacOS,包括具体版本号

计算机名

心跳上报

内核架构

心跳上报

例:x86_64

硬件

序列号

心跳上报

制造厂商

心跳上报

产品型号

心跳上报

CPU

型号

心跳上报

厂商

心跳上报

核数

心跳上报

占用核数

数据打点

频率

心跳上报

使用率

数据打点

user、sys、iowait、idle、busy

温度

数据打点

内存

容量

心跳上报/数据打点

单位:GiB

使用量

心跳上报/数据打点

单位:GiB

频率

心跳上报

磁盘

总空间

心跳上报/数据打点

单位:GiB

已使用空间

心跳上报/数据打点

单位:GiB

分区

心跳上报/数据打点

单位:GiB

驱动

心跳上报

包括型号、类型、状态

读写字节数

数据打点

单位:GiB

读写速度

数据打点

单位:KiB/s

读写次数

数据打点

网络

网络名

心跳上报

MTU

心跳上报

Mac地址

心跳上报

IP地址

心跳上报

驱动

心跳上报

包括名称、描述、厂商、产品号

上下行字节数

数据打点

单位:GiB

上下行数据包总数

数据打点

上下行速度

数据打点

单位:KiB/s

电池

状态

心跳上报/数据打点

状态码

心跳上报/数据打点

例:有无电池、使用电池/AC电源、是否满电

剩余电量

心跳上报/数据打点

剩余使用时间

心跳上报/数据打点

部分系统版本不支持此指标

进程

占用CPU进程

数据打点

记录前五个

占用内存进程

数据打点

占用IO进程

数据打点

WiFi

ESSID

数据打点

BSSID

数据打点

信号强度

数据打点

联网情况

ping内网

数据打点

min_rtt、max_rtt、avg_rtt、std_dev_rtt、loss

ping外网

数据打点

ping网关

数据打点

DNS解析

数据打点

是否能解析特定域名

三、数据采集

Agent客户端基于Go语言开发,通过gopsutil库、wmic等采集计算机数据。对于MacOS,则以解析ioreg、system_profiler等指令获取对应指标。由于采集指标众多,下面仅以电池和WiFi作为示例,讲解具体的数据采集原理。

电池信息

对于Windows,通过wmic指令调用Win32_Batteryapi,获取电脑的状态、状态码、剩余电量等信息。

//go:build windows

// +build windows

package battery

import (

       "errors"

       "math"

       "git.ppd.com/quark/pkg/logger"

       "git.ppd.com/quark/pkg/metrics"

       "github.com/shopspring/decimal"

       "github.com/yusufpapurcu/wmi"

)

type batteryInfo struct {

       Availability             aStatus

       BatteryStatus            bStatus

       Status                   string

       EstimatedChargeRemaining uint16

       EstimatedRunTime         uint32

       DesignCapacity           uint32

       FullChargeCapacity       uint32

}

func win32BatteryInfo() (*batteryInfo, error) {

       var batteryInfo []batteryInfo

      err := wmi.Query("SELECT * FROM Win32_Battery", &batteryInfo)

       if err != nil {

              return nil, err

       }

       if len(batteryInfo) > 0 {

              return &batteryInfo[0], nil

       }

       return nil, errors.New("empty battery info")

}

func BatteryInfo() metrics.BatteryInfo {

       battery, err := win32BatteryInfo()

       if err != nil {

              logger.Errorf("get BatteryInfo error: %s", err.Error())

              return metrics.BatteryInfo{Status: "NoBattery"}

       }

       var estimatedRunTime float64

       if battery.EstimatedRunTime == uint32(math.Pow(2, 32)/60) {

              estimatedRunTime = 0

       } else {

              tmpRunTime := decimal.NewFromFloat(float64(battery.EstimatedRunTime) / 60)

              estimatedRunTime, _ = tmpRunTime.Round(1).Float64()

       }

       return metrics.BatteryInfo{

              Status: battery.Status,

              Availability: metrics.BatStatus{

                     Code: uint16(battery.Availability),

                     Desc: battery.Availability.String(),

              },

              BatteryStatus: metrics.BatStatus{

                     Code: uint16(battery.BatteryStatus),

                     Desc: battery.BatteryStatus.String(),

              },

              CurrentCap:     float64(battery.FullChargeCapacity),

              DesignCap:      float64(battery.DesignCapacity),

              EstimatedTime:  estimatedRunTime,

             EstimatedPower: int64(battery.EstimatedChargeRemaining),

       }

}

对于MacOS,则通过解析system_profiler SPPowerDataType和pmset -g batt指令的返回获取电池的对应信息。

WiFi信息

对于Windows,通过解析netsh wlan show interfaces指令的返回获取WiFi的BSSID、ESSID和信号强度。

对于MacOS,则通过析/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I和ioreg -l -n AirPortDriver | perl -lne print $1 if $_ =~ /IO80211BSSID.*<(.*)>/指令获取WiFi的对应信息。

四、心跳上报

QRPC组件

Master集群与Agent客户端之间基于自研的QRPC组件进行长连接通信。与市面上常见的开源RPC组件相比,QRPC组件具有通信简单、连接管理透明、完全可控等优点。

开发者

简述

优点

缺点

GRPC

Google

功能强大,是一款非常完善的rpc框架

功能强大完善;多语言支持;序列化效率高

学习、维护的成本较高;引入大量第三方库,存在潜在风险

net/rpc(标准库)

GO Team

内置RPC包

标准库内置,无需引入新库

TCP网络连接管理缺失;序列化(GO内置)效率较低

QRPC

自研

用于Master集群与Agent客户端的通信

轻量化框架;通信简单;连接管理透明

适用性较窄

通信过程

首次心跳上报时,Agent需要先从Etcd中获取一个可用的Master节点,并采用Hash算法保证Master集群的负载均衡。获取到Master节点之后,建立长连接并写入Agent配置。Master节点接收到连接请求之后完成握手连接,并将心跳数据存储到Redis中,完成本次通信。如果发生意外导致连接中断或Master节点挂掉,Agent会自动重新获取新的可用节点。

获取Master节点实现如下:

import (

       "encoding/json"

       "fmt"

       "hash/fnv"

       "git.ppd.com/quark/agent_v2/config"

       "git.ppd.com/quark/agent_v2/stats"

       "git.ppd.com/quark/pkg/client/etcd"

       "git.ppd.com/quark/pkg/logger"

       "git.ppd.com/quark/pkg/metrics"

)

func (a *Agent) getMasterIP() (string, error) {

       // 获取本机IP

       localIP := stats.IP()

       if localIP == "" {

              return "", fmt.Errorf("get local_ip failed: local_ip is <nil>")

       }

       // 获取所有可用的Master节点

       // 要求:当前节点负载低于预设的最大负载

       var assignAddrs []string

       maxLoad := config.Config().MasterConfig.MaxLoad

       masterInfo := registryMasterNode()

       for _, v := range masterInfo {

              if v.ReportNum < maxLoad {

                     assignAddrs = append(assignAddrs, v.IP)

              }

       }

       // Hash算法获取Master节点

       hashValue := hash(localIP)

       index := int(hashValue) % len(assignAddrs)

       masterNode := assignAddrs[index]

       return masterNode, nil

}

// 获取已注册的Master节点

func registryMasterNode() map[string]*metrics.MasterHeartBeat {

       tmp := make(map[string]*metrics.MasterHeartBeat, 0)

       // etcd初始化

       cfg := config.Config().EtcdConfig

       etcd.Init(cfg.EtcdAddr)

       defer func() {

              if err := etcd.EClient.Close(); err != nil {

                     logger.Errorf("close etcd client failed: %s", err.Error())

              }

       }()

      resp, err := etcd.EClient.Get(cfg.MasterPath, clientv3.WithPrefix())

       if err != nil {

              logger.Errorf("从etcd获取master节点列表失败: %s", err.Error())

              return tmp

       }

       for k, v := range resp.Kvs {

              master := &metrics.MasterHeartBeat{}

              err := json.Unmarshal(v.Value, &master)

              if err != nil {

                     logger.Errorf("master节点%d序列化失败: %s", k, err.Error())

                     continue

              }

              tmp[master.IP] = master

       }

       return tmp

}

// Hash: string to int

func hash(key string) uint32 {

       h := fnv.New32a()

       h.Write([]byte(key))

       return h.Sum32()

}

Agent在线状态管理

Master获取Agent在线状态,主要是通过Redis的键过期订阅机制。预先设置Agent键的过期时间,每次心跳上报时更新值,同时重置初始时间。超过过期时间仍未更新,则视为Agent已离线。

五、历史数据打点

InfluxDB

InfluxDB是一个用于存储和分析时间序列数据的开源数据库,主要特性如下:

  • 内置HTTP接口,使用方便
  • 数据可以打标记,查询很灵活
  • 类SQL的查询语句
  • 安装管理简单,并且读写数据高效
  • 支持实时查询

Quark 2.0体系基于InfluxDB v1的HTTP POST方式,对Agent采集的历史数据进行打点存储。

InfluxDB-Relay

由于InfluxDB v1开源版不支持集群模式,故采用官方推荐的社区开源高可用方案InfluxDB-Relay。

InfluxDB-Relay为InfluxDB提供双写能力,确保其中一个节点挂掉后数据不会丢失。注意InfluxDB-Relay只代理写流量,查询数据时直接访问InfluxDB。

influxdb-relay.toml 配置如下:

[[http]]

name = "influx-http"

bind-addr = "127.0.0.1:9096"

output = [

    { name="db1", location = "http://XX.XXX.XX.XXX:8086/write" },

    { name="db2", location = "http://XX.XXX.XX.XXX:8086/write" },

    { name="db3", location = "http://XX.XXX.XX.XXX:8086/write" },

]

页面交互

“IT用户终端信息一体化管理平台”支持根据域账号和计算机名查询对应信息,包括用户的基本信息、名下资产和所在群组、办公电脑的IT资产信息、心跳数据和历史数据等。

六、未来展望

在后续的平台建设中,“IT用户终端信息一体化管理平台”还将完善接入更完整的其他用户信息,例如各相关系统权限、入网认证各环节状态、虚拟资产等信息,进行一体化关联,为实现终端智能化管理夯实基础。