feat: follow Agent object plans and daily storage observations
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "pigway-agent",
|
"id": "pigway-agent",
|
||||||
"name": "PIGWay Raspberry Pi Agent",
|
"name": "PIGWay Raspberry Pi Agent",
|
||||||
"version": "1.3.3",
|
"version": "1.4.0",
|
||||||
"protocol_version": 1,
|
"protocol_version": 1,
|
||||||
"entrypoint": "worker.py",
|
"entrypoint": "worker.py",
|
||||||
"runtime": "python3",
|
"runtime": "python3",
|
||||||
@@ -40,6 +40,6 @@
|
|||||||
},
|
},
|
||||||
"legacy_connection_fields": true,
|
"legacy_connection_fields": true,
|
||||||
"collection": {
|
"collection": {
|
||||||
"interval_seconds": 4
|
"interval_seconds": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ export function agentHostCard(entry) {
|
|||||||
diskTotal = y.disk_total_bytes;
|
diskTotal = y.disk_total_bytes;
|
||||||
const percent = n => Number.isFinite(n) ? `${n.toFixed(1)}%` : "--";
|
const percent = n => Number.isFinite(n) ? `${n.toFixed(1)}%` : "--";
|
||||||
const progress = n => Number.isFinite(n) ? meter(n) : "";
|
const progress = n => Number.isFinite(n) ? meter(n) : "";
|
||||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2><span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge good">${tr("在线")}</span></div><div class="host-grid"><div class="metric"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div><div class="metric"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div><div class="metric"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div><div class="metric"><div class="muted">${tr("显示状态")}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}</div></div><div class="metric"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div></div>${pluginSummary(s.plugins || {}, h.id)}<div class="group"><h3>${tr("服务与进程监控")}</h3><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up ? "good" : "bad"}">${tr(w.up ? "正常" : "停止")}</span></div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></article>`;
|
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2><span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge good">${tr("在线")}</span></div><div class="host-grid"><div class="metric" data-refresh-group="system"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric" data-refresh-group="system"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div><div class="metric" data-refresh-group="storage"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div><div class="metric" data-refresh-group="network"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div><div class="metric" data-refresh-group="display"><div class="muted">${tr("显示状态")}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}</div></div><div class="metric" data-refresh-group="alerts"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div></div>${pluginSummary(s.plugins || {}, h.id)}<div class="group"><h3>${tr("服务与进程监控")}</h3><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up ? "good" : "bad"}">${tr(w.up ? "正常" : "停止")}</span></div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></article>`;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -68,6 +68,9 @@ def collect(device):
|
|||||||
value = request(device, '/status')
|
value = request(device, '/status')
|
||||||
if not isinstance(value.get('system'), dict) or not isinstance(value.get('machine'), dict):
|
if not isinstance(value.get('system'), dict) or not isinstance(value.get('machine'), dict):
|
||||||
raise AdapterError('INVALID_RESPONSE')
|
raise AdapterError('INVALID_RESPONSE')
|
||||||
|
declaration = value.get('collection') if isinstance(value.get('collection'),dict) else {}
|
||||||
|
plans = declaration.get('refresh') if isinstance(declaration.get('refresh'),dict) else {}
|
||||||
|
storage_plan = plans.get('storage') if isinstance(plans.get('storage'),dict) else {}
|
||||||
metrics = []
|
metrics = []
|
||||||
units = {'cpu_percent': 'percent', 'memory_percent': 'percent', 'disk_percent': 'percent',
|
units = {'cpu_percent': 'percent', 'memory_percent': 'percent', 'disk_percent': 'percent',
|
||||||
'temperature_c': 'celsius', 'cpu_freq_mhz': 'megahertz',
|
'temperature_c': 'celsius', 'cpu_freq_mhz': 'megahertz',
|
||||||
@@ -90,17 +93,17 @@ def collect(device):
|
|||||||
metrics.append({'resource_id': 'host', 'key': key, 'value': rate, 'unit': 'bytes_per_second'})
|
metrics.append({'resource_id': 'host', 'key': key, 'value': rate, 'unit': 'bytes_per_second'})
|
||||||
for metric in metrics:
|
for metric in metrics:
|
||||||
source = value.get('network', {}) if metric['key'].startswith('network_') else value.get('system', {})
|
source = value.get('network', {}) if metric['key'].startswith('network_') else value.get('system', {})
|
||||||
at = source.get('observed_at') if metric['key'] != 'uptime_seconds' else value.get('timestamp')
|
at = source.get('storage_observed_at') if metric['key'].startswith('disk_') else source.get('observed_at') if metric['key'] != 'uptime_seconds' else value.get('timestamp')
|
||||||
if isinstance(at, (int, float)) and not isinstance(at, bool) and math.isfinite(at) and at > 0:
|
if isinstance(at, (int, float)) and not isinstance(at, bool) and math.isfinite(at) and at > 0:
|
||||||
metric['observed_at'] = at
|
metric['observed_at'] = at
|
||||||
return {'schema_version': 1, 'observed_at': value.get('timestamp'),
|
return {'schema_version': 1, 'observed_at': value.get('timestamp'),
|
||||||
'identity': {'reported_name': value['machine'].get('name'), 'category': 'host',
|
'identity': {'reported_name': value['machine'].get('name'), 'category': 'host',
|
||||||
'details': value.get('device', {})},
|
'details': value.get('device', {})},
|
||||||
'collection': {'interval_seconds': collection_interval(value)},
|
'collection': {**declaration, 'interval_seconds': collection_interval(value)},
|
||||||
'capabilities': MANIFEST['capabilities'], 'metrics': metrics,
|
'capabilities': MANIFEST['capabilities'], 'metrics': metrics,
|
||||||
'resources': [{'id': 'host', 'type': 'host'}],
|
'resources': [{'id': 'host', 'type': 'host'}],
|
||||||
'alerts': value.get('display', {}).get('alerts', []),
|
'alerts': value.get('display', {}).get('alerts', []),
|
||||||
'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list),
|
'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list) and not storage_plan.get('error',False),
|
||||||
'view': value}
|
'view': value}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ class MetricsTests(unittest.TestCase):
|
|||||||
self.assertEqual(values['network_rx_bytes_per_second'],0)
|
self.assertEqual(values['network_rx_bytes_per_second'],0)
|
||||||
self.assertEqual(values['network_tx_bytes_per_second'],1234)
|
self.assertEqual(values['network_tx_bytes_per_second'],1234)
|
||||||
self.assertEqual(next(m['observed_at'] for m in result['metrics'] if m['key']=='network_tx_bytes_per_second'),120)
|
self.assertEqual(next(m['observed_at'] for m in result['metrics'] if m['key']=='network_tx_bytes_per_second'),120)
|
||||||
self.assertEqual(result['collection']['interval_seconds'],4)
|
self.assertEqual(result['collection']['interval_seconds'],1)
|
||||||
self.assertTrue(result['alerts_complete'])
|
self.assertTrue(result['alerts_complete'])
|
||||||
|
|
||||||
def test_collection_minimum_and_slower_device(self):
|
def test_collection_minimum_and_slower_device(self):
|
||||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':1}}),4)
|
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':1}}),1)
|
||||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':5}}),5)
|
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':5}}),5)
|
||||||
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':True}}),4)
|
self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':True}}),1)
|
||||||
|
|
||||||
def test_absent_or_boolean_fields_not_invented(self):
|
def test_absent_or_boolean_fields_not_invented(self):
|
||||||
value={'system': {'cpu_percent': True}, 'machine':{}, 'network': {'link_speed_mbps': None, 'rx_bytes_per_second':float('nan'), 'tx_bytes_per_second':True}}
|
value={'system': {'cpu_percent': True}, 'machine':{}, 'network': {'link_speed_mbps': None, 'rx_bytes_per_second':float('nan'), 'tx_bytes_per_second':True}}
|
||||||
|
|||||||
Reference in New Issue
Block a user