LCOV - code coverage report
Current view: top level - pkg/systemd/overview-cards - usageCard.jsx Coverage Total Hit
Test: cockpit Lines: 96.0 % 124 119
Test Date: 2026-07-03 14:25:32

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2019 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5           94 : import React from 'react';
       6              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
       7              : import { Card, CardBody, CardFooter, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
       8              : import { Progress, ProgressMeasureLocation, ProgressVariant } from "@patternfly/react-core/dist/esm/components/Progress/index.js";
       9              : 
      10              : import * as machine_info from "machine-info.js";
      11           94 : import cockpit from "cockpit";
      12              : 
      13              : import "./usageCard.scss";
      14              : 
      15           94 : const _ = cockpit.gettext;
      16              : 
      17           94 : const METRICS_SPEC = {
      18           94 :     payload: "metrics1",
      19           94 :     source: "internal",
      20           94 :     interval: 3000,
      21           94 :     metrics: [
      22           94 :         { name: "cpu.basic.user", derive: "rate" },
      23           94 :         { name: "cpu.basic.system", derive: "rate" },
      24           94 :         { name: "cpu.basic.nice", derive: "rate" },
      25           94 :         { name: "memory.used" },
      26           94 :     ]
      27           94 : };
      28              : 
      29           94 : export class UsageCard extends React.Component {
      30           94 :     constructor(props) {
      31           94 :         super(props);
      32              : 
      33           94 :         this.metrics_channel = null;
      34           94 :         this.samples = [];
      35              : 
      36           94 :         this.state = {
      37           94 :             memTotal: 0, // bytes
      38           94 :             memUsed: 0, // bytes
      39           94 :             memUsedText: " ",
      40           94 :             numCpu: 1, // number
      41           94 :             cpuUsed: 0, // percentage
      42           94 :         };
      43              : 
      44           94 :         this.onVisibilityChange = this.onVisibilityChange.bind(this);
      45           94 :         this.onMetricsUpdate = this.onMetricsUpdate.bind(this);
      46           94 :     }
      47              : 
      48           94 :     componentDidMount() {
      49           94 :         machine_info.cpu_ram_info()
      50           86 :                 .then(info => this.setState({
      51           86 :                     memTotal: info.memory,
      52           86 :                     numCpu: info.cpus,
      53           86 :                 }));
      54           94 :         cockpit.addEventListener("visibilitychange", this.onVisibilityChange);
      55           94 :         this.onVisibilityChange();
      56           94 :     }
      57              : 
      58            0 :     componentWillUnmount() {
      59            0 :         cockpit.removeEventListener("visibilitychange", this.onVisibilityChange);
      60            0 :     }
      61              : 
      62          117 :     onVisibilityChange() {
      63           74 :         if (cockpit.hidden && this.metrics_channel !== null) {
      64           74 :             this.metrics_channel.removeEventListener("message", this.onMetricsUpdate);
      65           74 :             this.metrics_channel.close();
      66           74 :             this.metrics_channel = null;
      67           74 :             return;
      68           74 :         }
      69              : 
      70          116 :         if (!cockpit.hidden && this.metrics_channel === null) {
      71          116 :             this.metrics_channel = cockpit.channel(METRICS_SPEC);
      72            0 :             this.metrics_channel.addEventListener("closed", (ev, error) => console.error("metrics closed:", error));
      73          116 :             this.metrics_channel.addEventListener("message", this.onMetricsUpdate);
      74          116 :         }
      75          117 :     }
      76              : 
      77           88 :     onMetricsUpdate(event, message) {
      78           88 :         const data = JSON.parse(message);
      79              : 
      80              :         // reset state on meta messages
      81           88 :         if (!Array.isArray(data)) {
      82           88 :             this.samples = [];
      83           88 :             return;
      84           88 :         }
      85              : 
      86              :         // decompress
      87           88 :         data.forEach(samples => {
      88           88 :             samples.forEach((sample, i) => {
      89           88 :                 if (sample !== null)
      90           88 :                     this.samples[i] = sample;
      91           88 :             });
      92           88 :         });
      93              : 
      94              :         // CPU metrics are in ms/s; divide by 10 to get percentage
      95           40 :         if (this.samples[0] !== false) {
      96           40 :             const cpu = Math.round((this.samples[0] + this.samples[1] + this.samples[2]) / 10 / this.state.numCpu);
      97           40 :             this.setState({ cpuUsed: cpu });
      98           40 :         }
      99              : 
     100           88 :         let used_text;
     101           44 :         if (this.state.memTotal) {
     102           44 :             const [total_fmt, unit] = cockpit.format_bytes(this.state.memTotal, { base2: true, separate: true, precision: 2 });
     103              :             // FIXME: passing explicit unit is deprecated, redesign this
     104           44 :             const used_fmt = cockpit.format_bytes(this.samples[3], unit, { separate: true, precision: 2 })[0];
     105           44 :             used_text = cockpit.format("$0 / $1 $2", used_fmt, total_fmt, unit);
     106           44 :         } else {
     107           88 :             used_text = " ";
     108           88 :         }
     109              : 
     110           88 :         this.setState({ memUsed: this.samples[3], memUsedText: used_text });
     111           88 :     }
     112              : 
     113           94 :     render() {
     114           86 :         const fraction = this.state.memTotal ? this.state.memUsed / this.state.memTotal : 0;
     115           94 :         const cores_str = cockpit.format(cockpit.ngettext("of $0 CPU", "of $0 CPUs", this.state.numCpu), this.state.numCpu);
     116              : 
     117           94 :         return (
     118           94 :             <Card className="system-usage">
     119           94 :                 <CardTitle>{_("Usage")}</CardTitle>
     120           94 :                 <CardBody>
     121           94 :                     <table className="pf-v6-c-table pf-m-grid-md pf-m-compact">
     122           94 :                         <tbody className="pf-v6-c-table__tbody">
     123           94 :                             <tr className="pf-v6-c-table__tr">
     124           94 :                                 <th className="pf-v6-c-table__th" id="system-usage-cpu-progress" scope="row">{_("CPU")}</th>
     125           94 :                                 <td className="pf-v6-c-table__td">
     126           94 :                                     <Progress value={this.state.cpuUsed}
     127           94 :                                         className="pf-m-sm"
     128           94 :                                         min={0} max={100}
     129           19 :                                         variant={ this.state.cpuUsed > 90 ? ProgressVariant.danger : undefined }
     130           94 :                                         label={ this.state.cpuUsed + '% ' + cores_str }
     131           94 :                                         aria-labelledby="system-usage-cpu-progress"
     132           94 :                                         measureLocation={ProgressMeasureLocation.outside} />
     133           94 :                                 </td>
     134           94 :                             </tr>
     135           94 :                             <tr className="pf-v6-c-table__tr">
     136           94 :                                 <th className="pf-v6-c-table__th" id="system-usage-memory-progress" scope="row">{_("Memory")}</th>
     137           94 :                                 <td className="pf-v6-c-table__td">
     138           94 :                                     <Progress value={this.state.memUsed}
     139           94 :                                         className="pf-m-sm"
     140           94 :                                         min={0} max={this.state.memTotal}
     141           16 :                                         variant={fraction > 0.9 ? ProgressVariant.danger : undefined}
     142           94 :                                         aria-labelledby="system-usage-memory-progress"
     143           94 :                                         label={this.state.memUsedText}
     144           94 :                                         measureLocation={ProgressMeasureLocation.outside} />
     145           94 :                                 </td>
     146           94 :                             </tr>
     147           94 :                         </tbody>
     148           94 :                     </table>
     149           94 :                 </CardBody>
     150           94 :                 <CardFooter>
     151            0 :                     <Button isInline variant="link" component="a" onClick={ev => { ev.preventDefault(); cockpit.jump("/metrics", cockpit.transport.host) }}>
     152           94 :                         {_("View metrics and history")}
     153           94 :                     </Button>
     154           94 :                 </CardFooter>
     155           94 :             </Card>
     156              :         );
     157           94 :     }
     158           94 : }
        

Generated by: LCOV version 2.0-1