Eu descrevi o uso do sensor temper com raspberrypi no artigo
monitorando a temperatura da sala dos servidores.
O que eu não contei ali foi que eu passei a monitorar outros parâmetros mostrado pelo programa sensor.
O sensor faz parte do pacote lm-sensor no Ubuntu.
Rodando o programa com parâmetro -j mostra a saída em format JSON.
❯ sensors -j
{
"coretemp-isa-0000":{
"Adapter": "ISA adapter",
"Package id 0":{
"temp1_input": 61.000,
"temp1_max": 100.000,
"temp1_crit": 100.000,
"temp1_crit_alarm": 0.000
},
"Core 0":{
"temp2_input": 52.000,
"temp2_max": 100.000,
"temp2_crit": 100.000,
"temp2_crit_alarm": 0.000
},
"Core 4":{
"temp6_input": 54.000,
"temp6_max": 100.000,
"temp6_crit": 100.000,
"temp6_crit_alarm": 0.000
},
"Core 8":{
"temp10_input": 61.000,
"temp10_max": 100.000,
"temp10_crit": 100.000,
"temp10_crit_alarm": 0.000
},
"Core 9":{
"temp11_input": 61.000,
"temp11_max": 100.000,
"temp11_crit": 100.000,
"temp11_crit_alarm": 0.000
},
"Core 10":{
"temp12_input": 61.000,
"temp12_max": 100.000,
"temp12_crit": 100.000,
"temp12_crit_alarm": 0.000
},
"Core 11":{
"temp13_input": 61.000,
"temp13_max": 100.000,
"temp13_crit": 100.000,
"temp13_crit_alarm": 0.000
},
"Core 12":{
"temp14_input": 59.000,
"temp14_max": 100.000,
"temp14_crit": 100.000,
"temp14_crit_alarm": 0.000
},
"Core 13":{
"temp15_input": 59.000,
"temp15_max": 100.000,
"temp15_crit": 100.000,
"temp15_crit_alarm": 0.000
},
"Core 14":{
"temp16_input": 59.000,
"temp16_max": 100.000,
"temp16_crit": 100.000,
"temp16_crit_alarm": 0.000
},
"Core 15":{
"temp17_input": 60.000,
"temp17_max": 100.000,
"temp17_crit": 100.000,
"temp17_crit_alarm": 0.000
}
},
"thinkpad-isa-0000":{
"Adapter": "ISA adapter",
"fan1":{
"fan1_input": 2191.000
},
"CPU":{
"temp1_input": 59.000
},
"GPU":{
ERROR: Can't get value of subfeature temp2_input: Can't read
},
"temp3":{
"temp3_input": 59.000
},
"temp4":{
"temp4_input": 0.000
},
"temp5":{
"temp5_input": 59.000
},
"temp6":{
"temp6_input": 59.000
},
"temp7":{
"temp7_input": 59.000
},
"temp8":{
ERROR: Can't get value of subfeature temp8_input: Can't read
}
},
"ucsi_source_psy_USBC000:001-isa-0000":{
"Adapter": "ISA adapter",
"in0":{
"in0_input": 0.000,
"in0_min": 0.000,
"in0_max": 0.000
},
"curr1":{
"curr1_input": 0.000,
"curr1_max": 0.000
}
},
"BAT0-acpi-0":{
"Adapter": "ACPI interface",
"in0":{
"in0_input": 12.909
},
"power1":{
"power1_input": 0.000
}
},
"iwlwifi_1-virtual-0":{
"Adapter": "Virtual device",
"temp1":{
"temp1_input": 42.000
}
},
"ucsi_source_psy_USBC000:002-isa-0000":{
"Adapter": "ISA adapter",
"in0":{
"in0_input": 0.000,
"in0_min": 0.000,
"in0_max": 0.000
},
"curr1":{
"curr1_input": 3.000,
"curr1_max": 0.000
}
},
"nvme-pci-0200":{
"Adapter": "PCI adapter",
"Composite":{
"temp1_input": 44.850,
"temp1_max": 85.850,
"temp1_min": -273.150,
"temp1_crit": 86.850,
"temp1_alarm": 0.000
},
"Sensor 1":{
"temp2_input": 47.850,
"temp2_max": 65261.850,
"temp2_min": -273.150
},
"Sensor 2":{
"temp3_input": 44.850,
"temp3_max": 65261.850,
"temp3_min": -273.150
}
},
"acpitz-acpi-0":{
"Adapter": "ACPI interface",
"temp1":{
"temp1_input": 59.000
}
}
}
Essa saída de comando é do laptop de trabalho, onde estou escrevendo esse artigo.
É possível ver que aparecem alguns erros como ERROR: Can't get value of subfeature temp2_input: Can't read
e que alguns dados não tem valor como em { "coretemp-isa-0000":{ "Adapter": "ISA adapter" } }.
Quando fiz pro servidor, eu acabei meio que escrevendo o código na mão.
sensors = shellExec("/usr/bin/sensors -j")
logger.debug(f"sensors: {sensors}")
jResp = json.loads(sensors)
resp = list()
resp.append("#HELP server_board_temperature_celsius the server board current temperatures")
resp.append("#TYPE server_board_temperature_celsius gauge")
device_1 = "nvme-pci-2b00"
device_2 = "k10temp-pci-00c3"
composite = jResp[device_1]["Composite"]["temp1_input"]
logger.info(f"device: {device_1}, composite, temperature: {composite}")
resp.append("server_board_temperature_celsius{device=\"" + device_1 + "\",sensor=\"composite\"} " + "%0.2f" % composite )
sensor_1 = jResp[device_1]["Sensor 1"]["temp2_input"]
resp.append("server_board_temperature_celsius{device=\"" + device_1 + "\",sensor=\"sensor_1\"} " + "%0.2f" % sensor_1 )
sensor_2 = jResp[device_1]["Sensor 2"]["temp3_input"]
resp.append("server_board_temperature_celsius{device=\"" + device_1 + "\",sensor=\"sensor_2\"} " + "%0.2f" % sensor_2 )
tctl = jResp[device_2]["Tctl"]["temp1_input"]
resp.append("server_board_temperature_celsius{device=\"" + device_2 + "\",sensor=\"tctl\"} " + "%0.2f" % tctl )
tccd3 = jResp[device_2]["Tccd3"]["temp5_input"]
resp.append("server_board_temperature_celsius{device=\"" + device_2 + "\",sensor=\"tccd3\"} " + "%0.2f" % tccd3 )
tccd5 = jResp[device_2]["Tccd5"]["temp7_input"]
resp.append("server_board_temperature_celsius{device=\"" + device_2 + "\",sensor=\"tccd5\"} " + "%0.2f" % tccd5 )
resp.append("")
Então essa semana eu gastei um tempo pra fazer um script mais genérico.
Aparecem agora todos os dados que saem com valor no comando sensor -j, mas em contrapartida não sei exatamente o que são.
Sem mais delongas, aqui o código:
#! /usr/bin/env -S uv run --script
#
# /// script
# dependencies = [
# "uvicorn",
# "fastapi"
# ]
# ///
import argparse
import sys
import subprocess
import logging
import json
import re
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
import uvicorn
DEFAULTS = {"port": 9090, "path": "/metrics"}
__version__ = "0.1.0"
logger = logging.getLogger(__file__)
consoleOutputHandler = logging.StreamHandler()
formatter = logging.Formatter(
fmt="[%(asctime)s] (%(levelname)s) %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
consoleOutputHandler.setFormatter(formatter)
logger.addHandler(consoleOutputHandler)
logger.setLevel(logging.INFO)
def shellExec(command: str) -> str:
"run a command and return its output"
try:
# result = subprocess.getoutput(command, errors=subprocess.DEVNULL)
result = subprocess.check_output(
command, stderr=subprocess.DEVNULL, encoding="utf-8"
)
logger.debug(f"shellExec: {result}")
return result
except Exception as e:
logger.error(f"Error executing shell command '{command}': {e}")
return f"Error: {e}"
class SensorMetrics:
open_metrics: dict = {}
def generateMetrics(self) -> list:
self.dataReset()
sensors_output = shellExec(["sensors", "-j"])
logger.debug(f"sensors: {sensors_output}")
sensorsJSON = json.loads(sensors_output)
# sensorsJSON = {
# "thinkpad-isa-0000": {
# "Adapter": "ISA adapter",
# "fan1": {"fan1_input": 2786.000},
# "CPU": {"temp1_input": 69.000},
# "GPU": {},
# "temp3": {"temp3_input": 69.000},
# "temp4": {"temp4_input": 0.000},
# "temp5": {"temp5_input": 69.000},
# "temp6": {"temp6_input": 69.000},
# "temp7": {"temp7_input": 69.000},
# "temp8": {},
# }
# }
self.getOpenMetrics([], sensorsJSON)
return []
def dataReset(self):
self.open_metrics = {}
def getOpenMetrics(self, header: list, data_dict: dict):
if isinstance(data_dict, dict):
for k, v in data_dict.items():
logger.debug(f"k={k}, v={v}")
if isinstance(v, dict):
self.getOpenMetrics(header + [k], v)
else:
logger.debug(
f"Value is not dictionary: header={header}, k={k}, v={v}"
)
try:
v = float(v)
except ValueError:
logger.debug(f"invalid value: {v}")
continue
metric_head = self.generateMetricHeader(header, k)
metric_description = " ".join(header)
metric_description += f" {k}"
if metric_head in self.open_metrics:
logger.error(f"metric name '{metric_head}' already exists")
self.open_metrics[metric_head] = {
"value": v,
"description": metric_description,
}
logger.debug(
f"Adding: metric_head={metric_head}, value={v}, description='{metric_description}'"
)
else:
logger.debug(f"Not dictionary: header={header}, data_dict={data_dict}")
def generateMetricHeader(self, header: list, key: str) -> str:
metric_header = "_".join(header)
metric_header = re.sub(" ", "_", metric_header)
return f"{metric_header}_{key}"
if __name__ == "__main__":
parse = argparse.ArgumentParser(
description="Script to expose the sensors as open metrics"
)
parse.add_argument(
"--port", type=int, default=DEFAULTS["port"], help="Port to listen"
)
parse.add_argument(
"--path", default=DEFAULTS["path"], help="The path to serve the metrics"
)
parse.add_argument(
"--version",
action=argparse.BooleanOptionalAction,
help="Print version and exit",
)
parse.add_argument(
"--printout",
action=argparse.BooleanOptionalAction,
help="Print the exposed metrics found in the system",
)
parse.add_argument("--loglevel", default="info", help="Logging level")
args = parse.parse_args()
if args.loglevel != "info":
logger.setLevel(args.loglevel.upper())
if args.version is True:
print(sys.argv[0], __version__)
sys.exit(0)
if args.printout is True:
mts = SensorMetrics()
mts.generateMetrics()
for k, v in mts.open_metrics.items():
description = v["description"]
print(f"#HELP {k} {description}")
print(f"#TYPE {k} gauge")
print(f"{k}: {v['value']}")
sys.exit(0)
app = FastAPI()
@app.get(args.path, response_class=PlainTextResponse)
async def metrics():
logger.info(f"serving web page on {args.path}")
mts = SensorMetrics()
mts.generateMetrics()
resp = list()
for k, v in mts.open_metrics.items():
description = v["description"]
resp.append(f"#HELP {k} {description}")
resp.append(f"#TYPE {k} gauge")
resp.append(f"{k}: {v['value']}")
return "\n".join(resp)
logger.info(f"starting service on port {args.port}")
uvicorn.run(app, host="127.0.0.1", port=args.port)
O código pode ser encontrado aqui:
Olhando o código mais de perto
Como pode ser visto no cabeçalho:
#! /usr/bin/env -S uv run --script
#
# /// script
# dependencies = [
# "uvicorn",
# "fastapi"
# ]
# ///
abracei com força a dica do querido Riverfount, que mostrou que o uv podia fazer isso.
Rodando simplemente pelo shell, temos a seguinte saída (que pode mudar de acordo com seu hardware):
❯ sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0: +61.0°C (high = +100.0°C, crit = +100.0°C)
Core 0: +53.0°C (high = +100.0°C, crit = +100.0°C)
Core 4: +57.0°C (high = +100.0°C, crit = +100.0°C)
Core 8: +61.0°C (high = +100.0°C, crit = +100.0°C)
Core 9: +61.0°C (high = +100.0°C, crit = +100.0°C)
Core 10: +61.0°C (high = +100.0°C, crit = +100.0°C)
Core 11: +61.0°C (high = +100.0°C, crit = +100.0°C)
Core 12: +60.0°C (high = +100.0°C, crit = +100.0°C)
Core 13: +60.0°C (high = +100.0°C, crit = +100.0°C)
Core 14: +60.0°C (high = +100.0°C, crit = +100.0°C)
Core 15: +60.0°C (high = +100.0°C, crit = +100.0°C)
thinkpad-isa-0000
Adapter: ISA adapter
fan1: 2199 RPM
CPU: +60.0°C
GPU: N/A
temp3: +60.0°C
temp4: +0.0°C
temp5: +60.0°C
temp6: +60.0°C
temp7: +60.0°C
temp8: N/A
ucsi_source_psy_USBC000:001-isa-0000
Adapter: ISA adapter
in0: 0.00 V (min = +0.00 V, max = +0.00 V)
curr1: 0.00 A (max = +0.00 A)
BAT0-acpi-0
Adapter: ACPI interface
in0: 12.91 V
power1: 0.00 W
iwlwifi_1-virtual-0
Adapter: Virtual device
temp1: +43.0°C
ucsi_source_psy_USBC000:002-isa-0000
Adapter: ISA adapter
in0: 0.00 V (min = +0.00 V, max = +0.00 V)
curr1: 3.00 A (max = +0.00 A)
nvme-pci-0200
Adapter: PCI adapter
Composite: +45.9°C (low = -273.1°C, high = +85.8°C)
(crit = +86.8°C)
Sensor 1: +48.9°C (low = -273.1°C, high = +65261.8°C)
Sensor 2: +45.9°C (low = -273.1°C, high = +65261.8°C)
acpitz-acpi-0
Adapter: ACPI interface
temp1: +60.0°C
❯ ./sensors-open-metrics.py --printout
#HELP coretemp-isa-0000_Package_id_0_temp1_input coretemp-isa-0000 Package id 0 temp1_input
#TYPE coretemp-isa-0000_Package_id_0_temp1_input gauge
coretemp-isa-0000_Package_id_0_temp1_input: 66.0
#HELP coretemp-isa-0000_Package_id_0_temp1_max coretemp-isa-0000 Package id 0 temp1_max
#TYPE coretemp-isa-0000_Package_id_0_temp1_max gauge
coretemp-isa-0000_Package_id_0_temp1_max: 100.0
#HELP coretemp-isa-0000_Package_id_0_temp1_crit coretemp-isa-0000 Package id 0 temp1_crit
#TYPE coretemp-isa-0000_Package_id_0_temp1_crit gauge
coretemp-isa-0000_Package_id_0_temp1_crit: 100.0
#HELP coretemp-isa-0000_Package_id_0_temp1_crit_alarm coretemp-isa-0000 Package id 0 temp1_crit_alarm
#TYPE coretemp-isa-0000_Package_id_0_temp1_crit_alarm gauge
coretemp-isa-0000_Package_id_0_temp1_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_0_temp2_input coretemp-isa-0000 Core 0 temp2_input
#TYPE coretemp-isa-0000_Core_0_temp2_input gauge
coretemp-isa-0000_Core_0_temp2_input: 61.0
#HELP coretemp-isa-0000_Core_0_temp2_max coretemp-isa-0000 Core 0 temp2_max
#TYPE coretemp-isa-0000_Core_0_temp2_max gauge
coretemp-isa-0000_Core_0_temp2_max: 100.0
#HELP coretemp-isa-0000_Core_0_temp2_crit coretemp-isa-0000 Core 0 temp2_crit
#TYPE coretemp-isa-0000_Core_0_temp2_crit gauge
coretemp-isa-0000_Core_0_temp2_crit: 100.0
#HELP coretemp-isa-0000_Core_0_temp2_crit_alarm coretemp-isa-0000 Core 0 temp2_crit_alarm
#TYPE coretemp-isa-0000_Core_0_temp2_crit_alarm gauge
coretemp-isa-0000_Core_0_temp2_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_4_temp6_input coretemp-isa-0000 Core 4 temp6_input
#TYPE coretemp-isa-0000_Core_4_temp6_input gauge
coretemp-isa-0000_Core_4_temp6_input: 58.0
#HELP coretemp-isa-0000_Core_4_temp6_max coretemp-isa-0000 Core 4 temp6_max
#TYPE coretemp-isa-0000_Core_4_temp6_max gauge
coretemp-isa-0000_Core_4_temp6_max: 100.0
#HELP coretemp-isa-0000_Core_4_temp6_crit coretemp-isa-0000 Core 4 temp6_crit
#TYPE coretemp-isa-0000_Core_4_temp6_crit gauge
coretemp-isa-0000_Core_4_temp6_crit: 100.0
#HELP coretemp-isa-0000_Core_4_temp6_crit_alarm coretemp-isa-0000 Core 4 temp6_crit_alarm
#TYPE coretemp-isa-0000_Core_4_temp6_crit_alarm gauge
coretemp-isa-0000_Core_4_temp6_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_8_temp10_input coretemp-isa-0000 Core 8 temp10_input
#TYPE coretemp-isa-0000_Core_8_temp10_input gauge
coretemp-isa-0000_Core_8_temp10_input: 66.0
#HELP coretemp-isa-0000_Core_8_temp10_max coretemp-isa-0000 Core 8 temp10_max
#TYPE coretemp-isa-0000_Core_8_temp10_max gauge
coretemp-isa-0000_Core_8_temp10_max: 100.0
#HELP coretemp-isa-0000_Core_8_temp10_crit coretemp-isa-0000 Core 8 temp10_crit
#TYPE coretemp-isa-0000_Core_8_temp10_crit gauge
coretemp-isa-0000_Core_8_temp10_crit: 100.0
#HELP coretemp-isa-0000_Core_8_temp10_crit_alarm coretemp-isa-0000 Core 8 temp10_crit_alarm
#TYPE coretemp-isa-0000_Core_8_temp10_crit_alarm gauge
coretemp-isa-0000_Core_8_temp10_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_9_temp11_input coretemp-isa-0000 Core 9 temp11_input
#TYPE coretemp-isa-0000_Core_9_temp11_input gauge
coretemp-isa-0000_Core_9_temp11_input: 66.0
#HELP coretemp-isa-0000_Core_9_temp11_max coretemp-isa-0000 Core 9 temp11_max
#TYPE coretemp-isa-0000_Core_9_temp11_max gauge
coretemp-isa-0000_Core_9_temp11_max: 100.0
#HELP coretemp-isa-0000_Core_9_temp11_crit coretemp-isa-0000 Core 9 temp11_crit
#TYPE coretemp-isa-0000_Core_9_temp11_crit gauge
coretemp-isa-0000_Core_9_temp11_crit: 100.0
#HELP coretemp-isa-0000_Core_9_temp11_crit_alarm coretemp-isa-0000 Core 9 temp11_crit_alarm
#TYPE coretemp-isa-0000_Core_9_temp11_crit_alarm gauge
coretemp-isa-0000_Core_9_temp11_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_10_temp12_input coretemp-isa-0000 Core 10 temp12_input
#TYPE coretemp-isa-0000_Core_10_temp12_input gauge
coretemp-isa-0000_Core_10_temp12_input: 66.0
#HELP coretemp-isa-0000_Core_10_temp12_max coretemp-isa-0000 Core 10 temp12_max
#TYPE coretemp-isa-0000_Core_10_temp12_max gauge
coretemp-isa-0000_Core_10_temp12_max: 100.0
#HELP coretemp-isa-0000_Core_10_temp12_crit coretemp-isa-0000 Core 10 temp12_crit
#TYPE coretemp-isa-0000_Core_10_temp12_crit gauge
coretemp-isa-0000_Core_10_temp12_crit: 100.0
#HELP coretemp-isa-0000_Core_10_temp12_crit_alarm coretemp-isa-0000 Core 10 temp12_crit_alarm
#TYPE coretemp-isa-0000_Core_10_temp12_crit_alarm gauge
coretemp-isa-0000_Core_10_temp12_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_11_temp13_input coretemp-isa-0000 Core 11 temp13_input
#TYPE coretemp-isa-0000_Core_11_temp13_input gauge
coretemp-isa-0000_Core_11_temp13_input: 66.0
#HELP coretemp-isa-0000_Core_11_temp13_max coretemp-isa-0000 Core 11 temp13_max
#TYPE coretemp-isa-0000_Core_11_temp13_max gauge
coretemp-isa-0000_Core_11_temp13_max: 100.0
#HELP coretemp-isa-0000_Core_11_temp13_crit coretemp-isa-0000 Core 11 temp13_crit
#TYPE coretemp-isa-0000_Core_11_temp13_crit gauge
coretemp-isa-0000_Core_11_temp13_crit: 100.0
#HELP coretemp-isa-0000_Core_11_temp13_crit_alarm coretemp-isa-0000 Core 11 temp13_crit_alarm
#TYPE coretemp-isa-0000_Core_11_temp13_crit_alarm gauge
coretemp-isa-0000_Core_11_temp13_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_12_temp14_input coretemp-isa-0000 Core 12 temp14_input
#TYPE coretemp-isa-0000_Core_12_temp14_input gauge
coretemp-isa-0000_Core_12_temp14_input: 61.0
#HELP coretemp-isa-0000_Core_12_temp14_max coretemp-isa-0000 Core 12 temp14_max
#TYPE coretemp-isa-0000_Core_12_temp14_max gauge
coretemp-isa-0000_Core_12_temp14_max: 100.0
#HELP coretemp-isa-0000_Core_12_temp14_crit coretemp-isa-0000 Core 12 temp14_crit
#TYPE coretemp-isa-0000_Core_12_temp14_crit gauge
coretemp-isa-0000_Core_12_temp14_crit: 100.0
#HELP coretemp-isa-0000_Core_12_temp14_crit_alarm coretemp-isa-0000 Core 12 temp14_crit_alarm
#TYPE coretemp-isa-0000_Core_12_temp14_crit_alarm gauge
coretemp-isa-0000_Core_12_temp14_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_13_temp15_input coretemp-isa-0000 Core 13 temp15_input
#TYPE coretemp-isa-0000_Core_13_temp15_input gauge
coretemp-isa-0000_Core_13_temp15_input: 61.0
#HELP coretemp-isa-0000_Core_13_temp15_max coretemp-isa-0000 Core 13 temp15_max
#TYPE coretemp-isa-0000_Core_13_temp15_max gauge
coretemp-isa-0000_Core_13_temp15_max: 100.0
#HELP coretemp-isa-0000_Core_13_temp15_crit coretemp-isa-0000 Core 13 temp15_crit
#TYPE coretemp-isa-0000_Core_13_temp15_crit gauge
coretemp-isa-0000_Core_13_temp15_crit: 100.0
#HELP coretemp-isa-0000_Core_13_temp15_crit_alarm coretemp-isa-0000 Core 13 temp15_crit_alarm
#TYPE coretemp-isa-0000_Core_13_temp15_crit_alarm gauge
coretemp-isa-0000_Core_13_temp15_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_14_temp16_input coretemp-isa-0000 Core 14 temp16_input
#TYPE coretemp-isa-0000_Core_14_temp16_input gauge
coretemp-isa-0000_Core_14_temp16_input: 61.0
#HELP coretemp-isa-0000_Core_14_temp16_max coretemp-isa-0000 Core 14 temp16_max
#TYPE coretemp-isa-0000_Core_14_temp16_max gauge
coretemp-isa-0000_Core_14_temp16_max: 100.0
#HELP coretemp-isa-0000_Core_14_temp16_crit coretemp-isa-0000 Core 14 temp16_crit
#TYPE coretemp-isa-0000_Core_14_temp16_crit gauge
coretemp-isa-0000_Core_14_temp16_crit: 100.0
#HELP coretemp-isa-0000_Core_14_temp16_crit_alarm coretemp-isa-0000 Core 14 temp16_crit_alarm
#TYPE coretemp-isa-0000_Core_14_temp16_crit_alarm gauge
coretemp-isa-0000_Core_14_temp16_crit_alarm: 0.0
#HELP coretemp-isa-0000_Core_15_temp17_input coretemp-isa-0000 Core 15 temp17_input
#TYPE coretemp-isa-0000_Core_15_temp17_input gauge
coretemp-isa-0000_Core_15_temp17_input: 61.0
#HELP coretemp-isa-0000_Core_15_temp17_max coretemp-isa-0000 Core 15 temp17_max
#TYPE coretemp-isa-0000_Core_15_temp17_max gauge
coretemp-isa-0000_Core_15_temp17_max: 100.0
#HELP coretemp-isa-0000_Core_15_temp17_crit coretemp-isa-0000 Core 15 temp17_crit
#TYPE coretemp-isa-0000_Core_15_temp17_crit gauge
coretemp-isa-0000_Core_15_temp17_crit: 100.0
#HELP coretemp-isa-0000_Core_15_temp17_crit_alarm coretemp-isa-0000 Core 15 temp17_crit_alarm
#TYPE coretemp-isa-0000_Core_15_temp17_crit_alarm gauge
coretemp-isa-0000_Core_15_temp17_crit_alarm: 0.0
#HELP thinkpad-isa-0000_fan1_fan1_input thinkpad-isa-0000 fan1 fan1_input
#TYPE thinkpad-isa-0000_fan1_fan1_input gauge
thinkpad-isa-0000_fan1_fan1_input: 2191.0
#HELP thinkpad-isa-0000_CPU_temp1_input thinkpad-isa-0000 CPU temp1_input
#TYPE thinkpad-isa-0000_CPU_temp1_input gauge
thinkpad-isa-0000_CPU_temp1_input: 60.0
#HELP thinkpad-isa-0000_temp3_temp3_input thinkpad-isa-0000 temp3 temp3_input
#TYPE thinkpad-isa-0000_temp3_temp3_input gauge
thinkpad-isa-0000_temp3_temp3_input: 60.0
#HELP thinkpad-isa-0000_temp4_temp4_input thinkpad-isa-0000 temp4 temp4_input
#TYPE thinkpad-isa-0000_temp4_temp4_input gauge
thinkpad-isa-0000_temp4_temp4_input: 0.0
#HELP thinkpad-isa-0000_temp5_temp5_input thinkpad-isa-0000 temp5 temp5_input
#TYPE thinkpad-isa-0000_temp5_temp5_input gauge
thinkpad-isa-0000_temp5_temp5_input: 60.0
#HELP thinkpad-isa-0000_temp6_temp6_input thinkpad-isa-0000 temp6 temp6_input
#TYPE thinkpad-isa-0000_temp6_temp6_input gauge
thinkpad-isa-0000_temp6_temp6_input: 60.0
#HELP thinkpad-isa-0000_temp7_temp7_input thinkpad-isa-0000 temp7 temp7_input
#TYPE thinkpad-isa-0000_temp7_temp7_input gauge
thinkpad-isa-0000_temp7_temp7_input: 60.0
#HELP ucsi_source_psy_USBC000:001-isa-0000_in0_in0_input ucsi_source_psy_USBC000:001-isa-0000 in0 in0_input
#TYPE ucsi_source_psy_USBC000:001-isa-0000_in0_in0_input gauge
ucsi_source_psy_USBC000:001-isa-0000_in0_in0_input: 0.0
#HELP ucsi_source_psy_USBC000:001-isa-0000_in0_in0_min ucsi_source_psy_USBC000:001-isa-0000 in0 in0_min
#TYPE ucsi_source_psy_USBC000:001-isa-0000_in0_in0_min gauge
ucsi_source_psy_USBC000:001-isa-0000_in0_in0_min: 0.0
#HELP ucsi_source_psy_USBC000:001-isa-0000_in0_in0_max ucsi_source_psy_USBC000:001-isa-0000 in0 in0_max
#TYPE ucsi_source_psy_USBC000:001-isa-0000_in0_in0_max gauge
ucsi_source_psy_USBC000:001-isa-0000_in0_in0_max: 0.0
#HELP ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_input ucsi_source_psy_USBC000:001-isa-0000 curr1 curr1_input
#TYPE ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_input gauge
ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_input: 0.0
#HELP ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_max ucsi_source_psy_USBC000:001-isa-0000 curr1 curr1_max
#TYPE ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_max gauge
ucsi_source_psy_USBC000:001-isa-0000_curr1_curr1_max: 0.0
#HELP BAT0-acpi-0_in0_in0_input BAT0-acpi-0 in0 in0_input
#TYPE BAT0-acpi-0_in0_in0_input gauge
BAT0-acpi-0_in0_in0_input: 12.909
#HELP BAT0-acpi-0_power1_power1_input BAT0-acpi-0 power1 power1_input
#TYPE BAT0-acpi-0_power1_power1_input gauge
BAT0-acpi-0_power1_power1_input: 0.0
#HELP iwlwifi_1-virtual-0_temp1_temp1_input iwlwifi_1-virtual-0 temp1 temp1_input
#TYPE iwlwifi_1-virtual-0_temp1_temp1_input gauge
iwlwifi_1-virtual-0_temp1_temp1_input: 43.0
#HELP ucsi_source_psy_USBC000:002-isa-0000_in0_in0_input ucsi_source_psy_USBC000:002-isa-0000 in0 in0_input
#TYPE ucsi_source_psy_USBC000:002-isa-0000_in0_in0_input gauge
ucsi_source_psy_USBC000:002-isa-0000_in0_in0_input: 0.0
#HELP ucsi_source_psy_USBC000:002-isa-0000_in0_in0_min ucsi_source_psy_USBC000:002-isa-0000 in0 in0_min
#TYPE ucsi_source_psy_USBC000:002-isa-0000_in0_in0_min gauge
ucsi_source_psy_USBC000:002-isa-0000_in0_in0_min: 0.0
#HELP ucsi_source_psy_USBC000:002-isa-0000_in0_in0_max ucsi_source_psy_USBC000:002-isa-0000 in0 in0_max
#TYPE ucsi_source_psy_USBC000:002-isa-0000_in0_in0_max gauge
ucsi_source_psy_USBC000:002-isa-0000_in0_in0_max: 0.0
#HELP ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_input ucsi_source_psy_USBC000:002-isa-0000 curr1 curr1_input
#TYPE ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_input gauge
ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_input: 3.0
#HELP ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_max ucsi_source_psy_USBC000:002-isa-0000 curr1 curr1_max
#TYPE ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_max gauge
ucsi_source_psy_USBC000:002-isa-0000_curr1_curr1_max: 0.0
#HELP nvme-pci-0200_Composite_temp1_input nvme-pci-0200 Composite temp1_input
#TYPE nvme-pci-0200_Composite_temp1_input gauge
nvme-pci-0200_Composite_temp1_input: 45.85
#HELP nvme-pci-0200_Composite_temp1_max nvme-pci-0200 Composite temp1_max
#TYPE nvme-pci-0200_Composite_temp1_max gauge
nvme-pci-0200_Composite_temp1_max: 85.85
#HELP nvme-pci-0200_Composite_temp1_min nvme-pci-0200 Composite temp1_min
#TYPE nvme-pci-0200_Composite_temp1_min gauge
nvme-pci-0200_Composite_temp1_min: -273.15
#HELP nvme-pci-0200_Composite_temp1_crit nvme-pci-0200 Composite temp1_crit
#TYPE nvme-pci-0200_Composite_temp1_crit gauge
nvme-pci-0200_Composite_temp1_crit: 86.85
#HELP nvme-pci-0200_Composite_temp1_alarm nvme-pci-0200 Composite temp1_alarm
#TYPE nvme-pci-0200_Composite_temp1_alarm gauge
nvme-pci-0200_Composite_temp1_alarm: 0.0
#HELP nvme-pci-0200_Sensor_1_temp2_input nvme-pci-0200 Sensor 1 temp2_input
#TYPE nvme-pci-0200_Sensor_1_temp2_input gauge
nvme-pci-0200_Sensor_1_temp2_input: 50.85
#HELP nvme-pci-0200_Sensor_1_temp2_max nvme-pci-0200 Sensor 1 temp2_max
#TYPE nvme-pci-0200_Sensor_1_temp2_max gauge
nvme-pci-0200_Sensor_1_temp2_max: 65261.85
#HELP nvme-pci-0200_Sensor_1_temp2_min nvme-pci-0200 Sensor 1 temp2_min
#TYPE nvme-pci-0200_Sensor_1_temp2_min gauge
nvme-pci-0200_Sensor_1_temp2_min: -273.15
#HELP nvme-pci-0200_Sensor_2_temp3_input nvme-pci-0200 Sensor 2 temp3_input
#TYPE nvme-pci-0200_Sensor_2_temp3_input gauge
nvme-pci-0200_Sensor_2_temp3_input: 45.85
#HELP nvme-pci-0200_Sensor_2_temp3_max nvme-pci-0200 Sensor 2 temp3_max
#TYPE nvme-pci-0200_Sensor_2_temp3_max gauge
nvme-pci-0200_Sensor_2_temp3_max: 65261.85
#HELP nvme-pci-0200_Sensor_2_temp3_min nvme-pci-0200 Sensor 2 temp3_min
#TYPE nvme-pci-0200_Sensor_2_temp3_min gauge
nvme-pci-0200_Sensor_2_temp3_min: -273.15
#HELP acpitz-acpi-0_temp1_temp1_input acpitz-acpi-0 temp1 temp1_input
#TYPE acpitz-acpi-0_temp1_temp1_input gauge
acpitz-acpi-0_temp1_temp1_input: 60.0
Os dados aparecem no #HELP simplesmente sem os _.
Talvez eu devesse melhorar e fazer com que alguns valores fosse labels do valor maior.
Mas por enquanto está funcionando assim.
E pra rodar como serviço:
❯ ./sensors-open-metrics.py
[2025-12-12 19:54:43] (INFO) starting service on port 9090
INFO: Started server process [1078630]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9090 (Press CTRL+C to quit)
[2025-12-12 19:54:53] (INFO) serving web page on /metrics
INFO: 127.0.0.1:52250 - "GET /metrics HTTP/1.1" 200 OK
Em outro terminal:
❯ curl localhost:9090/metrics
#HELP coretemp-isa-0000_Package_id_0_temp1_input coretemp-isa-0000 Package id 0 temp1_input
#TYPE coretemp-isa-0000_Package_id_0_temp1_input gauge
coretemp-isa-0000_Package_id_0_temp1_input: 62.0
#HELP coretemp-isa-0000_Package_id_0_temp1_max coretemp-isa-0000 Package id 0 temp1_max
#TYPE coretemp-isa-0000_Package_id_0_temp1_max gauge
coretemp-isa-0000_Package_id_0_temp1_max: 100.0
#HELP coretemp-isa-0000_Package_id_0_temp1_crit coretemp-isa-0000 Package id 0 temp1_crit
#TYPE coretemp-isa-0000_Package_id_0_temp1_crit gauge
coretemp-isa-0000_Package_id_0_temp1_crit: 100.0
#HELP coretemp-isa-0000_Package_id_0_temp1_crit_alarm coretemp-isa-0000 Package id 0 temp1_crit_alarm
[...]
Pra rodar como serviço do systemd em /etc/systemd/system/sensors-open-metrics.service:
[Unit]
Description=Temperature sensors
Wants=network-online.target
After=network-online.target nginx.service
[Service]
Restart=always
User=root
Group=root
WorkingDirectory=/tmp
ExecStart=/usr/local/bin/sensors-open-metrics.py
TimeoutStopSec=5s
[Install]
WantedBy=multi-user.target
E finalmente adicionando em /etc/alloy/config.alloy:
[...]
discovery.relabel "temperature_metrics" {
targets = array.concat(
[{
__address__ = "localhost:9090",
}],
)
rule {
source_labels = ["__address__"]
target_label = "instance"
replacement = "nome_do_servidor"
}
}
prometheus.scrape "temperature_metrics" {
targets = discovery.relabel.temperature_metrics.output
forward_to = [prometheus.remote_write.prod.receiver]
job_name = "temperature"
}
Daí é só evocar o Grafana e partir por gráficos.
