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
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 });
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|