1#!/usr/bin/env python3 2 3import argparse 4from typing import NamedTuple 5 6import yaml 7 8 9class RptSensor(NamedTuple): 10 name: str 11 entityId: int 12 typeId: int 13 evtType: int 14 sensorId: int 15 fru: int 16 targetPath: str 17 18 19sampleDimmTemp = { 20 "bExp": 0, 21 "entityID": 32, 22 "entityInstance": 2, 23 "interfaces": { 24 "xyz.openbmc_project.Sensor.Value": { 25 "Value": {"Offsets": {255: {"type": "int64_t"}}} 26 } 27 }, 28 "multiplierM": 1, 29 "mutability": "Mutability::Write|Mutability::Read", 30 "offsetB": -127, 31 "path": "/xyz/openbmc_project/sensors/temperature/dimm0_temp", 32 "rExp": 0, 33 "readingType": "readingData", 34 "scale": -3, 35 "sensorNamePattern": "nameLeaf", 36 "sensorReadingType": 1, 37 "sensorType": 1, 38 "serviceInterface": "org.freedesktop.DBus.Properties", 39 "unit": "xyz.openbmc_project.Sensor.Value.Unit.DegreesC", 40} 41sampleCoreTemp = { 42 "bExp": 0, 43 "entityID": 208, 44 "entityInstance": 2, 45 "interfaces": { 46 "xyz.openbmc_project.Sensor.Value": { 47 "Value": {"Offsets": {255: {"type": "int64_t"}}} 48 } 49 }, 50 "multiplierM": 1, 51 "mutability": "Mutability::Write|Mutability::Read", 52 "offsetB": -127, 53 "path": "/xyz/openbmc_project/sensors/temperature/p0_core0_temp", 54 "rExp": 0, 55 "readingType": "readingData", 56 "scale": -3, 57 "sensorNamePattern": "nameLeaf", 58 "sensorReadingType": 1, 59 "sensorType": 1, 60 "serviceInterface": "org.freedesktop.DBus.Properties", 61 "unit": "xyz.openbmc_project.Sensor.Value.Unit.DegreesC", 62} 63samplePower = { 64 "bExp": 0, 65 "entityID": 10, 66 "entityInstance": 13, 67 "interfaces": { 68 "xyz.openbmc_project.Sensor.Value": { 69 "Value": {"Offsets": {255: {"type": "int64_t"}}} 70 } 71 }, 72 "multiplierM": 2, 73 "offsetB": 0, 74 "path": "/xyz/openbmc_project/sensors/power/p0_power", 75 "rExp": 0, 76 "readingType": "readingData", 77 "scale": -6, 78 "sensorNamePattern": "nameLeaf", 79 "sensorReadingType": 1, 80 "sensorType": 8, 81 "serviceInterface": "org.freedesktop.DBus.Properties", 82 "unit": "xyz.openbmc_project.Sensor.Value.Unit.Watts", 83} 84 85sampleDcmiSensor = { 86 "instance": 1, 87 "dbus": "/xyz/openbmc_project/sensors/temperature/p0_core0_temp", 88 "record_id": 91, 89} 90 91 92def openYaml(f): 93 return yaml.load(open(f)) 94 95 96def saveYaml(y, f, safe=True): 97 if safe: 98 noaliasDumper = yaml.dumper.SafeDumper 99 noaliasDumper.ignore_aliases = lambda self, data: True 100 yaml.dump( 101 y, open(f, "w"), default_flow_style=False, Dumper=noaliasDumper 102 ) 103 else: 104 yaml.dump(y, open(f, "w")) 105 106 107def getEntityIdAndNamePattern(p, intfs, m): 108 key = (p, intfs) 109 match = m.get(key, None) 110 if match is None: 111 # Workaround for P8's occ sensors, where the path look like 112 # /org/open_power/control/occ_3_0050 113 if ( 114 "/org/open_power/control/occ" in p 115 and "org.open_power.OCC.Status" in intfs 116 ): 117 return (210, "nameLeaf") 118 raise Exception("Unable to find sensor", key, "from map") 119 return (m[key]["entityID"], m[key]["sensorNamePattern"]) 120 121 122# Global entity instances 123entityInstances = {} 124 125 126def getEntityInstance(id): 127 instanceId = entityInstances.get(id, 0) 128 instanceId = instanceId + 1 129 entityInstances[id] = instanceId 130 print("EntityId:", id, "InstanceId:", instanceId) 131 return instanceId 132 133 134def loadRpt(rptFile): 135 sensors = [] 136 with open(rptFile) as f: 137 next(f) 138 next(f) 139 for line in f: 140 fields = line.strip().split("|") 141 fields = list(map(str.strip, fields)) 142 sensor = RptSensor( 143 fields[0], 144 int(fields[2], 16) if fields[2] else None, 145 int(fields[3], 16) if fields[3] else None, 146 int(fields[4], 16) if fields[4] else None, 147 int(fields[5], 16) if fields[5] else None, 148 int(fields[7], 16) if fields[7] else None, 149 fields[9], 150 ) 151 # print(sensor) 152 sensors.append(sensor) 153 return sensors 154 155 156def getDimmTempPath(p): 157 # Convert path like: /sys-0/node-0/motherboard-0/dimmconn-0/dimm-0 158 # to: /xyz/openbmc_project/sensors/temperature/dimm0_temp 159 import re 160 161 dimmconn = re.search(r"dimmconn-\d+", p).group() 162 dimmId = re.search(r"\d+", dimmconn).group() 163 return "/xyz/openbmc_project/sensors/temperature/dimm{}_temp".format( 164 dimmId 165 ) 166 167 168def getMembufTempPath(name): 169 # Convert names like MEMBUF0_Temp or CENTAUR0_Temp 170 # to: /xyz/openbmc_project/sensors/temperature/membuf0_temp 171 # to: /xyz/openbmc_project/sensors/temperature/centaur0_temp 172 return "/xyz/openbmc_project/sensors/temperature/{}".format(name.lower()) 173 174 175def getCoreTempPath(name, p): 176 # For different rpts: 177 # Convert path like: 178 # /sys-0/node-0/motherboard-0/proc_socket-0/module-0/p9_proc_s/eq0/ex0/core0 (for P9) # noqa: E501 179 # to: /xyz/openbmc_project/sensors/temperature/p0_core0_temp 180 # or name like: CORE0_Temp (for P8) 181 # to: /xyz/openbmc_project/sensors/temperature/core0_temp (for P8) 182 import re 183 184 if "p9_proc" in p: 185 splitted = p.split("/") 186 socket = re.search(r"\d+", splitted[4]).group() 187 core = re.search(r"\d+", splitted[9]).group() 188 return ( 189 "/xyz/openbmc_project/sensors/temperature/p{}_core{}_temp".format( 190 socket, core 191 ) 192 ) 193 else: 194 core = re.search(r"\d+", name).group() 195 return "/xyz/openbmc_project/sensors/temperature/core{}_temp".format( 196 core 197 ) 198 199 200def getPowerPath(name): 201 # Convert name like Proc0_Power 202 # to: /xyz/openbmc_project/sensors/power/p0_power 203 import re 204 205 r = re.search(r"\d+", name) 206 if r: 207 index = r.group() 208 else: 209 # Handle cases like IO_A_Power, Storage_Power_A 210 r = re.search(r"_[A|B|C|D]", name).group()[-1] 211 index = str(ord(r) - ord("A")) 212 prefix = "p" 213 m = None 214 if "memory_proc" in name.lower(): 215 prefix = None 216 m = "centaur" 217 elif "pcie_proc" in name.lower(): 218 m = "pcie" 219 elif "io" in name.lower(): 220 m = "io" 221 elif "fan" in name.lower(): 222 m = "fan" 223 elif "storage" in name.lower(): 224 m = "disk" 225 elif "total" in name.lower(): 226 prefix = None 227 m = "total" 228 elif "proc" in name.lower(): 229 # Default 230 pass 231 232 ret = "/xyz/openbmc_project/sensors/power/" 233 if prefix: 234 ret = ret + prefix + index 235 if m: 236 if prefix: 237 ret = ret + "_" + m 238 else: 239 ret = ret + m 240 if prefix is None: 241 ret = ret + index 242 ret = ret + "_power" 243 return ret 244 245 246def getDimmTempConfig(s): 247 r = sampleDimmTemp.copy() 248 r["entityInstance"] = getEntityInstance(r["entityID"]) 249 r["path"] = getDimmTempPath(s.targetPath) 250 return r 251 252 253def getMembufTempConfig(s): 254 r = sampleDimmTemp.copy() 255 r["entityID"] = 0xD1 256 r["entityInstance"] = getEntityInstance(r["entityID"]) 257 r["path"] = getMembufTempPath(s.name) 258 return r 259 260 261def getCoreTempConfig(s): 262 r = sampleCoreTemp.copy() 263 r["entityInstance"] = getEntityInstance(r["entityID"]) 264 r["path"] = getCoreTempPath(s.name, s.targetPath) 265 return r 266 267 268def getPowerConfig(s): 269 r = samplePower.copy() 270 r["entityInstance"] = getEntityInstance(r["entityID"]) 271 r["path"] = getPowerPath(s.name) 272 return r 273 274 275def isCoreTemp(p): 276 import re 277 278 m = re.search(r"p\d+_core\d+_temp", p) 279 return m is not None 280 281 282def getDcmiSensor(i, sensor): 283 import re 284 285 path = sensor["path"] 286 name = path.split("/")[-1] 287 m = re.findall(r"\d+", name) 288 socket, core = int(m[0]), int(m[1]) 289 instance = socket * 24 + core + 1 290 r = sampleDcmiSensor.copy() 291 r["instance"] = instance 292 r["dbus"] = path 293 r["record_id"] = i 294 return r 295 296 297def saveJson(data, f): 298 import json 299 300 with open(f, "w") as outfile: 301 json.dump(data, outfile, indent=4) 302 303 304def main(): 305 parser = argparse.ArgumentParser( 306 description="Yaml tool for updating ipmi sensor yaml config" 307 ) 308 parser.add_argument( 309 "-i", 310 "--input", 311 required=True, 312 dest="input", 313 help="The ipmi sensor yaml config", 314 ) 315 parser.add_argument( 316 "-o", 317 "--output", 318 required=True, 319 dest="output", 320 help="The output yaml file", 321 ) 322 parser.add_argument( 323 "-m", 324 "--map", 325 dest="map", 326 default="sensor_map.yaml", 327 help="The sample map yaml file", 328 ) 329 parser.add_argument( 330 "-r", "--rpt", dest="rpt", help="The .rpt file generated by op-build" 331 ) 332 parser.add_argument( 333 "-f", 334 "--fix", 335 action="store_true", 336 help="Fix entities and sensorNamePattern", 337 ) 338 339 # -g expects output as yaml for mapping of entityID/sensorNamePattern 340 # -d expects output as json config for dcmi sensors 341 # Do not mess the output by enforcing only one argument is passed 342 # TODO: -f and -r could be used together, and they are conflicted with 343 # -g or -d 344 group = parser.add_mutually_exclusive_group() 345 group.add_argument( 346 "-g", 347 "--generate", 348 action="store_true", 349 help="Generate maps for entityID and sensorNamePattern", 350 ) 351 group.add_argument( 352 "-d", 353 "--dcmi", 354 action="store_true", 355 help="Generate dcmi sensors json config", 356 ) 357 358 args = parser.parse_args() 359 args = vars(args) 360 361 if args["input"] is None or args["output"] is None: 362 parser.print_help() 363 exit(1) 364 365 y = openYaml(args["input"]) 366 367 if args["fix"]: 368 # Fix entities and sensorNamePattern 369 m = openYaml(args["map"]) 370 371 for i in y: 372 path = y[i]["path"] 373 intfs = tuple(sorted(list(y[i]["interfaces"].keys()))) 374 entityId, namePattern = getEntityIdAndNamePattern(path, intfs, m) 375 y[i]["entityID"] = entityId 376 y[i]["entityInstance"] = getEntityInstance(entityId) 377 y[i]["sensorNamePattern"] = namePattern 378 print( 379 y[i]["path"], 380 "id:", 381 entityId, 382 "instance:", 383 y[i]["entityInstance"], 384 ) 385 386 sensorIds = list(y.keys()) 387 if args["rpt"]: 388 unhandledSensors = [] 389 rptSensors = loadRpt(args["rpt"]) 390 for s in rptSensors: 391 if s.sensorId is not None and s.sensorId not in sensorIds: 392 print( 393 "Sensor ID", 394 s.sensorId, 395 "not in yaml:", 396 s.name, 397 ", path:", 398 s.targetPath, 399 ) 400 isAdded = False 401 if "temp" in s.name.lower(): 402 if "dimm" in s.targetPath.lower(): 403 y[s.sensorId] = getDimmTempConfig(s) 404 isAdded = True 405 elif "core" in s.targetPath.lower(): 406 y[s.sensorId] = getCoreTempConfig(s) 407 isAdded = True 408 elif ( 409 "centaur" in s.name.lower() 410 or "membuf" in s.name.lower() 411 ): 412 y[s.sensorId] = getMembufTempConfig(s) 413 isAdded = True 414 elif s.name.lower().endswith("_power"): 415 y[s.sensorId] = getPowerConfig(s) 416 isAdded = True 417 418 if isAdded: 419 print( 420 "Added sensor id:", 421 s.sensorId, 422 ", path:", 423 y[s.sensorId]["path"], 424 ) 425 else: 426 unhandledSensors.append(s) 427 428 print("Unhandled sensors:") 429 for s in unhandledSensors: 430 print(s) 431 432 if args["generate"]: 433 m = {} 434 for i in y: 435 path = y[i]["path"] 436 intfs = tuple(sorted(list(y[i]["interfaces"].keys()))) 437 entityId = y[i]["entityID"] 438 sensorNamePattern = y[i]["sensorNamePattern"] 439 m[(path, intfs)] = { 440 "entityID": entityId, 441 "sensorNamePattern": sensorNamePattern, 442 } 443 y = m 444 445 if args["dcmi"]: 446 d = [] 447 for i in y: 448 if isCoreTemp(y[i]["path"]): 449 s = getDcmiSensor(i, y[i]) 450 d.append(s) 451 print(s) 452 saveJson(d, args["output"]) 453 return 454 455 safe = False if args["generate"] else True 456 saveYaml(y, args["output"], safe) 457 458 459if __name__ == "__main__": 460 main() 461