/* Copyright (c) 2017 Daniel Widdis, All Rights Reserved
*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package com.sun.jna.platform.unix.solaris;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.platform.unix.solaris.LibKstat.Kstat;
import com.sun.jna.platform.unix.solaris.LibKstat.KstatCtl;
import com.sun.jna.platform.unix.solaris.LibKstat.KstatNamed;
import junit.framework.TestCase;
/**
* Exercise the {@link Kstat} class.
*
* @author widdis@gmail.com
*/
public class LibKstatTest extends TestCase {
public void testKstatLookupString() {
if (Platform.isSolaris()) {
KstatCtl kc = LibKstat.INSTANCE.kstat_open();
// Test reading string
Kstat ksp = kstatLookup(kc, "cpu_info", -1, null);
assertNotNull(ksp);
assertTrue(kstatRead(kc, ksp));
assertNotNull(kstatDataLookupString(ksp, "vendor_id"));
assertNotNull(kstatDataLookupString(ksp, "brand"));
assertNotNull(kstatDataLookupString(ksp, "stepping"));
assertNotNull(kstatDataLookupString(ksp, "model"));
assertNotNull(kstatDataLookupString(ksp, "family"));
LibKstat.INSTANCE.kstat_close(kc);
}
}
public void testKstatLookupLong() {
if (Platform.isSolaris()) {
KstatCtl kc = LibKstat.INSTANCE.kstat_open();
// Test reading long
Kstat ksp = kstatLookup(kc, null, -1, "file_cache");
assertNotNull(ksp);
assertTrue(kstatRead(kc, ksp));
assertTrue(kstatDataLookupLong(ksp, "buf_max") > 0);
LibKstat.INSTANCE.kstat_close(kc);
}
}
public void testKstatLookupAll() {
if (Platform.isSolaris()) {
KstatCtl kc = LibKstat.INSTANCE.kstat_open();
for (Kstat ksp : kstatLookupAll(kc, "cpu", -1, "sys")) {
if (kstatRead(kc, ksp)) {
assertTrue(kstatDataLookupLong(ksp, "cpu_ticks_idle") >= 0);
assertTrue(kstatDataLookupLong(ksp, "cpu_ticks_kernel") >= 0);
assertTrue(kstatDataLookupLong(ksp, "cpu_ticks_user") >= 0);
}
}
LibKstat.INSTANCE.kstat_close(kc);
}
}
/**
* Convenience method for kstat_data_lookup() with String return values.
* Searches the kstat's data section for the record with the specified name.
* This operation is valid only for kstat types which have named data
* records. Currently, only the KSTAT_TYPE_NAMED and KSTAT_TYPE_TIMER kstats
* have named data records.
*
* @param ksp
* The kstat to search
* @param name
* The key for the name-value pair, or name of the timer as
* applicable
* @return The value as a String.
*/
private static String kstatDataLookupString(Kstat ksp, String name) {
if (ksp.ks_type != LibKstat.KSTAT_TYPE_NAMED && ksp.ks_type != LibKstat.KSTAT_TYPE_TIMER) {
throw new IllegalArgumentException("Not a kstat_named or kstat_timer kstat.");
}
Pointer p = LibKstat.INSTANCE.kstat_data_lookup(ksp, name);
if (p == null) {
fail(String.format("Failed lo lookup kstat value for key %s", name));
return "";
}
KstatNamed data = new KstatNamed(p);
switch (data.data_type) {
case LibKstat.KSTAT_DATA_CHAR:
return new String(data.value.charc).trim();
case LibKstat.KSTAT_DATA_INT32:
return Integer.toString(data.value.i32);
case LibKstat.KSTAT_DATA_UINT32:
if (data.value.ui32 > 0) {
return Integer.toString(data.value.ui32);
}
return Long.toString(data.value.ui32 & 0x00000000ffffffffL);
case LibKstat.KSTAT_DATA_INT64:
return Long.toString(data.value.i64);
case LibKstat.KSTAT_DATA_UINT64:
if (data.value.ui64 > 0) {
return Long.toString(data.value.ui64);
}
return BigInteger.valueOf(data.value.ui64).add(BigInteger.ONE.shiftLeft(64)).toString();
case LibKstat.KSTAT_DATA_STRING:
return data.value.str.addr.getString(0);
default:
fail(String.format("Unimplemented kstat data type %d", data.data_type));
return "";
}
}
/**
* Convenience method for kstat_data_lookup() with numeric return values.
* Searches the kstat's data section for the record with the specified name.
* This operation is valid only for kstat types which have named data
* records. Currently, only the KSTAT_TYPE_NAMED and KSTAT_TYPE_TIMER kstats
* have named data records.
*
* @param ksp
* The kstat to search
* @param name
* The key for the name-value pair, or name of the timer as
* applicable
* @return The value as a long. If the data type is a character or string
* type, returns 0 and logs an error.
*/
private static long kstatDataLookupLong(Kstat ksp, String name) {
if (ksp.ks_type != LibKstat.KSTAT_TYPE_NAMED && ksp.ks_type != LibKstat.KSTAT_TYPE_TIMER) {
throw new IllegalArgumentException("Not a kstat_named or kstat_timer kstat.");
}
Pointer p = LibKstat.INSTANCE.kstat_data_lookup(ksp, name);
if (p == null) {
fail(String.format("Failed lo lookup kstat value on %s:%d:%s for key %s", new String(ksp.ks_module).trim(),
ksp.ks_instance, new String(ksp.ks_name).trim(), name));
return 0L;
}
KstatNamed data = new KstatNamed(p);
switch (data.data_type) {
case LibKstat.KSTAT_DATA_INT32:
return (long) data.value.i32;
case LibKstat.KSTAT_DATA_UINT32:
return data.value.ui32 & 0x00000000ffffffffL;
case LibKstat.KSTAT_DATA_INT64:
return data.value.i64;
case LibKstat.KSTAT_DATA_UINT64:
// Doesn't actually return unsigned; caller must interpret
return data.value.ui64;
default:
fail(String.format("Unimplemented or non-numeric kstat data type %d", data.data_type));
return 0L;
}
}
/**
* Convenience method for kstat_read() which gets data from the kernel for
* the kstat pointed to by ksp. ksp.ks_data is automatically allocated (or
* reallocated) to be large enough to hold all of the data. ksp.ks_ndata is
* set to the number of data fields, ksp.ks_data_size is set to the total
* size of the data, and ksp.ks_snaptime is set to the high-resolution time
* at which the data snapshot was taken.
*
* @param ksp
* The kstat from which to retrieve data
* @return True if successful; false otherwise
*/
private static boolean kstatRead(KstatCtl kc, Kstat ksp) {
int retry = 0;
while (0 > LibKstat.INSTANCE.kstat_read(kc, ksp, null)) {
if (LibKstat.EAGAIN != Native.getLastError() || 5 <= ++retry) {
fail(String.format("Failed to read kstat %s:%d:%s", new String(ksp.ks_module).trim(), ksp.ks_instance,
new String(ksp.ks_name).trim()));
return false;
}
try {
Thread.sleep(8 << retry);
} catch (InterruptedException e) {
fail(e.getMessage());
return false;
}
}
return true;
}
/**
* Convenience method for kstat_lookup(). Traverses the kstat chain,
* searching for a kstat with the same ks_module, ks_instance, and ks_name
* fields; this triplet uniquely identifies a kstat. If ks_module is NULL,
* ks_instance is -1, or ks_name is NULL, then those fields will be ignored
* in the search.
*
* @param module
* The module, or null to ignore
* @param instance
* The instance, or -1 to ignore
* @param name
* The name, or null to ignore
* @return The first match of the requested Kstat structure if found, or
* null
*/
private static Kstat kstatLookup(KstatCtl kc, String module, int instance, String name) {
int ret = LibKstat.INSTANCE.kstat_chain_update(kc);
if (ret < 0) {
fail(String.format("Failed to update kstat chain"));
return null;
}
return LibKstat.INSTANCE.kstat_lookup(kc, module, instance, name);
}
/**
* Convenience method for kstat_lookup(). Traverses the kstat chain,
* searching for all kstats with the same ks_module, ks_instance, and
* ks_name fields; this triplet uniquely identifies a kstat. If ks_module is
* NULL, ks_instance is -1, or ks_name is NULL, then those fields will be
* ignored in the search.
*
* @param module
* The module, or null to ignore
* @param instance
* The instance, or -1 to ignore
* @param name
* The name, or null to ignore
* @return All matches of the requested Kstat structure if found, or an
* empty list otherwise
*/
private static List<Kstat> kstatLookupAll(KstatCtl kc, String module, int instance, String name) {
List<Kstat> kstats = new ArrayList<Kstat>();
int ret = LibKstat.INSTANCE.kstat_chain_update(kc);
if (ret < 0) {
fail(String.format("Failed to update kstat chain"));
return kstats;
}
for (Kstat ksp = LibKstat.INSTANCE.kstat_lookup(kc, module, instance, name); ksp != null; ksp = ksp.next()) {
if ((module == null || module.equals(new String(ksp.ks_module).trim()))
&& (instance < 0 || instance == ksp.ks_instance)
&& (name == null || name.equals(new String(ksp.ks_name).trim()))) {
kstats.add(ksp);
}
}
return kstats;
}
}