Skip to content

Commit

Permalink
feat: hardware monitor (#751)
Browse files Browse the repository at this point in the history
  • Loading branch information
SAKURA-CAT authored Dec 15, 2024
1 parent 8ff44b9 commit 80ae5ad
Show file tree
Hide file tree
Showing 21 changed files with 1,027 additions and 160 deletions.
96 changes: 92 additions & 4 deletions docs/硬件信息采集.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,98 @@ class MonitorCron:
简单来讲,所有采集器需要返回两部分内容,一部分是硬件整体信息,一部分是对应的硬件信息采集函数列表,前者在不同的采集器中不一样,但是后者需要遵循相同的规范:

1. 如果没什么好采集的,返回空列表
2.

如果有需要采集的,列表内的函数签名应该一致——此函数不接受任何参数,返回一个字典,字典类型为[HardwareInfo](/swanlab/data/run/metadata/hardware/type.py)
2. 如果有需要采集的,采集函数应该来自同一个基类,`__call__`魔术方法签名相同

> HardwareInfo字典类型遵循swanlab创建column的协议,包含列名称、图表配置、组配置等信息
TODO
目前swanlab的硬件信息采集主要依赖于`psutil`库。

### CPU

注意:对于Apple系列的芯片的CPU信息,并不在此处采集和记录。

#### CPU Utilization (%)

代表当前cpu的平均利用率。swanlab为它打了一个 `cpu.pct` 标签。

#### CPU Utilization (per core) (%)

代表当前cpu每个核心的利用率。swanlab为它打了一个 `cpu.{cpu_index}` 标签,其中cpu_index代表cpu的核心编号。
所有核心的利用率将自动在一个图表中展示。

#### Process CPU Threads

代表当前进程的CPU线程数。swanlab为它打了一个 `cpu.thds` 标签。

### Memory

注意:对于Apple系列的芯片的内存信息,并不在此处采集和记录。

#### System Memory Utilization (%)

代表当前系统的内存利用率。swanlab为它打了一个 `mem` 标签。

#### Process Memory In Use (non-swap) (MB)

代表当前进程的内存利用率。swanlab为它打了一个 `mem.proc` 标签。

#### Process Memory In Use (non-swap) (%)

代表当前进程的内存利用率。swanlab为它打了一个 `mem.proc.pct` 标签。

#### Process Memory Available (non-swap) (MB)

代表当前进程的可用内存。swanlab为它打了一个 `mem.proc.avail` 标签。

### Apple SoC

由于Apple SoC可能需要额外适配,因此这部分的cpu、内存信息以及(未来会加上的)GPU信息需要单独采集。注意,当前swanlab只针对M系列芯片做了硬件信息采集适配,早期intel芯片暂无额外调试,可能会存在问题。
就目前而言,Apple的cpu信息、内存信息与上述的[CPU](#cpu)[Memory](#memory)信息相同,标签也相同(因为同出自`psutil`库)。

### Nvidia GPU

如果pynvml库可以识别到Nvidia GPU,swanlab还会采集Nvidia GPU的对应指标,他们的标签类似`gpu.{gpu_index}...`
同指标不同编号的GPU将自动在一个指标图表中展示。

#### GPU Utilization (%)

表示每个GPU的利用率百分比,swanlab为它打了一个 `gpu.{gpu_index}.pct` 标签。

#### GPU Memory Allocated (%)

表示每个GPU的显存利用率百分比,swanlab为它打了一个 `gpu.{gpu_index}.mem.ptc` 标签。

#### GPU Temperature (℃)

表示每个GPU的摄氏温度,swanlab为它打了一个 `gpu.{gpu_index}.temp` 标签。

#### GPU Power Usage (W)

表示每个GPU的功耗,swanlab为它打了一个 `gpu.{gpu_index}.power` 标签。

### Ascend NPU

如果swanlab识别到Ascend NPU,swanlab会采集Ascend NPU的对应指标,他们的标签类似`npu.{npu_index}...`
同指标不同编号的NPU将自动在一个指标图表中展示。
根据Ascend NPU的[官方文档](https://support.huawei.com/enterprise/zh/doc/EDOC1100388864/8c5e18a7)
唯一定位一块计算芯片需要同时知道NPU ID和Chip ID,因此对于Ascend NPU而言,`npu_index = f{npu_id}-{chip_id}`

#### NPU Utilization (%)

表示每个NPU的利用率百分比,swanlab为它打了一个 `npu.{npu_index}.pct` 标签。

#### NPU Memory Allocated (%)

表示每个NPU的HBM利用率百分比,swanlab为它打了一个 `npu.{npu_index}.mem.ptc` 标签。

#### NPU Temperature (℃)

表示每个NPU的摄氏温度,swanlab为它打了一个 `npu.{npu_index}.temp` 标签。

## TODO

在信息采集部分,未来还会上线:

1. 更详细的GPU、NPU信息(利用率、时钟信息等)
2. 更多的硬件信息采集器(如硬盘、网络等)
3. 更多的计算设备支持(如AMD GPU、Google TPU等)
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
swankit==0.1.2b2
swankit==0.1.2b6
swanboard==0.1.7b1
cos-python-sdk-v5
urllib3>=1.26.0
requests>=2.25.0
click
pyyaml
psutil>=5.0.0
gputil==1.4.0
pynvml
rich
35 changes: 25 additions & 10 deletions swanlab/api/upload/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from enum import Enum
from typing import List, Optional

from swankit.callback.models import ColumnClass
from swankit.callback.models import ColumnClass, ColumnConfig

from swanlab.data.modules import MediaBuffer

Expand All @@ -27,31 +27,33 @@ def __init__(
name: Optional[str],
cls: ColumnClass,
typ: str,
config: Optional[ColumnConfig],
section_name: Optional[str],
section_type: Optional[str],
error: dict = None,
):
"""
Args:
key: 键
name: 键的名称
cls: 键的类别
typ: 键的类型
section_name: 键所在的section的名称
section_type: 键所在的section的类型
error: 错误信息
key: 键
name: 键的名称
cls: 键的类别
typ: 键的类型
config: 键的配置
section_name: 键所在的section的名称
section_type: 键所在的section的类型
error: 错误信息
"""
self.key = key
self.name = name
self.cls = cls
self.typ = typ
self.config = config
self.section_name = section_name
self.section_type = section_type
self.error = error

def to_dict(self):
"""
序列化为Dict
序列化为Dict,传递给后端
"""
d = {
"class": self.cls,
Expand All @@ -70,6 +72,19 @@ def to_dict(self):
d.pop("sectionName")
if self.section_type is None:
d.pop("sectionType")
if self.config is None:
return d
# 将额外的图表配置信息加入
if self.config.y_range is not None:
d["yRange"] = self.config.y_range
if self.config.chart_name is not None:
d["chartName"] = self.config.chart_name
if self.config.chart_index is not None:
d["chartIndex"] = self.config.chart_index
if self.config.metric_name is not None:
d["metricName"] = self.config.metric_name
if self.config.metric_color is not None:
d["metricColors"] = self.config.metric_color
return d


Expand Down
1 change: 1 addition & 0 deletions swanlab/data/callback_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ def on_column_create(self, column_info: ColumnInfo):
key=column_info.key,
name=column_info.name,
cls=column_info.cls,
config=column_info.config,
typ=column_info.chart_type.value.column_type,
section_name=section_name,
section_type=column_info.section_type,
Expand Down
64 changes: 38 additions & 26 deletions swanlab/data/run/exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ def __init__(self, settings: SwanLabSharedSettings, operator: SwanLabRunOperator
def __add(
self,
key: str,
key_name: Optional[str],
key_class: ColumnClass,
name: Optional[str],
column_class: ColumnClass,
column_config: Optional[ColumnConfig],
section_type: SectionType,
data: DataWrapper,
step: int = None,
Expand All @@ -48,19 +49,19 @@ def __add(
----------
key : str
key的云端唯一标识
data : DataWrapper
包装后的数据,用于数据解析
key_class : str
key的类型,CUSTOM为自定义key,SYSTEM为系统key
key_name : str
name : str
key的实际名称
column_class : str
列类型,CUSTOM为自定义key,SYSTEM为系统key
section_type : str
key的组类型
data : DataWrapper
包装后的数据,用于数据解析
step : int, optional
步数,如果不传则默认当前步数为'已添加数据数量+1'
在log函数中已经做了处理,此处不需要考虑数值类型等情况
"""
key_index = f"{key_class}-{key}"
key_index = f"{column_class}-{key}"
# 判断tag是否存在,如果不存在则创建tag
key_obj: SwanLabKey = self.keys.get(key_index, None)

Expand All @@ -86,7 +87,15 @@ def __add(
key_obj = SwanLabKey(key, self.settings)
self.keys[key_index] = key_obj
# 新建图表,完成数据格式校验
column_info = key_obj.create_column(key, key_name, key_class, section_type, data, num)
column_info = key_obj.create_column(
key,
name,
column_class,
column_config,
section_type,
data,
num,
)
self.warn_type_error(key_index, key)
# 创建新列,生成回调
self.__operator.on_column_create(column_info)
Expand All @@ -104,32 +113,32 @@ def add(
self,
data: DataWrapper,
key: str,
key_name: str = None,
key_class: ColumnClass = 'CUSTOM',
name: str = None,
column_class: ColumnClass = 'CUSTOM',
column_config: Optional[ColumnConfig] = None,
section_type: SectionType = "PUBLIC",
step: int = None,
column_config: Optional[ColumnConfig] = None,
) -> MetricInfo:
"""记录一条新的key数据
Parameters
----------
data : DataWrapper
包装后的数据,用于数据解析
key : str
key的云端唯一标识
key_name : str
key的实际名称, 默认与key相同
key_class : str, optional
key的类型
列的云端唯一标识
name : str
列的实际名称, 默认与key相同
column_class : str, optional
列的类型
column_config : Optional[ColumnConfig], optional
列的额外配置信息
section_type : str, optional
key的组类型
step : int, optional
步数,如果不传则默认当前步数为'已添加数据数量+1'
在log函数中已经做了处理,此处不需要考虑数值类型等情况
column_config : Optional[ColumnConfig], optional
列的额外配置信息
"""
m = self.__add(key, key_name, key_class, section_type, data, step)
m = self.__add(key, name, column_class, column_config, section_type, data, step)
self.__operator.on_metric_create(m)
return m

Expand Down Expand Up @@ -291,17 +300,19 @@ def add(self, data: DataWrapper) -> MetricInfo:
def create_column(
self,
key: str,
key_name: Optional[str],
key_class: ColumnClass,
name: Optional[str],
column_class: ColumnClass,
column_config: Optional[ColumnConfig],
section_type: SectionType,
data: DataWrapper,
num: int,
) -> ColumnInfo:
"""
创建列信息,对当前key的基本信息做一个记录
:param key: str, key名称
:param key_name: str, key的实际名称
:param key_class: str, key的类型,CUSTOM为自定义key,SYSTEM为系统key
:param name: str, key的实际名称
:param column_class: str, key的类型
:param column_config: ColumnConfig, key的配置
:param section_type: str, key的组类型
:param data: DataType, 数据
:param num: 创建此列之前的列数量
Expand All @@ -320,8 +331,9 @@ def create_column(
column_info = ColumnInfo(
key=key,
kid=str(num),
name=key_name,
cls=key_class,
name=name,
cls=column_class,
config=column_config,
chart_type=result.chart,
section_name=result.section,
section_type=section_type,
Expand Down
20 changes: 17 additions & 3 deletions swanlab/data/run/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,17 @@ class MonitorCron:
用于定时采集系统信息
"""

SLEEP_TIME = 30

def __init__(self, monitor_func: Callable):
self.count = 0 # 计数器,执行次数

def _():
monitor_func()
self.timer = threading.Timer(self.SLEEP_TIME, _)
self.count += 1
self.timer = threading.Timer(self.sleep_time, _)
self.timer.daemon = True
self.timer.start()

# 立即执行
self.timer = threading.Timer(0, _)
self.timer.daemon = True
self.timer.start()
Expand All @@ -143,6 +145,18 @@ def cancel(self):
if self.timer is not None:
self.timer.cancel()

@property
def sleep_time(self):
# 采集10次以下,每次间隔10秒
# 采集10次到50次,每次间隔30秒
# 采集50次以上,每次间隔60秒
if self.count < 10:
return 10
elif self.count < 50:
return 30
else:
return 60


def check_log_level(log_level: Optional[str]) -> str:
"""检查日志等级是否合法"""
Expand Down
Loading

0 comments on commit 80ae5ad

Please sign in to comment.