13 | * When instantiated for the first time, JavaSysMon 14 | * will discover which operating system it is running on 15 | * and attempt to load the appropriate OS-specific 16 | * extensions. If JavaSysMon doesn't support the OS 17 | * you're running on, all calls to the API will return 18 | * null or zero values. Probably the best one to test is 19 | * osName. 20 | *
21 | * You can run JavaSysMon directly as a jar file, using
22 | * the command "java -jar javasysmon.jar", in which case
23 | * it will display output similar to the UNIX "top"
24 | * command. You can optionally specify a process id as an
25 | * argument, in which case JavaSysMon will attempt to
26 | * kill the process.
27 | *
28 | * @author Jez Humble
29 | */
30 | public class JavaSysMon implements Monitor {
31 |
32 | private static Monitor monitor = null;
33 | private static ArrayList supported = new ArrayList();
34 |
35 | /**
36 | * Allows you to register your own implementation of {@link Monitor}.
37 | *
38 | * @param myMonitor An implementation of the Monitor interface that all API calls will be delegated to
39 | */
40 | public static void setMonitor(Monitor myMonitor) {
41 | if (monitor == null || monitor instanceof NullMonitor) {
42 | monitor = myMonitor;
43 | }
44 | }
45 |
46 | static void addSupportedConfig(String config) {
47 | supported.add(config);
48 | }
49 |
50 | static {
51 | new MacOsXMonitor();
52 | new LinuxMonitor();
53 | new WindowsMonitor();
54 | new SolarisMonitor();
55 | new NullMonitor(); // make sure the API never gives back a NPE
56 | }
57 |
58 | /**
59 | * Creates a new JavaSysMon object through which to access
60 | * the JavaSysMon API. All necessary state is kept statically
61 | * so there is zero overhead to instantiating this class.
62 | */
63 | public JavaSysMon() {}
64 |
65 | /**
66 | * This is the main entry point when running the jar directly.
67 | * It prints out some system performance metrics and the process table
68 | * in a format similar to the UNIX top command. Optionally you can
69 | * specify a process id as an argument, in which case JavaSysMon
70 | * will attempt to kill the process specified by that pid.
71 | */
72 | public static void main (String[] params) throws Exception {
73 | if (monitor instanceof NullMonitor) {
74 | System.err.println("Couldn't find an implementation for OS: " + System.getProperty("os.name"));
75 | System.err.println("Supported configurations:");
76 | for (Iterator iter = supported.iterator(); iter.hasNext(); ) {
77 | String config = (String) iter.next();
78 | System.err.println(config);
79 | }
80 | } else {
81 | if (params.length == 1) {
82 | System.out.println("Attempting to kill process id " + params[0]);
83 | monitor.killProcess(Integer.parseInt(params[0]));
84 | }
85 | CpuTimes initialTimes = monitor.cpuTimes();
86 | System.out.println("OS name: " + monitor.osName() +
87 | " Uptime: " + secsInDaysAndHours(monitor.uptimeInSeconds()) +
88 | " Current PID: " + monitor.currentPid());
89 | System.out.println("Number of CPUs: " + monitor.numCpus() +
90 | " CPU frequency: " + monitor.cpuFrequencyInHz() / (1000*1000) + " MHz");
91 | System.out.println("RAM " + monitor.physical() + " SWAP " + monitor.swap());
92 | System.out.println("Sampling CPU usage...");
93 | Thread.sleep(500);
94 | System.out.println("CPU Usage: " + monitor.cpuTimes().getCpuUsage(initialTimes));
95 | System.out.println("\n" + ProcessInfo.header());
96 | ProcessInfo[] processes = monitor.processTable();
97 | for (int i = 0; i < processes.length; i++) {
98 | System.out.println(processes[i].toString());
99 | }
100 | }
101 | }
102 |
103 | /**
104 | * Whether or not JavaSysMon is running on a supported platform.
105 | *
106 | * @return true
if the platform is supported,
107 | * false
if it isn't.
108 | */
109 | public boolean supportedPlatform() {
110 | return !(monitor instanceof NullMonitor);
111 | }
112 |
113 | private static String secsInDaysAndHours(long seconds) {
114 | long days = seconds / (60 * 60 * 24);
115 | long hours = (seconds / (60 * 60)) - (days * 24);
116 | return days + " days " + hours + " hours";
117 | }
118 |
119 | // Following is the actual API
120 |
121 | /**
122 | * Get the operating system name.
123 | *
124 | * @return The operating system name.
125 | */
126 | public String osName() {
127 | return monitor.osName();
128 | }
129 |
130 | /**
131 | * Get the number of CPU cores.
132 | *
133 | * @return The number of CPU cores.
134 | */
135 | public int numCpus() {
136 | return monitor.numCpus();
137 | }
138 |
139 | /**
140 | * Get the CPU frequency in Hz
141 | *
142 | * @return the CPU frequency in Hz
143 | */
144 | public long cpuFrequencyInHz() {
145 | return monitor.cpuFrequencyInHz();
146 | }
147 |
148 | /**
149 | * How long the system has been up in seconds.
150 | * Doesn't generally include time that the system
151 | * has been hibernating or asleep.
152 | *
153 | * @return The time the system has been up in seconds.
154 | */
155 | public long uptimeInSeconds() {
156 | return monitor.uptimeInSeconds();
157 | }
158 |
159 | /**
160 | * Gets the pid of the process that is calling this method
161 | * (assuming it is running in the same process).
162 | *
163 | * @return The pid of the process calling this method.
164 | */
165 | public int currentPid() {
166 | return monitor.currentPid();
167 | }
168 |
169 | /**
170 | * Gets a snapshot which contains the total amount
171 | * of time the CPU has spent in user mode, kernel mode,
172 | * and idle. Given two snapshots, you can calculate
173 | * the CPU usage during that time. There is a convenience
174 | * method to perform this calculation in
175 | * {@link CpuTimes#getCpuUsage}
176 | *
177 | * @return An object containing the amount of time the
178 | * CPU has spent idle, in user mode and in kernel mode,
179 | * in milliseconds.
180 | */
181 | public CpuTimes cpuTimes() {
182 | return monitor.cpuTimes();
183 | }
184 |
185 | /**
186 | * Gets the physical memory installed, and the amount free.
187 | *
188 | * @return An object containing the amount of physical
189 | * memory installed, and the amount free.
190 | */
191 | public MemoryStats physical() {
192 | return monitor.physical();
193 | }
194 |
195 | /**
196 | * Gets the amount of swap available to the operating system,
197 | * and the amount that is free.
198 | *
199 | * @return An object containing the amount of swap available
200 | * to the system, and the amount free.
201 | */
202 | public MemoryStats swap() {
203 | return monitor.swap();
204 | }
205 |
206 | /**
207 | * Get the current process table. This call returns an array of
208 | * objects, each of which represents a single process. If you want
209 | * the objects in a tree structure, use {@link #processTree} instead.
210 | *
211 | * @return An array of objects, each of which represents a process.
212 | */
213 | public ProcessInfo[] processTable() {
214 | return monitor.processTable();
215 | }
216 |
217 | /**
218 | * Gets the current process table in the form of a process tree.
219 | * The object returned is a top-level container which doesn't actually
220 | * represent a process - its children are the top-level processes
221 | * running in the system. This is necessary because some operating systems
222 | * (Windows, for example) don't have a single top-level process (orphans
223 | * are literally orphaned), and because the process table snapshot
224 | * is not atomic. That means the process table thus returned can be
225 | * internally inconsistent.
226 | *
227 | * @return The current process table in the form of a process tree.
228 | */
229 | public OsProcess processTree() {
230 | return OsProcess.createTree(monitor.processTable());
231 | }
232 |
233 | /**
234 | * Attempts to kill the process identified by the integer id supplied.
235 | * This will silently fail if you don't have the authority to kill
236 | * that process. This method sends SIGTERM on the UNIX platform,
237 | * and kills the process using TerminateProcess on Windows.
238 | *
239 | * @param pid The id of the process to kill
240 | */
241 | public void killProcess(int pid) {
242 | monitor.killProcess(pid);
243 | }
244 |
245 | /**
246 | * Allows you to visit the process tree, starting at the node identified by pid.
247 | * The process tree is traversed depth-first.
248 | *
249 | * @param pid The identifier of the node to start visiting
250 | * @param processVisitor The visitor
251 | */
252 | public void visitProcessTree(final int pid, final ProcessVisitor processVisitor) {
253 | final OsProcess process = processTree().find(pid);
254 | if (process != null) {
255 | process.accept(processVisitor, 0);
256 | }
257 | }
258 |
259 | /**
260 | * Kills the process tree starting at the process identified by pid. The
261 | * process tree is killed from the bottom up to ensure that orphans are
262 | * not generated.
263 | *
264 | * This method uses {@link #visitProcessTree}. 265 | * 266 | * @param pid The identifier of the process at which to start killing the tree. 267 | * @param descendantsOnly Whether or not to kill the process you start at, 268 | * or only its descendants 269 | */ 270 | public void killProcessTree(final int pid, final boolean descendantsOnly) { 271 | visitProcessTree(pid, new ProcessVisitor() { 272 | public boolean visit(OsProcess process, int level) { 273 | return !descendantsOnly || (pid != process.processInfo().getPid()); 274 | } 275 | }); 276 | } 277 | 278 | /** 279 | * Attempts to kill all the descendants of the currently running process. 280 | */ 281 | public void infanticide() { 282 | killProcessTree(currentPid(), true); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/LinuxMonitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.lang.reflect.Method; 6 | import java.math.BigDecimal; 7 | import java.util.ArrayList; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | // Network stats will come from /proc/net/dev; disk stats will be from /proc/diskstats 14 | class LinuxMonitor implements Monitor { 15 | 16 | private static final Logger LOG = Logger.getLogger(LinuxMonitor.class.getName()); 17 | 18 | private static final Pattern TOTAL_MEMORY_PATTERN = 19 | Pattern.compile("MemTotal:\\s+(\\d+) kB", Pattern.MULTILINE); 20 | private static final Pattern FREE_MEMORY_PATTERN = 21 | Pattern.compile("MemFree:\\s+(\\d+) kB", Pattern.MULTILINE); 22 | private static final Pattern TOTAL_SWAP_PATTERN = 23 | Pattern.compile("SwapTotal:\\s+(\\d+) kB", Pattern.MULTILINE); 24 | private static final Pattern FREE_SWAP_PATTERN = 25 | Pattern.compile("SwapFree:\\s+(\\d+) kB", Pattern.MULTILINE); 26 | private static final Pattern CPU_JIFFIES_PATTERN = 27 | Pattern.compile("cpu\\s+(.*)", Pattern.MULTILINE); 28 | private static final Pattern NUM_CPU_PATTERN = 29 | Pattern.compile("processor\\s+:\\s+(\\d+)", Pattern.MULTILINE); 30 | private static final Pattern CPU_FREQ_PATTERN = 31 | Pattern.compile("model name[^@]*@\\s+([0-9.A-Za-z]*)", Pattern.MULTILINE); 32 | private static final Pattern UPTIME_PATTERN = 33 | Pattern.compile("([\\d]*).*"); 34 | private static final Pattern PID_PATTERN = 35 | Pattern.compile("([\\d]*).*"); 36 | private static final Pattern DISTRIBUTION = 37 | Pattern.compile("DISTRIB_DESCRIPTION=\"(.*)\"", Pattern.MULTILINE); 38 | 39 | private FileUtils fileUtils; 40 | private int userHz = 100; // Shouldn't be hardcoded. See below. 41 | 42 | LinuxMonitor(FileUtils fileUtils) { 43 | this.fileUtils = fileUtils; 44 | } 45 | 46 | public LinuxMonitor() { 47 | fileUtils = new FileUtils(); 48 | JavaSysMon.addSupportedConfig("Linux (only tested with x86)"); 49 | if (System.getProperty("os.name").toLowerCase().startsWith("linux")) { 50 | JavaSysMon.setMonitor(this); 51 | JavaSysMon.addSupportedConfig("Linux (only tested with x86)"); 52 | // In theory, this calculation should return userHz. It doesn't seem to work though. 53 | // long uptimeInSeconds = uptimeInSeconds(); 54 | // previousJiffies = fileUtils.runRegexOnFile(CPU_JIFFIES_PATTERN, "/proc/stat"); 55 | // long uptimeInJiffies = getTotalJiffies(previousJiffies.split("\\s+")); 56 | // userHz = (int) (uptimeInJiffies / uptimeInSeconds); 57 | } 58 | } 59 | 60 | public String osName() { 61 | String distribution = fileUtils.runRegexOnFile(DISTRIBUTION, "/etc/lsb-release"); 62 | if (null == distribution) { 63 | return System.getProperty("os.name"); 64 | } 65 | return distribution; 66 | } 67 | 68 | public MemoryStats physical() { 69 | String totalMemory = fileUtils.runRegexOnFile(TOTAL_MEMORY_PATTERN, "/proc/meminfo"); 70 | long total = Long.parseLong(totalMemory) * 1024; 71 | String freeMemory = fileUtils.runRegexOnFile(FREE_MEMORY_PATTERN, "/proc/meminfo"); 72 | long free = Long.parseLong(freeMemory) * 1024; 73 | return new MemoryStats(free, total); 74 | } 75 | 76 | public MemoryStats swap() { 77 | String totalMemory = fileUtils.runRegexOnFile(TOTAL_SWAP_PATTERN, "/proc/meminfo"); 78 | long total = Long.parseLong(totalMemory) * 1024; 79 | String freeMemory = fileUtils.runRegexOnFile(FREE_SWAP_PATTERN, "/proc/meminfo"); 80 | long free = Long.parseLong(freeMemory) * 1024; 81 | return new MemoryStats(free, total); 82 | } 83 | 84 | public int numCpus() { 85 | int numCpus = 0; 86 | try { 87 | String cpuInfo = fileUtils.slurp("/proc/cpuinfo"); 88 | Matcher matcher = NUM_CPU_PATTERN.matcher(cpuInfo); 89 | while (matcher.find()) { 90 | numCpus++; 91 | } 92 | return numCpus; 93 | } catch (IOException ioe) { 94 | // return nothing 95 | } 96 | return 0; 97 | } 98 | 99 | public long cpuFrequencyInHz() { 100 | String cpuFrequencyAsString = fileUtils.runRegexOnFile(CPU_FREQ_PATTERN, "/proc/cpuinfo"); 101 | int strLen = cpuFrequencyAsString.length(); 102 | BigDecimal cpuFrequency = new BigDecimal(cpuFrequencyAsString.substring(0, strLen - 3)); 103 | long multiplier = getMultiplier(cpuFrequencyAsString.charAt(strLen - 3)); 104 | return cpuFrequency.multiply(new BigDecimal(Long.toString(multiplier))).longValue(); 105 | } 106 | 107 | public long uptimeInSeconds() { 108 | String uptime = fileUtils.runRegexOnFile(UPTIME_PATTERN, "/proc/uptime"); 109 | return Long.parseLong(uptime); 110 | } 111 | 112 | public int currentPid() { 113 | String pid = fileUtils.runRegexOnFile(PID_PATTERN, "/proc/self/stat"); 114 | return Integer.parseInt(pid); 115 | } 116 | 117 | public ProcessInfo[] processTable() { 118 | ArrayList processTable = new ArrayList(); 119 | final String[] pids = fileUtils.pidsFromProcFilesystem(); 120 | for (int i = 0; i < pids.length; i++) { 121 | try { 122 | String stat = fileUtils.slurp("/proc/" + pids[i] + "/stat"); 123 | String status = fileUtils.slurp("/proc/" + pids[i] + "/status"); 124 | String cmdline = fileUtils.slurp("/proc/" + pids[i] + "/cmdline"); 125 | UnixPasswdParser passwdParser = new UnixPasswdParser(); 126 | final LinuxProcessInfoParser parser = new LinuxProcessInfoParser(stat, status, cmdline, passwdParser.parse(), userHz); 127 | processTable.add(parser.parse()); 128 | } catch (ParseException pe) { 129 | // Skip this process, but log a warning for diagnosis. 130 | LOG.log(Level.WARNING, pe.getMessage(), pe); 131 | } catch (IOException ioe) { 132 | // process probably died since we got the process list 133 | } 134 | } 135 | return (ProcessInfo[]) processTable.toArray(new ProcessInfo[processTable.size()]); 136 | } 137 | 138 | public CpuTimes cpuTimes() { 139 | String[] parsedJiffies = fileUtils.runRegexOnFile(CPU_JIFFIES_PATTERN, "/proc/stat").split("\\s+"); 140 | long userJiffies = Long.parseLong(parsedJiffies[0]) + Long.parseLong(parsedJiffies[1]); 141 | long idleJiffies = Long.parseLong(parsedJiffies[3]); 142 | long systemJiffies = Long.parseLong(parsedJiffies[2]); 143 | // this is for Linux >= 2.6 144 | if (parsedJiffies.length > 4) { 145 | for (int i = 4; i < parsedJiffies.length; i++) { 146 | systemJiffies += Long.parseLong(parsedJiffies[i]); 147 | } 148 | } 149 | return new CpuTimes(toMillis(userJiffies), toMillis(systemJiffies), toMillis(idleJiffies)); 150 | } 151 | 152 | public void killProcess(int pid) { 153 | try { 154 | ProcessKiller.DESTROY_PROCESS.invoke(null, new Object[]{new Integer(pid)}); 155 | } catch (Exception e) { 156 | throw new RuntimeException("Could not kill process id " + pid, e); 157 | } 158 | } 159 | 160 | private long getMultiplier(char multiplier) { 161 | switch (multiplier) { 162 | case 'G': 163 | return 1000000000; 164 | case 'M': 165 | return 1000000; 166 | case 'k': 167 | return 1000; 168 | } 169 | return 0; 170 | } 171 | 172 | private long toMillis(long jiffies) { 173 | int multiplier = 1000 / userHz; 174 | return jiffies * multiplier; 175 | } 176 | 177 | // Stole this from Hudson (hudson.util.ProcessTree). It's a hack because it's an undocumented API in the JVM. 178 | // However I can't think of any better way to do this without writing native code for Linux which I want to avoid. 179 | // Wouldn't it be nice if deleting a directory in the proc filesystem killed the process? 180 | private static final class ProcessKiller { 181 | private static Method DESTROY_PROCESS = null; 182 | 183 | static { 184 | try { 185 | Class clazz = Class.forName("java.lang.UNIXProcess"); 186 | DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", new Class[]{int.class}); 187 | DESTROY_PROCESS.setAccessible(true); 188 | } catch (Exception e) { 189 | LinkageError x = new LinkageError("Couldn't get method java.lang.UNIXProcess.destroyProcess(int)"); 190 | x.initCause(e); 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/LinuxProcessInfoParser.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.util.HashMap; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | class LinuxProcessInfoParser { 8 | private final String stat; 9 | private final String status; 10 | private final String cmdline; 11 | private final HashMap uids; 12 | private final int userHz; 13 | 14 | private static final Pattern STATUS_NAME_MATCHER = 15 | Pattern.compile("Name:\\s+(\\w+)", Pattern.MULTILINE); 16 | private static final Pattern STATUS_UID_MATCHER = 17 | Pattern.compile("Uid:\\s+(\\d+)\\s.*", Pattern.MULTILINE); 18 | private static final Pattern STATUS_VM_SIZE_MATCHER = 19 | Pattern.compile("VmSize:\\s+(\\d+) kB", Pattern.MULTILINE); 20 | private static final Pattern STATUS_VM_RSS_MATCHER = 21 | Pattern.compile("VmRSS:\\s+(\\d+) kB", Pattern.MULTILINE); 22 | 23 | public LinuxProcessInfoParser(String stat, String status, String cmdline, HashMap uids, int userHz) { 24 | this.stat = stat; 25 | this.status = status; 26 | this.cmdline = cmdline; 27 | this.uids = uids; 28 | this.userHz = userHz; 29 | } 30 | 31 | public ProcessInfo parse() throws ParseException { 32 | int openParen = stat.indexOf("("); 33 | int closeParen = stat.lastIndexOf(")"); 34 | if (openParen <= 1 || closeParen < 0 || closeParen > stat.length() - 2) { 35 | throw new ParseException("Stat '" + stat + "' does not include expected parens around process name"); 36 | } 37 | 38 | // Start splitting after close of proc name 39 | String[] statElements = stat.substring(closeParen + 2).split(" "); 40 | if (statElements.length < 13) { 41 | throw new ParseException("Stat '" + stat + "' contains fewer elements than expected"); 42 | } 43 | 44 | String pidStr = stat.substring(0, openParen - 1); 45 | 46 | int pid; 47 | int parentPid; 48 | long userMillis; 49 | long systemMillis; 50 | try 51 | { 52 | pid = Integer.parseInt(pidStr); 53 | parentPid = Integer.parseInt(statElements[1]); 54 | userMillis = Long.parseLong(statElements[11]) * (1000 / userHz); 55 | systemMillis = Long.parseLong(statElements[12]) * (1000 / userHz); 56 | } 57 | catch (NumberFormatException e) 58 | { 59 | throw new ParseException("Unable to parse stat '" + stat + "'"); 60 | } 61 | 62 | long residentBytes; 63 | long totalBytes; 64 | try 65 | { 66 | residentBytes = Long.parseLong(getFirstMatch(STATUS_VM_RSS_MATCHER, status)) * 1024; 67 | totalBytes = Long.parseLong(getFirstMatch(STATUS_VM_SIZE_MATCHER, status)) * 1024; 68 | } 69 | catch (NumberFormatException e) 70 | { 71 | throw new ParseException("Unable to extract memory usage information from status '" + status + "'"); 72 | } 73 | 74 | return new ProcessInfo(pid, 75 | parentPid, 76 | trim(cmdline), 77 | getFirstMatch(STATUS_NAME_MATCHER, status), 78 | (String) uids.get(getFirstMatch(STATUS_UID_MATCHER, status)), 79 | userMillis, 80 | systemMillis, 81 | residentBytes, 82 | totalBytes); 83 | } 84 | 85 | private String trim(String cmdline) { 86 | return cmdline.replace('\000', ' ').replace('\n', ' '); 87 | } 88 | 89 | public String getFirstMatch(Pattern pattern, String string) { 90 | try { 91 | Matcher matcher = pattern.matcher(string); 92 | matcher.find(); 93 | return matcher.group(1); 94 | } catch (Exception e) { 95 | return "0"; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/MacOsXMonitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | class MacOsXMonitor implements Monitor { 4 | 5 | private static Monitor monitor = null; 6 | 7 | static { 8 | if (System.getProperty("os.name").toLowerCase().equals("mac os x")) { 9 | new NativeLibraryLoader().loadLibrary("libjavasysmon.jnilib"); 10 | monitor = new MacOsXMonitor(); 11 | } 12 | } 13 | 14 | public MacOsXMonitor() { 15 | JavaSysMon.addSupportedConfig("Mac Os X (PPC, x86, X86_64)"); 16 | if (monitor != null) { 17 | JavaSysMon.setMonitor(monitor); 18 | } 19 | } 20 | 21 | public String osName() { 22 | return System.getProperty("os.name") + " " + System.getProperty("os.version"); 23 | } 24 | 25 | public native int numCpus(); 26 | public native long cpuFrequencyInHz(); 27 | public native long uptimeInSeconds(); 28 | public native int currentPid(); 29 | public native CpuTimes cpuTimes(); 30 | public native MemoryStats physical(); 31 | public native MemoryStats swap(); 32 | public native ProcessInfo[] processTable(); 33 | public native void killProcess(int pid); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/MemoryStats.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | /** 4 | * This object represents a snapshot detailing the total memory of 5 | * some type (physical or swap) available to the operating system, 6 | * and the amount that is currently free. 7 | */ 8 | public class MemoryStats { 9 | 10 | private final static int ONE_MB = 1024 * 1024; 11 | private final long free; 12 | private final long total; 13 | 14 | public MemoryStats(long free, long total) { 15 | this.free = free; 16 | this.total = total; 17 | } 18 | 19 | /** 20 | * The amount of memory that is currently free, in bytes. 21 | * 22 | * @return The amount of memory that is currently free. 23 | */ 24 | public long getFreeBytes() { 25 | return free; 26 | } 27 | 28 | /** 29 | * The amount of memory that is available to the operating system, 30 | * in bytes. 31 | * 32 | * @return The total amount of memory that is available. 33 | */ 34 | public long getTotalBytes() { 35 | return total; 36 | } 37 | 38 | public String toString() { 39 | return "total: " + total / ONE_MB + "Mb free: " + free / ONE_MB + "Mb"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/Monitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | /** 4 | * This is the interface that needs to be 5 | * implemented for any platform that JavaSysMon 6 | * supports. If you write your own implementation, 7 | * you can test it by injecting it into 8 | * {@link JavaSysMon#setMonitor} 9 | */ 10 | public interface Monitor { 11 | /** 12 | * Get the operating system name. 13 | * 14 | * @return The operating system name. 15 | */ 16 | public String osName(); 17 | 18 | /** 19 | * Get the number of CPU cores. 20 | * 21 | * @return The number of CPU cores. 22 | */ 23 | public int numCpus(); 24 | 25 | /** 26 | * Get the CPU frequency in Hz 27 | * 28 | * @return the CPU frequency in Hz 29 | */ 30 | public long cpuFrequencyInHz(); 31 | 32 | /** 33 | * How long the system has been up in seconds. 34 | * Doesn't generally include time that the system 35 | * has been hibernating or asleep. 36 | * 37 | * @return The time the system has been up in seconds. 38 | */ 39 | public long uptimeInSeconds(); 40 | 41 | /** 42 | * Gets a snapshot which contains the total amount 43 | * of time the CPU has spent in user mode, kernel mode, 44 | * and idle. Given two snapshots, you can calculate 45 | * the CPU usage during that time. There is a convenience 46 | * method to perform this calculation in 47 | * {@link CpuTimes#getCpuUsage} 48 | * 49 | * @return An object containing the amount of time the 50 | * CPU has spent idle, in user mode and in kernel mode, 51 | * in milliseconds. 52 | */ 53 | public CpuTimes cpuTimes(); 54 | 55 | /** 56 | * Gets the physical memory installed, and the amount free. 57 | * 58 | * @return An object containing the amount of physical 59 | * memory installed, and the amount free. 60 | */ 61 | public MemoryStats physical(); 62 | 63 | /** 64 | * Gets the amount of swap available to the operating system, 65 | * and the amount that is free. 66 | * 67 | * @return An object containing the amount of swap available 68 | * to the system, and the amount free. 69 | */ 70 | public MemoryStats swap(); 71 | 72 | /** 73 | * Gets the pid of the process that is calling this method 74 | * (assuming it is running in the same process). 75 | * 76 | * @return The pid of the process calling this method. 77 | */ 78 | public int currentPid(); 79 | 80 | /** 81 | * Get the current process table. This call returns an array of 82 | * objects, each of which represents a single process. 83 | * 84 | * @return An array of objects, each of which represents a process. 85 | */ 86 | ProcessInfo[] processTable(); 87 | 88 | /** 89 | * Attempts to kill the process identified by the integer id supplied. 90 | * This will silently fail if you don't have the authority to kill 91 | * that process. This method sends SIGTERM on the UNIX platform, 92 | * and kills the process using TerminateProcess on Windows. 93 | * 94 | * @param pid The id of the process to kill 95 | */ 96 | public void killProcess(int pid); 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/NativeLibraryLoader.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.io.*; 4 | 5 | // This is "optimised" based on the fact we only load each native library once. 6 | class NativeLibraryLoader { 7 | 8 | public static final String JAVA_SYS_MON_TEMP_DIR = "JAVA_SYS_MON_TEMP_DIR"; 9 | 10 | public void loadLibrary(String libraryName) { 11 | try { 12 | InputStream is = this.getClass().getResourceAsStream("/" + libraryName); 13 | File tempNativeLib = getTempFile(libraryName); 14 | FileOutputStream os = new FileOutputStream(tempNativeLib); 15 | copyAndClose(is, os); 16 | System.load(tempNativeLib.getAbsolutePath()); 17 | } catch (IOException ioe) { 18 | throw new RuntimeException("Couldn't load native library " + libraryName, ioe); 19 | } 20 | } 21 | 22 | private void copyAndClose(InputStream is, OutputStream os) throws IOException { 23 | byte[] buffer = new byte[1024]; 24 | while (true) { 25 | int len = is.read(buffer); 26 | if (len < 0) break; 27 | os.write(buffer, 0, len); 28 | } 29 | is.close(); 30 | os.close(); 31 | } 32 | 33 | File getTempFile(String libraryName) throws IOException { 34 | int suffixSeparator = libraryName.lastIndexOf("."); 35 | String suffix = null; 36 | String prefix = libraryName; 37 | if (suffixSeparator >= 0) { 38 | suffix = libraryName.substring(suffixSeparator); 39 | prefix = libraryName.substring(0, suffixSeparator - 1); 40 | } 41 | File tempFile = createTempFile(suffix, prefix); 42 | tempFile.deleteOnExit(); 43 | return tempFile; 44 | } 45 | 46 | private File createTempFile(String suffix, String prefix) throws IOException { 47 | String tempDirProp = System.getProperty(JAVA_SYS_MON_TEMP_DIR); 48 | if (tempDirProp == null || tempDirProp.trim().length() == 0) { 49 | return File.createTempFile(prefix, suffix); 50 | } 51 | return File.createTempFile(prefix, suffix, new File(tempDirProp)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/NullMonitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | class NullMonitor implements Monitor { 4 | 5 | public NullMonitor() { 6 | JavaSysMon.setMonitor(this); 7 | } 8 | 9 | public String osName() { 10 | return System.getProperty("os.name"); 11 | } 12 | 13 | public int numCpus() { 14 | return 0; 15 | } 16 | 17 | public long cpuFrequencyInHz() { 18 | return 0; 19 | } 20 | 21 | public CpuTimes cpuTimes() { 22 | return null; 23 | } 24 | 25 | public MemoryStats physical() { 26 | return null; 27 | } 28 | 29 | public MemoryStats swap() { 30 | return null; 31 | } 32 | 33 | public long uptimeInSeconds() { 34 | return 0; 35 | } 36 | 37 | public int currentPid() { 38 | return 0; 39 | } 40 | 41 | public ProcessInfo[] processTable() { 42 | return new ProcessInfo[0]; 43 | } 44 | 45 | public void killProcess(int pid) { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/OsProcess.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | /** 9 | * This object represents a node in the process tree. It knows 10 | * information about the process it represents, and also 11 | * what its children are. 12 | */ 13 | public class OsProcess { 14 | 15 | private final ArrayList children = new ArrayList(); 16 | private final ProcessInfo processInfo; 17 | 18 | private OsProcess(ProcessInfo processInfo) { 19 | this.processInfo = processInfo; 20 | } 21 | 22 | /** 23 | * This method is the only way to create an OsProcess object. 24 | * It creates a graph of OsProcess objects from an array of 25 | * ProcessInfo objects representing the processes in the system. 26 | * 27 | * @param processTable An array of objects representing the processes in the system. 28 | * @return A graph of OsProcess objects. 29 | */ 30 | public static OsProcess createTree(ProcessInfo[] processTable) { 31 | HashMap processes = new HashMap(); 32 | OsProcess topLevelProcess = new OsProcess(null); 33 | for (int i = 0; i < processTable.length; i++) { 34 | OsProcess process = new OsProcess(processTable[i]); 35 | processes.put(new Integer(processTable[i].getPid()), process); 36 | } 37 | for (int i = 0; i < processTable.length; i++) { 38 | int pid = processTable[i].getPid(); 39 | int ppid = processTable[i].getParentPid(); 40 | OsProcess process = (OsProcess) processes.get(new Integer(pid)); 41 | if (ppid == pid || !processes.containsKey(new Integer(ppid))) { 42 | topLevelProcess.children.add(process); 43 | } else { 44 | ((OsProcess) processes.get(new Integer(ppid))).children.add(process); 45 | } 46 | } 47 | return topLevelProcess; 48 | } 49 | 50 | /** 51 | * Gets the list of child processes of this object. 52 | * 53 | * @return The list of child processes of this object. 54 | */ 55 | public List children() { 56 | return children; 57 | } 58 | 59 | /** 60 | * Information about this process. 61 | * 62 | * @return Information about this process. 63 | */ 64 | public ProcessInfo processInfo() { 65 | return processInfo; 66 | } 67 | 68 | /** 69 | * Finds and returns a particular node in the process tree 70 | * by its id. 71 | * 72 | * @param pid the id of the process to find. 73 | * @return The process node in the tree. 74 | */ 75 | public OsProcess find(int pid) { 76 | if (this.processInfo != null && this.processInfo.getPid() == pid) { 77 | return this; 78 | } 79 | for (Iterator it = children.iterator(); it.hasNext();) { 80 | final OsProcess found = ((OsProcess) it.next()).find(pid); 81 | if (found != null) { 82 | return found; 83 | } 84 | } 85 | return null; 86 | } 87 | 88 | /** 89 | * Method to allow visiting the process tree. Use the convenience method 90 | * {@link JavaSysMon#visitProcessTree} 91 | * 92 | * @param processVisitor An instance of {@link ProcessVisitor} 93 | * @param level The level currently being visited 94 | */ 95 | public void accept(ProcessVisitor processVisitor, int level) { 96 | for (Iterator it = children.iterator(); it.hasNext();) { 97 | ((OsProcess) it.next()).accept(processVisitor, level + 1); 98 | } 99 | if (this.processInfo != null) { 100 | if (processVisitor.visit(this, level)) { 101 | new JavaSysMon().killProcess(processInfo.getPid()); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/ParseException.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | public class ParseException extends RuntimeException { 4 | 5 | public ParseException() { 6 | } 7 | 8 | public ParseException(String s) { 9 | super(s); 10 | } 11 | 12 | public ParseException(String s, Throwable throwable) { 13 | super(s, throwable); 14 | } 15 | 16 | public ParseException(Throwable throwable) { 17 | super(throwable); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/ProcessInfo.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.text.DecimalFormat; 4 | 5 | /** 6 | * This object represents JavaSysMon's understanding of a process. 7 | * You can get all the information JavaSysMon knows about a 8 | * particular process from this object. 9 | *
10 | * There are also convenience methods that can be used to print out 11 | * a process table in a monospace font (for example, on the console). 12 | */ 13 | public class ProcessInfo { 14 | 15 | // Process id info 16 | private int pid; 17 | private int parentPid; 18 | private String command; 19 | private String name; 20 | private String owner; 21 | 22 | // Performance info 23 | private long userMillis; 24 | private long systemMillis; 25 | private long residentBytes; 26 | private long totalBytes; 27 | 28 | public ProcessInfo(int pid, int parentPid, String command, String name, String owner, 29 | long userMillis, long systemMillis, long residentBytes, long totalBytes) { 30 | 31 | this.pid = pid; 32 | this.parentPid = parentPid; 33 | this.command = command; 34 | this.name = name; 35 | this.owner = owner; 36 | this.userMillis = userMillis; 37 | this.systemMillis = systemMillis; 38 | this.residentBytes = residentBytes; 39 | this.totalBytes = totalBytes; 40 | } 41 | 42 | /** 43 | * The id of this process 44 | * 45 | * @return The id of this process 46 | */ 47 | public int getPid() { 48 | return pid; 49 | } 50 | 51 | /** 52 | * The id of the parent process of this parent 53 | * 54 | * @return The id of the parent process of this parent 55 | */ 56 | public int getParentPid() { 57 | return parentPid; 58 | } 59 | 60 | /** 61 | * The command that was originally used to start this process. 62 | * Not currently available on Mac OSX or Solaris (the C source 63 | * contains some information on how to get this data for anyone 64 | * interested in implementing it) 65 | * 66 | * @return A string representing the command that was originally 67 | * used to start this process. 68 | */ 69 | public String getCommand() { 70 | return command; 71 | } 72 | 73 | /** 74 | * The name of this process. This is for display purposes only. 75 | * 76 | * @return The name of this process. 77 | */ 78 | public String getName() { 79 | return name; 80 | } 81 | 82 | /** 83 | * The name of the owner of this process. This is derived from 84 | * the uid, not the effective id. On Windows, this is in the format 85 | * DOMAIN\USERNAME 86 | * 87 | * @return The name of the owner of this process. 88 | */ 89 | public String getOwner() { 90 | return owner; 91 | } 92 | 93 | /** 94 | * The number of milliseconds that this process has been running 95 | * on the CPUs in user mode. Note that this is not "wall time". 96 | * On Mac OSX this information is only available for the current process. 97 | * 98 | * @return The number of milliseconds that this process has been 99 | * running on the CPUs in user mode. 100 | */ 101 | public long getUserMillis() { 102 | return userMillis; 103 | } 104 | 105 | /** 106 | * The number of milliseconds that this process has been running 107 | * on the CPUs in kernel mode. Note that this is not "wall time". 108 | * On Mac OSX this information is only available for the current process. 109 | * 110 | * @return The number of milliseconds that this process has been 111 | * running on the CPUs in kernel mode. 112 | */ 113 | public long getSystemMillis() { 114 | return systemMillis; 115 | } 116 | 117 | /** 118 | * The number of bytes used by this process that are currently in physical 119 | * memory. On Mac OSX this information is only available for the current 120 | * process. 121 | * 122 | * @return The number of bytes used by this process that are currently in 123 | * physical memory. 124 | */ 125 | public long getResidentBytes() { 126 | return residentBytes; 127 | } 128 | 129 | /** 130 | * The total size of this process in bytes. On Mac OSX this information 131 | * is only available for the current process. 132 | * 133 | * @return The total number of bytes used by this process. 134 | */ 135 | public long getTotalBytes() { 136 | return totalBytes; 137 | } 138 | 139 | /** 140 | * Prints out a header that can be used along with {@link #toString} 141 | * (assuming you use a monospace font). 142 | * 143 | * @return A header that can be used when printing out the process table. 144 | */ 145 | public static String header() { 146 | return " pid name ppid user total res time command\n" + 147 | "================================================================================"; 148 | } 149 | 150 | /** 151 | * A one-line string representation of some of the information in this object 152 | * that can be used to print out a single line in a process table. 153 | * Fields have a fixed length so that the table looks nice in a monospace font. 154 | * 155 | * @return a single line representing some of the information about this process. 156 | */ 157 | public String toString() { 158 | // No bloody string formatting in Java 1.4. Starting to reconsider support for it. 159 | // Even C can do this ffs 160 | return stringFormat(pid, 5) + " " + 161 | stringFormat(name, 10) + " " + 162 | stringFormat(parentPid, 5) + " " + 163 | stringFormat(owner, 10) + " " + 164 | stringFormat(totalBytes / (1024 * 1024), 4) + "Mb " + 165 | stringFormat(residentBytes / (1024 * 1024), 4) + "Mb " + 166 | formatMillisecs(userMillis + systemMillis) + " " + 167 | stringFormat(command, 23); 168 | } 169 | 170 | private static String stringFormat(int intToFormat, int fieldSize) { 171 | return stringFormat(Integer.toString(intToFormat), fieldSize, true); 172 | } 173 | 174 | private static String stringFormat(long longToFormat, int fieldSize) { 175 | return stringFormat(Long.toString(longToFormat), fieldSize, true); 176 | } 177 | 178 | private static String stringFormat(String stringToFormat, int fieldSize) { 179 | return stringFormat(stringToFormat, fieldSize, false); 180 | } 181 | 182 | private static String stringFormat(String stringToFormat, int fieldSize, boolean rightJustify) { 183 | // and Java doesn't really excel at this kind of thing either 184 | if (stringToFormat.length() >= fieldSize) { 185 | return stringToFormat.substring(0, fieldSize); 186 | } else { 187 | return rightJustify ? 188 | PADDING.substring(0, fieldSize - stringToFormat.length()) + stringToFormat: 189 | stringToFormat + PADDING.substring(0, fieldSize - stringToFormat.length()); 190 | } 191 | } 192 | 193 | // gotta love this hack 194 | final private static String PADDING = 195 | " "; 196 | 197 | private static String formatMillisecs(long millisecs) { 198 | long secs = millisecs / 1000; 199 | long hours = secs / 3600; 200 | long mins = (secs - (hours * 3600)) / 60; 201 | secs = (secs - (hours * 3600) - (mins * 60)); 202 | DecimalFormat format = new DecimalFormat("00"); 203 | return format.format(hours) + ":" + format.format(mins) + ":" + format.format(secs); 204 | } 205 | } -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/ProcessVisitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | /** 4 | * Allows you to visit the process tree using {@link JavaSysMon#visitProcessTree} 5 | */ 6 | public interface ProcessVisitor { 7 | /** 8 | * Called on every node. The process tree is traversed depth-first 9 | * @param process The current process being visited 10 | * @param level How many levels beneath the initial node visited you are (0-indexed) 11 | * @return Whether or not to kill the process being visited 12 | */ 13 | boolean visit(OsProcess process, int level); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/SolarisMonitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | 6 | class SolarisMonitor implements Monitor { 7 | private static Monitor monitor = null; 8 | private final FileUtils fileUtils; 9 | 10 | static { 11 | if (System.getProperty("os.name").toLowerCase().startsWith("sunos")) { 12 | if (System.getProperty("os.arch").toLowerCase().startsWith("x86")) { 13 | new NativeLibraryLoader().loadLibrary("javasysmonsolx86.so"); 14 | monitor = new SolarisMonitor(); 15 | } 16 | } 17 | } 18 | 19 | public SolarisMonitor() { 20 | JavaSysMon.addSupportedConfig("Solaris (x86)"); 21 | if (monitor != null) { 22 | JavaSysMon.setMonitor(monitor); 23 | } 24 | fileUtils = new FileUtils(); 25 | } 26 | 27 | public String osName() { 28 | return System.getProperty("os.name"); 29 | } 30 | 31 | public ProcessInfo[] processTable() { 32 | ArrayList processTable = new ArrayList(); 33 | final String[] pids = fileUtils.pidsFromProcFilesystem(); 34 | for (int i = 0; i < pids.length; i++) { 35 | try { 36 | byte[] psinfo = fileUtils.slurpToByteArray("/proc/" + pids[i] + "/psinfo"); 37 | byte[] usage = fileUtils.slurpToByteArray("/proc/" + pids[i] + "/usage"); 38 | processTable.add(psinfoToProcess(psinfo, usage)); 39 | } catch (IOException e) { 40 | // process doesn't exist any more 41 | } 42 | } 43 | return (ProcessInfo[]) processTable.toArray(new ProcessInfo[processTable.size()]); 44 | } 45 | 46 | public native ProcessInfo psinfoToProcess(byte[] psinfo, byte[] usage); 47 | public native int numCpus(); 48 | public native long cpuFrequencyInHz(); 49 | public native long uptimeInSeconds(); 50 | public native int currentPid(); 51 | public native CpuTimes cpuTimes(); 52 | public native MemoryStats physical(); 53 | public native MemoryStats swap(); 54 | public native void killProcess(int pid); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/UnixPasswdParser.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.util.HashMap; 8 | 9 | class UnixPasswdParser { 10 | 11 | public HashMap parse(BufferedReader reader) { 12 | if (reader == null) { 13 | System.err.println("Error parsing password file: reader is null"); 14 | return new HashMap(); 15 | } 16 | 17 | HashMap users = new HashMap(); 18 | try { 19 | String line; 20 | while ((line = reader.readLine()) != null) { 21 | String[] fields = line.split(":"); 22 | if (fields.length >= 2) { 23 | users.put(fields[2], fields[0]); 24 | } 25 | } 26 | return users; 27 | } catch (IOException e) { 28 | System.err.println("Error parsing password file: " + e.getMessage()); 29 | return new HashMap(); 30 | } finally { 31 | try { 32 | reader.close(); 33 | } catch (IOException e) { 34 | System.err.println("Error closing reader: " + e.getMessage()); 35 | } 36 | } 37 | } 38 | 39 | public HashMap parse() { 40 | try { 41 | final FileInputStream passwdFile = new FileInputStream("/etc/passwd"); 42 | BufferedReader reader = new BufferedReader(new InputStreamReader(passwdFile, "UTF-8")); 43 | return parse(reader); 44 | } catch (IOException e) { 45 | System.err.println("Error reading password file: " + e.getMessage()); 46 | return new HashMap(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/WindowsMonitor.java: -------------------------------------------------------------------------------- 1 | package com.jezhumble.javasysmon; 2 | 3 | class WindowsMonitor implements Monitor { 4 | private static Monitor monitor = null; 5 | 6 | static { 7 | if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { 8 | if (System.getProperty("os.arch").indexOf("64") > -1) { 9 | new NativeLibraryLoader().loadLibrary("javasysmon64.dll"); 10 | } else { 11 | new NativeLibraryLoader().loadLibrary("javasysmon.dll"); 12 | } 13 | monitor = new WindowsMonitor(); 14 | } 15 | } 16 | 17 | public WindowsMonitor() { 18 | JavaSysMon.addSupportedConfig("Windows (x86)"); 19 | if (monitor != null) { 20 | JavaSysMon.setMonitor(monitor); 21 | } 22 | } 23 | 24 | public String osName() { 25 | return System.getProperty("os.name"); 26 | } 27 | 28 | public native int numCpus(); 29 | public native int currentPid(); 30 | public native long cpuFrequencyInHz(); 31 | public native long uptimeInSeconds(); 32 | public native CpuTimes cpuTimes(); 33 | public native MemoryStats physical(); 34 | public native MemoryStats swap(); 35 | public native ProcessInfo[] processTable(); 36 | public native void killProcess(int pid); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/jezhumble/javasysmon/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |JavaSysMon is designed to provide an OS-independent way to get live system information, 6 | such as CPU and memory usage, and manage processes, distributed as a single jar file. 7 | It is written in C and Java. However the native binaries are hidden away inside the jar, 8 | so you never need to worry about them.
9 | 10 |The main API is to be found in {@link com.jezhumble.javasysmon.JavaSysMon}, so start there.
11 | 12 |JavaSysMon is licensed according to the terms of the NetBSD (2-line BSD) license.
13 | 14 |