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 07:31:16

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

Generated by: LCOV version 2.0-1