View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.demo.gui;
6   
7   import java.awt.BorderLayout;
8   import java.util.ArrayList;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Locale;
12  import java.util.Map;
13  import java.util.Map.Entry;
14  
15  import javax.swing.ButtonGroup;
16  import javax.swing.JLabel;
17  import javax.swing.JPanel;
18  import javax.swing.JRadioButton;
19  import javax.swing.JScrollPane;
20  import javax.swing.JTable;
21  import javax.swing.ScrollPaneConstants;
22  import javax.swing.Timer;
23  import javax.swing.table.DefaultTableModel;
24  import javax.swing.table.TableColumn;
25  import javax.swing.table.TableColumnModel;
26  import javax.swing.table.TableModel;
27  
28  import oshi.PlatformEnum;
29  import oshi.SystemInfo;
30  import oshi.software.os.OSProcess;
31  import oshi.software.os.OperatingSystem;
32  import oshi.util.FormatUtil;
33  
34  /**
35   * Displays a process list, such as ps or task manager. This performs more like Windows Task Manger with current CPU as
36   * measured between polling intervals, while PS uses a cumulative CPU value.
37   */
38  public class ProcessPanel extends OshiJPanel { // NOSONAR squid:S110
39  
40      private static final long serialVersionUID = 1L;
41  
42      private static final String PROCESSES = "Processes";
43      private static final String[] COLUMNS = { "PID", "PPID", "Threads", "% CPU", "Cumulative", "VSZ", "RSS", "% Memory",
44              "Process Name" };
45      private static final double[] COLUMN_WIDTH_PERCENT = { 0.07, 0.07, 0.07, 0.07, 0.09, 0.1, 0.1, 0.08, 0.35 };
46  
47      private transient Map<Integer, OSProcess> priorSnapshotMap = new HashMap<>();
48  
49      private transient ButtonGroup cpuOption = new ButtonGroup();
50      private transient JRadioButton perProc = new JRadioButton("of one Processor");
51      private transient JRadioButton perSystem = new JRadioButton("of System");
52  
53      private transient ButtonGroup sortOption = new ButtonGroup();
54      private transient JRadioButton cpuButton = new JRadioButton("CPU %");
55      private transient JRadioButton cumulativeCpuButton = new JRadioButton("Cumulative CPU");
56      private transient JRadioButton memButton = new JRadioButton("Memory %");
57  
58      public ProcessPanel(SystemInfo si) {
59          super();
60          init(si);
61      }
62  
63      private void init(SystemInfo si) {
64          OperatingSystem os = si.getOperatingSystem();
65          JLabel procLabel = new JLabel(PROCESSES);
66          add(procLabel, BorderLayout.NORTH);
67  
68          JPanel settings = new JPanel();
69  
70          JLabel cpuChoice = new JLabel("          CPU %:");
71          settings.add(cpuChoice);
72          cpuOption.add(perProc);
73          settings.add(perProc);
74          cpuOption.add(perSystem);
75          settings.add(perSystem);
76          if (SystemInfo.getCurrentPlatform().equals(PlatformEnum.WINDOWS)) {
77              perSystem.setSelected(true);
78          } else {
79              perProc.setSelected(true);
80          }
81  
82          JLabel sortChoice = new JLabel("          Sort by:");
83          settings.add(sortChoice);
84          sortOption.add(cpuButton);
85          settings.add(cpuButton);
86          sortOption.add(cumulativeCpuButton);
87          settings.add(cumulativeCpuButton);
88          sortOption.add(memButton);
89          settings.add(memButton);
90          cpuButton.setSelected(true);
91  
92          TableModel model = new DefaultTableModel(parseProcesses(os.getProcesses(null, null, 0), si), COLUMNS);
93          JTable procTable = new JTable(model);
94          JScrollPane scrollV = new JScrollPane(procTable);
95          scrollV.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
96          resizeColumns(procTable.getColumnModel());
97  
98          add(scrollV, BorderLayout.CENTER);
99          add(settings, BorderLayout.SOUTH);
100 
101         Timer timer = new Timer(Config.REFRESH_SLOW, e -> {
102             DefaultTableModel tableModel = (DefaultTableModel) procTable.getModel();
103             Object[][] newData = parseProcesses(os.getProcesses(null, null, 0), si);
104             int rowCount = tableModel.getRowCount();
105             for (int row = 0; row < newData.length; row++) {
106                 if (row < rowCount) {
107                     // Overwrite row
108                     for (int col = 0; col < newData[row].length; col++) {
109                         tableModel.setValueAt(newData[row][col], row, col);
110                     }
111                 } else {
112                     // Add row
113                     tableModel.addRow(newData[row]);
114                 }
115             }
116             // Delete any extra rows
117             for (int row = rowCount - 1; row >= newData.length; row--) {
118                 tableModel.removeRow(row);
119             }
120         });
121         timer.start();
122     }
123 
124     private Object[][] parseProcesses(List<OSProcess> list, SystemInfo si) {
125         long totalMem = si.getHardware().getMemory().getTotal();
126         int cpuCount = si.getHardware().getProcessor().getLogicalProcessorCount();
127         // Build a map with a value for each process to control the sort
128         Map<OSProcess, Double> processSortValueMap = new HashMap<>();
129         for (OSProcess p : list) {
130             int pid = p.getProcessID();
131             // Ignore the Idle process on Windows
132             if (pid > 0 || !SystemInfo.getCurrentPlatform().equals(PlatformEnum.WINDOWS)) {
133                 // Set up for appropriate sort
134                 if (cpuButton.isSelected()) {
135                     processSortValueMap.put(p, p.getProcessCpuLoadBetweenTicks(priorSnapshotMap.get(pid)));
136                 } else if (cumulativeCpuButton.isSelected()) {
137                     processSortValueMap.put(p, p.getProcessCpuLoadCumulative());
138                 } else {
139                     processSortValueMap.put(p, (double) p.getResidentSetSize());
140                 }
141             }
142         }
143         // Now sort the list by the values
144         List<Entry<OSProcess, Double>> procList = new ArrayList<>(processSortValueMap.entrySet());
145         procList.sort(Entry.comparingByValue());
146         // Insert into array in reverse order (lowest sort value last)
147         int i = procList.size();
148         Object[][] procArr = new Object[i][COLUMNS.length];
149         // These are in descending CPU order
150         for (Entry<OSProcess, Double> e : procList) {
151             OSProcess p = e.getKey();
152             // Matches order of COLUMNS field
153             i--;
154             int pid = p.getProcessID();
155             procArr[i][0] = pid;
156             procArr[i][1] = p.getParentProcessID();
157             procArr[i][2] = p.getThreadCount();
158             if (perProc.isSelected()) {
159                 procArr[i][3] = String.format(Locale.ROOT, "%.1f",
160                         100d * p.getProcessCpuLoadBetweenTicks(priorSnapshotMap.get(pid)) * cpuCount);
161                 procArr[i][4] = String.format(Locale.ROOT, "%.1f", 100d * p.getProcessCpuLoadCumulative() * cpuCount);
162             } else {
163                 procArr[i][3] = String.format(Locale.ROOT, "%.1f",
164                         100d * p.getProcessCpuLoadBetweenTicks(priorSnapshotMap.get(pid)));
165                 procArr[i][4] = String.format(Locale.ROOT, "%.1f", 100d * p.getProcessCpuLoadCumulative());
166             }
167             procArr[i][5] = FormatUtil.formatBytes(p.getVirtualSize());
168             procArr[i][6] = FormatUtil.formatBytes(p.getResidentSetSize());
169             procArr[i][7] = String.format(Locale.ROOT, "%.1f", 100d * p.getResidentSetSize() / totalMem);
170             procArr[i][8] = p.getName();
171         }
172         // Re-populate snapshot map
173         priorSnapshotMap.clear();
174         for (OSProcess p : list) {
175             priorSnapshotMap.put(p.getProcessID(), p);
176         }
177         return procArr;
178     }
179 
180     private static void resizeColumns(TableColumnModel tableColumnModel) {
181         TableColumn column;
182         int tW = tableColumnModel.getTotalColumnWidth();
183         int cantCols = tableColumnModel.getColumnCount();
184         for (int i = 0; i < cantCols; i++) {
185             column = tableColumnModel.getColumn(i);
186             int pWidth = (int) Math.round(COLUMN_WIDTH_PERCENT[i] * tW);
187             column.setPreferredWidth(pWidth);
188         }
189     }
190 }