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 }