View Javadoc
1   /*
2    * Copyright 2025 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util;
6   
7   import java.util.Arrays;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.regex.Pattern;
12  
13  import oshi.annotation.concurrent.ThreadSafe;
14  
15  @ThreadSafe
16  public final class ProcUtil {
17  
18      private ProcUtil() {
19      }
20  
21      /**
22       * Parses /proc files with a given structure consisting of a keyed header line followed by a keyed value line.
23       * {@code /proc/net/netstat} and {@code /proc/net/snmp} are specific examples of this. The returned map is of the
24       * form {key: {stat: value, stat: value, ...}}. An example of the file structure is:
25       *
26       * <pre>
27       *     TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed ...
28       *     TcpExt: 0 4 0 ...
29       *     IpExt: InNoRoutes InTruncatedPkts InMcastPkts OutMcastPkts ...
30       *     IpExt: 55 0 27786 1435 ...
31       *     MPTcpExt: MPCapableSYNRX MPCapableSYNTX MPCapableSYNACKRX ...
32       *     MPTcpExt: 0 0 0 ...
33       * </pre>
34       *
35       * Which would produce a mapping structure like:
36       *
37       * <pre>
38       *     {
39       *         "TcpExt": {"SyncookiesSent":0, "SyncookiesRecv":4, "SyncookiesFailed":0, ... }
40       *         "IpExt": {"InNoRoutes":55, "InTruncatedPkts":27786, "InMcastPkts":1435, ... }
41       *         "MPTcpExt": {"MPCapableSYNACKRX":0, "MPCapableSYNTX":0, "MPCapableSYNACKRX":0, ... }
42       *     }
43       * </pre>
44       *
45       * @param procFile the file to process
46       * @param keys     an optional array of keys to return in the outer map. If none are given, all found keys are
47       *                 returned.
48       * @return a map of keys to stats
49       */
50      public static Map<String, Map<String, Long>> parseNestedStatistics(String procFile, String... keys) {
51          Map<String, Map<String, Long>> result = new HashMap<>();
52          List<String> keyList = Arrays.asList(keys);
53  
54          List<String> lines = FileUtil.readFile(procFile);
55          String previousKey = null;
56          String[] statNames = null;
57  
58          for (String line : lines) {
59              String[] parts = ParseUtil.whitespaces.split(line);
60              if (parts.length == 0) {
61                  continue;
62              }
63  
64              // This would happen if the line starts with whitespace
65              if (parts[0].isEmpty()) {
66                  parts = Arrays.copyOfRange(parts, 1, parts.length);
67              }
68  
69              String key = parts[0].substring(0, parts[0].length() - 1);
70  
71              if (!keyList.isEmpty() && !keyList.contains(key)) {
72                  continue;
73              }
74  
75              if (key.equals(previousKey)) {
76                  if (parts.length == statNames.length) {
77                      Map<String, Long> stats = new HashMap<>(parts.length - 1);
78                      for (int i = 1; i < parts.length; i++) {
79                          stats.put(statNames[i], ParseUtil.parseLongOrDefault(parts[i], 0));
80                      }
81                      result.put(key, stats);
82                  }
83              } else {
84                  statNames = parts;
85              }
86  
87              previousKey = key;
88          }
89  
90          return result;
91      }
92  
93      /**
94       * Parses /proc files formatted as "statistic (long)value" to produce a simple mapping. An example would be
95       * /proc/net/snmp6. The file format would look like:
96       *
97       * <pre>
98       *    Ip6InReceives             8026
99       *    Ip6InHdrErrors            0
100      *    Icmp6InMsgs               2
101      *    Icmp6InErrors             0
102      *    Icmp6OutMsgs              424
103      *    Udp6IgnoredMulti          5
104      *    Udp6MemErrors             1
105      *    UdpLite6InDatagrams       37
106      *    UdpLite6NoPorts           1
107      * </pre>
108      *
109      * Which would produce a mapping structure like:
110      *
111      * <pre>
112      *     {
113      *         "Ip6InReceives":8026,
114      *         "Ip6InHdrErrors":0,
115      *         "Icmp6InMsgs":2,
116      *         "Icmp6InErrors":0,
117      *         ...
118      *     }
119      * </pre>
120      *
121      * @param procFile  the file to process
122      * @param separator a regex specifying the separator between statistic and value
123      * @return a map of statistics and associated values
124      */
125     public static Map<String, Long> parseStatistics(String procFile, Pattern separator) {
126         Map<String, Long> result = new HashMap<>();
127         List<String> lines = FileUtil.readFile(procFile);
128         for (String line : lines) {
129             String[] parts = separator.split(line);
130 
131             // This would happen if the line starts with the given separator (whitespace?)
132             if (parts[0].isEmpty()) {
133                 parts = Arrays.copyOfRange(parts, 1, parts.length);
134             }
135 
136             if (parts.length == 2) {
137                 result.put(parts[0], ParseUtil.parseLongOrDefault(parts[1], 0));
138             }
139         }
140 
141         return result;
142     }
143 
144     /**
145      * Overloaded {@link #parseStatistics(String, Pattern)} using a whitespace separator.
146      *
147      * @param procFile the file to process
148      * @return a map of statistics and associated values
149      * @see #parseStatistics(String, Pattern)
150      */
151     public static Map<String, Long> parseStatistics(String procFile) {
152         return parseStatistics(procFile, ParseUtil.whitespaces);
153     }
154 }