banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Hardware

banshee/hardware-collection/hardware/redfish/https/

memory

The health and size of each memory module of a server, and its total memory, by Redfish.

  • hardware
  • server
  • redfish
  • bmc
  • memory
  • dimm
  • https

by banshee1 installation1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 119 lines
// The memory of a server, by the Redfish service of its management card (DMTF Redfish): the health and the
// size of each memory module, and the memory the system counts in all, which drops when the firmware
// disables a failing module at boot. Any card that serves Redfish: Dell iDRAC, HPE iLO, Supermicro, Lenovo
// XCC, OpenBMC.
need('protocol:http');
$address = need('host:address');
$user = need('protocol:http:user');
$password = need('protocol:http:password');

per('module', 'Memory modules');
output('health', 'text', array('label' => 'Health', 'per' => 'module'));
output('size', 'number', array('label' => 'Size', 'unit' => 'MiB', 'per' => 'module'));
output('total', 'number', array('label' => 'Total memory', 'unit' => 'GiB'));

watch.state('unhealthy', 'Memory module degraded', 'health', array('warning' => array('warning'), 'critical' => array('critical')));
watch.expect('total', 'Unexpected total memory', 'total', array(
    'help'    => 'Raises when the total memory is not the size set on the probe, in GiB: a module disabled at boot lowers it.',
    'enabled' => false,
));
watch.missing('missing', 'Memory module missing', 'health', array('enabled' => false));

// Returns one Redfish resource, read by its path (/redfish/v1/...), the account in Basic. The password only
// goes in the Basic header: the answer is not secret.
function redfish_get($address, $path, $user, $password) {
    $answer = http.get('https://' . $address . $path, headers=array('Accept' => 'application/json'), user=$user, password=$password);
    if ($answer['status'] == 401 || $answer['status'] == 403) {
        fail('misconfigured', 'The Redfish service refuses the account, code ' . $answer['status'], $path);
    }
    if ($answer['status'] != 200) {
        fail('unreachable', 'The Redfish service answers code ' . $answer['status'], $path);
    }
    return json.read($answer['body']);
}

// Returns the health of a resource, in lower case (ok, warning, critical): the health it rolls up from what
// is under it when it gives one, its own otherwise, unknown when it gives neither.
function health_of($resource) {
    $status = list.get($resource, 'Status', array());
    $health = list.get($status, 'HealthRollup', '');
    if ($health == '') {
        $health = list.get($status, 'Health', '');
    }
    if ($health == '') {
        return 'unknown';
    }
    return text.lower($health);
}

// Returns the instance name of each item: its name, or, when another item bears the same name, its name
// followed by its id.
function instance_names($items) {
    $counts = array();
    foreach ($items as $item) {
        $counts[$item['name']] = list.get($counts, $item['name'], 0) + 1;
    }
    $names = array();
    foreach ($items as $key => $item) {
        $names[$key] = $item['name'];
        if ($item['name'] == '' || $counts[$item['name']] > 1) {
            $names[$key] = text.trim($item['name'] . ' ' . $item['id']);
        }
        // A card that gives neither a name nor an id (both null, read as '').
        if ($names[$key] == '') {
            $names[$key] = 'unnamed ' . ($key + 1);
        }
    }
    return $names;
}

// A system: {"MemorySummary": {"TotalSystemMemoryGiB": 256}, "Memory": {"@odata.id": ".../Memory"}}. A
// module: {"Id": "DIMM.Socket.A1", "Name": "DIMM A1", "CapacityMiB": 32768, "Status": {"State": "Enabled",
// "Health": "OK"}}. An empty slot is a module the card says absent (HPE iLO lists them).
$items = array();
$total = 0;
$counted = false;
$systems = redfish_get($address, '/redfish/v1/Systems', $user, $password);
foreach (list.get($systems, 'Members', array()) as $member) {
    $system = redfish_get($address, $member['@odata.id'], $user, $password);
    $summary = list.get($system, 'MemorySummary', array());
    if (list.get($summary, 'TotalSystemMemoryGiB', '') != '') {
        $total = $total + $summary['TotalSystemMemoryGiB'];
        $counted = true;
    }
    if (!list.has($system, 'Memory')) {
        continue;
    }
    $modules = redfish_get($address, $system['Memory']['@odata.id'], $user, $password);
    foreach (list.get($modules, 'Members', array()) as $link) {
        $module = redfish_get($address, $link['@odata.id'], $user, $password);
        if (list.get(list.get($module, 'Status', array()), 'State', '') == 'Absent') {
            continue;
        }
        $item = array('name' => list.get($module, 'Name', 'Module'), 'id' => list.get($module, 'Id', ''),
            'health' => health_of($module));
        if (list.get($module, 'CapacityMiB', '') != '') {
            $item['size'] = $module['CapacityMiB'];
        }
        $items = list.add($items, $item);
    }
}

if (list.count($items) == 0) {
    fail('misconfigured', 'The Redfish service serves no memory module',
        'No system links to a Memory collection, or every slot is empty.');
}
$names = instance_names($items);
foreach ($items as $key => $item) {
    value('health', $item['health'], $names[$key]);
    if (list.has($item, 'size')) {
        value('size', $item['size'], $names[$key]);
    }
}
// The sum of the modules when no system gives its total.
if (!$counted) {
    foreach ($items as $item) {
        $total = $total + list.get($item, 'size', 0) / 1024;
    }
}
value('total', number.round($total, 1));