Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: hardware monitor #751

Merged
merged 21 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading