Line data Source code
1 : /*
2 : * Copyright (C) 2018 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 105 : import cockpit from "cockpit";
7 105 : const _ = cockpit.gettext;
8 :
9 105 : export const cpu_ram_info = () =>
10 105 : cockpit.spawn(["cat", "/proc/meminfo", "/proc/cpuinfo"])
11 96 : .then(text => {
12 96 : const info = { };
13 96 : const memtotal_match = text.match(/MemTotal:[^0-9]*([0-9]+) [kK]B/);
14 96 : const total_kb = memtotal_match && parseInt(memtotal_match[1], 10);
15 96 : if (total_kb)
16 96 : info.memory = total_kb * 1024;
17 :
18 96 : const available_match = text.match(/MemAvailable:[^0-9]*([0-9]+) [kK]B/);
19 96 : const available_kb = available_match && parseInt(available_match[1], 10);
20 96 : if (available_kb)
21 96 : info.available_memory = available_kb * 1024;
22 :
23 96 : const swap_match = text.match(/SwapTotal:[^0-9]*([0-9]+) [kK]B/);
24 96 : const swap_total_kb = swap_match && parseInt(swap_match[1], 10);
25 96 : if (swap_total_kb)
26 96 : info.swap = swap_total_kb * 1024;
27 :
28 96 : let model_match = text.match(/^model name\s*:\s*(.*)$/m);
29 96 : if (!model_match)
30 18 : model_match = text.match(/^cpu\s*:\s*(.*)$/m); // PowerPC
31 96 : if (!model_match)
32 18 : model_match = text.match(/^vendor_id\s*:\s*(.*)$/m); // s390x
33 96 : if (!model_match)
34 18 : model_match = text.match(/^Model Name\s*:\s*(.*)$/m); // LoongArch
35 96 : if (model_match)
36 96 : info.cpu_model = model_match[1];
37 :
38 96 : info.cpus = 0;
39 96 : const re = /^(processor|cpu number)\s*:/gm;
40 96 : while (re.test(text))
41 96 : info.cpus += 1;
42 96 : return info;
43 96 : });
44 :
45 : // https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
46 105 : const chassis_types = [
47 105 : undefined,
48 105 : _("Other"),
49 105 : _("Unknown"),
50 105 : _("Desktop"),
51 105 : _("Low profile desktop"),
52 105 : _("Pizza box"),
53 105 : _("Mini tower"),
54 105 : _("Tower"),
55 105 : _("Portable"),
56 105 : _("Laptop"),
57 105 : _("Notebook"),
58 105 : _("Handheld"),
59 105 : _("Docking station"),
60 105 : _("All-in-one"),
61 105 : _("Sub-Notebook"),
62 105 : _("Space-saving computer"),
63 105 : _("Lunch box"), /* 0x10 */
64 105 : _("Main server chassis"),
65 105 : _("Expansion chassis"),
66 105 : _("Sub-Chassis"),
67 105 : _("Bus expansion chassis"),
68 105 : _("Peripheral chassis"),
69 105 : _("RAID chassis"),
70 105 : _("Rack mount chassis"),
71 105 : _("Sealed-case PC"),
72 105 : _("Multi-system chassis"),
73 105 : _("Compact PCI"), /* 0x1A */
74 105 : _("Advanced TCA"),
75 105 : _("Blade"),
76 105 : _("Blade enclosure"),
77 105 : _("Tablet"),
78 105 : _("Convertible"),
79 105 : _("Detachable"), /* 0x20 */
80 105 : _("IoT gateway"),
81 105 : _("Embedded PC"),
82 105 : _("Mini PC"),
83 105 : _("Stick PC"),
84 105 : ];
85 :
86 84 : function parseDMIFields(text) {
87 84 : const info = {};
88 84 : text.split("\n").forEach(line => {
89 84 : const sep = line.indexOf(':');
90 84 : if (sep <= 0)
91 84 : return;
92 84 : const file = line.slice(0, sep);
93 84 : const key = file.slice(file.lastIndexOf('/') + 1);
94 84 : let value = line.slice(sep + 1);
95 :
96 : // clean up after lazy OEMs
97 84 : if (value.match(/to be filled by o\.?e\.?m\.?/i))
98 17 : value = "";
99 :
100 84 : info[key] = value;
101 :
102 84 : if (key === "chassis_type")
103 17 : info[key + "_str"] = chassis_types[parseInt(value)] || chassis_types[2]; // fall back to "Unknown"
104 84 : });
105 84 : return info;
106 84 : }
107 :
108 94 : export function dmi_info() {
109 : // the grep often/usually exits with 2, that's okay as long as we find *some* information
110 94 : return cockpit.script("grep -r . /sys/class/dmi/id || true", null,
111 94 : { err: "message", superuser: "try" })
112 84 : .then((output) => parseDMIFields(output));
113 94 : }
114 :
115 : // decode a binary Uint8Array with a trailing null byte
116 0 : function decode_proc_str(s) {
117 0 : return new TextDecoder().decode(s.slice(0, -1));
118 0 : }
119 :
120 2 : export function devicetree_info() {
121 2 : let model;
122 2 : let serial;
123 :
124 2 : return Promise.all([
125 : // these succeed with content === null if files are absent
126 2 : cockpit.file("/proc/device-tree/model", { binary: true }).read()
127 1 : .then(content => { model = content ? decode_proc_str(content) : null }),
128 2 : cockpit.file("/proc/device-tree/serial-number", { binary: true }).read()
129 1 : .then(content => { serial = content ? decode_proc_str(content) : null }),
130 2 : ])
131 2 : .then(() => ({ model, serial }));
132 2 : }
133 :
134 : /* we expect udev db paragraphs like this:
135 : *
136 : P: /devices/virtual/mem/null
137 : N: null
138 : E: DEVMODE=0666
139 : E: DEVNAME=/dev/null
140 : E: SUBSYSTEM=mem
141 : */
142 :
143 2 : const udevPathRE = /^P: (.*)$/;
144 2 : const udevPropertyRE = /^E: (\w+)=(.*)$/;
145 :
146 2 : function parseUdevDB(text) {
147 2 : const info = {};
148 2 : text.split("\n\n").forEach(paragraph => {
149 2 : let syspath = null;
150 2 : const props = {};
151 :
152 2 : paragraph = paragraph.trim();
153 2 : if (!paragraph)
154 2 : return;
155 :
156 2 : paragraph.split("\n").forEach(line => {
157 2 : let match = line.match(udevPathRE);
158 2 : if (match) {
159 2 : syspath = match[1];
160 2 : } else {
161 2 : match = line.match(udevPropertyRE);
162 2 : if (match)
163 2 : props[match[1]] = match[2];
164 2 : }
165 2 : });
166 :
167 2 : if (syspath)
168 1 : info[syspath] = props;
169 : else
170 1 : console.log("udev database paragraph is missing P:", paragraph);
171 2 : });
172 2 : return info;
173 2 : }
174 :
175 2 : export function udev_info() {
176 2 : return cockpit.spawn(["udevadm", "info", "--export-db"], { err: "message" })
177 2 : .then(output => parseUdevDB(output));
178 2 : }
179 :
180 2 : const memoryRE = /^([ \w]+): (.*)/;
181 :
182 : // Process the dmidecode output and create a mapping of locator to DIMM properties
183 2 : function parseMemoryInfo(text) {
184 2 : const info = {};
185 2 : text.split("\n\n").forEach(paragraph => {
186 2 : let locator = null;
187 2 : let bankLocator = null;
188 2 : const props = {};
189 2 : paragraph = paragraph.trim();
190 2 : if (!paragraph)
191 2 : return;
192 :
193 2 : paragraph.split("\n").forEach(line => {
194 2 : line = line.trim();
195 2 : const match = line.match(memoryRE);
196 2 : if (match)
197 2 : props[match[1]] = match[2];
198 2 : });
199 :
200 2 : locator = props.Locator;
201 2 : bankLocator = props['Bank Locator'];
202 2 : if (locator)
203 2 : info[bankLocator + locator] = props;
204 2 : });
205 2 : return processMemory(info);
206 2 : }
207 :
208 : // Select the useful properties to display
209 2 : function processMemory(info) {
210 2 : const memoryArray = [];
211 :
212 2 : for (const dimm in info) {
213 2 : const memoryProperty = info[dimm];
214 :
215 1 : let memorySize = memoryProperty.Size || _("Unknown");
216 1 : if (memorySize.includes("MB")) {
217 1 : const memorySizeValue = parseInt(memorySize, 10);
218 1 : memorySize = cockpit.format(_("$0 GiB"), memorySizeValue / 1024);
219 1 : }
220 :
221 2 : let memoryTechnology = memoryProperty["Memory technology"];
222 1 : if (!memoryTechnology || memoryTechnology == "<OUT OF SPEC>")
223 2 : memoryTechnology = _("Unknown");
224 :
225 1 : let memoryRank = memoryProperty.Rank || _("Unknown");
226 2 : if (memoryRank == 1)
227 1 : memoryRank = _("Single rank");
228 2 : if (memoryRank == 2)
229 1 : memoryRank = _("Dual rank");
230 :
231 2 : memoryArray.push({
232 1 : locator: (memoryProperty['Bank Locator'] + ': ' + memoryProperty.Locator) || _("Unknown"),
233 2 : technology: memoryTechnology,
234 1 : type: memoryProperty.Type || _("Unknown"),
235 2 : size: memorySize,
236 1 : state: memoryProperty["Total Width"] == "Unknown" ? _("Absent") : _("Present"),
237 2 : rank: memoryRank,
238 1 : speed: memoryProperty.Speed || _("Unknown")
239 2 : });
240 2 : }
241 :
242 2 : return memoryArray;
243 2 : }
244 :
245 2 : export function memory_info() {
246 2 : return cockpit.spawn(["dmidecode", "-t", "memory"], { environ: ["LC_ALL=C"], err: "message", superuser: "try" })
247 2 : .then(output => parseMemoryInfo(output));
248 2 : }
|