| #!/usr/bin/env python3 |
| |
| import yaml |
| import argparse |
| |
| from typing import NamedTuple |
| |
| |
| class RptSensor(NamedTuple): |
| name: str |
| entityId: int |
| typeId: int |
| evtType: int |
| sensorId: int |
| fru: int |
| targetPath: str |
| |
| |
| sampleDimmTemp = { |
| 'bExp': 0, |
| 'entityID': 32, |
| 'entityInstance': 2, |
| 'interfaces': { |
| 'xyz.openbmc_project.Sensor.Value': { |
| 'Value': { |
| 'Offsets': { |
| 255: { |
| 'type': 'int64_t' |
| } |
| } |
| } |
| } |
| }, |
| 'multiplierM': 1, |
| 'mutability': 'Mutability::Write|Mutability::Read', |
| 'offsetB': -127, |
| 'path': '/xyz/openbmc_project/sensors/temperature/dimm0_temp', |
| 'rExp': 0, |
| 'readingType': 'readingData', |
| 'scale': -3, |
| 'sensorNamePattern': 'nameLeaf', |
| 'sensorReadingType': 1, |
| 'sensorType': 1, |
| 'serviceInterface': 'org.freedesktop.DBus.Properties', |
| 'unit': 'xyz.openbmc_project.Sensor.Value.Unit.DegreesC' |
| } |
| sampleCoreTemp = { |
| 'bExp': 0, |
| 'entityID': 208, |
| 'entityInstance': 2, |
| 'interfaces': { |
| 'xyz.openbmc_project.Sensor.Value': { |
| 'Value': { |
| 'Offsets': { |
| 255: { |
| 'type': 'int64_t' |
| } |
| } |
| } |
| } |
| }, |
| 'multiplierM': 1, |
| 'mutability': 'Mutability::Write|Mutability::Read', |
| 'offsetB': -127, |
| 'path': '/xyz/openbmc_project/sensors/temperature/p0_core0_temp', |
| 'rExp': 0, |
| 'readingType': 'readingData', |
| 'scale': -3, |
| 'sensorNamePattern': 'nameLeaf', |
| 'sensorReadingType': 1, |
| 'sensorType': 1, |
| 'serviceInterface': 'org.freedesktop.DBus.Properties', |
| 'unit': 'xyz.openbmc_project.Sensor.Value.Unit.DegreesC' |
| } |
| samplePower = { |
| 'bExp': 0, |
| 'entityID': 10, |
| 'entityInstance': 13, |
| 'interfaces': { |
| 'xyz.openbmc_project.Sensor.Value': { |
| 'Value': { |
| 'Offsets': { |
| 255: { |
| 'type': 'int64_t' |
| } |
| } |
| } |
| } |
| }, |
| 'multiplierM': 2, |
| 'offsetB': 0, |
| 'path': '/xyz/openbmc_project/sensors/power/p0_power', |
| 'rExp': 0, |
| 'readingType': 'readingData', |
| 'scale': -6, |
| 'sensorNamePattern': 'nameLeaf', |
| 'sensorReadingType': 1, |
| 'sensorType': 8, |
| 'serviceInterface': 'org.freedesktop.DBus.Properties', |
| 'unit': 'xyz.openbmc_project.Sensor.Value.Unit.Watts' |
| } |
| |
| sampleDcmiSensor = { |
| "instance": 1, |
| "dbus": "/xyz/openbmc_project/sensors/temperature/p0_core0_temp", |
| "record_id": 91 |
| } |
| |
| |
| def openYaml(f): |
| return yaml.load(open(f)) |
| |
| |
| def saveYaml(y, f, safe=True): |
| if safe: |
| noaliasDumper = yaml.dumper.SafeDumper |
| noaliasDumper.ignore_aliases = lambda self, data: True |
| yaml.dump(y, open(f, "w"), default_flow_style=False, |
| Dumper=noaliasDumper) |
| else: |
| yaml.dump(y, open(f, "w")) |
| |
| |
| def getEntityIdAndNamePattern(p, intfs, m): |
| key = (p, intfs) |
| match = m.get(key, None) |
| if match is None: |
| # Workaround for P8's occ sensors, where the path look like |
| # /org/open_power/control/occ_3_0050 |
| if '/org/open_power/control/occ' in p \ |
| and 'org.open_power.OCC.Status' in intfs: |
| return (210, 'nameLeaf') |
| raise Exception('Unable to find sensor', key, 'from map') |
| return (m[key]['entityID'], m[key]['sensorNamePattern']) |
| |
| |
| # Global entity instances |
| entityInstances = {} |
| |
| |
| def getEntityInstance(id): |
| instanceId = entityInstances.get(id, 0) |
| instanceId = instanceId + 1 |
| entityInstances[id] = instanceId |
| print("EntityId:", id, "InstanceId:", instanceId) |
| return instanceId |
| |
| |
| def loadRpt(rptFile): |
| sensors = [] |
| with open(rptFile) as f: |
| next(f) |
| next(f) |
| for line in f: |
| fields = line.strip().split('|') |
| fields = list(map(str.strip, fields)) |
| sensor = RptSensor( |
| fields[0], |
| int(fields[2], 16) if fields[2] else None, |
| int(fields[3], 16) if fields[3] else None, |
| int(fields[4], 16) if fields[4] else None, |
| int(fields[5], 16) if fields[5] else None, |
| int(fields[7], 16) if fields[7] else None, |
| fields[9]) |
| # print(sensor) |
| sensors.append(sensor) |
| return sensors |
| |
| |
| def getDimmTempPath(p): |
| # Convert path like: /sys-0/node-0/motherboard-0/dimmconn-0/dimm-0 |
| # to: /xyz/openbmc_project/sensors/temperature/dimm0_temp |
| import re |
| dimmconn = re.search(r'dimmconn-\d+', p).group() |
| dimmId = re.search(r'\d+', dimmconn).group() |
| return '/xyz/openbmc_project/sensors/temperature/dimm{}_temp'.format(dimmId) |
| |
| |
| def getMembufTempPath(name): |
| # Convert names like MEMBUF0_Temp or CENTAUR0_Temp |
| # to: /xyz/openbmc_project/sensors/temperature/membuf0_temp |
| # to: /xyz/openbmc_project/sensors/temperature/centaur0_temp |
| return '/xyz/openbmc_project/sensors/temperature/{}'.format(name.lower()) |
| |
| |
| def getCoreTempPath(name, p): |
| # For different rpts: |
| # Convert path like: |
| # /sys-0/node-0/motherboard-0/proc_socket-0/module-0/p9_proc_s/eq0/ex0/core0 (for P9) |
| # to: /xyz/openbmc_project/sensors/temperature/p0_core0_temp |
| # or name like: CORE0_Temp (for P8) |
| # to: /xyz/openbmc_project/sensors/temperature/core0_temp (for P8) |
| import re |
| if 'p9_proc' in p: |
| splitted = p.split('/') |
| socket = re.search(r'\d+', splitted[4]).group() |
| core = re.search(r'\d+', splitted[9]).group() |
| return '/xyz/openbmc_project/sensors/temperature/p{}_core{}_temp'.format(socket, core) |
| else: |
| core = re.search(r'\d+', name).group() |
| return '/xyz/openbmc_project/sensors/temperature/core{}_temp'.format(core) |
| |
| |
| def getPowerPath(name): |
| # Convert name like Proc0_Power |
| # to: /xyz/openbmc_project/sensors/power/p0_power |
| import re |
| r = re.search(r'\d+', name) |
| if r: |
| index = r.group() |
| else: |
| # Handle cases like IO_A_Power, Storage_Power_A |
| r = re.search(r'_[A|B|C|D]', name).group()[-1] |
| index = str(ord(r) - ord('A')) |
| prefix = 'p' |
| m = None |
| if 'memory_proc' in name.lower(): |
| prefix = None |
| m = 'centaur' |
| elif 'pcie_proc' in name.lower(): |
| m = 'pcie' |
| elif 'io' in name.lower(): |
| m = 'io' |
| elif 'fan' in name.lower(): |
| m = 'fan' |
| elif 'storage' in name.lower(): |
| m = 'disk' |
| elif 'total' in name.lower(): |
| prefix = None |
| m = 'total' |
| elif 'proc' in name.lower(): |
| # Default |
| pass |
| |
| ret = '/xyz/openbmc_project/sensors/power/' |
| if prefix: |
| ret = ret + prefix + index |
| if m: |
| if prefix: |
| ret = ret + '_' + m |
| else: |
| ret = ret + m |
| if prefix is None: |
| ret = ret + index |
| ret = ret + '_power' |
| return ret |
| |
| |
| def getDimmTempConfig(s): |
| r = sampleDimmTemp.copy() |
| r['entityInstance'] = getEntityInstance(r['entityID']) |
| r['path'] = getDimmTempPath(s.targetPath) |
| return r |
| |
| |
| def getMembufTempConfig(s): |
| r = sampleDimmTemp.copy() |
| r['entityID'] = 0xD1 |
| r['entityInstance'] = getEntityInstance(r['entityID']) |
| r['path'] = getMembufTempPath(s.name) |
| return r |
| |
| |
| def getCoreTempConfig(s): |
| r = sampleCoreTemp.copy() |
| r['entityInstance'] = getEntityInstance(r['entityID']) |
| r['path'] = getCoreTempPath(s.name, s.targetPath) |
| return r |
| |
| |
| def getPowerConfig(s): |
| r = samplePower.copy() |
| r['entityInstance'] = getEntityInstance(r['entityID']) |
| r['path'] = getPowerPath(s.name) |
| return r |
| |
| |
| def isCoreTemp(p): |
| import re |
| m = re.search(r'p\d+_core\d+_temp', p) |
| return m is not None |
| |
| |
| def getDcmiSensor(i, sensor): |
| import re |
| path = sensor['path'] |
| name = path.split('/')[-1] |
| m = re.findall(r'\d+', name) |
| socket, core = int(m[0]), int(m[1]) |
| instance = socket * 24 + core + 1 |
| r = sampleDcmiSensor.copy() |
| r['instance'] = instance |
| r['dbus'] = path |
| r['record_id'] = i |
| return r |
| |
| |
| def saveJson(data, f): |
| import json |
| with open(f, 'w') as outfile: |
| json.dump(data, outfile, indent=4) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description='Yaml tool for updating ipmi sensor yaml config') |
| parser.add_argument('-i', '--input', required=True, dest='input', |
| help='The ipmi sensor yaml config') |
| parser.add_argument('-o', '--output', required=True, dest='output', |
| help='The output yaml file') |
| parser.add_argument('-m', '--map', dest='map', default='sensor_map.yaml', |
| help='The sample map yaml file') |
| parser.add_argument('-r', '--rpt', dest='rpt', |
| help='The .rpt file generated by op-build') |
| parser.add_argument('-f', '--fix', action='store_true', |
| help='Fix entities and sensorNamePattern') |
| |
| # -g expects output as yaml for mapping of entityID/sensorNamePattern |
| # -d expects output as json config for dcmi sensors |
| # Do not mess the output by enforcing only one argument is passed |
| # TODO: -f and -r could be used together, and they are conflicted with -g or -d |
| group = parser.add_mutually_exclusive_group() |
| group.add_argument('-g', '--generate', action='store_true', |
| help='Generate maps for entityID and sensorNamePattern') |
| group.add_argument('-d', '--dcmi', action='store_true', |
| help='Generate dcmi sensors json config') |
| |
| args = parser.parse_args() |
| args = vars(args) |
| |
| if args['input'] is None or args['output'] is None: |
| parser.print_help() |
| exit(1) |
| |
| y = openYaml(args['input']) |
| |
| if args['fix']: |
| # Fix entities and sensorNamePattern |
| m = openYaml(args['map']) |
| |
| for i in y: |
| path = y[i]['path'] |
| intfs = tuple(sorted(list(y[i]['interfaces'].keys()))) |
| entityId, namePattern = getEntityIdAndNamePattern(path, intfs, m) |
| y[i]['entityID'] = entityId |
| y[i]['entityInstance'] = getEntityInstance(entityId) |
| y[i]['sensorNamePattern'] = namePattern |
| print(y[i]['path'], "id:", entityId, |
| "instance:", y[i]['entityInstance']) |
| |
| sensorIds = list(y.keys()) |
| if args['rpt']: |
| unhandledSensors = [] |
| rptSensors = loadRpt(args['rpt']) |
| for s in rptSensors: |
| if s.sensorId is not None and s.sensorId not in sensorIds: |
| print("Sensor ID", s.sensorId, "not in yaml:", |
| s.name, ", path:", s.targetPath) |
| isAdded = False |
| if 'temp' in s.name.lower(): |
| if 'dimm' in s.targetPath.lower(): |
| y[s.sensorId] = getDimmTempConfig(s) |
| isAdded = True |
| elif 'core' in s.targetPath.lower(): |
| y[s.sensorId] = getCoreTempConfig(s) |
| isAdded = True |
| elif 'centaur' in s.name.lower() or 'membuf' in s.name.lower(): |
| y[s.sensorId] = getMembufTempConfig(s) |
| isAdded = True |
| elif s.name.lower().endswith('_power'): |
| y[s.sensorId] = getPowerConfig(s) |
| isAdded = True |
| |
| if isAdded: |
| print('Added sensor id:', s.sensorId, |
| ', path:', y[s.sensorId]['path']) |
| else: |
| unhandledSensors.append(s) |
| |
| print('Unhandled sensors:') |
| for s in unhandledSensors: |
| print(s) |
| |
| if args['generate']: |
| m = {} |
| for i in y: |
| path = y[i]['path'] |
| intfs = tuple(sorted(list(y[i]['interfaces'].keys()))) |
| entityId = y[i]['entityID'] |
| sensorNamePattern = y[i]['sensorNamePattern'] |
| m[(path, intfs)] = {'entityID': entityId, |
| 'sensorNamePattern': sensorNamePattern} |
| y = m |
| |
| if args['dcmi']: |
| d = [] |
| for i in y: |
| if isCoreTemp(y[i]['path']): |
| s = getDcmiSensor(i, y[i]) |
| d.append(s) |
| print(s) |
| saveJson(d, args['output']) |
| return |
| |
| safe = False if args['generate'] else True |
| saveYaml(y, args['output'], safe) |
| |
| |
| if __name__ == "__main__": |
| main() |