changeset 2747:2397ba60a7d6

Record loaded native libraries for a given jvm Reviewed-by: jerboaa, stooke, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-August/024516.html
author Miloslav Zezulka <mzezulka@redhat.com>
date Wed, 06 Sep 2017 11:49:23 +0200
parents f902a9f69e4b
children dd5a4d080f46
files plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/JvmStatHostListener.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAO.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapter.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmLinuxNativeLibsExtractor.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibrariesBackend.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsExtractor.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsExtractorFactory.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsVmListener.java plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImplTest.java plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapterTest.java plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmLinuxNativeLibsExtractorTest.java plugins/jvm-overview/agent/src/test/resources/native_lib_empty plugins/jvm-overview/agent/src/test/resources/native_lib_no_libs plugins/jvm-overview/agent/src/test/resources/native_lib_three_libs
diffstat 15 files changed, 604 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/JvmStatHostListener.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/JvmStatHostListener.java	Wed Sep 06 11:49:23 2017 +0200
@@ -52,6 +52,7 @@
 import com.redhat.thermostat.jvm.overview.agent.VmStatusListener.Status;
 import com.redhat.thermostat.jvm.overview.agent.internal.model.InfoBuilderFactory;
 import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAO;
+import com.redhat.thermostat.jvm.overview.agent.internal.model.VmNativeLibsExtractorFactory;
 import com.redhat.thermostat.jvm.overview.agent.model.VmInfo;
 import com.redhat.thermostat.storage.core.WriterID;
 
@@ -149,8 +150,7 @@
             JvmStatDataExtractor extractor) throws MonitorException {
         Map<String, String> properties = new HashMap<String, String>();
         Map<String, String> environment = InfoBuilderFactory.INSTANCE.createProcessEnvironmentBuilder().build(vmPid);
-        // TODO actually figure out the loaded libraries.
-        String[] loadedNativeLibraries = new String[0];
+        String[] loadedNativeLibraries = VmNativeLibsExtractorFactory.getInstance(vmPid).getNativeLibs();
         ProcessUserInfo userInfo = userInfoBuilder.build(vmPid);
         VmInfo info = new VmInfo(writerId.getWriterID(), vmId, vmPid, extractor.getVmStartTime(), stopTime,
                                  extractor.getJavaVersion(), extractor.getJavaHome(),
--- a/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAO.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAO.java	Wed Sep 06 11:49:23 2017 +0200
@@ -59,5 +59,7 @@
     void putVmInfo(VmInfo info);
 
     void putVmStoppedTime(String agentId, String vmId, long since);
+
+    void updateVmNativeLibs(String vmId, String[] newLibs);
 }
 
--- a/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java	Wed Sep 06 11:49:23 2017 +0200
@@ -70,7 +70,7 @@
     private static final String VM_PATH = "/jvms/";
 
     private final Logger logger = LoggingUtils.getLogger(VmInfoDAOImpl.class);
-    
+
     private final JsonHelper jsonHelper;
     private final ConfigurationCreator configCreator;
 
@@ -119,7 +119,8 @@
             URI uri = getAddURI();
             httpRequestService.sendHttpRequest(json, uri, HttpRequestService.Method.POST);
         } catch (IOException | RequestFailedException e) {
-           logger.log(Level.WARNING, "Failed to send JVM information to web gateway", e);
+            logger.log(Level.WARNING, "Failed to send JVM information to web gateway");
+            logger.log(Level.INFO, e.getMessage());
         }
     }
 
@@ -128,14 +129,29 @@
         URI uri = getUpdateURI(vmId);
         try {
             // Encode as JSON and send as PUT request
-            VmInfoUpdate update = new VmInfoUpdate(timestamp);
+            VmInfoUpdate update = new VmInfoLongUpdate(timestamp);
             String json = jsonHelper.toJson(update);
             httpRequestService.sendHttpRequest(json, uri, HttpRequestService.Method.PUT);
         } catch (IOException | RequestFailedException e) {
-           logger.log(Level.WARNING, "Failed to send JVM information update to web gateway at: " + uri, e);
+            logger.log(Level.WARNING, "Failed to send JVM information update to web gateway at: " + uri);
+            logger.log(Level.INFO, e.getMessage());
         }
     }
-    
+
+    @Override
+    public void updateVmNativeLibs(String vmId, String[] newLibs) {
+        URI uri = getUpdateURI(vmId);
+        try {
+            // Encode as JSON and send as PUT request
+            VmInfoUpdate update = new VmInfoStringArrayUpdate(newLibs);
+            String json = jsonHelper.toJson(update);
+            httpRequestService.sendHttpRequest(json, uri, HttpRequestService.Method.PUT);
+        } catch (IOException | RequestFailedException e) {
+            logger.log(Level.WARNING, "Failed to send JVM information update to web gateway at: " + uri);
+            logger.log(Level.INFO, e.getMessage());
+        }
+    }
+
     private URI getAddURI() {
         StringBuilder builder = new StringBuilder();
         builder.append(SYSTEM_PATH);
@@ -151,7 +167,7 @@
         builder.append(vmId);
         return gatewayURL.resolve(builder.toString());
     }
-    
+
     protected void bindHttpRequestService(HttpRequestService httpRequestService) {
         this.httpRequestService = httpRequestService;
     }
@@ -168,39 +184,53 @@
     protected void unbindSystemId(SystemID id) {
         this.systemID = null;
     }
-    
-    static class VmInfoUpdate {
-        
-        private final long stoppedTime;
-        
-        VmInfoUpdate(long stoppedTime) {
-           this.stoppedTime = stoppedTime;
+
+    static abstract class VmInfoUpdate<T> {
+
+        private final T param;
+
+        VmInfoUpdate(T param) {
+            this.param = param;
         }
-        
-        long getStoppedTime() {
-            return stoppedTime;
+
+        T getParam() {
+            return param;
         }
     }
-    
+
+    static class VmInfoLongUpdate extends VmInfoUpdate<Long>  {
+
+        public VmInfoLongUpdate(Long param) {
+            super(param);
+        }
+    }
+
+    static class VmInfoStringArrayUpdate extends VmInfoUpdate<String[]>  {
+
+        public VmInfoStringArrayUpdate(String[] param) {
+            super(param);
+        }
+    }
+
     // For testing purposes
     static class JsonHelper {
-        
+
         private final VmInfoTypeAdapter typeAdapter;
         private final VmInfoUpdateTypeAdapter updateTypeAdapter;
-        
+
         public JsonHelper(VmInfoTypeAdapter typeAdapter, VmInfoUpdateTypeAdapter updateTypeAdapter) {
             this.typeAdapter = typeAdapter;
             this.updateTypeAdapter = updateTypeAdapter;
         }
-        
+
         String toJson(List<VmInfo> infos) throws IOException {
             return typeAdapter.toJson(infos);
         }
-        
-        String toJson(VmInfoUpdate update) throws IOException {
+
+        String toJson(VmInfoUpdate<?> update) throws IOException {
             return updateTypeAdapter.toJson(update);
         }
-        
+
     }
 
     // For Testing purposes
@@ -212,4 +242,3 @@
 
     }
 }
-
--- a/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapter.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapter.java	Wed Sep 06 11:49:23 2017 +0200
@@ -45,11 +45,13 @@
 import com.google.gson.TypeAdapter;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAOImpl.VmInfoLongUpdate;
+import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAOImpl.VmInfoStringArrayUpdate;
 import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAOImpl.VmInfoUpdate;
 import com.redhat.thermostat.jvm.overview.agent.model.VmInfo;
 
 public class VmInfoTypeAdapter extends TypeAdapter<List<VmInfo>> {
-    
+
     private static final String AGENT_ID = "agentId";
     private static final String JVM_ID = "jvmId";
     private static final String JVM_PID = "jvmPid";
@@ -76,11 +78,11 @@
     public void write(JsonWriter out, List<VmInfo> value) throws IOException {
         // Request is an array of VmInfo objects
         out.beginArray();
-        
+
         for (VmInfo info : value) {
             writeVmInfo(out, info);
         }
-        
+
         out.endArray();
     }
 
@@ -124,14 +126,14 @@
         writeLong(out, info.getUid());
         out.name(USERNAME);
         out.value(info.getUsername());
-        
+
         out.endObject();
     }
-    
+
     private void writeStringMap(JsonWriter out, Map<String, String> map) throws IOException {
         // Write contents of Map as an array of JSON objects
         out.beginArray();
-        
+
         Set<Entry<String, String>> entries = map.entrySet();
         for (Map.Entry<String, String> entry : entries) {
             // Create JSON object with key and value labeled as JSON names
@@ -142,21 +144,21 @@
             out.value(entry.getValue());
             out.endObject();
         }
-        
+
         out.endArray();
     }
-    
-    private void writeStringArray(JsonWriter out, String[] array) throws IOException {
+
+    private static void writeStringArray(JsonWriter out, String[] array) throws IOException {
         // Write String[] as JSON array
         out.beginArray();
-        
+
         for (String item : array) {
             out.value(item);
         }
-        
+
         out.endArray();
     }
-    
+
     private static void writeLong(JsonWriter out, long value) throws IOException {
         // Write MongoDB representation of a Long
         out.beginObject();
@@ -169,22 +171,31 @@
     public List<VmInfo> read(JsonReader in) throws IOException {
         throw new UnsupportedOperationException();
     }
-    
+
     static class VmInfoUpdateTypeAdapter extends TypeAdapter<VmInfoUpdate> {
 
         private static final String SET = "set";
-        
+
         @Override
         public void write(JsonWriter out, VmInfoUpdate value) throws IOException {
             // List fields to update as part of a JSON object with name "set"
             out.beginObject();
             out.name(SET);
-            
+
             out.beginObject();
-            out.name(STOP_TIME_STAMP);
-            writeLong(out, value.getStoppedTime());
+            if (value instanceof VmInfoLongUpdate) {
+                VmInfoLongUpdate castVal = (VmInfoLongUpdate) value;
+                out.name(STOP_TIME_STAMP);
+                writeLong(out, castVal.getParam());
+            } else if (value instanceof VmInfoStringArrayUpdate) {
+                VmInfoStringArrayUpdate castVal = (VmInfoStringArrayUpdate) value;
+                out.name(LOADED_NATIVE_LIBRARIES);
+                writeStringArray(out, castVal.getParam());
+            } else {
+                throw new IllegalArgumentException("illegal update data type");
+            }
             out.endObject();
-            
+
             out.endObject();
         }
 
@@ -192,7 +203,7 @@
         public VmInfoUpdate read(JsonReader in) throws IOException {
             throw new UnsupportedOperationException();
         }
-        
+
     }
-    
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmLinuxNativeLibsExtractor.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class VmLinuxNativeLibsExtractor implements VmNativeLibsExtractor {
+
+    private static final Logger LOGGER
+            = LoggingUtils.getLogger(VmInfoDAOImpl.class);
+
+    private final Integer pid;
+
+    public VmLinuxNativeLibsExtractor(Integer pid) {
+        Objects.requireNonNull(pid);
+        this.pid = pid;
+    }
+
+    private String[] getNativeLibsFromReader(File nativeLibFile) {
+        final String soGrep = ".+\\.so.*";
+        Set<String> result = new HashSet<>();
+        try (BufferedReader br
+                = new BufferedReader(new FileReader(nativeLibFile))) {
+            String next = br.readLine();
+            while (next != null) {
+                next = next.trim();
+                if (next.matches(soGrep)) {
+                    String candidate = next.substring(next.lastIndexOf(' ') + 1);
+                    result.add(candidate);
+                }
+                next = br.readLine();
+            }
+            return result.toArray(new String[0]);
+        } catch (IOException ex) {
+            LOGGER.log(Level.WARNING, "Unable to retrieve native libraries.");
+            LOGGER.log(Level.INFO, ex.getMessage());
+            return new String[0];
+        }
+    }
+
+    @Override
+    public String[] getNativeLibs() {
+        return getNativeLibsFromReader(new File(String.format("/proc/%d/maps", pid)));
+    }
+
+    // for testing purposes only
+    String[] getNativeLibs(File testFile) {
+        return getNativeLibsFromReader(testFile);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibrariesBackend.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+import com.redhat.thermostat.backend.Backend;
+import com.redhat.thermostat.common.Ordered;
+import com.redhat.thermostat.jvm.overview.agent.VmListenerBackend;
+import com.redhat.thermostat.jvm.overview.agent.VmUpdateListener;
+import com.redhat.thermostat.storage.core.WriterID;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+
+@Component
+@Service(value = Backend.class)
+public class VmNativeLibrariesBackend extends VmListenerBackend {
+
+    private final ListenerCreator listenerCreator;
+    public static final int ORDER = Ordered.ORDER_MEMORY_GROUP + 60;
+
+    public VmNativeLibrariesBackend() {
+        this(new ListenerCreator());
+    }
+
+    VmNativeLibrariesBackend(ListenerCreator creator) {
+        super("VM Native Libs Retrieval Backend", "Gathers loaded native libraries for a given JVM", "Red Hat, Inc.", true);
+        this.listenerCreator = creator;
+    }
+
+    @Reference
+    private VmInfoDAO vmInfoDao;
+    @Reference
+    private WriterID writerId;
+
+    @Override
+    protected VmUpdateListener createVmListener(String writerId, String vmId, int pid) {
+        return listenerCreator.create(vmInfoDao, vmId, pid);
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER;
+    }
+
+    // DS bind method
+    protected void bindVmGcStats(VmInfoDAO dao) {
+        this.vmInfoDao = dao;
+    }
+
+    // DS bind method
+    protected void bindWriterId(WriterID id) {
+        this.writerId = id;
+    }
+
+    // For testing purposes
+    static class ListenerCreator {
+
+        VmNativeLibsVmListener create(VmInfoDAO dao, String vmId, int pid) {
+            return new VmNativeLibsVmListener(dao, vmId, pid);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsExtractor.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+public interface VmNativeLibsExtractor {
+
+    /**
+     * Returns an array of native libraries names loaded by the JVM. Extracting
+     * native libraries is OS specific and it is suggested that each
+     * implementation contains name of the OS the implementation is related to.
+     */
+    String[] getNativeLibs();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsExtractorFactory.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+import com.redhat.thermostat.shared.config.OS;
+
+public final class VmNativeLibsExtractorFactory {
+
+    public static VmNativeLibsExtractor getInstance(Integer pid) {
+        if (OS.IS_LINUX) {
+            return new VmLinuxNativeLibsExtractor(pid);
+        } else {
+            throw new UnsupportedOperationException("Extractor for the given OS not supported yet.");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmNativeLibsVmListener.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.jvm.overview.agent.VmUpdate;
+import com.redhat.thermostat.jvm.overview.agent.VmUpdateException;
+import com.redhat.thermostat.jvm.overview.agent.VmUpdateListener;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class VmNativeLibsVmListener implements VmUpdateListener {
+
+    private static final Logger logger = LoggingUtils.getLogger(VmNativeLibsVmListener.class);
+
+    private final VmInfoDAO vmInfoDAO;
+    private final String vmId;
+    private final int pid;
+    private final VmNativeLibsExtractor libExtractor;
+
+    public VmNativeLibsVmListener(VmInfoDAO vmInfoDAO, String vmId, int pid) {
+        this.vmInfoDAO = vmInfoDAO;
+        this.vmId = vmId;
+        this.pid = pid;
+        this.libExtractor = VmNativeLibsExtractorFactory.getInstance(pid);
+    }
+
+    @Override
+    public void countersUpdated(VmUpdate update) {
+        VmClassloaderInfoExtractor extractor = new VmClassloaderInfoExtractor(update);
+        recordNativeLibs(extractor);
+    }
+
+    /**
+     * Heuristic for when to extract native libraries: either number of loaded
+     * classes counter changed or this counter did not change but N different 
+     * classes were loaded instead of N unloaded ones before next VmEvent was 
+     * issued, therefore leaving the amount of loaded classes the same but with
+     * different set of classes.
+     */
+    void recordNativeLibs(VmClassloaderInfoExtractor extractor) {
+        try {
+            Long loadedClassesCount = extractor.getLoadedClassesCount();
+            Long unloadedClassesCount = extractor.getUnloadedClassesCount();
+            if (loadedClassesCount != null || unloadedClassesCount != null) {
+                vmInfoDAO.updateVmNativeLibs(vmId, libExtractor.getNativeLibs());
+            }
+        } catch (VmUpdateException ex) {
+            logger.log(Level.WARNING, "Error gathering native libs for VM " + vmId, ex);
+        }
+    }
+
+}
--- a/plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImplTest.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImplTest.java	Wed Sep 06 11:49:23 2017 +0200
@@ -69,7 +69,7 @@
     private static final URI UPDATE_URI = GATEWAY_URI.resolve("systems/foo/jvms/vmId");
     private static final String SOME_JSON = "{\"some\" : \"json\"}";
     private static final String SOME_OTHER_JSON = "{\"some\" : {\"other\" : \"json\"}}";
-    
+
     private VmInfo info;
     private JsonHelper jsonHelper;
     private HttpRequestService httpRequestService;
@@ -122,7 +122,7 @@
         dao.bindHttpRequestService(httpRequestService);
         dao.activate();
         dao.putVmInfo(info);
-        
+
         verify(jsonHelper).toJson(eq(Arrays.asList(info)));
         verify(httpRequestService).sendHttpRequest(SOME_JSON, POST_URI, HttpRequestService.Method.POST);
     }
@@ -138,10 +138,9 @@
         ArgumentCaptor<VmInfoUpdate> updateCaptor = ArgumentCaptor.forClass(VmInfoUpdate.class);
         verify(jsonHelper).toJson(updateCaptor.capture());
         VmInfoUpdate update = updateCaptor.getValue();
-        assertEquals(3L, update.getStoppedTime());
-                
+        assertEquals(3L, update.getParam());
+
         verify(httpRequestService).sendHttpRequest(SOME_OTHER_JSON, UPDATE_URI, HttpRequestService.Method.PUT);
     }
 
 }
-
--- a/plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapterTest.java	Mon Sep 04 14:39:53 2017 +0200
+++ b/plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoTypeAdapterTest.java	Wed Sep 06 11:49:23 2017 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.jvm.overview.agent.internal.model;
 
+import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAOImpl.VmInfoLongUpdate;
+import com.redhat.thermostat.jvm.overview.agent.internal.model.VmInfoDAOImpl.VmInfoStringArrayUpdate;
 import static com.redhat.thermostat.testutils.JsonUtils.assertJsonEquals;
 
 import java.util.Arrays;
@@ -49,7 +51,7 @@
 import org.junit.Test;
 
 public class VmInfoTypeAdapterTest {
-    
+
     @Test
     public void testWrite() throws Exception {
         VmInfoTypeAdapter adapter = new VmInfoTypeAdapter();
@@ -68,16 +70,16 @@
                 "\"environment\":[{\"key\":\"A\",\"value\":\"B\"},{\"key\":\"C\",\"value\":\"D\"}]," +
                 "\"loadedNativeLibraries\":[\"libhello\",\"libworld\"],\"uid\":{\"$numberLong\":\"5678\"}," +
                 "\"username\":\"user\"}]";
-        
+
         final Map<String, String> props = new HashMap<>();
         props.put("A", "B");
         props.put("C", "D");
         final Map<String, String> env = new HashMap<>();
         env.put("E", "F");
         env.put("G", "H");
-        VmInfo first = new VmInfo("agent1", "vm1", 8000, 50000L, Long.MIN_VALUE, "1.8.0", "/path/to/java", "myClass", 
+        VmInfo first = new VmInfo("agent1", "vm1", 8000, 50000L, Long.MIN_VALUE, "1.8.0", "/path/to/java", "myClass",
                 "java myClass", "myJVM", "interesting", "1800", "-Dhello", props, env, new String[0], 1234L, "test");
-        
+
         final Map<String, String> props2 = new HashMap<>();
         final Map<String, String> env2 = new HashMap<>();
         env2.put("A", "B");
@@ -86,19 +88,29 @@
         VmInfo second = new VmInfo("agent2", "vm2", 9000, 100000L, 200000L, "1.7.0", "/path/to/jre", "myOtherClass",
                                    "otherClass.sh", "myOtherJVM", "info", "1700", "-Dworld", props2, env2, libs, 5678L, "user");
         List<VmInfo> infos = Arrays.asList(first, second);
-        
+
         String json = adapter.toJson(infos);
         assertJsonEquals(expected, json);
     }
-    
+
     @Test
-    public void testUpdate() throws Exception {
+    public void testUpdateLong() throws Exception {
         VmInfoUpdateTypeAdapter adapter = new VmInfoUpdateTypeAdapter();
         final String expected = "{\"set\":{\"stopTime\":{\"$numberLong\":\"5000\"}}}";
-        
-        VmInfoUpdate update = new VmInfoUpdate(5000L);
+
+        VmInfoUpdate update = new VmInfoLongUpdate(5000L);
         String json = adapter.toJson(update);
         assertJsonEquals(expected, json);
     }
-    
+
+    @Test
+    public void testUpdateStringArray() throws Exception {
+        VmInfoUpdateTypeAdapter adapter = new VmInfoUpdateTypeAdapter();
+        final String expected = "{\"set\":{\"loadedNativeLibraries\":[\"libupdate\",\"libthree\",\"libitems\"]}}";
+        String[] libs = new String[]{"libupdate", "libthree", "libitems"};
+        VmInfoUpdate update = new VmInfoStringArrayUpdate(libs);
+        String json = adapter.toJson(update);
+        assertJsonEquals(expected, json);
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/test/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmLinuxNativeLibsExtractorTest.java	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012-2017 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.jvm.overview.agent.internal.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.HashSet;
+import org.junit.Before;
+import org.junit.Test;
+
+public class VmLinuxNativeLibsExtractorTest {
+
+    private VmLinuxNativeLibsExtractor extractor;
+
+    private static final Integer VM_PID = 0;
+
+    private final File THREE_LIBS = getFileFromTestSources("native_lib_three_libs");
+    private final File NO_LIBS = getFileFromTestSources("native_lib_no_libs");
+    private final File EMPTY = getFileFromTestSources("native_lib_empty");
+
+    private File getFileFromTestSources(String path) {
+        path = '/' + path;
+        return new File(decodeFilePath(this.getClass().getResource(path)));
+    }
+
+    private String decodeFilePath(URL url) {
+        try {
+            return URLDecoder.decode(url.getFile(), "UTF-8");
+        } catch (UnsupportedEncodingException ex) {
+            throw new AssertionError(ex);
+        }
+    }
+
+    @Before
+    public void setup() {
+        extractor = new VmLinuxNativeLibsExtractor(VM_PID);
+    }
+
+    @Test
+    public void threeLibs() {
+        HashSet<String> expectedLibs = new HashSet<>(Arrays.asList("/usr/foo/libhello.so",
+                "/usr/bar/libworld.so.0.18.0", "/tmp/libnew.so.so"));
+
+        // Cannot compare arrays directly since the implementation internally gathers libs
+        // to a Set, which might yield a different ordering of items in the collection in the end
+        assertEquals(expectedLibs, new HashSet<>(Arrays.asList(extractor.getNativeLibs(THREE_LIBS))));
+    }
+
+    @Test
+    public void noLibs() {
+        String[] result = extractor.getNativeLibs(NO_LIBS);
+        int numExpectedLibs = 0;
+
+        assertNotNull(result);
+        assertEquals(result.length, numExpectedLibs);
+
+    }
+
+    @Test
+    public void empty() {
+        String[] result = extractor.getNativeLibs(EMPTY);
+        int numExpectedLibs = 0;
+
+        assertNotNull(result);
+        assertEquals(result.length, numExpectedLibs);
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/test/resources/native_lib_empty	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,1 @@
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/test/resources/native_lib_no_libs	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,3 @@
+000000000000-00000000f000 r-xp 00000000 fd:01 1234567                    /usr/foo/libhello
+00000000f000-000000050000 ---p 00085000 fd:02 1234567                    /usr/bar/libworld
+000000050000-000000e00000 r--p 00085000 fd:03 1234567                    /tmp/libnew
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/jvm-overview/agent/src/test/resources/native_lib_three_libs	Wed Sep 06 11:49:23 2017 +0200
@@ -0,0 +1,4 @@
+000000000000-00000000f000 r-xp 00000000 fd:01 1234567                    /usr/foo/libhello.so
+00000000f000-000000050000 ---p 00085000 fd:02 1234567                    /usr/bar/libworld.so.0.18.0
+000000050000-000000e00000 r--p 00085000 fd:03 1234567                    /tmp/libnew.so.so
+