changeset 1102:8757e35030f2

Add user ID to VmInfo and display in clients This commit adds user ID information to the VmInfo Pojo, which is gathered by SystemBackend. The UID for a VM is collected by parsing /proc/${pid}/status, and we then get the username for the UID from the native function getpwuid_r. Both the vm-info command and the VM Overview tab of the Swing GUI now show the user under Process Information. I took inspiration from the id Unix program for the formatting, which is "uid(username)". The native component of this commit is added to agent-core for use by system-backend, this was done on request by Mario since agent-core already has a native component for getting the hostname. I made this component an OSGi service primarily to hide its implementation details. Reviewed-by: omajid, jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-May/006674.html
author Elliott Baron <ebaron@redhat.com>
date Tue, 21 May 2013 12:43:17 -0400
parents 97e66ed2e4ae
children 27b8deb8a6b2
files agent/core/Makefile agent/core/pom.xml agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java agent/core/src/main/java/com/redhat/thermostat/utils/ProcDataSource.java agent/core/src/main/java/com/redhat/thermostat/utils/username/UserNameUtil.java agent/core/src/main/java/com/redhat/thermostat/utils/username/internal/UserNameUtilImpl.java agent/core/src/main/native/UserNameUtilImpl.c agent/core/src/test/java/com/redhat/thermostat/agent/internal/ActivatorTest.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMInfoCommand.java client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ListVMsCommandTest.java client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VMInfoCommandTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java distribution/pom.xml storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java storage/core/src/main/java/com/redhat/thermostat/storage/model/VmInfo.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java system-backend/src/main/java/com/redhat/thermostat/backend/system/JvmStatHostListener.java system-backend/src/main/java/com/redhat/thermostat/backend/system/ProcessUserInfoBuilder.java system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java system-backend/src/test/java/com/redhat/thermostat/backend/system/JvmStatHostListenerTest.java system-backend/src/test/java/com/redhat/thermostat/backend/system/ProcessUserInfoBuilderTest.java system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/VmOverviewView.java vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewController.java vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/locale/LocaleResources.java vm-overview/client-core/src/main/resources/com/redhat/thermostat/vm/overview/client/locale/strings.properties vm-overview/client-core/src/test/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewControllerTest.java vm-overview/client-swing/src/main/java/com/redhat/thermostat/vm/overview/client/swing/internal/VmOverviewPanel.java
diffstat 31 files changed, 745 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/agent/core/Makefile	Mon May 13 16:26:16 2013 -0600
+++ b/agent/core/Makefile	Tue May 21 12:43:17 2013 -0400
@@ -8,35 +8,48 @@
 TARGET_DIR = target
 
 INCLUDE    = -I $(TARGET_DIR) -I $(JAVA_HOME)/include/ -I $(JAVA_HOME)/include/linux
-SOURCES    = src/main/native/HostName.c
-TARGET     = $(TARGET_DIR)/HostName.c
-OBJECTS    = $(TARGET:.c=.o)
+HOSTNAME_SOURCES    = src/main/native/HostName.c
+HOSTNAME_TARGET     = $(TARGET_DIR)/HostName.c
+HOSTNAME_OBJECTS    = $(HOSTNAME_TARGET:.c=.o)
+
+HOSTNAME_EXECUTABLE = libHostNameWrapper.so
 
-EXECUTABLE = libHostNameWrapper.so
+USERNAME_SOURCES    = src/main/native/UserNameUtilImpl.c
+USERNAME_TARGET     = $(TARGET_DIR)/UserNameUtilImpl.c
+USERNAME_OBJECTS    = $(USERNAME_TARGET:.c=.o)
+
+USERNAME_EXECUTABLE = libUserNameUtilWrapper.so
 
 .PHONY:
-JNI_LIST = com.redhat.thermostat.utils.hostname.HostName
+JNI_LIST = com.redhat.thermostat.utils.hostname.HostName\
+ com.redhat.thermostat.utils.username.internal.UserNameUtilImpl
 
 $(JNI_LIST):
 	$(JAVAH) -force -classpath $(CLASSPATH) -d $(TARGET_DIR) $(JNI_LIST)
 
-all: $(JNI_LIST) init $(SOURCES) $(EXECUTABLE)
+all: $(JNI_LIST) init $(HOSTNAME_SOURCES) $(HOSTNAME_EXECUTABLE) $(USERNAME_SOURCES) $(USERNAME_EXECUTABLE)
 
 .PHONY:
 init:
-	$(COPY) $(SOURCES) $(TARGET)
+	$(COPY) $(HOSTNAME_SOURCES) $(HOSTNAME_TARGET)
+	$(COPY) $(USERNAME_SOURCES) $(USERNAME_TARGET)
 
-$(EXECUTABLE): $(OBJECTS)
-	$(CC) $(OBJECTS) -o $(TARGET_DIR)/$@ $(MYLDFLAGS) $(LDFLAGS)
+$(HOSTNAME_EXECUTABLE): $(HOSTNAME_OBJECTS)
+	$(CC) $(HOSTNAME_OBJECTS) -o $(TARGET_DIR)/$@ $(MYLDFLAGS) $(LDFLAGS)
+	
+$(USERNAME_EXECUTABLE): $(USERNAME_OBJECTS)
+	$(CC) $(USERNAME_OBJECTS) -o $(TARGET_DIR)/$@ $(MYLDFLAGS) $(LDFLAGS)
 
 .c.o:
 	$(CC) $(MYCFLAGS) $(CFLAGS) $(INCLUDE) $< -o $@
 
 clean-lib:
-	rm $(TARGET_DIR)/$(EXECUTABLE)
+	rm $(TARGET_DIR)/$(HOSTNAME_EXECUTABLE)
+	rm $(TARGET_DIR)/$(USERNAME_EXECUTABLE)
 	
 clean-obj:
-	rm $(OBJECTS) $(TARGET)
+	rm $(HOSTNAME_OBJECTS) $(HOSTNAME_TARGET)
+	rm $(USERNAME_OBJECTS) $(USERNAME_TARGET)
 	
 clean: clean-obj clean-lib
 	
--- a/agent/core/pom.xml	Mon May 13 16:26:16 2013 -0600
+++ b/agent/core/pom.xml	Tue May 21 12:43:17 2013 -0400
@@ -119,12 +119,14 @@
               com.redhat.thermostat.utils.hostname,
               com.redhat.thermostat.utils,
               com.redhat.thermostat.utils.management,
+              com.redhat.thermostat.utils.username,
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.agent.internal,
               com.redhat.thermostat.agent.locale,
               com.redhat.thermostat.backend.internal,
               com.redhat.thermostat.utils.management.internal,
+              com.redhat.thermostat.utils.username.internal,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java	Mon May 13 16:26:16 2013 -0600
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java	Tue May 21 12:43:17 2013 -0400
@@ -38,23 +38,23 @@
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.utils.management.MXBeanConnectionPool;
 import com.redhat.thermostat.utils.management.internal.MXBeanConnectionPoolImpl;
+import com.redhat.thermostat.utils.username.UserNameUtil;
+import com.redhat.thermostat.utils.username.internal.UserNameUtilImpl;
 
 public class Activator implements BundleActivator {
 
-    private ServiceRegistration registration;
-
     @Override
     public void start(BundleContext context) throws Exception {
-        registration = context.registerService(MXBeanConnectionPool.class, new MXBeanConnectionPoolImpl(), null);
+        context.registerService(MXBeanConnectionPool.class, new MXBeanConnectionPoolImpl(), null);
+        context.registerService(UserNameUtil.class, new UserNameUtilImpl(), null);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        registration.unregister();
+        // Services automatically unregistered by framework
     }
 
 }
--- a/agent/core/src/main/java/com/redhat/thermostat/utils/ProcDataSource.java	Mon May 13 16:26:16 2013 -0600
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/ProcDataSource.java	Tue May 21 12:43:17 2013 -0400
@@ -51,6 +51,7 @@
     private static final String CPUINFO_FILE = "/proc/cpuinfo";
 
     private static final String PID_STAT_FILE = "/proc/${pid}/stat";
+    private static final String PID_STATUS_FILE = "/proc/${pid}/status";
     private static final String PID_ENVIRON_FILE = "/proc/${pid}/environ";
 
     /**
@@ -87,6 +88,13 @@
     public Reader getStatReader(int pid) throws IOException {
         return new FileReader(getPidFile(PID_STAT_FILE, pid));
     }
+    
+    /**
+     * Returns a reader for /proc/$PID/status
+     */
+    public Reader getStatusReader(int pid) throws IOException {
+        return new FileReader(getPidFile(PID_STATUS_FILE, pid));
+    }
 
     /**
      * Returns a reader for /proc/$PID/environ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/username/UserNameUtil.java	Tue May 21 12:43:17 2013 -0400
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.utils.username;
+
+
+/**
+ * Utility to query the operating system for a user name.
+ */
+public interface UserNameUtil {
+    
+    /**
+     * Returns the user name corresponding to the provided UID.
+     * @param uid - the UID whose name to return
+     * @return the user name if found, or null otherwise
+     */
+    String getUserName(long uid);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/utils/username/internal/UserNameUtilImpl.java	Tue May 21 12:43:17 2013 -0400
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.utils.username.internal;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.utils.username.UserNameUtil;
+
+public class UserNameUtilImpl implements UserNameUtil {
+    private static final Logger logger = LoggingUtils.getLogger(UserNameUtilImpl.class);
+    
+    static {
+        /*
+         * TODO Change to System.load
+         * http://icedtea.classpath.org/pipermail/thermostat/2013-May/006657.html
+         */
+        System.loadLibrary("UserNameUtilWrapper");
+    }
+    
+    public String getUserName(long uid) {
+        String result = null;
+        if (uid >= 0) {
+            try {
+                result = getUserName0(uid);
+            } catch (IOException e) {
+                logger.log(Level.WARNING, "Unable to retrieve username for uid: " + uid, e);
+            }
+        }
+        return result;
+    }
+    
+    private native String getUserName0(long uid) throws IOException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/native/UserNameUtilImpl.c	Tue May 21 12:43:17 2013 -0400
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+#include "com_redhat_thermostat_utils_username_internal_UserNameUtilImpl.h"
+
+#include <jni.h>
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static jint throw_IOException(JNIEnv *env, const char *message) {
+    const char *class_name = "java/io/IOException";
+    jclass class = (*env)->FindClass(env, class_name);
+    if (class == NULL) {
+        return -1;
+    }
+    return (*env)->ThrowNew(env, class, message);
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_redhat_thermostat_utils_username_internal_UserNameUtilImpl_getUserName0
+    (JNIEnv *env, jclass ProcessUserInfoBuilderClass, jlong uid) {
+    size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (bufsize < 0) {
+        throw_IOException(env, "Unable to retrieve recommended buffer size from sysconf");
+        return NULL;
+    }
+
+    char *buf = malloc(bufsize * sizeof(char));
+    if (!buf) {
+        throw_IOException(env, "Unable to allocate buffer for username");
+        return NULL;
+    }
+
+    struct passwd pwd;
+    struct passwd *ret;
+    int rc = getpwuid_r(uid, &pwd, buf, bufsize, &ret);
+    if (rc) {
+        // Error occurred
+        const char *error_message = strerror(rc);
+        throw_IOException(env, error_message);
+        free(buf);
+        return NULL;
+    }
+    if (!ret) {
+        // No username found
+        char err_buf[128]; // Large enough for even the largest UIDs
+        snprintf(err_buf, sizeof(err_buf), "Username not found for uid: %ld", uid);
+        throw_IOException(env, err_buf);
+        free(buf);
+        return NULL;
+    }
+
+    jstring name = (*env)->NewStringUTF(env, pwd.pw_name);
+    free(buf);
+    return name;
+}
--- a/agent/core/src/test/java/com/redhat/thermostat/agent/internal/ActivatorTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/internal/ActivatorTest.java	Tue May 21 12:43:17 2013 -0400
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.agent.internal;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
@@ -44,6 +43,8 @@
 import com.redhat.thermostat.testutils.StubBundleContext;
 import com.redhat.thermostat.utils.management.MXBeanConnectionPool;
 import com.redhat.thermostat.utils.management.internal.MXBeanConnectionPoolImpl;
+import com.redhat.thermostat.utils.username.UserNameUtil;
+import com.redhat.thermostat.utils.username.internal.UserNameUtilImpl;
 
 public class ActivatorTest {
     @Test
@@ -56,9 +57,6 @@
         activator.start(context);
 
         assertTrue(context.isServiceRegistered(MXBeanConnectionPool.class.getName(), MXBeanConnectionPoolImpl.class));
-
-        activator.stop(context);
-
-        assertFalse(context.isServiceRegistered(MXBeanConnectionPool.class.getName(), MXBeanConnectionPoolImpl.class));
+        assertTrue(context.isServiceRegistered(UserNameUtil.class.getName(), UserNameUtilImpl.class));
     }
 }
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Mon May 13 16:26:16 2013 -0600
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Tue May 21 12:43:17 2013 -0400
@@ -68,6 +68,8 @@
     VM_INFO_JAVA_VERSION,
     VM_INFO_VIRTUAL_MACHINE,
     VM_INFO_VM_ARGUMENTS,
+    VM_INFO_USER,
+    VM_INFO_USER_UNKNOWN,
 
     COLUMN_HEADER_HOST_ID,
     COLUMN_HEADER_HOST,
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMInfoCommand.java	Mon May 13 16:26:16 2013 -0600
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMInfoCommand.java	Tue May 21 12:43:17 2013 -0400
@@ -117,15 +117,33 @@
         } else {
             table.printLine(translator.localize(LocaleResources.VM_INFO_STOP_TIME).getContents(), new Date(vmInfo.getStopTimeStamp()).toString());
         }
+        printUserInfo(vmInfo, table);
         table.printLine(translator.localize(LocaleResources.VM_INFO_MAIN_CLASS).getContents(), vmInfo.getMainClass());
         table.printLine(translator.localize(LocaleResources.VM_INFO_COMMAND_LINE).getContents(), vmInfo.getJavaCommandLine());
         table.printLine(translator.localize(LocaleResources.VM_INFO_JAVA_VERSION).getContents(), vmInfo.getJavaVersion());
         table.printLine(translator.localize(LocaleResources.VM_INFO_VIRTUAL_MACHINE).getContents(), vmInfo.getVmName());
         table.printLine(translator.localize(LocaleResources.VM_INFO_VM_ARGUMENTS).getContents(), vmInfo.getVmArguments());
-
+        
         PrintStream out = ctx.getConsole().getOutput();
         table.render(out);
     }
 
+    private void printUserInfo(VmInfo vmInfo, TableRenderer table) {
+        // Check if we have valid user info
+        long uid = vmInfo.getUid();
+        String user;
+        if (uid >= 0) {
+            user = String.valueOf(uid);
+            String username = vmInfo.getUsername();
+            if (username != null) {
+                user += "(" + username + ")";
+            }
+        }
+        else {
+            user = translator.localize(LocaleResources.VM_INFO_USER_UNKNOWN).getContents();
+        }
+        table.printLine(translator.localize(LocaleResources.VM_INFO_USER).getContents(), user);
+    }
+
 }
 
--- a/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Mon May 13 16:26:16 2013 -0600
+++ b/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Tue May 21 12:43:17 2013 -0400
@@ -26,6 +26,8 @@
 VM_INFO_JAVA_VERSION = Java version:
 VM_INFO_VIRTUAL_MACHINE = Virtual machine:
 VM_INFO_VM_ARGUMENTS = VM arguments:
+VM_INFO_USER = User ID:
+VM_INFO_USER_UNKNOWN = <Unknown>
 
 COLUMN_HEADER_HOST_ID = HOST_ID
 COLUMN_HEADER_HOST = HOST
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ListVMsCommandTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ListVMsCommandTest.java	Tue May 21 12:43:17 2013 -0400
@@ -101,7 +101,7 @@
 
         HostRef host1 = new HostRef("123", "h1");
         VmRef vm1 = new VmRef(host1, 1, "n");
-        VmInfo vm1Info = new VmInfo(1, 0, 1, "", "", "", "", "", "", "", "", null, null, null);
+        VmInfo vm1Info = new VmInfo(1, 0, 1, "", "", "", "", "", "", "", "", null, null, null, -1, null);
         when(hostsDAO.getHosts()).thenReturn(Arrays.asList(host1));
         when(vmsDAO.getVMs(host1)).thenReturn(Arrays.asList(vm1));
         when(vmsDAO.getVmInfo(eq(vm1))).thenReturn(vm1Info);
@@ -130,7 +130,7 @@
         VmRef vm2 = new VmRef(host1, 2, "n1");
         VmRef vm3 = new VmRef(host2, 123456, "longvmname");
 
-        VmInfo vmInfo = new VmInfo(1, 0, 1, "", "", "", "", "", "", "", "", null, null, null);
+        VmInfo vmInfo = new VmInfo(1, 0, 1, "", "", "", "", "", "", "", "", null, null, null, -1, null);
 
         when(vmsDAO.getVMs(host1)).thenReturn(Arrays.asList(vm1, vm2));
         when(vmsDAO.getVMs(host2)).thenReturn(Arrays.asList(vm3));
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VMInfoCommandTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VMInfoCommandTest.java	Tue May 21 12:43:17 2013 -0400
@@ -106,7 +106,7 @@
         start.set(2012, 5, 7, 15, 32, 0);
         Calendar end = Calendar.getInstance();
         end.set(2013, 10, 1, 1, 22, 0);
-        VmInfo vmInfo = new VmInfo(234, start.getTimeInMillis(), end.getTimeInMillis(), "vmVersion", "javaHome", "mainClass", "commandLine", "vmName", "vmInfo", "vmVersion", "vmArguments", new HashMap<String,String>(), new HashMap<String,String>(), new String[0]);
+        VmInfo vmInfo = new VmInfo(234, start.getTimeInMillis(), end.getTimeInMillis(), "vmVersion", "javaHome", "mainClass", "commandLine", "vmName", "vmInfo", "vmVersion", "vmArguments", new HashMap<String,String>(), new HashMap<String,String>(), new String[0], 2000, "myUser");
         when(vmsDAO.getVmInfo(vm)).thenReturn(vmInfo);
         when(vmsDAO.getVmInfo(new VmRef(host, 9876, "dummy"))).thenThrow(new DAOException("Unknown VM ID: 9876"));
         when(vmsDAO.getVMs(host)).thenReturn(Arrays.asList(vm));
@@ -124,6 +124,55 @@
         String expected = "Process ID:      234\n" +
                           "Start time:      Thu Jun 07 15:32:00 UTC 2012\n" +
                           "Stop time:       Fri Nov 01 01:22:00 UTC 2013\n" +
+                          "User ID:         2000(myUser)\n" +
+                          "Main class:      mainClass\n" +
+                          "Command line:    commandLine\n" +
+                          "Java version:    vmVersion\n" +
+                          "Virtual machine: vmName\n" +
+                          "VM arguments:    vmArguments\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
+    }
+    
+    @Test
+    public void testVmInfoNoUid() throws CommandException {
+        VmInfo info = vmsDAO.getVmInfo(vm);
+        // Set parameters to those where user info cannot be obtained
+        info.setUid(-1);
+        info.setUsername(null);
+        context.registerService(VmInfoDAO.class, vmsDAO, null);
+        cmd = new VMInfoCommand(context);
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "Process ID:      234\n" +
+                          "Start time:      Thu Jun 07 15:32:00 UTC 2012\n" +
+                          "Stop time:       Fri Nov 01 01:22:00 UTC 2013\n" +
+                          "User ID:         <Unknown>\n" +
+                          "Main class:      mainClass\n" +
+                          "Command line:    commandLine\n" +
+                          "Java version:    vmVersion\n" +
+                          "Virtual machine: vmName\n" +
+                          "VM arguments:    vmArguments\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
+    }
+    
+    @Test
+    public void testVmInfoNoUsername() throws CommandException {
+        VmInfo info = vmsDAO.getVmInfo(vm);
+        // Set parameters to those where user info cannot be obtained
+        info.setUid(2000);
+        info.setUsername(null);
+        context.registerService(VmInfoDAO.class, vmsDAO, null);
+        cmd = new VMInfoCommand(context);
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "Process ID:      234\n" +
+                          "Start time:      Thu Jun 07 15:32:00 UTC 2012\n" +
+                          "Stop time:       Fri Nov 01 01:22:00 UTC 2013\n" +
+                          "User ID:         2000\n" +
                           "Main class:      mainClass\n" +
                           "Command line:    commandLine\n" +
                           "Java version:    vmVersion\n" +
@@ -157,6 +206,7 @@
         String expected = "Process ID:      234\n" +
                           "Start time:      Thu Jun 07 15:32:00 UTC 2012\n" +
                           "Stop time:       Fri Nov 01 01:22:00 UTC 2013\n" +
+                          "User ID:         2000(myUser)\n" +
                           "Main class:      mainClass\n" +
                           "Command line:    commandLine\n" +
                           "Java version:    vmVersion\n" +
@@ -202,7 +252,7 @@
         cmd = new VMInfoCommand(context);
         Calendar start = Calendar.getInstance();
         start.set(2012, 5, 7, 15, 32, 0);
-        VmInfo vmInfo = new VmInfo(234, start.getTimeInMillis(), Long.MIN_VALUE, "vmVersion", "javaHome", "mainClass", "commandLine", "vmName", "vmInfo", "vmVersion", "vmArguments", new HashMap<String,String>(), new HashMap<String,String>(), new String[0]);
+        VmInfo vmInfo = new VmInfo(234, start.getTimeInMillis(), Long.MIN_VALUE, "vmVersion", "javaHome", "mainClass", "commandLine", "vmName", "vmInfo", "vmVersion", "vmArguments", new HashMap<String,String>(), new HashMap<String,String>(), new String[0], 2000, "myUser");
         when(vmsDAO.getVmInfo(vm)).thenReturn(vmInfo);
 
         SimpleArguments args = new SimpleArguments();
@@ -212,6 +262,7 @@
         String expected = "Process ID:      234\n" +
                           "Start time:      Thu Jun 07 15:32:00 UTC 2012\n" +
                           "Stop time:       <Running>\n" +
+                          "User ID:         2000(myUser)\n" +
                           "Main class:      mainClass\n" +
                           "Command line:    commandLine\n" +
                           "Java version:    vmVersion\n" +
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Tue May 21 12:43:17 2013 -0400
@@ -567,7 +567,7 @@
 
     @Test
     public void verityVMActionsAreShown() {
-        VmInfo vmInfo = new VmInfo(0, 1, 2, null, null, null, null, null, null, null, null, null, null, null);
+        VmInfo vmInfo = new VmInfo(0, 1, 2, null, null, null, null, null, null, null, null, null, null, null, -1, null);
         when(mockVmsDAO.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
 
         VmRef ref = mock(VmRef.class);
--- a/distribution/pom.xml	Mon May 13 16:26:16 2013 -0600
+++ b/distribution/pom.xml	Tue May 21 12:43:17 2013 -0400
@@ -231,6 +231,8 @@
                       todir="${project.build.directory}/libs/native" />
                 <copy file="${main.basedir}/agent/core/target/libHostNameWrapper.so"
                       todir="${project.build.directory}/libs/native" />
+                <copy file="${main.basedir}/agent/core/target/libUserNameUtilWrapper.so"
+                      todir="${project.build.directory}/libs/native" />
               </target>
             </configuration>
             <goals>
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java	Mon May 13 16:26:16 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java	Tue May 21 12:43:17 2013 -0400
@@ -65,13 +65,16 @@
     static final Key<List<String>> librariesKey = new Key<>("loadedNativeLibraries", false);
     static final Key<Long> startTimeKey = new Key<>("startTimeStamp", false);
     static final Key<Long> stopTimeKey = new Key<>("stopTimeStamp", false);
+    static final Key<Long> uidKey = new Key<>("uid", false);
+    static final Key<String> usernameKey = new Key<>("username", false);
 
     static final Category<VmInfo> vmInfoCategory = new Category<>("vm-info", VmInfo.class,
             Key.AGENT_ID, Key.VM_ID, vmPidKey, runtimeVersionKey, javaHomeKey,
             mainClassKey, commandLineKey,
             vmArgumentsKey, vmNameKey, vmInfoKey, vmVersionKey,
             propertiesKey, environmentKey, librariesKey,
-            startTimeKey, stopTimeKey);
+            startTimeKey, stopTimeKey,
+            uidKey, usernameKey);
 
     public VmInfo getVmInfo(VmRef ref);
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/model/VmInfo.java	Mon May 13 16:26:16 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/model/VmInfo.java	Tue May 21 12:43:17 2013 -0400
@@ -98,6 +98,8 @@
     private Map<String, String> properties = new HashMap<String, String>();
     private Map<String, String> environment = new HashMap<String, String>();
     private String[] loadedNativeLibraries;
+    private long uid;
+    private String username;
 
     public VmInfo() {
         /* use defaults */
@@ -107,7 +109,8 @@
             String javaVersion, String javaHome,
             String mainClass, String commandLine,
             String vmName, String vmInfo, String vmVersion, String vmArguments,
-            Map<String, String> properties, Map<String, String> environment, String[] loadedNativeLibraries) {
+            Map<String, String> properties, Map<String, String> environment, String[] loadedNativeLibraries,
+            long uid, String username) {
         this.vmPid = vmPid;
         this.startTime = startTime;
         this.stopTime = stopTime;
@@ -122,6 +125,8 @@
         this.properties = properties;
         this.environment = environment;
         this.loadedNativeLibraries = loadedNativeLibraries;
+        this.uid = uid;
+        this.username = username;
     }
 
     @Persist
@@ -318,5 +323,33 @@
     public void setLoadedNativeLibraries(String[] loadedNativeLibraries) {
         this.loadedNativeLibraries = loadedNativeLibraries;
     }
+    
+    /**
+     * Returns the system user id for the owner of this JVM process,
+     * or -1 if an owner could not be found.
+     */
+    @Persist
+    public long getUid() {
+        return uid;
+    }
+    
+    @Persist
+    public void setUid(long uid) {
+        this.uid = uid;
+    }
+    
+    /**
+     * Returns the system user name for the owner of this JVM process,
+     * or null if an owner could not be found.
+     */
+    @Persist
+    public String getUsername() {
+        return username;
+    }
+    
+    @Persist
+    public void setUsername(String username) {
+        this.username = username;
+    }
 }
 
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Tue May 21 12:43:17 2013 -0400
@@ -82,6 +82,8 @@
     private Map<String, String> props;
     private Map<String, String> env;
     private String[] libs;
+    private long uid;
+    private String username;
 
     @Before
     public void setUp() {
@@ -99,6 +101,8 @@
         props = new HashMap<>();
         env = new HashMap<>();
         libs = new String[0];
+        uid = 2000;
+        username = "myUser";
     }
 
     @Test
@@ -121,7 +125,9 @@
         assertTrue(keys.contains(new Key<List<String>>("loadedNativeLibraries", false)));
         assertTrue(keys.contains(new Key<Long>("startTimeStamp", false)));
         assertTrue(keys.contains(new Key<Long>("stopTimeStamp", false)));
-        assertEquals(16, keys.size());
+        assertTrue(keys.contains(new Key<Long>("uid", false)));
+        assertTrue(keys.contains(new Key<Long>("username", false)));
+        assertEquals(18, keys.size());
     }
 
     @Test
@@ -130,7 +136,7 @@
         Storage storage = mock(Storage.class);
         Query query = mock(Query.class);
         when(storage.createQuery(any(Category.class))).thenReturn(query);
-        VmInfo expected = new VmInfo(vmId, startTime, stopTime, jVersion, jHome, mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs, props, env, libs);
+        VmInfo expected = new VmInfo(vmId, startTime, stopTime, jVersion, jHome, mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs, props, env, libs, uid, username);
         Cursor cursor = mock(Cursor.class);
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(expected).thenReturn(null);
@@ -262,7 +268,7 @@
 
         VmInfo info = new VmInfo(vmId, startTime, stopTime, jVersion, jHome,
                 mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs,
-                props, env, libs);
+                props, env, libs, uid, username);
         VmInfoDAO dao = new VmInfoDAOImpl(storage);
         dao.putVmInfo(info);
 
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Mon May 13 16:26:16 2013 -0600
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Tue May 21 12:43:17 2013 -0400
@@ -40,7 +40,6 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -52,8 +51,8 @@
 import sun.jvmstat.monitor.event.HostListener;
 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
 
-import com.redhat.thermostat.agent.VmStatusListener;
 import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.backend.system.ProcessUserInfoBuilder.ProcessUserInfo;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.VmInfo;
@@ -69,9 +68,12 @@
     
     private VmStatusChangeNotifier notifier;
 
-    JvmStatHostListener(VmInfoDAO vmInfoDAO, VmStatusChangeNotifier notifier) {
+    private ProcessUserInfoBuilder userInfoBuilder;
+
+    JvmStatHostListener(VmInfoDAO vmInfoDAO, VmStatusChangeNotifier notifier, ProcessUserInfoBuilder userInfoBuilder) {
         this.vmInfoDAO = vmInfoDAO;
         this.notifier = notifier;
+        this.userInfoBuilder = userInfoBuilder;
     }
 
     @Override
@@ -135,11 +137,12 @@
         Map<String, String> environment = new ProcessEnvironmentBuilder(dataSource).build(vmId);
         // TODO actually figure out the loaded libraries.
         String[] loadedNativeLibraries = new String[0];
+        ProcessUserInfo userInfo = userInfoBuilder.build(vmId);
         VmInfo info = new VmInfo(vmId, startTime, stopTime,
                 extractor.getJavaVersion(), extractor.getJavaHome(),
                 extractor.getMainClass(), extractor.getCommandLine(),
                 extractor.getVmName(), extractor.getVmInfo(), extractor.getVmVersion(), extractor.getVmArguments(),
-                properties, environment, loadedNativeLibraries);
+                properties, environment, loadedNativeLibraries, userInfo.getUid(), userInfo.getUsername());
         vmInfoDAO.putVmInfo(info);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/ProcessUserInfoBuilder.java	Tue May 21 12:43:17 2013 -0400
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.backend.system;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.utils.ProcDataSource;
+import com.redhat.thermostat.utils.username.UserNameUtil;
+
+class ProcessUserInfoBuilder {
+    
+    private static final ProcessUserInfo NON_EXISTENT_USER = new ProcessUserInfo();
+    private static final String PROC_STATUS_UID = "Uid:";
+    private static final Logger logger = LoggingUtils.getLogger(ProcessUserInfoBuilder.class);
+    private ProcDataSource source;
+    private UserNameUtil userNameUtil;
+    
+    ProcessUserInfoBuilder(ProcDataSource source, UserNameUtil userNameUtil) {
+        this.source = source;
+        this.userNameUtil = userNameUtil;
+    }
+    
+    static class ProcessUserInfo {
+        
+        private long uid;
+        private String username;
+        
+        ProcessUserInfo(long uid, String username) {
+            this.uid = uid;
+            this.username = username;
+        }
+        
+        ProcessUserInfo() {
+            this.uid = -1;
+            this.username = null;
+        }
+        
+        public long getUid() {
+            return uid;
+        }
+        
+        public String getUsername() {
+            return username;
+        }
+    }
+    
+    ProcessUserInfo build(int pid) {
+        ProcessUserInfo info = NON_EXISTENT_USER;
+        try {
+            Reader reader = source.getStatusReader(pid);
+            long uid = getUidFromProcfs(new BufferedReader(reader));
+            String name = userNameUtil.getUserName(uid);
+            info = new ProcessUserInfo(uid, name);
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "Unable to read user info for " + pid, e);
+        }
+        
+        return info;
+    }
+
+    /*
+     * Look for the following line:
+     * Uid:  <RealUid>   <EffectiveUid>   <SavedUid>   <FSUid>
+     */
+    private long getUidFromProcfs(BufferedReader br) throws IOException {
+        long uid = -1;
+        String line;
+        while ((line = br.readLine()) != null) {
+            line = line.trim();
+            if (line.startsWith(PROC_STATUS_UID)) {
+                String[] parts = line.split("\\s+");
+                if (parts.length == 5) {
+                    try {
+                        // Use Real UID
+                        uid = Long.parseLong(parts[1]);
+                    } catch (NumberFormatException e) {
+                        throw new IOException("Unexpected output from ps command", e);
+                    }
+                }
+                else {
+                    throw new IOException("Expected 5 parts from split /proc/${pid}/status output, got " + parts.length);
+                }
+            }
+        }
+        if (uid < 0) {
+            throw new IOException("Unable to determine UID from /proc/${pid}/status");
+        }
+        return uid;
+    }
+
+
+}
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Mon May 13 16:26:16 2013 -0600
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Tue May 21 12:43:17 2013 -0400
@@ -54,6 +54,7 @@
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
 import com.redhat.thermostat.utils.ProcDataSource;
+import com.redhat.thermostat.utils.username.UserNameUtil;
 
 public class SystemBackend extends BaseBackend {
 
@@ -74,7 +75,7 @@
 
 
     public SystemBackend(HostInfoDAO hostInfoDAO, NetworkInterfaceInfoDAO netInfoDAO, VmInfoDAO vmInfoDAO,
-            Version version, VmStatusChangeNotifier notifier) {
+            Version version, VmStatusChangeNotifier notifier, UserNameUtil userNameUtil) {
         super("System Backend",
                 "Gathers basic information from the system",
                 "Red Hat, Inc.",
@@ -84,7 +85,7 @@
 
         ProcDataSource source = new ProcDataSource();
         hostInfoBuilder = new HostInfoBuilder(source);
-        hostListener = new JvmStatHostListener(vmInfoDAO, notifier);
+        hostListener = new JvmStatHostListener(vmInfoDAO, notifier, new ProcessUserInfoBuilder(source, userNameUtil));
     }
 
     @Override
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Mon May 13 16:26:16 2013 -0600
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Tue May 21 12:43:17 2013 -0400
@@ -52,6 +52,7 @@
 import com.redhat.thermostat.storage.dao.HostInfoDAO;
 import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.utils.username.UserNameUtil;
 
 @SuppressWarnings("rawtypes")
 public class SystemBackendActivator implements BundleActivator {
@@ -71,7 +72,8 @@
                 BackendService.class,
                 HostInfoDAO.class,
                 NetworkInterfaceInfoDAO.class,
-                VmInfoDAO.class
+                VmInfoDAO.class,
+                UserNameUtil.class
         };
         tracker = new MultipleServiceTracker(context, deps, new Action() {
             @Override
@@ -80,8 +82,9 @@
                 NetworkInterfaceInfoDAO netInfoDAO = (NetworkInterfaceInfoDAO) services
                         .get(NetworkInterfaceInfoDAO.class.getName());
                 VmInfoDAO vmInfoDAO = (VmInfoDAO) services.get(VmInfoDAO.class.getName());
+                UserNameUtil userNameUtil = (UserNameUtil) services.get(UserNameUtil.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new SystemBackend(hostInfoDAO, netInfoDAO, vmInfoDAO, version, notifier);
+                backend = new SystemBackend(hostInfoDAO, netInfoDAO, vmInfoDAO, version, notifier, userNameUtil);
                 reg = context.registerService(Backend.class, backend, null);
             }
             
--- a/system-backend/src/test/java/com/redhat/thermostat/backend/system/JvmStatHostListenerTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/JvmStatHostListenerTest.java	Tue May 21 12:43:17 2013 -0400
@@ -65,6 +65,7 @@
 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
 
 import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.backend.system.ProcessUserInfoBuilder.ProcessUserInfo;
 import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
 
@@ -78,6 +79,8 @@
     private static String INFO_VMINFO = "Info";
     private static String INFO_VMNAME = "MyJVM";
     private static String INFO_VMVER = "90.01";
+    private static long INFO_VMUSERID = 2000;
+    private static String INFO_VMUSERNAME = "User";
 
     private JvmStatHostListener hostListener;
     private MonitoredHost host;
@@ -91,8 +94,12 @@
     public void setup() throws MonitorException, URISyntaxException {
         vmInfoDAO = mock(VmInfoDAO.class);
         notifier = mock(VmStatusChangeNotifier.class);
+        
+        ProcessUserInfoBuilder userInfoBuilder = mock(ProcessUserInfoBuilder.class);
+        ProcessUserInfo userInfo = new ProcessUserInfo(INFO_VMUSERID, INFO_VMUSERNAME);
+        when(userInfoBuilder.build(any(int.class))).thenReturn(userInfo);
 
-        hostListener = new JvmStatHostListener(vmInfoDAO, notifier);
+        hostListener = new JvmStatHostListener(vmInfoDAO, notifier, userInfoBuilder);
         
         host = mock(MonitoredHost.class);
         HostIdentifier hostId = mock(HostIdentifier.class);
@@ -195,5 +202,7 @@
         assertEquals(INFO_VMINFO, info.getVmInfo());
         assertEquals(INFO_VMNAME, info.getVmName());
         assertEquals(INFO_VMVER, info.getVmVersion());
+        assertEquals(INFO_VMUSERID, info.getUid());
+        assertEquals(INFO_VMUSERNAME, info.getUsername());
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/ProcessUserInfoBuilderTest.java	Tue May 21 12:43:17 2013 -0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.backend.system;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.backend.system.ProcessUserInfoBuilder.ProcessUserInfo;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.utils.ProcDataSource;
+import com.redhat.thermostat.utils.username.UserNameUtil;
+
+public class ProcessUserInfoBuilderTest {
+    
+    @Test
+    public void testBuild() throws IOException, ApplicationException {
+        StringReader reader = new StringReader("Uid:   2000  2000  2000  2000");
+        ProcDataSource source = mock(ProcDataSource.class);
+        UserNameUtil util = mock(UserNameUtil.class);
+        when(util.getUserName(2000)).thenReturn("myUser");
+        when(source.getStatusReader(anyInt())).thenReturn(reader);
+        ProcessUserInfoBuilder builder = new ProcessUserInfoBuilder(source, util);
+        ProcessUserInfo info = builder.build(0);
+        
+        assertEquals(2000, info.getUid());
+        assertEquals("myUser", info.getUsername());
+    }
+    
+    @Test
+    public void testBuildErrorUid() throws IOException, ApplicationException {
+        StringReader reader = new StringReader("");
+        ProcDataSource source = mock(ProcDataSource.class);
+        UserNameUtil util = mock(UserNameUtil.class);
+        when(source.getStatusReader(anyInt())).thenReturn(reader);
+        ProcessUserInfoBuilder builder = new ProcessUserInfoBuilder(source, util);
+        ProcessUserInfo info = builder.build(0);
+        
+        assertEquals(-1, info.getUid());
+        assertEquals(null, info.getUsername());
+    }
+    
+    @Test
+    public void testBuildErrorUsername() throws IOException, ApplicationException {
+        StringReader reader = new StringReader("Uid:   2000  2000  2000  2000");
+        ProcDataSource source = mock(ProcDataSource.class);
+        UserNameUtil util = mock(UserNameUtil.class);
+        when(util.getUserName(2000)).thenReturn(null);
+        when(source.getStatusReader(anyInt())).thenReturn(reader);
+        ProcessUserInfoBuilder builder = new ProcessUserInfoBuilder(source, util);
+        ProcessUserInfo info = builder.build(0);
+        
+        assertEquals(2000, info.getUid());
+        assertEquals(null, info.getUsername());
+    }
+
+}
--- a/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Tue May 21 12:43:17 2013 -0400
@@ -49,6 +49,7 @@
 import com.redhat.thermostat.storage.dao.HostInfoDAO;
 import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.utils.username.UserNameUtil;
 
 public class SystemBackendTest {
 
@@ -65,8 +66,9 @@
         when(version.getVersionNumber()).thenReturn(VERSION);
         
         VmStatusChangeNotifier notifier = mock(VmStatusChangeNotifier.class);
+        UserNameUtil util = mock(UserNameUtil.class);
 
-        b = new SystemBackend(hDAO, nDAO, vmInfoDAO, version, notifier);
+        b = new SystemBackend(hDAO, nDAO, vmInfoDAO, version, notifier, util);
     }
 
     @Test
--- a/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/VmOverviewView.java	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/VmOverviewView.java	Tue May 21 12:43:17 2013 -0400
@@ -59,6 +59,8 @@
     public abstract void setVmNameAndVersion(String vmNameAndVersion);
 
     public abstract void setVmArguments(String vmArguments);
+    
+    public abstract void setUserID(String userID);
 
 }
 
--- a/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewController.java	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewController.java	Tue May 21 12:43:17 2013 -0400
@@ -120,6 +120,18 @@
                 view.setVmNameAndVersion(translator.localize(LocaleResources.VM_INFO_VM_NAME_AND_VERSION,
                         actualVmName, actualVmVersion, actualVmInfo).getContents());
                 view.setVmArguments(info.getVmArguments());
+                long uid = info.getUid();
+                if (uid >= 0) {
+                    String user = String.valueOf(uid);
+                    String username = info.getUsername();
+                    if (username != null) {
+                        user += "(" + username + ")";
+                    }
+                    view.setUserID(user);
+                }
+                else {
+                    view.setUserID(translator.localize(LocaleResources.VM_INFO_USER_UNKNOWN).getContents());
+                }
             }
         });
         timer.setInitialDelay(0);
--- a/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/locale/LocaleResources.java	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/locale/LocaleResources.java	Tue May 21 12:43:17 2013 -0400
@@ -58,6 +58,8 @@
     VM_INFO_PROPERTIES,
     VM_INFO_ENVIRONMENT,
     VM_INFO_LIBRARIES,
+    VM_INFO_USER,
+    VM_INFO_USER_UNKNOWN,
     ;
 
     static final String RESOURCE_BUNDLE =
--- a/vm-overview/client-core/src/main/resources/com/redhat/thermostat/vm/overview/client/locale/strings.properties	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-core/src/main/resources/com/redhat/thermostat/vm/overview/client/locale/strings.properties	Tue May 21 12:43:17 2013 -0400
@@ -17,3 +17,5 @@
 VM_INFO_PROPERTIES = Properties
 VM_INFO_ENVIRONMENT = Environment
 VM_INFO_LIBRARIES = Native Libraries
+VM_INFO_USER = User Id
+VM_INFO_USER_UNKNOWN = <Unknown>
--- a/vm-overview/client-core/src/test/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewControllerTest.java	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-core/src/test/java/com/redhat/thermostat/vm/overview/client/core/internal/VmOverviewControllerTest.java	Tue May 21 12:43:17 2013 -0400
@@ -37,7 +37,6 @@
 package com.redhat.thermostat.vm.overview.client.core.internal;
 
 import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNotNull;
 import static org.mockito.Mockito.doNothing;
@@ -51,7 +50,6 @@
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -68,7 +66,6 @@
 import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.vm.overview.client.core.VmOverviewView;
 import com.redhat.thermostat.vm.overview.client.core.VmOverviewViewProvider;
-import com.redhat.thermostat.vm.overview.client.core.internal.VmOverviewController;
 import com.redhat.thermostat.vm.overview.client.locale.LocaleResources;
 
 public class VmOverviewControllerTest {
@@ -88,6 +85,8 @@
     private static final Map<String, String> PROPS = Collections.emptyMap();
     private static final Map<String, String> ENV = Collections.emptyMap();
     private static final String[] LIBS = new String[0];
+    private static final long UID = 2000;
+    private static final String USERNAME = "myUser";
 
     private Timer timer;
     private Runnable timerAction;
@@ -96,8 +95,7 @@
     private VmOverviewController controller;
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Before
-    public void setUp() {
+    private void createController(VmInfo info) {
         // Setup timer
         timer = mock(Timer.class);
         ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor
@@ -110,14 +108,10 @@
         when(appSvc.getTimerFactory()).thenReturn(timerFactory);
 
         // Setup DAOs
-        VmInfo vmInfo = new VmInfo(VM_PID, START_TIME, STOP_TIME, JAVA_VERSION,
-                JAVA_HOME, MAIN_CLASS, COMMAND_LINE, VM_NAME, VM_INFO,
-                VM_VERSION, VM_ARGS, PROPS, ENV, LIBS);
-
         VmRef ref = mock(VmRef.class);
 
         VmInfoDAO vmInfoDao = mock(VmInfoDAO.class);
-        when(vmInfoDao.getVmInfo(any(VmRef.class))).thenReturn(vmInfo);
+        when(vmInfoDao.getVmInfo(eq(ref))).thenReturn(info);
 
         // Setup View
         ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor
@@ -133,8 +127,41 @@
         timerAction = timerActionCaptor.getValue();
     }
 
+    private VmInfo createVmInfo() {
+        VmInfo vmInfo = new VmInfo(VM_PID, START_TIME, STOP_TIME, JAVA_VERSION,
+                JAVA_HOME, MAIN_CLASS, COMMAND_LINE, VM_NAME, VM_INFO,
+                VM_VERSION, VM_ARGS, PROPS, ENV, LIBS, UID, USERNAME);
+        return vmInfo;
+    }
+
     @Test
     public void verifyViewIsUpdatedWithData() {
+        createController(createVmInfo());
+        timerAction.run();
+
+        DateFormat timestampFormat = controller.getDateFormat();
+        verify(view).setVmPid(eq(String.valueOf(VM_PID)));
+        verify(view).setVmStartTimeStamp(eq(timestampFormat.format(new Date(START_TIME))));
+        verify(view).setVmStopTimeStamp(eq(timestampFormat.format(new Date(STOP_TIME))));
+        verify(view).setJavaVersion(eq(JAVA_VERSION));
+        verify(view).setJavaHome(eq(JAVA_HOME));
+        verify(view).setMainClass(eq(MAIN_CLASS));
+        verify(view).setJavaCommandLine(eq(COMMAND_LINE));
+        
+        verify(view).setVmNameAndVersion(eq(translator.localize(LocaleResources.VM_INFO_VM_NAME_AND_VERSION,
+                        VM_NAME, VM_VERSION, VM_INFO).getContents()));
+        verify(view).setVmArguments(eq(VM_ARGS));
+        String userID = String.valueOf(UID) + "(" + USERNAME + ")";
+        verify(view).setUserID(eq(userID));
+    }
+    
+    @Test
+    public void verifyViewIsUpdatedWithDataNoUid() {
+        VmInfo vmInfo = new VmInfo(VM_PID, START_TIME, STOP_TIME, JAVA_VERSION,
+                JAVA_HOME, MAIN_CLASS, COMMAND_LINE, VM_NAME, VM_INFO,
+                VM_VERSION, VM_ARGS, PROPS, ENV, LIBS, -1, null);
+        createController(vmInfo);
+        
         timerAction.run();
 
         DateFormat timestampFormat = controller.getDateFormat();
@@ -149,10 +176,40 @@
         verify(view).setVmNameAndVersion(eq(translator.localize(LocaleResources.VM_INFO_VM_NAME_AND_VERSION,
                         VM_NAME, VM_VERSION, VM_INFO).getContents()));
         verify(view).setVmArguments(eq(VM_ARGS));
+        
+        // Ensure user is unknown
+        verify(view).setUserID(eq(translator.localize(LocaleResources.VM_INFO_USER_UNKNOWN).getContents()));
+    }
+    
+    @Test
+    public void verifyViewIsUpdatedWithDataNoUsername() {
+        VmInfo vmInfo = new VmInfo(VM_PID, START_TIME, STOP_TIME, JAVA_VERSION,
+                JAVA_HOME, MAIN_CLASS, COMMAND_LINE, VM_NAME, VM_INFO,
+                VM_VERSION, VM_ARGS, PROPS, ENV, LIBS, UID, null);
+        createController(vmInfo);
+        
+        timerAction.run();
+
+        DateFormat timestampFormat = controller.getDateFormat();
+        verify(view).setVmPid(eq(String.valueOf(VM_PID)));
+        verify(view).setVmStartTimeStamp(eq(timestampFormat.format(new Date(START_TIME))));
+        verify(view).setVmStopTimeStamp(eq(timestampFormat.format(new Date(STOP_TIME))));
+        verify(view).setJavaVersion(eq(JAVA_VERSION));
+        verify(view).setJavaHome(eq(JAVA_HOME));
+        verify(view).setMainClass(eq(MAIN_CLASS));
+        verify(view).setJavaCommandLine(eq(COMMAND_LINE));
+        
+        verify(view).setVmNameAndVersion(eq(translator.localize(LocaleResources.VM_INFO_VM_NAME_AND_VERSION,
+                        VM_NAME, VM_VERSION, VM_INFO).getContents()));
+        verify(view).setVmArguments(eq(VM_ARGS));
+        
+        // Ensure only user ID is shown
+        verify(view).setUserID(eq(String.valueOf(UID)));
     }
 
     @Test
     public void verifyTimerIsSetUpCorrectly() {
+        createController(createVmInfo());
         assertNotNull(timer);
 
         verify(timer).setAction(isNotNull(Runnable.class));
@@ -164,6 +221,7 @@
 
     @Test
     public void verifyTimerRunsWhenNeeded() {
+        createController(createVmInfo());
         listener.actionPerformed(new ActionEvent<>(view, Action.VISIBLE));
 
         verify(timer).start();
--- a/vm-overview/client-swing/src/main/java/com/redhat/thermostat/vm/overview/client/swing/internal/VmOverviewPanel.java	Mon May 13 16:26:16 2013 -0600
+++ b/vm-overview/client-swing/src/main/java/com/redhat/thermostat/vm/overview/client/swing/internal/VmOverviewPanel.java	Tue May 21 12:43:17 2013 -0400
@@ -73,6 +73,7 @@
     private final ValueField javaVersion = new ValueField("");
     private final ValueField vmNameAndVersion = new ValueField("");
     private final ValueField vmArguments = new ValueField("");
+    private final ValueField user = new ValueField("");
 
     public VmOverviewPanel() {
         super();
@@ -192,6 +193,16 @@
     }
 
     @Override
+    public void setUserID(final String userID) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                user.setText(userID);
+            }
+        });
+    }
+
+    @Override
     public Component getUiComponent() {
         return visiblePanel;
     }
@@ -205,6 +216,7 @@
         LabelField pidLabel = new LabelField(translator.localize(LocaleResources.VM_INFO_PROCESS_ID));
         LabelField startTimeLabel = new LabelField(translator.localize(LocaleResources.VM_INFO_START_TIME));
         LabelField stopTimeLabel = new LabelField(translator.localize(LocaleResources.VM_INFO_STOP_TIME));
+        LabelField userLabel = new LabelField(translator.localize(LocaleResources.VM_INFO_USER));
 
         SectionHeader javaSection = new SectionHeader(translator.localize(LocaleResources.VM_INFO_SECTION_JAVA));
 
@@ -228,6 +240,7 @@
                                 .addComponent(pidLabel)
                                 .addComponent(startTimeLabel)
                                 .addComponent(stopTimeLabel)
+                                .addComponent(userLabel)
                                 .addComponent(mainClassLabel)
                                 .addComponent(javaCommandLineLabel)
                                 .addComponent(javaVersionLabel)
@@ -238,6 +251,7 @@
                                 .addComponent(pid)
                                 .addComponent(startTimeStamp)
                                 .addComponent(stopTimeStamp)
+                                .addComponent(user)
                                 .addComponent(mainClass)
                                 .addComponent(javaCommandLine)
                                 .addComponent(javaVersion)
@@ -260,6 +274,10 @@
                 .addGroup(gl.createParallelGroup(Alignment.LEADING, false)
                         .addComponent(stopTimeLabel)
                         .addComponent(stopTimeStamp))
+                .addPreferredGap(ComponentPlacement.RELATED)
+                .addGroup(gl.createParallelGroup(Alignment.LEADING, false)
+                        .addComponent(userLabel)
+                        .addComponent(user))
                 .addPreferredGap(ComponentPlacement.UNRELATED)
                 .addComponent(javaSection)
                 .addPreferredGap(ComponentPlacement.RELATED)