mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-12 07:05:56 -07:00
Enhancement: fallback for missing si network stats (#6367)
This commit is contained in:
@@ -7,13 +7,17 @@ You can include all or some of the available resources. If you do not want to se
|
|||||||
|
|
||||||
The disk path is the path reported by `df` (Mounted On), or the mount point of the disk.
|
The disk path is the path reported by `df` (Mounted On), or the mount point of the disk.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
Any disk you wish to access must be mounted to your container as a volume.
|
||||||
|
|
||||||
The cpu and memory resource information are the container's usage while [glances](glances.md) displays statistics for the host machine on which it is installed.
|
The cpu and memory resource information are the container's usage while [glances](glances.md) displays statistics for the host machine on which it is installed.
|
||||||
|
|
||||||
The resources widget primarily relies on a popular tool called [systeminformation](https://systeminformation.io). Thus, any limitiations of that software apply, for example, BRTFS RAID is not supported for the disk usage. In this case users may want to use the [glances widget](glances.md) instead.
|
The resources widget primarily relies on a popular tool called [systeminformation](https://systeminformation.io). Thus, any limitiations of that software apply, for example, BRTFS RAID is not supported for the disk usage. In this case users may want to use the [glances widget](glances.md) instead.
|
||||||
|
|
||||||
_Note: unfortunately, the package used for getting CPU temp ([systeminformation](https://systeminformation.io)) is not compatible with some setups and will not report any value(s) for CPU temp._
|
!!! warning
|
||||||
|
|
||||||
**Any disk you wish to access must be mounted to your container as a volume.**
|
The package used for getting CPU temp ([systeminformation](https://systeminformation.io)) is not compatible with some setups and will not report any value(s) for CPU temp.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- resources:
|
- resources:
|
||||||
@@ -75,3 +79,10 @@ You can additionally supply an optional `expanded` property set to true in order
|
|||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
To monitor a named host network interface in Docker (for example `network: eno1`), mount host `/sys` (read-only):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- /sys:/sys:ro
|
||||||
|
```
|
||||||
|
|||||||
@@ -90,17 +90,74 @@ describe("pages/api/widgets/resources", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when requested network interface does not exist", async () => {
|
it("returns 404 when requested network interface does not exist", async () => {
|
||||||
si.networkStats.mockResolvedValueOnce([{ iface: "en0" }]);
|
si.networkStats.mockResolvedValueOnce([{ iface: "en0" }]).mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
iface: "missing",
|
||||||
|
operstate: "unknown",
|
||||||
|
rx_bytes: 0,
|
||||||
|
rx_dropped: 0,
|
||||||
|
rx_errors: 0,
|
||||||
|
tx_bytes: 0,
|
||||||
|
tx_dropped: 0,
|
||||||
|
tx_errors: 0,
|
||||||
|
rx_sec: null,
|
||||||
|
tx_sec: null,
|
||||||
|
ms: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const req = { query: { type: "network", interfaceName: "missing" } };
|
const req = { query: { type: "network", interfaceName: "missing" } };
|
||||||
const res = createMockRes();
|
const res = createMockRes();
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
|
|
||||||
|
expect(si.networkStats).toHaveBeenNthCalledWith(1, "*");
|
||||||
|
expect(si.networkStats).toHaveBeenNthCalledWith(2, "missing");
|
||||||
expect(res.statusCode).toBe(404);
|
expect(res.statusCode).toBe(404);
|
||||||
expect(res.body).toEqual({ error: "Interface not found" });
|
expect(res.body).toEqual({ error: "Interface not found" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to direct named interface query when wildcard enumeration misses it", async () => {
|
||||||
|
si.networkStats.mockResolvedValueOnce([{ iface: "eth0", rx_bytes: 1 }]).mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
iface: "eno1",
|
||||||
|
operstate: "up",
|
||||||
|
rx_bytes: 1000,
|
||||||
|
rx_dropped: 0,
|
||||||
|
rx_errors: 0,
|
||||||
|
tx_bytes: 500,
|
||||||
|
tx_dropped: 0,
|
||||||
|
tx_errors: 0,
|
||||||
|
rx_sec: null,
|
||||||
|
tx_sec: null,
|
||||||
|
ms: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const req = { query: { type: "network", interfaceName: "eno1" } };
|
||||||
|
const res = createMockRes();
|
||||||
|
|
||||||
|
await handler(req, res);
|
||||||
|
|
||||||
|
expect(si.networkStats).toHaveBeenNthCalledWith(1, "*");
|
||||||
|
expect(si.networkStats).toHaveBeenNthCalledWith(2, "eno1");
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
expect(res.body.interface).toBe("eno1");
|
||||||
|
expect(res.body.network).toEqual({
|
||||||
|
iface: "eno1",
|
||||||
|
operstate: "up",
|
||||||
|
rx_bytes: 1000,
|
||||||
|
rx_dropped: 0,
|
||||||
|
rx_errors: 0,
|
||||||
|
tx_bytes: 500,
|
||||||
|
tx_dropped: 0,
|
||||||
|
tx_errors: 0,
|
||||||
|
rx_sec: null,
|
||||||
|
tx_sec: null,
|
||||||
|
ms: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("returns default interface network stats", async () => {
|
it("returns default interface network stats", async () => {
|
||||||
si.networkStats.mockResolvedValueOnce([{ iface: "en0", rx_bytes: 1 }]);
|
si.networkStats.mockResolvedValueOnce([{ iface: "en0", rx_bytes: 1 }]);
|
||||||
si.networkInterfaceDefault.mockResolvedValueOnce("en0");
|
si.networkInterfaceDefault.mockResolvedValueOnce("en0");
|
||||||
|
|||||||
@@ -4,6 +4,21 @@ import createLogger from "utils/logger";
|
|||||||
|
|
||||||
const logger = createLogger("resources");
|
const logger = createLogger("resources");
|
||||||
|
|
||||||
|
function isMissingNetworkStat(networkData, interfaceName) {
|
||||||
|
return (
|
||||||
|
networkData.operstate === "unknown" &&
|
||||||
|
networkData.rx_bytes === 0 &&
|
||||||
|
networkData.rx_dropped === 0 &&
|
||||||
|
networkData.rx_errors === 0 &&
|
||||||
|
networkData.tx_bytes === 0 &&
|
||||||
|
networkData.tx_dropped === 0 &&
|
||||||
|
networkData.tx_errors === 0 &&
|
||||||
|
networkData.rx_sec === null &&
|
||||||
|
networkData.tx_sec === null &&
|
||||||
|
networkData.ms === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
const { type, target, interfaceName = "default" } = req.query;
|
const { type, target, interfaceName = "default" } = req.query;
|
||||||
|
|
||||||
@@ -64,6 +79,17 @@ export default async function handler(req, res) {
|
|||||||
logger.debug("networkData:", JSON.stringify(networkData));
|
logger.debug("networkData:", JSON.stringify(networkData));
|
||||||
if (interfaceName && interfaceName !== "default") {
|
if (interfaceName && interfaceName !== "default") {
|
||||||
networkData = networkData.filter((network) => network.iface === interfaceName).at(0);
|
networkData = networkData.filter((network) => network.iface === interfaceName).at(0);
|
||||||
|
if (!networkData) {
|
||||||
|
// Fallback for e.g. docker where networkStats("*") may not return stats for host interfaces
|
||||||
|
const directNetworkData = await si.networkStats(interfaceName);
|
||||||
|
logger.debug("directNetworkData:", JSON.stringify(directNetworkData));
|
||||||
|
networkData = Array.isArray(directNetworkData) ? directNetworkData.at(0) : null;
|
||||||
|
|
||||||
|
// si returns unknown + zeroes when interface truly does not exist
|
||||||
|
if (!networkData || isMissingNetworkStat(networkData, interfaceName)) {
|
||||||
|
networkData = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!networkData) {
|
if (!networkData) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
error: "Interface not found",
|
error: "Interface not found",
|
||||||
|
|||||||
Reference in New Issue
Block a user