View Javadoc
1   /*
2    * Copyright 2016-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util.platform.unix.solaris;
6   
7   import java.nio.charset.StandardCharsets;
8   import java.util.ArrayList;
9   import java.util.Arrays;
10  import java.util.List;
11  import java.util.concurrent.locks.Lock;
12  import java.util.concurrent.locks.ReentrantLock;
13  
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import com.sun.jna.Native;
18  import com.sun.jna.Pointer;
19  import com.sun.jna.platform.unix.solaris.Kstat2;
20  import com.sun.jna.platform.unix.solaris.Kstat2.Kstat2Handle;
21  import com.sun.jna.platform.unix.solaris.Kstat2.Kstat2Map;
22  import com.sun.jna.platform.unix.solaris.Kstat2.Kstat2MatcherList;
23  import com.sun.jna.platform.unix.solaris.Kstat2StatusException;
24  import com.sun.jna.platform.unix.solaris.LibKstat;
25  import com.sun.jna.platform.unix.solaris.LibKstat.Kstat;
26  import com.sun.jna.platform.unix.solaris.LibKstat.KstatCtl;
27  import com.sun.jna.platform.unix.solaris.LibKstat.KstatNamed;
28  
29  import oshi.annotation.concurrent.GuardedBy;
30  import oshi.annotation.concurrent.ThreadSafe;
31  import oshi.software.os.unix.solaris.SolarisOperatingSystem;
32  import oshi.util.FormatUtil;
33  import oshi.util.Util;
34  
35  /**
36   * Provides access to kstat information on Solaris
37   */
38  @ThreadSafe
39  public final class KstatUtil {
40  
41      private static final Logger LOG = LoggerFactory.getLogger(KstatUtil.class);
42  
43      private static final Lock CHAIN = new ReentrantLock();
44      // Only one thread may access the chain at any time, so we wrap this object in
45      // the KstatChain class locked until the lock is released on auto-close.
46      @GuardedBy("CHAIN")
47      private static KstatCtl kstatCtl = null;
48  
49      private KstatUtil() {
50      }
51  
52      /**
53       * A copy of the Kstat chain, encapsulating a {@code kstat_ctl_t} object. Only one thread may actively use this
54       * object at any time.
55       * <p>
56       * The chain is created once calling {@link LibKstat#kstat_open} and then this object is instantiated using the
57       * {@link KstatUtil#openChain} method. Instantiating this object updates the chain using
58       * {@link LibKstat#kstat_chain_update}. The control object should be closed with {@link #close}, which releases the
59       * lock and allows another instance to be instantiated.
60       */
61      public static final class KstatChain implements AutoCloseable {
62  
63          private final KstatCtl localCtlRef;
64  
65          private KstatChain(KstatCtl ctl) {
66              this.localCtlRef = ctl;
67              update();
68          }
69  
70          /**
71           * Convenience method for {@link LibKstat#kstat_read} which gets data from the kernel for the kstat pointed to
72           * by {@code ksp}. {@code ksp.ks_data} is automatically allocated (or reallocated) to be large enough to hold
73           * all of the data. {@code ksp.ks_ndata} is set to the number of data fields, {@code ksp.ks_data_size} is set to
74           * the total size of the data, and ksp.ks_snaptime is set to the high-resolution time at which the data snapshot
75           * was taken.
76           *
77           * @param ksp The kstat from which to retrieve data
78           * @return {@code true} if successful; {@code false} otherwise
79           */
80          @GuardedBy("CHAIN")
81          public boolean read(Kstat ksp) {
82              int retry = 0;
83              while (0 > LibKstat.INSTANCE.kstat_read(localCtlRef, ksp, null)) {
84                  if (LibKstat.EAGAIN != Native.getLastError() || 5 <= ++retry) {
85                      if (LOG.isDebugEnabled()) {
86                          LOG.debug("Failed to read kstat {}:{}:{}",
87                                  Native.toString(ksp.ks_module, StandardCharsets.US_ASCII), ksp.ks_instance,
88                                  Native.toString(ksp.ks_name, StandardCharsets.US_ASCII));
89                      }
90                      return false;
91                  }
92                  Util.sleep(8 << retry);
93              }
94              return true;
95          }
96  
97          /**
98           * Convenience method for {@link LibKstat#kstat_lookup}. Traverses the kstat chain, searching for a kstat with
99           * the same {@code module}, {@code instance}, and {@code name} fields; this triplet uniquely identifies a kstat.
100          * If {@code module} is {@code null}, {@code instance} is -1, or {@code name} is {@code null}, then those fields
101          * will be ignored in the search.
102          *
103          * @param module   The module, or null to ignore
104          * @param instance The instance, or -1 to ignore
105          * @param name     The name, or null to ignore
106          * @return The first match of the requested Kstat structure if found, or {@code null}
107          */
108         @GuardedBy("CHAIN")
109         public Kstat lookup(String module, int instance, String name) {
110             return LibKstat.INSTANCE.kstat_lookup(localCtlRef, module, instance, name);
111         }
112 
113         /**
114          * Convenience method for {@link LibKstat#kstat_lookup}. Traverses the kstat chain, searching for all kstats
115          * with the same {@code module}, {@code instance}, and {@code name} fields; this triplet uniquely identifies a
116          * kstat. If {@code module} is {@code null}, {@code instance} is -1, or {@code name} is {@code null}, then those
117          * fields will be ignored in the search.
118          *
119          * @param module   The module, or null to ignore
120          * @param instance The instance, or -1 to ignore
121          * @param name     The name, or null to ignore
122          * @return All matches of the requested Kstat structure if found, or an empty list otherwise
123          */
124         @GuardedBy("CHAIN")
125         public List<Kstat> lookupAll(String module, int instance, String name) {
126             List<Kstat> kstats = new ArrayList<>();
127             for (Kstat ksp = LibKstat.INSTANCE.kstat_lookup(localCtlRef, module, instance, name); ksp != null; ksp = ksp
128                     .next()) {
129                 if ((module == null || module.equals(Native.toString(ksp.ks_module, StandardCharsets.US_ASCII)))
130                         && (instance < 0 || instance == ksp.ks_instance)
131                         && (name == null || name.equals(Native.toString(ksp.ks_name, StandardCharsets.US_ASCII)))) {
132                     kstats.add(ksp);
133                 }
134             }
135             return kstats;
136         }
137 
138         /**
139          * Convenience method for {@link LibKstat#kstat_chain_update}. Brings this kstat header chain in sync with that
140          * of the kernel.
141          * <p>
142          * This function compares the kernel's current kstat chain ID(KCID), which is incremented every time the kstat
143          * chain changes, to this object's KCID.
144          *
145          * @return the new KCID if the kstat chain has changed, 0 if it hasn't, or -1 on failure.
146          */
147         @GuardedBy("CHAIN")
148         public int update() {
149             return LibKstat.INSTANCE.kstat_chain_update(localCtlRef);
150         }
151 
152         /**
153          * Release the lock on the chain.
154          */
155         @Override
156         public void close() {
157             CHAIN.unlock();
158         }
159     }
160 
161     /**
162      * Lock the Kstat chain for use by this object until it's closed.
163      *
164      * @return A locked copy of the chain. It should be unlocked/released when you are done with it with
165      *         {@link KstatChain#close()}.
166      */
167     public static synchronized KstatChain openChain() {
168         CHAIN.lock();
169         if (kstatCtl == null) {
170             kstatCtl = LibKstat.INSTANCE.kstat_open();
171         }
172         return new KstatChain(kstatCtl);
173     }
174 
175     /**
176      * Convenience method for {@link LibKstat#kstat_data_lookup} with String return values. Searches the kstat's data
177      * section for the record with the specified name. This operation is valid only for kstat types which have named
178      * data records. Currently, only the KSTAT_TYPE_NAMED and KSTAT_TYPE_TIMER kstats have named data records.
179      *
180      * @param ksp  The kstat to search
181      * @param name The key for the name-value pair, or name of the timer as applicable
182      * @return The value as a String.
183      */
184     public static String dataLookupString(Kstat ksp, String name) {
185         if (ksp.ks_type != LibKstat.KSTAT_TYPE_NAMED && ksp.ks_type != LibKstat.KSTAT_TYPE_TIMER) {
186             throw new IllegalArgumentException("Not a kstat_named or kstat_timer kstat.");
187         }
188         Pointer p = LibKstat.INSTANCE.kstat_data_lookup(ksp, name);
189         if (p == null) {
190             LOG.debug("Failed to lookup kstat value for key {}", name);
191             return "";
192         }
193         KstatNamed data = new KstatNamed(p);
194         switch (data.data_type) {
195         case LibKstat.KSTAT_DATA_CHAR:
196             return Native.toString(data.value.charc, StandardCharsets.UTF_8);
197         case LibKstat.KSTAT_DATA_INT32:
198             return Integer.toString(data.value.i32);
199         case LibKstat.KSTAT_DATA_UINT32:
200             return FormatUtil.toUnsignedString(data.value.ui32);
201         case LibKstat.KSTAT_DATA_INT64:
202             return Long.toString(data.value.i64);
203         case LibKstat.KSTAT_DATA_UINT64:
204             return FormatUtil.toUnsignedString(data.value.ui64);
205         case LibKstat.KSTAT_DATA_STRING:
206             return data.value.str.addr.getString(0);
207         default:
208             LOG.error("Unimplemented kstat data type {}", data.data_type);
209             return "";
210         }
211     }
212 
213     /**
214      * Convenience method for {@link LibKstat#kstat_data_lookup} with numeric return values. Searches the kstat's data
215      * section for the record with the specified name. This operation is valid only for kstat types which have named
216      * data records. Currently, only the KSTAT_TYPE_NAMED and KSTAT_TYPE_TIMER kstats have named data records.
217      *
218      * @param ksp  The kstat to search
219      * @param name The key for the name-value pair, or name of the timer as applicable
220      * @return The value as a long. If the data type is a character or string type, returns 0 and logs an error.
221      */
222     public static long dataLookupLong(Kstat ksp, String name) {
223         if (ksp.ks_type != LibKstat.KSTAT_TYPE_NAMED && ksp.ks_type != LibKstat.KSTAT_TYPE_TIMER) {
224             throw new IllegalArgumentException("Not a kstat_named or kstat_timer kstat.");
225         }
226         Pointer p = LibKstat.INSTANCE.kstat_data_lookup(ksp, name);
227         if (p == null) {
228             if (LOG.isDebugEnabled()) {
229                 LOG.debug("Failed lo lookup kstat value on {}:{}:{} for key {}",
230                         Native.toString(ksp.ks_module, StandardCharsets.US_ASCII), ksp.ks_instance,
231                         Native.toString(ksp.ks_name, StandardCharsets.US_ASCII), name);
232             }
233             return 0L;
234         }
235         KstatNamed data = new KstatNamed(p);
236         switch (data.data_type) {
237         case LibKstat.KSTAT_DATA_INT32:
238             return data.value.i32;
239         case LibKstat.KSTAT_DATA_UINT32:
240             return FormatUtil.getUnsignedInt(data.value.ui32);
241         case LibKstat.KSTAT_DATA_INT64:
242             return data.value.i64;
243         case LibKstat.KSTAT_DATA_UINT64:
244             return data.value.ui64;
245         default:
246             LOG.error("Unimplemented or non-numeric kstat data type {}", data.data_type);
247             return 0L;
248         }
249     }
250 
251     /**
252      * Query Kstat2 with a single map
253      *
254      * @param mapStr The map to query
255      * @param names  Names of data to query
256      * @return An object array with the data corresponding to the names
257      */
258     public static Object[] queryKstat2(String mapStr, String... names) {
259         if (!SolarisOperatingSystem.HAS_KSTAT2) {
260             throw new UnsupportedOperationException(
261                     "Kstat2 requires Solaris 11.4+. Use SolarisOperatingSystem#HAS_KSTAT2 to test this.");
262         }
263         Object[] result = new Object[names.length];
264         Kstat2MatcherList matchers = new Kstat2MatcherList();
265         KstatUtil.CHAIN.lock();
266         try {
267             matchers.addMatcher(Kstat2.KSTAT2_M_STRING, mapStr);
268             Kstat2Handle handle = new Kstat2Handle();
269             try {
270                 Kstat2Map map = handle.lookupMap(mapStr);
271                 for (int i = 0; i < names.length; i++) {
272                     result[i] = map.getValue(names[i]);
273                 }
274             } finally {
275                 handle.close();
276             }
277         } catch (Kstat2StatusException e) {
278             LOG.debug("Failed to get stats on {} for names {}: {}", mapStr, Arrays.toString(names), e.getMessage());
279         } finally {
280             KstatUtil.CHAIN.unlock();
281             matchers.free();
282         }
283         return result;
284     }
285 
286     /**
287      * Query Kstat2 iterating over maps using a wildcard indicating a 0-indexed list, such as a cpu.
288      *
289      * @param beforeStr The part of the string before the wildcard
290      * @param afterStr  The part of the string after the wildcard
291      * @param names     Names of data to query
292      * @return A list of object arrays with the data corresponding to the names
293      */
294     public static List<Object[]> queryKstat2List(String beforeStr, String afterStr, String... names) {
295         if (!SolarisOperatingSystem.HAS_KSTAT2) {
296             throw new UnsupportedOperationException(
297                     "Kstat2 requires Solaris 11.4+. Use SolarisOperatingSystem#HAS_KSTAT2 to test this.");
298         }
299         List<Object[]> results = new ArrayList<>();
300         int s = 0;
301         Kstat2MatcherList matchers = new Kstat2MatcherList();
302         KstatUtil.CHAIN.lock();
303         try {
304             matchers.addMatcher(Kstat2.KSTAT2_M_GLOB, beforeStr + "*" + afterStr);
305             Kstat2Handle handle = new Kstat2Handle();
306             try {
307                 for (s = 0; s < Integer.MAX_VALUE; s++) {
308                     Object[] result = new Object[names.length];
309                     Kstat2Map map = handle.lookupMap(beforeStr + s + afterStr);
310                     for (int i = 0; i < names.length; i++) {
311                         result[i] = map.getValue(names[i]);
312                     }
313                     results.add(result);
314                 }
315             } finally {
316                 handle.close();
317             }
318         } catch (Kstat2StatusException e) {
319             // Expected to end iteration
320             LOG.debug("Failed to get stats on {}{}{} for names {}: {}", beforeStr, s, afterStr, Arrays.toString(names),
321                     e.getMessage());
322         } finally {
323             KstatUtil.CHAIN.unlock();
324             matchers.free();
325         }
326         return results;
327     }
328 }