View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  
12  /*
13   * MIT License
14   *
15   * Copyright (c) 2010 - 2020 The OSHI Project Contributors: https://github.com/oshi/oshi/graphs/contributors
16   *
17   * Permission is hereby granted, free of charge, to any person obtaining a copy
18   * of this software and associated documentation files (the "Software"), to deal
19   * in the Software without restriction, including without limitation the rights
20   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21   * copies of the Software, and to permit persons to whom the Software is
22   * furnished to do so, subject to the following conditions:
23   *
24   * The above copyright notice and this permission notice shall be included in all
25   * copies or substantial portions of the Software.
26   *
27   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33   * SOFTWARE.
34   */
35  package psiprobe.controllers.oshi;
36  
37  import jakarta.servlet.http.HttpServletRequest;
38  import jakarta.servlet.http.HttpServletResponse;
39  
40  import java.time.Instant;
41  import java.util.ArrayList;
42  import java.util.Arrays;
43  import java.util.List;
44  import java.util.Map.Entry;
45  
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  import org.springframework.beans.factory.annotation.Value;
49  import org.springframework.stereotype.Controller;
50  import org.springframework.web.bind.annotation.RequestMapping;
51  import org.springframework.web.servlet.ModelAndView;
52  
53  import oshi.PlatformEnum;
54  import oshi.SystemInfo;
55  import oshi.hardware.CentralProcessor;
56  import oshi.hardware.CentralProcessor.PhysicalProcessor;
57  import oshi.hardware.CentralProcessor.TickType;
58  import oshi.hardware.ComputerSystem;
59  import oshi.hardware.Display;
60  import oshi.hardware.GlobalMemory;
61  import oshi.hardware.GraphicsCard;
62  import oshi.hardware.HWDiskStore;
63  import oshi.hardware.HWPartition;
64  import oshi.hardware.HardwareAbstractionLayer;
65  import oshi.hardware.LogicalVolumeGroup;
66  import oshi.hardware.NetworkIF;
67  import oshi.hardware.PhysicalMemory;
68  import oshi.hardware.PowerSource;
69  import oshi.hardware.Sensors;
70  import oshi.hardware.SoundCard;
71  import oshi.hardware.UsbDevice;
72  import oshi.hardware.VirtualMemory;
73  import oshi.software.os.FileSystem;
74  import oshi.software.os.InternetProtocolStats;
75  import oshi.software.os.NetworkParams;
76  import oshi.software.os.OSFileStore;
77  import oshi.software.os.OSProcess;
78  import oshi.software.os.OSService;
79  import oshi.software.os.OSSession;
80  import oshi.software.os.OperatingSystem;
81  import oshi.software.os.OperatingSystem.ProcessFiltering;
82  import oshi.software.os.OperatingSystem.ProcessSorting;
83  import oshi.util.FormatUtil;
84  import oshi.util.Util;
85  
86  import psiprobe.controllers.AbstractTomcatContainerController;
87  
88  /**
89   * Creates an instance of Operating System and Hardware Information based on Oshi SystemInfoTest.
90   * <p>
91   * A demonstration of access to many of OSHI's capabilities
92   */
93  @Controller
94  public class OshiController extends AbstractTomcatContainerController {
95  
96    /** The Constant logger. */
97    private static final Logger logger = LoggerFactory.getLogger(OshiController.class);
98  
99    /** Oshi Cache. */
100   private static List<String> oshi = new ArrayList<>();
101 
102   @RequestMapping(path = "/adm/oshi.htm")
103   @Override
104   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
105       throws Exception {
106     return super.handleRequest(request, response);
107   }
108 
109   @Override
110   protected ModelAndView handleRequestInternal(HttpServletRequest request,
111       HttpServletResponse response) throws Exception {
112 
113     if (!oshi.isEmpty()) {
114       ModelAndView mv = new ModelAndView(getViewName());
115       mv.addObject("oshi", oshi);
116       return mv;
117     }
118 
119     // TODO: Remove once no longer experimental
120     oshi.add(
121         "Oshi results are performed as a system dump to screen here using Oshi SystemInfoTest logic.");
122     oshi.add(
123         "Please be advised this is experimental in use and is slow. Resulting data is entirely cached.");
124     oshi.add("Issues with library should be directed to https://github.com/oshi/oshi");
125     oshi.add("For issues with our library usage of Oshi, please submit pull requests");
126     oshi.add("");
127     oshi.add("");
128 
129     this.initialize();
130 
131     ModelAndView mv = new ModelAndView(getViewName());
132     mv.addObject("oshi", oshi);
133     return mv;
134   }
135 
136   @Value("oshi")
137   @Override
138   public void setViewName(String viewName) {
139     super.setViewName(viewName);
140   }
141 
142   /**
143    * Process initialization using Oshi System Info Test.
144    * <p>
145    * Code copied and adjusted for Psi Probem from Oshi SystemInfoTest.main at revision
146    *
147    * <pre>
148    * https://github.com/oshi/oshi/blob/cf45b1f528f99ca353655dea5f154940c76c0bdb/oshi-core/src/test/java/oshi/SystemInfoTest.java
149    * </pre>
150    *
151    * <pre>
152    * Psi Probe differences
153    * - Noted directly in area of change as possible
154    * - Logging switched from 'info' to 'debug'
155    * - Javadocs throughout
156    * - Formatting differences (2 vs 4 spaces)
157    * </pre>
158    */
159   private void initialize() {
160     logger.debug("Initializing System...");
161     SystemInfo si = new SystemInfo();
162 
163     // Psi Probe adjusted oshi initial test to confirm platform supported before attempting to run
164     if (PlatformEnum.UNKNOWN.equals(SystemInfo.getCurrentPlatform())) {
165       logger.error("Oshi not supported on current platform");
166       oshi.add("Oshi not supported on current platform");
167       oshi.add("");
168       return;
169     }
170 
171     HardwareAbstractionLayer hal = si.getHardware();
172     OperatingSystem os = si.getOperatingSystem();
173 
174     printOperatingSystem(os);
175 
176     logger.debug("Checking computer system...");
177     printComputerSystem(hal.getComputerSystem());
178 
179     logger.debug("Checking Processor...");
180     printProcessor(hal.getProcessor());
181 
182     logger.debug("Checking Memory...");
183     printMemory(hal.getMemory());
184 
185     logger.debug("Checking CPU...");
186     printCpu(hal.getProcessor());
187 
188     logger.debug("Checking Processes...");
189     printProcesses(os, hal.getMemory());
190 
191     logger.debug("Checking Services...");
192     printServices(os);
193 
194     logger.debug("Checking Sensors...");
195     printSensors(hal.getSensors());
196 
197     logger.debug("Checking Power sources...");
198     printPowerSources(hal.getPowerSources());
199 
200     logger.debug("Checking Disks...");
201     printDisks(hal.getDiskStores());
202 
203     logger.debug("Checking Logical Volume Groups ...");
204     printLogicalVolumegroups(hal.getLogicalVolumeGroups());
205 
206     logger.debug("Checking File System...");
207     printFileSystem(os.getFileSystem());
208 
209     logger.debug("Checking Network interfaces...");
210     printNetworkInterfaces(hal.getNetworkIFs());
211 
212     logger.debug("Checking Network parameters...");
213     printNetworkParameters(os.getNetworkParams());
214 
215     logger.debug("Checking IP statistics...");
216     printInternetProtocolStats(os.getInternetProtocolStats());
217 
218     logger.debug("Checking Displays...");
219     printDisplays(hal.getDisplays());
220 
221     logger.debug("Checking USB Devices...");
222     printUsbDevices(hal.getUsbDevices(true));
223 
224     logger.debug("Checking Sound Cards...");
225     printSoundCards(hal.getSoundCards());
226 
227     logger.debug("Checking Graphics Cards...");
228     printGraphicsCards(hal.getGraphicsCards());
229 
230     // Psi Probe addition to note finished
231     oshi.add("Finished Operating System and Hardware Info Dump");
232 
233     StringBuilder output = new StringBuilder();
234     for (String element : oshi) {
235       output.append(element);
236       // Psi Probe fix as output check 'endsWith' was wrong
237       if (!"\n".equals(element)) {
238         output.append('\n');
239       }
240     }
241     logger.info("Printing Operating System and Hardware Info:{}{}", '\n', output);
242   }
243 
244   /**
245    * Prints the operating system.
246    *
247    * @param operatingSystem the operating system
248    */
249   private static void printOperatingSystem(final OperatingSystem operatingSystem) {
250     oshi.add(String.valueOf(operatingSystem));
251     oshi.add("Booted: " + Instant.ofEpochSecond(operatingSystem.getSystemBootTime()));
252     oshi.add("Uptime: " + FormatUtil.formatElapsedSecs(operatingSystem.getSystemUptime()));
253     oshi.add(
254         "Running with" + (operatingSystem.isElevated() ? "" : "out") + " elevated permissions.");
255     oshi.add("Sessions:");
256     for (OSSession s : operatingSystem.getSessions()) {
257       oshi.add(" " + s.toString());
258     }
259   }
260 
261   /**
262    * Prints the computer system.
263    *
264    * @param computerSystem the computer system
265    */
266   private static void printComputerSystem(final ComputerSystem computerSystem) {
267     oshi.add("System: " + computerSystem.toString());
268     oshi.add(" Firmware: " + computerSystem.getFirmware().toString());
269     oshi.add(" Baseboard: " + computerSystem.getBaseboard().toString());
270   }
271 
272   /**
273    * Prints the processor.
274    *
275    * @param processor the processor
276    */
277   private static void printProcessor(CentralProcessor processor) {
278     oshi.add(processor.toString());
279     oshi.add(" Cores:");
280     for (PhysicalProcessor p : processor.getPhysicalProcessors()) {
281       oshi.add(
282           "  " + (processor.getPhysicalPackageCount() > 1 ? p.getPhysicalPackageNumber() + "," : "")
283               + p.getPhysicalProcessorNumber() + ": efficiency=" + p.getEfficiency() + ", id="
284               + p.getIdString());
285     }
286   }
287 
288   /**
289    * Prints the memory.
290    *
291    * @param memory the memory
292    */
293   private static void printMemory(GlobalMemory memory) {
294     oshi.add("Physical Memory: \n " + memory.toString());
295     VirtualMemory vm = memory.getVirtualMemory();
296     oshi.add("Virtual Memory: \n " + vm.toString());
297     List<PhysicalMemory> pmList = memory.getPhysicalMemory();
298     if (!pmList.isEmpty()) {
299       oshi.add("Physical Memory: ");
300       for (PhysicalMemory pm : pmList) {
301         oshi.add(" " + pm.toString());
302       }
303     }
304   }
305 
306   /**
307    * Prints the cpu.
308    *
309    * @param processor the processor
310    */
311   private static void printCpu(CentralProcessor processor) {
312     oshi.add("Context Switches/Interrupts: " + processor.getContextSwitches() + " / "
313         + processor.getInterrupts());
314 
315     long[] prevTicks = processor.getSystemCpuLoadTicks();
316     oshi.add("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks));
317     // Wait a second...
318     Util.sleep(1000);
319     long[] ticks = processor.getSystemCpuLoadTicks();
320     oshi.add("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks));
321     long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
322     long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
323     long sys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
324     long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
325     long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
326     long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
327     long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
328     long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
329     long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;
330 
331     oshi.add(String.format(
332         "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%",
333         100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu,
334         100d * idle / totalCpu, 100d * iowait / totalCpu, 100d * irq / totalCpu,
335         100d * softirq / totalCpu, 100d * steal / totalCpu));
336     oshi.add(
337         String.format("CPU load: %.1f%%", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100));
338     double[] loadAverage = processor.getSystemLoadAverage(3);
339     oshi.add("CPU load averages:"
340         + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0]))
341         + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1]))
342         + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2])));
343     // per core CPU
344     StringBuilder procCpu = new StringBuilder("CPU load per processor:");
345     long[][] prevProcTicks = processor.getProcessorCpuLoadTicks();
346     double[] load = processor.getProcessorCpuLoadBetweenTicks(prevProcTicks);
347     for (double avg : load) {
348       procCpu.append(String.format(" %.1f%%", avg * 100));
349     }
350     oshi.add(procCpu.toString());
351     long freq = processor.getProcessorIdentifier().getVendorFreq();
352     if (freq > 0) {
353       oshi.add("Vendor Frequency: " + FormatUtil.formatHertz(freq));
354     }
355     freq = processor.getMaxFreq();
356     if (freq > 0) {
357       oshi.add("Max Frequency: " + FormatUtil.formatHertz(freq));
358     }
359     long[] freqs = processor.getCurrentFreq();
360     if (freqs[0] > 0) {
361       StringBuilder sb = new StringBuilder("Current Frequencies: ");
362       for (int i = 0; i < freqs.length; i++) {
363         if (i > 0) {
364           sb.append(", ");
365         }
366         sb.append(FormatUtil.formatHertz(freqs[i]));
367       }
368       oshi.add(sb.toString());
369     }
370   }
371 
372   /**
373    * Prints the processes.
374    *
375    * @param os the os
376    * @param memory the memory
377    */
378   private static void printProcesses(OperatingSystem os, GlobalMemory memory) {
379     OSProcess myProc = os.getProcess(os.getProcessId());
380     // current process will never be null. Other code should check for null here
381     oshi.add("My PID: " + myProc.getProcessID() + " with affinity "
382         + Long.toBinaryString(myProc.getAffinityMask()));
383     oshi.add("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount());
384     // Sort by highest CPU
385     List<OSProcess> procs =
386         os.getProcesses(ProcessFiltering.ALL_PROCESSES, ProcessSorting.CPU_DESC, 5);
387     oshi.add("   PID  %CPU %MEM       VSZ       RSS Name");
388     for (int i = 0; i < procs.size() && i < 5; i++) {
389       OSProcess p = procs.get(i);
390       oshi.add(String.format(" %5d %5.1f %4.1f %9s %9s %s", p.getProcessID(),
391           100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(),
392           100d * p.getResidentSetSize() / memory.getTotal(),
393           FormatUtil.formatBytes(p.getVirtualSize()),
394           FormatUtil.formatBytes(p.getResidentSetSize()), p.getName()));
395     }
396     OSProcess p = os.getProcess(os.getProcessId());
397     oshi.add("Current process arguments: ");
398     for (String s : p.getArguments()) {
399       oshi.add("  " + s);
400     }
401     oshi.add("Current process environment: ");
402     for (Entry<String, String> e : p.getEnvironmentVariables().entrySet()) {
403       oshi.add("  " + e.getKey() + '=' + e.getValue());
404     }
405   }
406 
407   /**
408    * Prints the services.
409    *
410    * @param os the operating system
411    */
412   private static void printServices(OperatingSystem os) {
413     oshi.add("Services: ");
414     oshi.add("   PID   State   Name");
415     // DO 5 each of running and stopped
416     int i = 0;
417     for (OSService s : os.getServices()) {
418       if (s.getState().equals(OSService.State.RUNNING) && i++ < 5) {
419         oshi.add(String.format(" %5d  %7s  %s", s.getProcessID(), s.getState(), s.getName()));
420       }
421     }
422     i = 0;
423     for (OSService s : os.getServices()) {
424       if (s.getState().equals(OSService.State.STOPPED) && i++ < 5) {
425         oshi.add(String.format(" %5d  %7s  %s", s.getProcessID(), s.getState(), s.getName()));
426       }
427     }
428   }
429 
430   /**
431    * Prints the sensors.
432    *
433    * @param sensors the sensors
434    */
435   private static void printSensors(Sensors sensors) {
436     oshi.add("Sensors: " + sensors.toString());
437   }
438 
439   /**
440    * Prints the power sources.
441    *
442    * @param list the power sources
443    */
444   private static void printPowerSources(List<PowerSource> list) {
445     StringBuilder sb = new StringBuilder("Power Sources: ");
446     if (list.isEmpty()) {
447       sb.append("Unknown");
448     }
449     for (PowerSource powerSource : list) {
450       sb.append("\n ").append(powerSource.toString());
451     }
452     oshi.add(sb.toString());
453   }
454 
455   /**
456    * Prints the disks.
457    *
458    * @param list the disk stores
459    */
460   private static void printDisks(List<HWDiskStore> list) {
461     oshi.add("Disks:");
462     for (HWDiskStore disk : list) {
463       oshi.add(" " + disk.toString());
464 
465       List<HWPartition> partitions = disk.getPartitions();
466       for (HWPartition part : partitions) {
467         oshi.add(" |-- " + part.toString());
468       }
469     }
470 
471   }
472 
473   /**
474    * Prints the logical volume groups.
475    *
476    * @param list the logical volume groups
477    */
478   private static void printLogicalVolumegroups(List<LogicalVolumeGroup> list) {
479     if (!list.isEmpty()) {
480       oshi.add("Logical Volume Groups:");
481       for (LogicalVolumeGroup lvg : list) {
482         oshi.add(" " + lvg.toString());
483       }
484     }
485   }
486 
487   /**
488    * Prints the file system.
489    *
490    * @param fileSystem the file system
491    */
492   private static void printFileSystem(FileSystem fileSystem) {
493     oshi.add("File System:");
494 
495     oshi.add(String.format(" File Descriptors: %d/%d", fileSystem.getOpenFileDescriptors(),
496         fileSystem.getMaxFileDescriptors()));
497 
498     for (OSFileStore fs : fileSystem.getFileStores()) {
499       long usable = fs.getUsableSpace();
500       long total = fs.getTotalSpace();
501       oshi.add(String.format(
502           " %s (%s) [%s] %s of %s free (%.1f%%), %s of %s files free (%.1f%%) is %s "
503               + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]"
504                   : "%s")
505               + " and is mounted at %s",
506           fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(),
507           fs.getType(), FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()),
508           100d * usable / total, FormatUtil.formatValue(fs.getFreeInodes(), ""),
509           FormatUtil.formatValue(fs.getTotalInodes(), ""),
510           100d * fs.getFreeInodes() / fs.getTotalInodes(), fs.getVolume(), fs.getLogicalVolume(),
511           fs.getMount()));
512     }
513   }
514 
515   /**
516    * Prints the network interfaces.
517    *
518    * @param list the network interfaces
519    */
520   private static void printNetworkInterfaces(List<NetworkIF> list) {
521     StringBuilder sb = new StringBuilder("Network Interfaces:");
522     if (list.isEmpty()) {
523       sb.append(" Unknown");
524     } else {
525       for (NetworkIF net : list) {
526         sb.append("\n ").append(net.toString());
527       }
528     }
529     oshi.add(sb.toString());
530   }
531 
532   /**
533    * Prints the network parameters.
534    *
535    * @param networkParams the network params
536    */
537   private static void printNetworkParameters(NetworkParams networkParams) {
538     oshi.add("Network parameters:\n " + networkParams.toString());
539   }
540 
541   /**
542    * Prints the internet protocol stats.
543    *
544    * @param internetProtocolStats the internet protocal stats
545    */
546   private static void printInternetProtocolStats(InternetProtocolStats internetProtocolStats) {
547     oshi.add("Internet Protocol statistics:");
548     oshi.add(" TCPv4: " + internetProtocolStats.getTCPv4Stats());
549     oshi.add(" TCPv6: " + internetProtocolStats.getTCPv6Stats());
550     oshi.add(" UDPv4: " + internetProtocolStats.getUDPv4Stats());
551     oshi.add(" UDPv6: " + internetProtocolStats.getUDPv6Stats());
552   }
553 
554   /**
555    * Prints the displays.
556    *
557    * @param list the displays
558    */
559   private static void printDisplays(List<Display> list) {
560     oshi.add("Displays:");
561     int i = 0;
562     for (Display display : list) {
563       oshi.add(" Display " + i + ":");
564       oshi.add(String.valueOf(display));
565       i++;
566     }
567   }
568 
569   /**
570    * Prints the usb devices.
571    *
572    * @param list the usb devices
573    */
574   private static void printUsbDevices(List<UsbDevice> list) {
575     oshi.add("USB Devices:");
576     for (UsbDevice usbDevice : list) {
577       oshi.add(String.valueOf(usbDevice));
578     }
579   }
580 
581   /**
582    * Prints the sound cards.
583    *
584    * @param list the sound cards
585    */
586   private static void printSoundCards(List<SoundCard> list) {
587     oshi.add("Sound Cards:");
588     for (SoundCard card : list) {
589       oshi.add(" " + String.valueOf(card));
590     }
591   }
592 
593   /**
594    * Prints the graphic cards.
595    *
596    * @param list the graphics cards
597    */
598   private static void printGraphicsCards(List<GraphicsCard> list) {
599     oshi.add("Graphics Cards:");
600     if (list.isEmpty()) {
601       oshi.add(" None detected.");
602     } else {
603       for (GraphicsCard card : list) {
604         oshi.add(" " + String.valueOf(card));
605       }
606     }
607   }
608 
609 }