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