You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

163 lines
5.5 KiB
JavaScript

"use strict";
const DataLoader = require("dataloader");
const dlayerSource = require("../dlayer-source");
const dlayerWrap = require("../dlayer-wrap");
const nvmeCLI = require("../exec-nvme-cli");
const smartctl = require("../exec-smartctl");
const evaluateSingle = require("../evaluate-single");
const evaluateAndPick = require("../evaluate-and-pick");
const caseSnakeUpper = require("../case-snake-upper");
const map = require("../map");
const mapFlat = require("../map-flat");
const mapFromSource = require("../map-from-source");
function generateNamespacePaths(basePath, namespaceIDs) {
return map(namespaceIDs, (namespaceID) => {
return `${basePath}n${namespaceID}`;
});
}
module.exports = {
name: "sysquery.drives",
makeContext: function () {
return {
nvmeIdentifyController: new DataLoader(evaluateSingle({
command: (path) => nvmeCLI.identifyController({ devicePath: path })
})),
nvmeListNamespaces: new DataLoader(evaluateSingle({
command: (path) => nvmeCLI.listNamespaces({ devicePath: path })
})),
smartctlScan: new DataLoader(evaluateAndPick({
command: smartctl.scan,
selectID: (device) => device.path
})),
smartctlInfo: new DataLoader(evaluateSingle({
command: (path) => dlayerWrap(() => smartctl.info({ devicePath: path }), {
allowedErrors: [ smartctl.InfoError ]
})
})),
smartctlAttributes: new DataLoader(evaluateSingle({
command: (path) => dlayerWrap(() => smartctl.attributes({ devicePath: path }), {
allowedErrors: [ smartctl.AttributesError ]
})
})),
};
},
types: {
"sysquery.drives.Drive": function ({ path }) {
return {
path: path,
blockDevice: async function(_, { $getProperty, $make }) {
// NVMe controllers do not have a single block device at the root; but rather one or more 'namespaces', which may each be block devices
if (await $getProperty(this, "interface") === "nvme") {
return null;
} else {
return $make("sysquery.blockDevices.BlockDevice", { path: path });
}
},
allBlockDevices: async function(_, { nvmeListNamespaces, $getProperty, $make }) {
let rootPaths = (await $getProperty(this, "interface") === "nvme")
? generateNamespacePaths(path, await nvmeListNamespaces.load(path))
: [ path ];
return mapFlat(rootPaths, (path) => {
return $make("sysquery.blockDevices.BlockDevice", { path: path });
});
},
size: async function (_, { nvmeIdentifyController, $getProperty, $make }) {
if (await $getProperty(this, "interface") === "nvme") {
let controllerData = await nvmeIdentifyController.load(path);
return controllerData.totalSpace;
} else {
// NOTE: We're using the BlockDevice module for this because SMART info is not (reliably) available on all controllers
// TODO: Find a better way to obtain this number, that doesn't require the blockDevices module
let blockDevice = await $make("sysquery.blockDevices.BlockDevice", { path: path });
return $getProperty(blockDevice, "size");
}
},
... dlayerSource("smartctlScan", {
[dlayerSource.ID]: path,
interface: "interface"
}),
... dlayerSource("smartctlInfo", {
[dlayerSource.ID]: path,
// NOTE: We allow allowable errors here because the SMART subsystem failing doesn't affect any other aspect of the drive's information, so the Drive object as a whole should not yield an error
[dlayerSource.AllowErrors]: true,
model: "model",
modelFamily: "modelFamily",
smartAvailable: "smartAvailable",
smartEnabled: "smartEnabled",
serialNumber: "serialNumber",
wwn: "wwn",
firmwareVersion: "firmwareVersion",
// size: "size",
rpm: "rpm",
logicalSectorSize: (device) => device.sectorSizes.logical,
physicalSectorSize: (device) => device.sectorSizes.physical,
formFactor: "formFactor",
ataVersion: "ataVersion",
sataVersion: "sataVersion"
}),
... dlayerSource("smartctlAttributes", {
[dlayerSource.ID]: path,
[dlayerSource.AllowErrors]: true,
smartFunctioning: (attributes) => {
return (attributes.isOK);
},
smartAttributes: (attributesResult) => {
if (attributesResult.isOK) {
let attributes = attributesResult.value();
return attributes.map((attribute) => {
return {
... attribute,
type: caseSnakeUpper(attribute.type),
updatedWhen: caseSnakeUpper(attribute.updatedWhen)
};
});
} else {
return [];
}
},
smartHealth: (attributesResult) => {
if (attributesResult.isOK) {
let attributes = attributesResult.value();
// FIXME: This is getting values in an inconsistent format? Different for SATA vs. NVMe
let failed = attributes.filter((item) => {
return (item.failingNow === true || item.failedBefore === true);
});
let deteriorating = attributes.filter((item) => {
return (item.type === "preFail" && item.worstValueSeen < 100);
});
if (failed.length > 0) {
return "FAILING";
} else if (deteriorating.length > 0) {
return "DETERIORATING";
} else {
return "HEALTHY";
}
} else {
// We can't get SMART data
return "UNKNOWN";
}
}
})
};
}
},
extensions: {},
root: {
hardware: {
drives: function ({ paths }, { smartctlScan, $make }) {
return mapFromSource(smartctlScan, paths, (device) => {
return $make("sysquery.drives.Drive", { path: device.path });
});
}
}
}
};