JPPF Issue Tracker
star_faded.png
Please log in to bookmark issues
feature_request_small.png
CLOSED  Feature request JPPF-396  -  Provide information on remote drivers/nodes not natively available from the JDK
Posted May 17, 2015 - updated May 25, 2018
action_vote_minus_faded.png
0
Votes
action_vote_plus_faded.png
icon_info.png This issue has been closed with status "Closed" and resolution "RESOLVED".
Issue details
  • Type of issue
    Feature request
  • Status
     
    Closed
  • Assigned to
     lolo4j
  • Type of bug
    Not triaged
  • Likelihood
    Not triaged
  • Effect
    Not triaged
  • Posted by
     lolo4j
  • Owned by
    Not owned by anyone
  • Category
    Management / Monitoring
  • Resolution
    RESOLVED
  • Priority
    Normal
  • Targetted for
    icon_milestones.png JPPF 6.0
Issue description
Some hardware/OS/JVM process information either can not be obtained from existing JDK APIs or is provided with incorrect values. For instance the free system RAM provided by com.sun.management.OperatingSystemMXBean.getFreePhysicalMemorySize() returns an incorrect value on Linux, as it does not count the cached and buffers memory. Unfortunately, there is no Java API to get those.

Thus, some information that would really be useful can only be obtained via native (JNI) calls or by parsing the output of shell commands.

We propose to provide this additional information as part of the data available in the Diagnostics MBean, in some to-be-determined future version. The following possiblilities are being considered:
  • use the existing project SIGAR, which does exactly what we need. Unfortunately, this project has seen no update in almost 5 years and its Java API is designed to work with JDK 1.4 (no generics)
  • leverage the project Oshi, once it matures enough, if it ever does. The project claims to leverage JNA to make their native calls, however their current code shows that they mostly use command-line parsing, apart from two functions on Windows platforms
  • use JNA ourselves and build our own API, which means dealing with platform-specific implemntations of whatever interface we come up with, just like we did for the idle host feature

#1
Comment posted by
 Daniel Widdis
May 19, 18:44
I've started contributing to Oshi. Here's the Mac OS X code for free (available) and total system RAM.

SystemB.java
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
 
/**
 * Memory and CPU stats from vm_stat and sysctl
 * 
 * @author widdis[at]gmail[dot]com
 */
public interface SystemB extends Library {
 // TODO: Submit this class to JNA
 SystemB INSTANCE = (SystemB) Native.loadLibrary("System", SystemB.class);
 
 // host_statistics()
  static int HOST_LOAD_INFO = 1;// System loading stats
 static int HOST_VM_INFO = 2; // Virtual memory stats
  static int HOST_CPU_LOAD_INFO = 3;// CPU load stats
 
 // host_statistics64()
  static int HOST_VM_INFO64 = 4; // 64-bit virtual memory stats
 static int HOST_EXTMOD_INFO64 = 5;// External modification stats
  static int HOST_EXPIRED_TASK_INFO = 6; // Statistics for expired tasks
 
  // sysctl()
 static int CTL_KERN = 1; // "high kernel": proc, limits
 static int KERN_OSVERSION = 65;
 
 static int CTL_HW = 6; // generic cpu/io
  static int HW_MEMSIZE = 24; // uint64_t: physical ram size
  static int HW_LOGICALCPU = 103;
 static int HW_LOGICALCPU_MAX = 104;
 static int HW_CPU64BIT_CAPABLE = 107;
 
 static int CTL_MACHDEP = 7; // machine dependent
  static int MACHDEP_CPU = 102; // cpu
  static int MACHDEP_CPU_VENDOR = 103;
  static int MACHDEP_CPU_BRAND_STRING = 104;
  static int MACHDEP_CPU_FAMILY = 105;
  static int MACHDEP_CPU_MODEL = 106;
 static int MACHDEP_CPU_STEPPING = 109;
 
  // host_cpu_load_info()
 static int CPU_STATE_MAX = 4;
 static int CPU_STATE_USER = 0;
  static int CPU_STATE_SYSTEM = 1;
  static int CPU_STATE_IDLE = 2;
  static int CPU_STATE_NICE = 3;
 
  // Data size
  static int UINT64_SIZE = Native.getNativeSize(long.class);
  static int INT_SIZE = Native.getNativeSize(int.class);
 
  public static class HostCpuLoadInfo extends Structure {
   public int cpu_ticks[] = new int[CPU_STATE_MAX];
  }
 
 public static class HostLoadInfo extends Structure {
    public int[] avenrun = new int[3]; // scaled by LOAD_SCALE
    public int[] mach_factor = new int[3]; // scaled by LOAD_SCALE
  }
 
 public static class VMStatistics extends Structure {
    public int free_count; // # of pages free
   public int active_count; // # of pages active
   public int inactive_count; // # of pages inactive
   public int wire_count; // # of pages wired down
   public int zero_fill_count; // # of zero fill pages
   public int reactivations; // # of pages reactivated
   public int pageins; // # of pageins
   public int pageouts; // # of pageouts
   public int faults; // # of faults
   public int cow_faults; // # of copy-on-writes
   public int lookups; // object cache lookups
   public int hits; // object cache hits
   public int purgeable_count; // # of pages purgeable
   public int purges; // # of pages purged
   // # of pages speculative (included in free_count)
    public int speculative_count;
 }
 
 public static class VMStatistics64 extends Structure {
    public int free_count; // # of pages free
   public int active_count; // # of pages active
   public int inactive_count; // # of pages inactive
   public int wire_count; // # of pages wired down
   public long zero_fill_count; // # of zero fill pages
    public long reactivations; // # of pages reactivated
    public long pageins; // # of pageins
    public long pageouts; // # of pageouts
    public long faults; // # of faults
    public long cow_faults; // # of copy-on-writes
    public long lookups; // object cache lookups
    public long hits; // object cache hits
    public long purges; // # of pages purged
    public int purgeable_count; // # of pages purgeable
   // # of pages speculative (included in free_count)
    public int speculative_count;
   public long decompressions; // # of pages decompressed
    public long compressions; // # of pages compressed
    // # of pages swapped in (via compression segments)
   public long swapins;
    // # of pages swapped out (via compression segments)
    public long swapouts;
   // # of pages used by the compressed pager to hold all the
    // compressed data
    public int compressor_page_count;
   public int throttled_count; // # of pages throttled
   // # of pages that are file-backed (non-swap)
   public int external_page_count;
   public int internal_page_count; // # of pages that are anonymous
    // # of pages (uncompressed) held within the compressor.
    public long total_uncompressed_pages_in_compressor;
 }
 
 int mach_host_self();
 
 int host_page_size(int machPort, LongByReference pPageSize);
 
  int host_statistics(int machPort, int hostStat, Object stats,
     IntByReference count);
 
  int host_statistics64(int machPort, int hostStat, Object stats,
     IntByReference count);
 
  int sysctl(int[] name, int namelen, Pointer oldp, IntByReference oldlenp,
     Pointer newp, int newlen);
 
  int sysctlbyname(String name, Pointer oldp, IntByReference oldlenp,
     Pointer newp, int newlen);
}
GlobalMemory.java
import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
 
/**
 * Memory obtained by host_statistics (vm_stat) and sysctl
 * 
 * @author widdis[at]gmail[dot]com
 */
public class GlobalMemory implements Memory {
 
 public long getAvailable() {
    long availableMemory = 0;
   long pageSize = 4096;
 
   int machPort = SystemB.INSTANCE.mach_host_self();
 
   LongByReference pPageSize = new LongByReference();
    if (0 != SystemB.INSTANCE.host_page_size(machPort, pPageSize))
      throw new LastErrorException("Error code: " + Native.getLastError());
   pageSize = pPageSize.getValue();
 
    VMStatistics vmStats = new VMStatistics();
    if (0 != SystemB.INSTANCE.host_statistics(machPort,
       SystemB.HOST_VM_INFO, vmStats,
        new IntByReference(vmStats.size() / SystemB.INT_SIZE)))
     throw new LastErrorException("Error code: " + Native.getLastError());
   availableMemory = (vmStats.free_count + vmStats.inactive_count)
       * pageSize;
 
   return availableMemory;
 }
 
 public long getTotal() {
    long totalMemory = 0;
   int[] mib = { SystemB.CTL_HW, SystemB.HW_MEMSIZE };
   Pointer pMemSize = new com.sun.jna.Memory(SystemB.UINT64_SIZE);
   if (0 != SystemB.INSTANCE.sysctl(mib, mib.length, pMemSize,
       new IntByReference(SystemB.UINT64_SIZE), null, 0))
      throw new LastErrorException("Error code: " + Native.getLastError());
   totalMemory = pMemSize.getLong(0);
    return totalMemory;
 }
}
#2
Comment posted by
 Daniel Widdis
May 22, 17:32
My Mac port has been merged to oshi. I've updated the above snippets to the final version. Now if I can figure out how to get an Ubuntu VM running I can start working on the Linux port...
#3
Comment posted by
 Daniel Widdis
May 23, 23:16
Since getting true "available memory" was what prompted this request, I thought I'd update on the current state of my stumblings in Linux with JNA.

Linux provides some memory information from the sysinfo() function and its associated structure. (Aside: the structure has a byte-array as padding which ends up 0-length on most 64-bit systems (if sizeof(long)=8 and sizeof(int)=4) which JNA can't handle, so there's a bit of complexity just to grab the "total" and "free" amounts from here.)

Unfortunately, this method does not provide access to inactive memory, which is necessary to calculate truly available memory. To get this value, one must call global_page_state() twice with appropriate integers corresponding to 2 different enums. And while I can read the source code and know the value of the important enums (I know it's 0 for one and 2 for another) I'm not sure it's good practice to rely on nobody every inserting a new value in the enum and throwing off future calculations. Also sysconf() has to be called to get the page size.

So, for available memory, one has to call 4 linux native functions, sysinfo (for free bytes), global_page_state twice (using enums that may one day change) for inactive pages of two different types, and sysconf() for page size.

Alternately, one can simply read the pseudo-file /proc/meminfo. This isn't an actual file; it's a hook into the kernel's meminfo function, eventually returning a string that can be parsed to get the information we want... a lot more reliably and flexibly. No disk reads are involved, so it's fast. And probably the best option.

#4
Comment posted by
 Daniel Widdis
icon_reply.pngOct 20, 08:08, in reply to comment #3
I was just peeking around for this issue to suggest it's probably ready to go into 5.2 and see it's already targeted for that milestone!

I've essentially taken over Oshi as the main "developer" (high honors for an amateur coder) and have written most of the existing code for the memory and CPU functions. I think the EPL license is liberal enough for you to take what you need here, but I'm happy to personally contribute any patches you need if licensing is a concern. I've already contributed the Mac SystemB code quoted above to the JNA project, which is in the current version, making implementation even easier.

It would be nice to get a true "free memory" reading on my linux JPPF nodes in the next minor release.
#6
Comment posted by
 lolo4j
Oct 09, 07:58
I realize it's been a (busy) year since the last commnent, but I have not given up! I gave a try to oshi a couple days ago, and I'm really happy with the results, the clarity and ease of use of the APIs is impressive, and it just works. Licensing is not a problem, EPL is fully compatible with ASL 2.0. My main concern was with the size of the additional dependencies for the driver and nodes, but we already use the largest jars for the JNA libs, so it's mostly a matter of upgrading JNA to the version used by Oshi (done).

Instead of just adding the information provided by Oshi, I will also refactor the diagnostics so that it can take additional information from pluggable providers. In particular, the HealthSnapshot class will become a collection of named properties fed by the providers. To validate the excercise, a built-in data provider based on Oshi will be implemented and made available in the distribution.

The counterpart to this is the ability to add columns to the JVM health view of the desktop and web admin consoles, along with the ability to make data availble for the charts in the desktop console (I haven't found an "easy" way to the web console yet). This will be covered in another feature request I'm about to create.
#8
Comment posted by
 lolo4j
Oct 09, 08:08
The corresponding UI-based feature: Feature request JPPF-519 - Admin console: ability to add custom data to the JVM health view and the charts, which depends on this feature.
#9
Comment posted by
 lolo4j
May 11, 00:23
Committed first draft of the MonitoringDataProvider API, with the existing data refactored as a built-in provider.

Now on to implementing a provider wrapping the OSHI APIs.
#10
Comment posted by
 lolo4j
icon_reply.pngMay 25, 08:13, in reply to comment #9
Oshi is working very smoothly and is now integrated with JPPF drivers and nodes. New monitoring data fields can be easily added in the built-in monitring data provider, and they will be automatically added to the JVM health view and charts of the desktop admin console, as well as the JVM health page of the web console. The driver and node distributions are somewhat larger due to the additional dependencies, but I think that's worth it.

lolo4j wrote:
Now on to implementing a provider wrapping the OSHI APIs.