Skip to content

Commit 29d2663

Browse files
dmytroxIntelpull[bot]
authored andcommitted
[BFN]: Implement getting psu related sensors in sonic_platform directly from BMC (sonic-net#12786)
Why I did it Platform interface doesn't provide all sensors and using it isn't effective How I did it Request sensors via http from BMC server and parse the result How to verify it Related daemon in pmon populates redis db, run this command to view the contents
1 parent dffae6e commit 29d2663

File tree

3 files changed

+170
-6
lines changed

3 files changed

+170
-6
lines changed

dockers/docker-platform-monitor/Dockerfile.j2

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ RUN apt-get update && \
3939
RUN pip3 install grpcio==1.39.0 \
4040
grpcio-tools==1.39.0
4141

42-
# Barefoot platform vendors' sonic_platform packages import the Python 'thrift' library
43-
RUN pip3 install thrift==0.13.0
42+
# Barefoot platform vendors' sonic_platform packages import these Python libraries
43+
RUN pip3 install thrift==0.13.0 netifaces
4444

4545
# We install the libpci module in order to be able to do PCI transactions
4646
RUN pip3 install libpci
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from netifaces import ifaddresses, AF_INET6
2+
from subprocess import Popen, PIPE, DEVNULL
3+
import json
4+
import os
5+
6+
class Metric(object):
7+
8+
def __init__(self, sensor_id, sensor_key, value, label):
9+
self._value = self.parse_value(value)
10+
self._sensor_id = sensor_id
11+
self._sensor_key = sensor_key
12+
self._label = label
13+
14+
@classmethod
15+
def parse_value(cls, value):
16+
parse = getattr(cls, "parse")
17+
return parse(value)
18+
19+
# For debug purposes
20+
def __repr__(self):
21+
return "%s, %s: %s %s [%s]" % (
22+
self._sensor_id,
23+
self._sensor_key,
24+
self._value,
25+
getattr(self, "unit", "?"),
26+
self._label)
27+
28+
class Temperature(Metric):
29+
parse = float
30+
unit = "°C"
31+
32+
class FanRpm(Metric):
33+
parse = float
34+
unit = "RPM"
35+
36+
class FanFault(Metric):
37+
parse = float
38+
39+
class Voltage(Metric):
40+
parse = float
41+
unit = "V"
42+
43+
class Power(Metric):
44+
parse = float
45+
unit = "W"
46+
47+
class Current(Metric):
48+
parse = float
49+
unit = "A"
50+
51+
def get_metric_value(metrics, name):
52+
label, sensor_id, sensor_key = name.split("_")
53+
for metric in metrics:
54+
if metric._label == label and metric._sensor_id == sensor_id and metric._sensor_key == sensor_key:
55+
return metric._value
56+
return None
57+
58+
def get_link_local_interface():
59+
cdc_ether_path = "/sys/bus/usb/drivers/cdc_ether"
60+
for ether in os.listdir(cdc_ether_path):
61+
concrete_ether = os.path.join(cdc_ether_path, ether)
62+
if os.path.isdir(concrete_ether):
63+
concrete_ether_net = os.path.join(concrete_ether, 'net')
64+
if os.path.exists(concrete_ether_net):
65+
return os.listdir(concrete_ether_net)[0]
66+
67+
def get_link_local_address(link_local_interface):
68+
for addr in ifaddresses(link_local_interface)[AF_INET6]:
69+
address = addr['addr'].split('%')[0]
70+
# according to rfc4291 this ipv6 address is used for link local connection
71+
if address.startswith('fe80:'):
72+
# first address is taken for BMC and second for this host
73+
return address[:-1] + '1'
74+
return None
75+
76+
def get_psu_metrics():
77+
link_local_interface = get_link_local_interface()
78+
link_local_address = get_link_local_address(link_local_interface)
79+
80+
http_address = "http://[%s%%%s]:8080" % (link_local_address, link_local_interface)
81+
args = "/api/sys/bmc/sensors/%20-A%20-u%20"
82+
cmd = "curl " + http_address + args
83+
output = Popen(cmd.split(), stdout=PIPE, stderr=DEVNULL).stdout.read()
84+
output = json.loads(output.decode())["Information"]["Description"][0].strip()
85+
sections = output.split("\n\n")
86+
87+
metrics = []
88+
# iterating through drivers and their sensors
89+
for section in sections:
90+
fields = section.split("\n")
91+
92+
label = None
93+
# iterating through sensors and their inputs
94+
for field in fields[1:]: # skipping driver name
95+
# parsing input sensor
96+
if field.startswith(" "):
97+
field = field.replace(" ", "")
98+
# split sensor into name and value
99+
field_key, field_value = field.split(": ")
100+
if "_" in field_key:
101+
sensor_id, sensor_key = field_key.split("_", 1)
102+
if sensor_key == "input":
103+
if sensor_id.startswith("temp"):
104+
metrics.append(
105+
Temperature(sensor_id, sensor_key, field_value, label=label))
106+
elif sensor_id.startswith("in"):
107+
metrics.append(
108+
Voltage(sensor_id, sensor_key, field_value, label=label))
109+
elif sensor_id.startswith("power"):
110+
metrics.append(
111+
Power(sensor_id, sensor_key, field_value, label=label))
112+
elif sensor_id.startswith("curr"):
113+
metrics.append(
114+
Current(sensor_id, sensor_key, field_value, label=label))
115+
elif sensor_id.startswith("fan"):
116+
metrics.append(
117+
FanRpm(sensor_id, sensor_key, field_value, label=label))
118+
elif sensor_key == "fault":
119+
if sensor_id.startswith("fan"):
120+
metrics.append(
121+
FanFault(sensor_id, sensor_key, field_value, label=label))
122+
elif field.startswith("ERROR"):
123+
syslog.syslog(syslog.LOG_INFO, field)
124+
else:
125+
label = field[:-1] # strip off trailing ":" character
126+
127+
return metrics

platform/barefoot/sonic-platform-modules-bfn-montara/sonic_platform/psu.py

+41-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import time
77
import signal
88
import syslog
9+
import logging
10+
import threading
911

1012
sys.path.append(os.path.dirname(__file__))
1113

@@ -14,12 +16,18 @@
1416
from sonic_platform_base.psu_base import PsuBase
1517
from sonic_platform.thermal import psu_thermals_list_get
1618
from platform_utils import cancel_on_sigterm
19+
from sonic_platform.bfn_extensions.psu_sensors import get_psu_metrics
20+
from sonic_platform.bfn_extensions.psu_sensors import get_metric_value
1721
except ImportError as e:
1822
raise ImportError (str(e) + "- required module not found")
1923

2024
class Psu(PsuBase):
2125
"""Platform-specific PSU class"""
2226

27+
__lock = threading.Lock()
28+
__sensors_info = None
29+
__timestamp = 0
30+
2331
sigterm = False
2432
sigterm_default_handler = None
2533
cls_inited = False
@@ -48,6 +56,20 @@ def signal_handler(cls, sig, frame):
4856
syslog.syslog(syslog.LOG_INFO, "Canceling PSU platform API calls...")
4957
cls.sigterm = True
5058

59+
@classmethod
60+
def __sensors_get(cls, cached=True):
61+
cls.__lock.acquire()
62+
if time.time() > cls.__timestamp + 15:
63+
# Update cache once per 15 seconds
64+
try:
65+
cls.__sensors_info = get_psu_metrics()
66+
cls.__timestamp = time.time()
67+
except Exception as e:
68+
logging.warning("Failed to update sensors cache: " + str(e))
69+
info = cls.__sensors_info
70+
cls.__lock.release()
71+
return info
72+
5173
'''
5274
Units of returned info object values:
5375
vin - V
@@ -105,8 +127,7 @@ def get_voltage(self):
105127
A float number, the output voltage in volts,
106128
e.g. 12.1
107129
"""
108-
info = self.__info_get()
109-
return float(info.vout) if info else 0
130+
return get_metric_value(Psu.__sensors_get(), "PSU%d 12V Output Voltage_in1_input" % self.__index)
110131

111132
def get_current(self):
112133
"""
@@ -115,8 +136,24 @@ def get_current(self):
115136
Returns:
116137
A float number, the electric current in amperes, e.g 15.4
117138
"""
118-
info = self.__info_get()
119-
return info.iout / 1000 if info else 0
139+
return get_metric_value(Psu.__sensors_get(), "PSU%d 12V Output Current_curr2_input" % self.__index)
140+
141+
def get_input_voltage(self):
142+
"""
143+
Retrieves current PSU voltage input
144+
Returns:
145+
A float number, the input voltage in volts,
146+
e.g. 220
147+
"""
148+
return get_metric_value(Psu.__sensors_get(), "PSU%d Input Voltage_in0_input" % self.__index)
149+
150+
def get_input_current(self):
151+
"""
152+
Retrieves the input current draw of the power supply
153+
Returns:
154+
A float number, the electric current in amperes, e.g 0.8
155+
"""
156+
return get_metric_value(Psu.__sensors_get(), "PSU%d Input Current_curr1_input" % self.__index)
120157

121158
def get_power(self):
122159
"""

0 commit comments

Comments
 (0)