Learn › BMC & Server Management › Modern Management
Thermal and power telemetry as clean JSON: pull the Redfish Thermal and Power endpoints with curl, filter readings against critical thresholds with jq, cross-check the Dell shell sensor table, then build the NOC monitoring script on a real machine.
It is 06:52 at the DC-EAST facility in Ashburn, Virginia. Facilities starts pulling hot-aisle containment panels in your hall this week. Scheduled work, fully approved, and guaranteed to disturb the airflow around Bay 14, Row 7, where your Dell PowerEdge R750xs lives.
The NOC wants eyes on that machine for the whole window: live temperatures, live power draw, checked every five minutes, with nobody walking the floor. That request is Work Order DC-EAST-WO-1013.
You already know one way to read this server's sensors: SSH into the Dell shell on the BMC and read the table off the screen. It works, but it needs human eyes every time. The work order asks for something different: the same numbers as clean JSON, machine-readable, so a script can do the watching.
The BMC has spoken JSON all along. That dialect is called Redfish, and this lesson is where you learn to speak it back.
This is a Practice Zone. The diagrams react to you: click the boxes, flip the switches. The practice terminals check only the one command being taught. The real machine comes at the end, under Ready to practice.
Inside the BMC there is exactly one sensor table. Four temperature probes, six fans, three power meters: thirteen sensors, thirteen numbers. Every tool that asks gets its answer from that same table.
What changes is the wire format. IPMI serves the table as compact binary over UDP port 623: fast, universal, unreadable without a tool like ipmitool. Redfish serves the same table as JSON over HTTP on a web port: readable by curl, parseable by any language on Earth.
{ "height": 400, "caption": "One sensor table, two doors. Flip the dialect and watch what never dims.", "nodes": [ { "id": "you", "label": "You (ops)", "kind": "admin", "x": 0, "y": 170, "detail": "Your workstation on the management network. Both doors below answer to it." }, { "id": "msw", "label": "Mgmt switch", "kind": "net", "x": 220, "y": 170, "detail": "The management switch for this row. Both protocols ride the same wire to the same BMC port." }, { "id": "ipmidoor", "label": "UDP 623: IPMI", "kind": "nic", "x": 450, "y": 50, "detail": "Binary request and response. ipmitool speaks it. This is the dialect you used in module 1." }, { "id": "rfdoor", "label": "TCP 5000: Redfish", "kind": "nic", "x": 450, "y": 290, "detail": "HTTP GET plus JSON. curl speaks it. A real iDRAC serves this over HTTPS on port 443." }, { "id": "bmc", "label": "BMC", "kind": "bmc", "x": 680, "y": 170, "detail": "One controller answers both doors, reading from one sensor table." }, { "id": "temps", "label": "Temp sensors", "kind": "sensor", "x": 900, "y": 40, "detail": "CPU0, CPU1, inlet, exhaust. Four probes, one truth." }, { "id": "fans", "label": "Fans", "kind": "sensor", "x": 900, "y": 170, "detail": "Six fans reporting RPM." }, { "id": "psus", "label": "PSUs", "kind": "psu", "x": 900, "y": 300, "detail": "Two supplies reporting watts out." } ], "edges": [ { "from": "you", "to": "msw", "kind": "mgmt" }, { "from": "msw", "to": "ipmidoor", "label": "ipmitool", "kind": "oob" }, { "from": "msw", "to": "rfdoor", "label": "curl + jq", "kind": "mgmt" }, { "from": "ipmidoor", "to": "bmc", "kind": "oob" }, { "from": "rfdoor", "to": "bmc", "kind": "mgmt" }, { "from": "bmc", "to": "temps", "kind": "plain" }, { "from": "bmc", "to": "fans", "kind": "plain" }, { "from": "bmc", "to": "psus", "kind": "power" } ], "toggle": { "label": "Dialect:", "on": "Redfish JSON", "off": "IPMI binary", "dimOn": ["ipmidoor", "e:msw-ipmidoor", "e:ipmidoor-bmc"], "dimOff": ["rfdoor", "e:msw-rfdoor", "e:rfdoor-bmc"] } }
Flip the dialect switch and watch closely: the sensors on the right never dim. Only the door changes. Same thirteen sensors, two representations.
>>> The same reading. There is one set of physical sensors and two presentation layers on top of it. If the two dialects ever disagree about the same sensor, you have not found a second truth, you have found a firmware bug.
Redfish is a REST API: you send an HTTP GET request to a URL, you get JSON back. No agent on the host, no vendor tool, just web plumbing. The whole API hangs off one root, /redfish/v1/, and that is the only URL you ever memorize. Every resource names its children with @odata.id links, so you discover the tree by walking it.
{ "height": 400, "caption": "One root, three branches. Narrow the view to what the work order needs.", "nodes": [ { "id": "you", "label": "You (ops)", "kind": "admin", "x": 0, "y": 180, "detail": "Every hop starts the same way: an HTTP GET with Basic auth." }, { "id": "root", "label": "/redfish/v1/", "kind": "net", "x": 230, "y": 180, "detail": "The service root, the only URL you memorize. Everything else is discovered by following @odata.id links." }, { "id": "systems", "label": "/Systems/1", "kind": "host", "x": 480, "y": 40, "detail": "Inventory lives here: CPUs, DIMMs, BIOS versions." }, { "id": "managers", "label": "/Managers/1", "kind": "bmc", "x": 480, "y": 320, "detail": "The BMC describing itself: firmware version, its own network interface." }, { "id": "chassis", "label": "/Chassis/1", "kind": "net", "x": 480, "y": 180, "detail": "The physical box: power state, plus the links to Thermal and Power." }, { "id": "thermal", "label": "/Thermal", "kind": "sensor", "x": 760, "y": 110, "detail": "Temperatures[] with thresholds, plus Fans[] in RPM." }, { "id": "power", "label": "/Power", "kind": "psu", "x": 760, "y": 260, "detail": "PowerControl[] in watts, plus PowerSupplies[] health and capacity." } ], "edges": [ { "from": "you", "to": "root", "label": "GET + Basic auth", "kind": "mgmt" }, { "from": "root", "to": "systems", "kind": "plain" }, { "from": "root", "to": "chassis", "kind": "plain" }, { "from": "root", "to": "managers", "kind": "plain" }, { "from": "chassis", "to": "thermal", "label": "Temperatures[] Fans[]", "kind": "plain" }, { "from": "chassis", "to": "power", "label": "PowerControl[] PowerSupplies[]", "kind": "power" } ], "toggle": { "label": "Work order scope:", "on": "Monitoring path", "off": "Whole tree", "dimOn": ["systems", "managers", "e:root-systems", "e:root-managers"], "dimOff": [] } }
Telemetry lives under Chassis, the resource that describes the physical box. Today's whole job is two GETs off that one branch.
Time to knock on the door. On the lab machine, $BMC_IP holds the BMC's address and the emulated iDRAC serves Redfish on port 5000. Ask the chassis to describe itself with curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1:
prompt: ops@dc-east-ws01:~$ answer: curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1 output: {"@odata.id":"/redfish/v1/Chassis/1","@odata.type":"#Chassis.v1_20_0.Chassis","ChassisType":"RackMount","Id":"1","Links":{"ComputerSystems":[{"@odata.id":"/redfish/v1/Systems/1"}],"ManagedBy":[{"@odata.id":"/redfish/v1/Managers/1"}]},"Manufacturer":"Dell Inc.","Model":"PowerEdge R750xs","Name":"Computer System Chassis","PartNumber":"0CNCJWA08","Power":{"@odata.id":"/redfish/v1/Chassis/1/Power"},"PowerState":"On","SKU":"SVC1234567","SerialNumber":"SVC1234567","Status":{"Health":"OK","State":"Enabled"},"Thermal":{"@odata.id":"/redfish/v1/Chassis/1/Thermal"}} hint: Type it exactly: curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1
Reading the command left to right: curl makes HTTP requests from the shell, -s keeps progress noise off the screen, -u ADMIN:ADMIN sends the BMC credentials as Basic auth, and the URL names chassis number 1. The reply is real JSON, and nearly unreadable: a wall of data on a single line.
That is why every Redfish command you run this week ends the same way: piped into jq, the command-line JSON processor. Give jq a filter naming the fields you want and it returns just those, pretty-printed. Run the same GET through it:
ops@dc-east-ws01:~$ curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1 | jq '{Model, PowerState, Thermal, Power}'
{
"Model": "PowerEdge R750xs",
"PowerState": "On",
"Thermal": {
"@odata.id": "/redfish/v1/Chassis/1/Thermal"
},
"Power": {
"@odata.id": "/redfish/v1/Chassis/1/Power"
}
}
Four fields, and two of them are signposts: the chassis itself tells you where Thermal and Power live. That is the discovery pattern in action.
>>> Telemetry is a map of your data center: what runs hot, what draws power, where the weak points are. Past the service root, every Redfish request authenticates, even a read. The refusal itself arrives as JSON, because everything in Redfish is JSON, including the ways it says no.
Prove it. Knock on the thermal endpoint without credentials: curl -s http://$BMC_IP:5000/redfish/v1/Chassis/1/Thermal
prompt: ops@dc-east-ws01:~$ answer: curl -s http://$BMC_IP:5000/redfish/v1/Chassis/1/Thermal output: {"error": {"code": "Base.1.0.AccessDenied", "message": "Authentication required"}} hint: Same curl, but drop the -u ADMIN:ADMIN flag and aim at /redfish/v1/Chassis/1/Thermal
Now the endpoint this work order is really about: /redfish/v1/Chassis/1/Thermal. In the lab you will pull it and trim it with jq. Here is the exact readout from the machine you are about to touch:
ops@dc-east-ws01:~$ curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1/Thermal | jq '.Temperatures[] | {Name, ReadingCelsius, UpperThresholdCritical, Health: .Status.Health}'
{
"Name": "CPU0 Temp",
"ReadingCelsius": 55,
"UpperThresholdCritical": 90,
"Health": "OK"
}
{
"Name": "CPU1 Temp",
"ReadingCelsius": 52,
"UpperThresholdCritical": 90,
"Health": "OK"
}
{
"Name": "Inlet Temp",
"ReadingCelsius": 28,
"UpperThresholdCritical": 42,
"Health": "OK"
}
{
"Name": "Exhaust Temp",
"ReadingCelsius": 44,
"UpperThresholdCritical": 75,
"Health": "OK"
}
The jq filter reads as: take the Temperatures array, walk every element with [], and build a small object from each one. Now read one entry the way an operator does:
The row that matters this week is the inlet: reading 28, critical threshold 42. That is 14C of headroom, and the hot-aisle work is aimed straight at it.
Fans ride the same payload in an array called Fans, reporting RPM against lower thresholds: FAN1 turns at 9200 RPM and alarms below 2000, because a fan is a problem when it is slow, not when it is fast.
Thermal has a sibling on the same branch: /redfish/v1/Chassis/1/Power. Same GET, same auth, same jq muscle. Its headline array is PowerControl, the whole-server power picture:
ops@dc-east-ws01:~$ curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1/Power | jq '.PowerControl[0] | {PowerConsumedWatts, PowerCapacityWatts}'
{
"PowerConsumedWatts": 615,
"PowerCapacityWatts": 2200
}
The box draws 615 watts against 2200 of installed capacity. The same payload carries PowerSupplies, one entry per PSU:
ops@dc-east-ws01:~$ curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1/Power | jq '.PowerSupplies[] | {Name, LastPowerOutputWatts, PowerCapacityWatts, Health: .Status.Health}'
{
"Name": "PSU1",
"LastPowerOutputWatts": 310,
"PowerCapacityWatts": 1100,
"Health": "OK"
}
{
"Name": "PSU2",
"LastPowerOutputWatts": 305,
"PowerCapacityWatts": 1100,
"Health": "OK"
}
>>> Near-equal sharing is what redundancy looks like when it is working: two supplies, each carrying about half the load, each with room to carry all of it alone. A 610 and 5 split is the pattern that precedes a 3 a.m. page.
Nobody curls a BMC by hand at 3 a.m. In production, the commands you just learned live inside a script, and cron, the scheduler built into every Linux system, runs that script on a fixed beat: every five minutes, forever. The script compares each ReadingCelsius against its UpperThresholdCritical, feeds a dashboard while the numbers behave, and pages the on-call engineer the moment one crosses.
{ "height": 380, "caption": "Monitoring is a loop. Flip the inlet reading and watch the path change.", "nodes": [ { "id": "inlet", "label": "Inlet sensor", "kind": "sensor", "x": 0, "y": 170, "detail": "The physical probe at the server intake. This is the reading the hot-aisle work could push up." }, { "id": "bmc", "label": "BMC", "kind": "bmc", "x": 210, "y": 170, "detail": "Samples the probe and serves the number to anything that asks." }, { "id": "rfep", "label": "GET /Thermal", "kind": "nic", "x": 420, "y": 170, "detail": "The endpoint monitor.sh polls. JSON in, numbers out." }, { "id": "ws", "label": "monitor.sh (cron)", "kind": "admin", "x": 630, "y": 170, "detail": "Your script on the workstation, run every five minutes by cron. It compares ReadingCelsius to UpperThresholdCritical." }, { "id": "noc", "label": "NOC dashboard", "kind": "host", "x": 860, "y": 60, "detail": "Green tile while the reading stays under the threshold." }, { "id": "alert", "label": "On-call page", "kind": "net", "x": 860, "y": 290, "detail": "Fires when a reading crosses its critical threshold. Thresholds turn data into decisions." } ], "edges": [ { "from": "inlet", "to": "bmc", "label": "sampled", "kind": "plain" }, { "from": "bmc", "to": "rfep", "kind": "mgmt" }, { "from": "rfep", "to": "ws", "label": "JSON every 5 min", "kind": "mgmt" }, { "from": "ws", "to": "noc", "label": "28C < 42C: OK", "kind": "inband" }, { "from": "ws", "to": "alert", "label": "breach: page", "kind": "oob" } ], "toggle": { "label": "Inlet reading:", "on": "28C (normal)", "off": "43C (over critical)", "dimOn": ["alert", "e:ws-alert"], "dimOff": ["noc", "e:ws-noc"] } }
The threshold fields you saw in the JSON are what make this loop able to decide anything at all. Without them, a reading of 43 is just a number. With them, it is a page.
Truth in simulation: this lab's BMC speaks real Redfish JSON, but over plain HTTP on port 5000, with readings that hold still. A production iDRAC serves HTTPS on port 443 with certificates and session tokens, exposes many more sensors, and its numbers drift second to second. The schema, the jq filters, and the muscle memory transfer unchanged.
Time to do it for real. On the machine you are about to open, $BMC_IP is preset, the credentials are ADMIN / ADMIN, and Redfish answers on port 5000. Six objectives close Work Order DC-EAST-WO-1013:
1. File the heat. The full thermal telemetry, as formatted JSON, at ~/thermal.json. 2. File the watts. The full power telemetry, as formatted JSON, at ~/power.json. 3. Thresholds on screen. Each temperature sensor's name, current reading, and critical threshold, side by side, nothing else. 4. Trust, then verify. Pull the Dell shell's sensor table, the readout you learned in module 1, and confirm the new API agrees with it. 5. Build the monitor. The NOC's script at ~/scripts/monitor.sh: both telemetry endpoints, readable output, executable. 6. The challenge. The pre-containment baseline the crew signs off on, at ~/baseline-report.txt. No structure given.
Here is the deal: the workspace shows no commands. It hands you one objective at a time, and you recall the move. That recall is the whole point; it is how this hour's reading becomes your skill.
Stuck is normal. Hit Request a signal on any objective for a nudge, or ask Daemon. It knows exactly which task you are on and will nudge, not spoil.
Your workstation dc-east-ws01 is booting in the bay below, with the Dell BMC on the management network beside it. Facilities pulls the first panel at 06:00 tomorrow. Launch the machine and close the work order before they do.
Practice Redfish Monitoring in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.