changeset 273:ec644f678e6e

Initial JCMD Plugin review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/025195.html reviewed-by: stooke
author Mario Torre <neugens.limasoftware@gmail.com>
date Mon, 09 Oct 2017 15:57:22 +0200
parents 353ace15de10
children 0ce9c3b75ab6
files common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java distribution/pom.xml distribution/src/etc/services.properties pom.xml services/jcmd/pom.xml services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JCMDModelConverter.java services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JSONConverter.java services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JcmdHttpHandler.java services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/Model.java services/jcmd/src/main/resources/jcmd-swagger.yaml services/jcmd/src/main/webapp/WEB-INF/web.xml services/jcmd/src/test/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JCMDModelConverterTest.java services/jcmd/src/test/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JSONConverterTest.java services/pom.xml
diffstat 14 files changed, 916 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java	Mon Oct 09 15:57:14 2017 +0200
+++ b/common/mongodb/src/main/java/com/redhat/thermostat/gateway/common/mongodb/executor/MongoExecutor.java	Mon Oct 09 15:57:22 2017 +0200
@@ -46,6 +46,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import com.mongodb.client.result.DeleteResult;
 import com.mongodb.client.result.UpdateResult;
@@ -140,7 +142,6 @@
     public MongoDataResultContainer execPostRequest(MongoCollection<DBObject> collection, String body,
                                                     Set<String> realms, String systemId, String jvmId) {
         MongoDataResultContainer metaDataContainer = new MongoDataResultContainer();
-
         if (body.length() > 0) {
             List<DBObject> inputList = (List<DBObject>) JSON.parse(body);
 
@@ -214,4 +215,4 @@
     private boolean isNullOrEmpty(final String s) {
         return s == null || s.isEmpty();
     }
-}
\ No newline at end of file
+}
--- a/distribution/pom.xml	Mon Oct 09 15:57:14 2017 +0200
+++ b/distribution/pom.xml	Mon Oct 09 15:57:22 2017 +0200
@@ -258,6 +258,13 @@
                         </artifactItem>
                         <artifactItem>
                             <groupId>com.redhat.thermostat</groupId>
+                            <artifactId>thermostat-web-gateway-service-jcmd</artifactId>
+                            <version>${project.version}</version>
+                            <type>war</type>
+                            <overWrite>false</overWrite>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>com.redhat.thermostat</groupId>
                             <artifactId>thermostat-web-gateway-service-jvm-memory</artifactId>
                             <version>${project.version}</version>
                             <type>war</type>
@@ -340,6 +347,13 @@
         </dependency>
         <dependency>
             <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-web-gateway-service-jcmd</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.redhat.thermostat</groupId>
             <artifactId>thermostat-web-gateway-service-jvm-memory</artifactId>
             <version>${project.version}</version>
             <type>war</type>
--- a/distribution/src/etc/services.properties	Mon Oct 09 15:57:14 2017 +0200
+++ b/distribution/src/etc/services.properties	Mon Oct 09 15:57:22 2017 +0200
@@ -1,5 +1,6 @@
 /commands = thermostat-web-gateway-service-commands-@project.version@.war
 /jvm-byteman = thermostat-web-gateway-service-jvm-byteman-@project.version@.war
+/jcmd = thermostat-web-gateway-service-jcmd-@project.version@.war
 /jvm-cpu = thermostat-web-gateway-service-jvm-cpu-@project.version@.war
 /jvm-compiler = thermostat-web-gateway-service-jvm-compiler-@project.version@.war
 /jvm-gc = thermostat-web-gateway-service-jvm-gc-@project.version@.war
@@ -10,3 +11,4 @@
 /system-cpu = thermostat-web-gateway-service-system-cpu-@project.version@.war
 /system-memory = thermostat-web-gateway-service-system-memory-@project.version@.war
 /system-network = thermostat-web-gateway-service-system-network-@project.version@.war
+
--- a/pom.xml	Mon Oct 09 15:57:14 2017 +0200
+++ b/pom.xml	Mon Oct 09 15:57:22 2017 +0200
@@ -59,6 +59,8 @@
     </modules>
 
     <properties>
+        <thermostat.common.version>0.1.0</thermostat.common.version>
+
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.7</java.version>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/pom.xml	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,115 @@
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>thermostat-web-gateway-services</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>1.99.12-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-web-gateway-service-jcmd</artifactId>
+
+  <packaging>war</packaging>
+
+  <name>Thermostat Web Gateway JCMD Service</name>
+  
+  <properties>
+    <com.redhat.thermostat.gateway.SERVICE_NAME>jcmd</com.redhat.thermostat.gateway.SERVICE_NAME>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-war-plugin</artifactId>
+          <configuration>
+          <webResources>
+            <resource>
+              <directory>src/main/webapp</directory>
+              <filtering>true</filtering>
+            </resource>
+          </webResources>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <!-- Thermostat Web Gateway Dependencies -->
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-web-gateway-common-mongodb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-web-gateway-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <!-- Servlet API deps -->
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <version>${javax.servlet.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- JAX-RS Dependencies -->
+    <dependency>
+      <groupId>javax.ws.rs</groupId>
+      <artifactId>javax.ws.rs-api</artifactId>
+      <version>${javax-rs-api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common</artifactId>
+      <version>${thermostat.common.version}</version>
+    </dependency>
+
+    <!-- test scoped deps -->
+    <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>${junit.version}</version>
+        <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JCMDModelConverter.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,112 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import com.google.gson.Gson;
+import com.redhat.thermostat.lang.schema.models.Timestamp;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+class JCMDModelConverter {
+
+    // TODO: we need to export this model from the agent somehow, models
+    // this is basically a copy of
+    // com.redhat.thermostat.jcmd.backend.internal.model.GCStatsModel
+    private static class RawModel {
+        private Timestamp timestamp;
+        private String payload;
+    }
+
+    public static Model createModel(String json, String systemId, String jvmId) {
+
+        Model model = new Model();
+        model.jvmId = jvmId;
+        model.systemId = systemId;
+
+        RawModel rawModel = new Gson().fromJson(json, RawModel.class);
+        model.timestamp = rawModel.timestamp.get();
+
+        BufferedReader reader = new BufferedReader(new StringReader(rawModel.payload));
+        try {
+            List<String> headers = new ArrayList<>(Arrays.asList(reader.readLine().split("\\s+|,")));
+            headers.add("systemId");
+            headers.add("jvmId");
+
+            model.headers = headers;
+
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                if (line.endsWith("Total") || line.startsWith(headers.get(0)) ||
+                    line.contains("%"))
+                {
+                    // we reached end, the last line is the same as the first
+                    // one, the last two lines are some simple
+                    // stats we can infer from the other numbers
+                    break;
+                }
+                // we need to take care for the last block, since it may contain
+                // a legitimate whitespace
+                // note: we depend on the fact that the last block is *always*
+                // ClassName,ClassLoader, while this should not change across
+                // releases, if it does we need a smarter parsing code
+                // it may be worth to revisit this code anyway to make it
+                // less dependent on the input
+                String[] blocks = line.trim().split(",");
+                String classLoaderField = blocks[1];
+                List<String> classData = new ArrayList<>(Arrays.asList(blocks[0].split("\\s+")));
+                String className = classData.get(classData.size() - 1);
+
+                classData.add(classLoaderField);
+
+                classData.add(systemId);
+                classData.add(jvmId);
+
+                model.data.put(className, classData);
+            }
+
+        } catch (IOException ignored) {
+            ignored.printStackTrace();
+        }
+
+        return model;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JSONConverter.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,135 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JSONConverter {
+
+    class JCMDEntry {
+        long timestamp;
+        String key;
+        String value;
+    }
+
+    private GsonBuilder builder;
+    JSONConverter() {
+        builder = new GsonBuilder();
+        // leave commented out for debugging
+        //builder.setPrettyPrinting();
+        builder.registerTypeAdapter(HashMap.class, new ModelAdapter());
+    }
+
+    public String convert(Model model) {
+        Map<String, List<JCMDEntry>> jsonMapping = new HashMap<>();
+        for (String key : model.data.keySet()) {
+            List<String> objectData = model.data.get(key);
+            int i = 0;
+
+            List<JCMDEntry> entries = new ArrayList<>();
+            for (String fieldEntry : objectData) {
+                JCMDEntry jcmdEntry = new JCMDEntry();
+                jcmdEntry.key = model.headers.get(i++);
+                jcmdEntry.value = fieldEntry;
+
+                jcmdEntry.timestamp = model.timestamp;
+
+                entries.add(jcmdEntry);
+            }
+            jsonMapping.put(key, entries);
+        }
+
+        return builder.create().toJson(jsonMapping);
+    }
+
+    private class ModelAdapter extends TypeAdapter<Object> {
+
+        @Override
+        public void write(JsonWriter out, Object value) throws IOException {
+            Map<String, List<JCMDEntry>> jsonObject  = (Map<String, List<JCMDEntry>>) value;
+
+            out.beginArray();
+
+            for (String name : jsonObject.keySet()) {
+                out.beginObject();
+
+                for (JCMDEntry property : jsonObject.get(name)) {
+
+                    out.name(property.key);
+
+                    if (property.value.matches("-?\\d+(\\.\\d+)?")) {
+                        out.beginObject();
+                        out.name("$numberLong");
+                        out.value(property.value);
+                        out.endObject();
+                    } else {
+                        out.value(property.value);
+                    }
+                }
+
+                // the timestamp is not part of the properties so we add
+                // manually here
+                {
+                    out.name("timeStamp");
+                    out.beginObject();
+                    out.name("$numberLong");
+                    out.value("" + jsonObject.get(name).get(0).timestamp);
+                    out.endObject();
+                }
+                // end: timeStamp
+
+                out.endObject();
+            }
+
+            out.endArray();
+        }
+
+        @Override
+        public Object read(JsonReader in) throws IOException {
+            throw new UnsupportedOperationException("read() not yet implemented");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JcmdHttpHandler.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,97 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import com.redhat.thermostat.gateway.common.core.servlet.CommonQueryParams;
+import com.redhat.thermostat.gateway.common.core.servlet.RequestParameters;
+import com.redhat.thermostat.gateway.common.mongodb.servlet.MongoHttpHandlerHelper;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+@Path("/")
+public class JcmdHttpHandler {
+    private static final String COLLECTION_NAME = "jcmd";
+    private final MongoHttpHandlerHelper metricsServiceHelper = new MongoHttpHandlerHelper(COLLECTION_NAME);
+
+    @POST
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response postJCMDData(String body,
+                                 @PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                                 @PathParam(RequestParameters.JVM_ID) String jvmId,
+                                 @Context ServletContext context,
+                                 @Context HttpServletRequest httpServletRequest)
+    {
+        //Logger.getLogger(JcmdHttpHandler.class.getName()).log(Level.WARNING, "jvmid: " + jvmId + ", body: " + body);
+        Model model = JCMDModelConverter.createModel(body, systemId, jvmId);
+        String json = new JSONConverter().convert(model);
+
+        return metricsServiceHelper.handlePostWithJvmID(httpServletRequest, context, systemId, jvmId, false, json);
+    }
+
+    @GET
+    @Path("/systems/{" + RequestParameters.SYSTEM_ID +"}/jvms/{" + RequestParameters.JVM_ID +"}")
+    @Consumes({ "application/json" })
+    @Produces({ "application/json", "text/html; charset=utf-8" })
+    public Response getJvmGc(@PathParam(RequestParameters.SYSTEM_ID) String systemId,
+                             @PathParam(RequestParameters.JVM_ID) String jvmId,
+                             @QueryParam(RequestParameters.LIMIT) @DefaultValue("1") Integer limit,
+                             @QueryParam(RequestParameters.OFFSET) @DefaultValue("0") Integer offset,
+                             @QueryParam(RequestParameters.SORT) String sort,
+                             @QueryParam(RequestParameters.QUERY) String queries,
+                             @QueryParam(RequestParameters.INCLUDE) String includes,
+                             @QueryParam(RequestParameters.EXCLUDE) String excludes,
+                             @QueryParam(RequestParameters.METADATA) @DefaultValue("false") Boolean metadata,
+                             @Context HttpServletRequest httpServletRequest,
+                             @Context ServletContext context)
+    {
+        return metricsServiceHelper.handleGetWithJvmID(httpServletRequest, context, systemId, jvmId,
+                new CommonQueryParams(limit, offset, sort, queries, includes, excludes, metadata));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/java/com/redhat/thermostat/gateway/service/jvm/jcmd/Model.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class Model {
+    List<String> headers;
+    Map<String, List<String>> data;
+    String systemId;
+    String jvmId;
+    long timestamp;
+
+    public Model() {
+        data = new HashMap<>();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/resources/jcmd-swagger.yaml	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,108 @@
+swagger: '2.0'
+info:
+  version: 0.0.1
+  title: Thermostat Web Gateway JCMD API
+  license:
+    name: GPL v2 with Classpath Exception
+    url: 'http://www.gnu.org/licenses'
+consumes:
+  - application/json
+produces:
+  - application/json
+basePath: /jcmd/0.0.1
+paths:
+  /systems/{systemId}/jvms/{jvmId}:
+    parameters:
+      - $ref: '#/parameters/thermostat-realms'
+      - $ref: '#/parameters/system-id'
+      - $ref: '#/parameters/jvm-id'
+    get:
+      description: Get JCMD information for {jvmId}
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/jvm-jcmd-get-response'
+    post:
+      description: Add JCMD information for {jvmId}
+      parameters:
+        - $ref: '#/parameters/jvm-jcmd-info'
+        - $ref: '#/parameters/metadata'
+      responses:
+        '200':
+          description: OK
+          schema:
+            $ref: '#/definitions/metadata'
+definitions:
+  jvm-jcmd-get-response:
+    type: object
+    properties:
+      response:
+          $ref: '#/definitions/jvm-jcmd-info'
+      metadata:
+        $ref: '#/definitions/metadata'
+  jvm-jcmd-info:
+    type: object
+    properties:
+      jvmId:
+        type: string
+      timeStamp:
+        $ref: '#/definitions/timestamp'
+      payload:
+        type: string
+  metadata:
+      type: object
+      properties:
+        payloadCount:
+          type: integer
+        count:
+          type: integer
+        prev:
+          type: string
+        next:
+          type: string
+        first:
+          type: string
+        last:
+          type: string
+        insertCount:
+          type: integer
+        matchCount:
+          type: integer
+        elapsed:
+          type: integer
+          format: int64
+  timestamp:
+    description: UNIX timestamp in milliseconds
+    type: object
+    properties:
+      $numberLong:
+        type: string
+parameters:
+  system-id:
+    name: systemId
+    in: path
+    required: true
+    type: string
+  jvm-id:
+    name: jvmId
+    in: path
+    required: true
+    type: string
+  jvm-jcmd-info:
+    name: jvm-jcmd-info
+    in: body
+    description: The JVM JCMD information
+    required: true
+    schema:
+      $ref: '#/definitions/jvm-jcmd-info'
+  metadata:
+    name: metadata
+    type: boolean
+    in: query
+    description: "Metadata flag. If set to 'true', the subsequent request response will return metadata information. If set to 'false', such metadata information will be omitted."
+  thermostat-realms:
+      name: X-Thermostat-Realms
+      type: string
+      in: header
+      description: "Realms Header used to specify a subset of roles to use for Keycloak authorization. Attempts to specify realms that the client does not have, or no valid realms at all will result in a 400 Bad Request response. Expects a space separated list of realms. Example 'X-Thermostat-Realms: realm-one realm-two'"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/main/webapp/WEB-INF/web.xml	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,88 @@
+<!--
+
+ 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.
+
+-->
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+         version="3.1">
+    <servlet>
+        <servlet-name>JCMDServlet</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>
+                jersey.config.server.provider.packages
+            </param-name>
+            <param-value>
+                com.redhat.thermostat.gateway.service.jvm.jcmd,
+            </param-value>
+        </init-param>
+    </servlet>
+    <filter>
+        <filter-name>ServiceVersionFilter</filter-name>
+        <filter-class>com.redhat.thermostat.gateway.common.core.servlet.ServiceVersionFilter</filter-class>
+        <init-param>
+            <param-name>version</param-name>
+            <param-value>0.0.3</param-value>
+        </init-param>
+    </filter>
+    <filter-mapping>
+        <filter-name>ServiceVersionFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+    <servlet-mapping>
+        <servlet-name>JCMDServlet</servlet-name>
+        <url-pattern>/0.0.3/*</url-pattern>
+    </servlet-mapping>
+    <!-- Service configuration -->
+    <context-param>
+        <param-name>com.redhat.thermostat.gateway.SERVICE_NAME</param-name>
+        <param-value>@com.redhat.thermostat.gateway.SERVICE_NAME@</param-value>
+    </context-param>
+    <!-- Listener for setting up the storage connection -->
+    <listener>
+        <listener-class>com.redhat.thermostat.gateway.common.mongodb.servlet.StorageConnectionSettingListener</listener-class>
+    </listener>
+    <!-- Allow viewing of API spec without authentication -->
+    <security-constraint>
+      <web-resource-collection>
+        <web-resource-name>Swagger API Spec File</web-resource-name>
+        <url-pattern>/0.0.1/doc/@com.redhat.thermostat.gateway.SERVICE_NAME@-swagger.yaml</url-pattern>
+      </web-resource-collection>
+      <!-- Explicitly no auth constraint for this file -->
+    </security-constraint>
+</web-app>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/test/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JCMDModelConverterTest.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,115 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class JCMDModelConverterTest {
+
+    private static String RAW_PAYLOAD =
+            "{\"timestamp\":{\"timestamp\":\"1505992968442\"},"               +
+            "\"payload\":\"Index Super InstCount InstBytes KlassBytes "       +
+            "annotations   CpAll MethodCount Bytecodes MethodAll   ROAll   "  +
+            "RWAll   Total ClassName,ClassLoader\\n    1    -1      6126    " +
+            "398176        464           0       0           0         "      +
+            "0         0      24     568     592 [C,NULL class_loader\\n    " +
+            "2    48      1614    182816        632           0   "           +
+            "20824         130      4973     37080   20768   39544   "        +
+            "60312 java.lang.Class,NULL class_loader\\n    3    -1       "    +
+            "471    154720        464           0       0           "         +
+            "0         0         0      24     568     592 "                  +
+            "[B,NULL class_loader\\n"                                         +
+            "Index Super InstCount InstBytes KlassBytes annotations   "       +
+            "CpAll MethodCount Bytecodes MethodAll   ROAll   RWAll   "        +
+            "Total ClassName,ClassLoader\\n\"}";
+
+    @Test
+    public void testModelCreation() {
+
+        Model model = JCMDModelConverter.createModel(RAW_PAYLOAD, "0x4A", "42");
+        assertTrue(model != null);
+        assertEquals(3, model.data.size());
+
+        assertTrue(model.data.containsKey("[C"));
+        assertTrue(model.data.containsKey("[B"));
+        assertTrue(model.data.containsKey("java.lang.Class"));
+
+        int i = 0;
+        assertEquals("Index", model.headers.get(i++));
+        assertEquals("Super", model.headers.get(i++));
+        assertEquals("InstCount", model.headers.get(i++));
+        assertEquals("InstBytes", model.headers.get(i++));
+        assertEquals("KlassBytes", model.headers.get(i++));
+        assertEquals("annotations", model.headers.get(i++));
+        assertEquals("CpAll", model.headers.get(i++));
+        assertEquals("MethodCount", model.headers.get(i++));
+        assertEquals("Bytecodes", model.headers.get(i++));
+        assertEquals("MethodAll", model.headers.get(i++));
+        assertEquals("ROAll", model.headers.get(i++));
+        assertEquals("RWAll", model.headers.get(i++));
+        assertEquals("Total", model.headers.get(i++));
+        assertEquals("ClassName", model.headers.get(i++));
+        assertEquals("ClassLoader", model.headers.get(i++));
+        assertEquals("systemId", model.headers.get(i++));
+        assertEquals("jvmId", model.headers.get(i++));
+
+        i = 0;
+        List<String> data = model.data.get("[C");
+        assertEquals("1", data.get(i++));
+        assertEquals("-1", data.get(i++));
+        assertEquals("6126", data.get(i++));
+        assertEquals("398176", data.get(i++));
+        assertEquals("464", data.get(i++));
+        assertEquals("0", data.get(i++));
+        assertEquals("0", data.get(i++));
+        assertEquals("0", data.get(i++));
+        assertEquals("0", data.get(i++));
+        assertEquals("0", data.get(i++));
+        assertEquals("24", data.get(i++));
+        assertEquals("568", data.get(i++));
+        assertEquals("592", data.get(i++));
+        assertEquals("[C", data.get(i++));
+        assertEquals("NULL class_loader", data.get(i++));
+        assertEquals("0x4A", data.get(i++));
+        assertEquals("42", data.get(i++));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/services/jcmd/src/test/java/com/redhat/thermostat/gateway/service/jvm/jcmd/JSONConverterTest.java	Mon Oct 09 15:57:22 2017 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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.gateway.service.jvm.jcmd;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class JSONConverterTest {
+    private static String RAW_PAYLOAD =
+            "{\"timestamp\":{\"timestamp\":\"1505992968442\"},"               +
+                    "\"payload\":\"Index Super InstCount InstBytes KlassBytes "       +
+                    "annotations   CpAll MethodCount Bytecodes MethodAll   ROAll   "  +
+                    "RWAll   Total ClassName,ClassLoader\\n    1    -1      6126    " +
+                    "398176        464           0       0           0         "      +
+                    "0         0      24     568     592 [C,NULL class_loader\\n    " +
+                    "2    48      1614    182816        632           0   "           +
+                    "20824         130      4973     37080   20768   39544   "        +
+                    "60312 java.lang.Class,NULL class_loader\\n    3    -1       "    +
+                    "471    154720        464           0       0           "         +
+                    "0         0         0      24     568     592 "                  +
+                    "[B,NULL class_loader\\n"                                         +
+                    "Index Super InstCount InstBytes KlassBytes annotations   "       +
+                    "CpAll MethodCount Bytecodes MethodAll   ROAll   RWAll   "        +
+                    "Total ClassName,ClassLoader\\n\"}";
+
+    private static final String EXPECTED = "[{\"Index\":{\"$numberLong\":\"3\"},\"Super\":{\"$numberLong\":\"-1\"},\"InstCount\":{\"$numberLong\":\"471\"},\"InstBytes\":{\"$numberLong\":\"154720\"},\"KlassBytes\":{\"$numberLong\":\"464\"},\"annotations\":{\"$numberLong\":\"0\"},\"CpAll\":{\"$numberLong\":\"0\"},\"MethodCount\":{\"$numberLong\":\"0\"},\"Bytecodes\":{\"$numberLong\":\"0\"},\"MethodAll\":{\"$numberLong\":\"0\"},\"ROAll\":{\"$numberLong\":\"24\"},\"RWAll\":{\"$numberLong\":\"568\"},\"Total\":{\"$numberLong\":\"592\"},\"ClassName\":\"[B\",\"ClassLoader\":\"NULL class_loader\",\"systemId\":\"0x4A\",\"jvmId\":{\"$numberLong\":\"42\"},\"timeStamp\":{\"$numberLong\":\"1505992968442\"}},{\"Index\":{\"$numberLong\":\"1\"},\"Super\":{\"$numberLong\":\"-1\"},\"InstCount\":{\"$numberLong\":\"6126\"},\"InstBytes\":{\"$numberLong\":\"398176\"},\"KlassBytes\":{\"$numberLong\":\"464\"},\"annotations\":{\"$numberLong\":\"0\"},\"CpAll\":{\"$numberLong\":\"0\"},\"MethodCount\":{\"$numberLong\":\"0\"},\"Bytecodes\":{\"$numberLong\":\"0\"},\"MethodAll\":{\"$numberLong\":\"0\"},\"ROAll\":{\"$numberLong\":\"24\"},\"RWAll\":{\"$numberLong\":\"568\"},\"Total\":{\"$numberLong\":\"592\"},\"ClassName\":\"[C\",\"ClassLoader\":\"NULL class_loader\",\"systemId\":\"0x4A\",\"jvmId\":{\"$numberLong\":\"42\"},\"timeStamp\":{\"$numberLong\":\"1505992968442\"}},{\"Index\":{\"$numberLong\":\"2\"},\"Super\":{\"$numberLong\":\"48\"},\"InstCount\":{\"$numberLong\":\"1614\"},\"InstBytes\":{\"$numberLong\":\"182816\"},\"KlassBytes\":{\"$numberLong\":\"632\"},\"annotations\":{\"$numberLong\":\"0\"},\"CpAll\":{\"$numberLong\":\"20824\"},\"MethodCount\":{\"$numberLong\":\"130\"},\"Bytecodes\":{\"$numberLong\":\"4973\"},\"MethodAll\":{\"$numberLong\":\"37080\"},\"ROAll\":{\"$numberLong\":\"20768\"},\"RWAll\":{\"$numberLong\":\"39544\"},\"Total\":{\"$numberLong\":\"60312\"},\"ClassName\":\"java.lang.Class\",\"ClassLoader\":\"NULL class_loader\",\"systemId\":\"0x4A\",\"jvmId\":{\"$numberLong\":\"42\"},\"timeStamp\":{\"$numberLong\":\"1505992968442\"}}]";
+
+    private static Model MODEL = JCMDModelConverter.createModel(RAW_PAYLOAD, "0x4A", "42");
+
+    @Test
+    public void add() throws Exception {
+        JSONConverter handler = new JSONConverter();
+        String json = handler.convert(MODEL);
+        assertEquals(EXPECTED, json);
+    }
+}
--- a/services/pom.xml	Mon Oct 09 15:57:14 2017 +0200
+++ b/services/pom.xml	Mon Oct 09 15:57:22 2017 +0200
@@ -53,6 +53,7 @@
     <modules>
         <module>commands</module>
         <module>jvms</module>
+        <module>jcmd</module>
         <module>jvm-byteman</module>
         <module>jvm-cpu</module>
         <module>jvm-gc</module>