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-02 14:11:36

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

Generated by: LCOV version 2.0-1