You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2020/08/31 13:47:43 UTC

[knox] branch master updated: KNOX-2402 - Adding Gateway performance testing (#365)

This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new a48eff8  KNOX-2402 - Adding Gateway performance testing (#365)
a48eff8 is described below

commit a48eff8425c90db6b58b871053de8506550045fd
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Mon Aug 31 15:47:32 2020 +0200

    KNOX-2402 - Adding Gateway performance testing (#365)
---
 .../main/resources/build-tools/spotbugs-filter.xml |   4 +
 gateway-performance-test/pom.xml                   | 126 +++++++++++++++
 .../performance/test/ExecutorServiceUtils.java     |  44 +++++
 .../test/PerformanceTestConfiguration.java         | 141 ++++++++++++++++
 .../test/PerformanceTestLifeCyleListener.java      |  24 +++
 .../performance/test/PerformanceTestMessages.java  |  79 +++++++++
 .../performance/test/PerformanceTestRunner.java    |  49 ++++++
 .../performance/test/ResponseTimeCache.java        |  45 ++++++
 .../gateway/performance/test/UseCaseRunner.java    |  30 ++++
 .../test/knoxtoken/KnoxTokenAction.java            |  23 +++
 .../performance/test/knoxtoken/KnoxTokenCache.java |  41 +++++
 .../test/knoxtoken/KnoxTokenUseCaseRunner.java     |  79 +++++++++
 .../test/knoxtoken/KnoxTokenWorkerThread.java      | 178 +++++++++++++++++++++
 .../test/reporting/AbstractReportEngine.java       |  66 ++++++++
 .../test/reporting/GatewayMetricsReporter.java     | 142 ++++++++++++++++
 .../test/reporting/JsonReportEngine.java           |  44 +++++
 .../performance/test/reporting/ReportEngine.java   |  26 +++
 .../test/reporting/YamlReportEngine.java           |  47 ++++++
 ...che.knox.gateway.performance.test.UseCaseRunner |  19 +++
 .../performance.test.configuration.properties      |  35 ++++
 .../resources/performanceTest-log4j.properties     |  35 ++++
 gateway-release/pom.xml                            |   4 +
 .../security/impl/DefaultKeystoreService.java      |   2 +-
 .../token/TokenStateServiceStatistics.java         |  91 +++++++++++
 .../token/impl/AliasBasedTokenStateService.java    |  52 ++++--
 .../token/impl/DefaultTokenStateService.java       |  55 +++++--
 .../apache/knox/gateway/shell/AbstractRequest.java |   9 ++
 .../token/TokenStateServiceStatisticsMBean.java    |  31 ++++
 pom.xml                                            |   6 +
 29 files changed, 1500 insertions(+), 27 deletions(-)

diff --git a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
index 4bd0c00..80057e5 100644
--- a/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
+++ b/build-tools/src/main/resources/build-tools/spotbugs-filter.xml
@@ -76,4 +76,8 @@ limitations under the License.
     <Bug pattern="NM_SAME_SIMPLE_NAME_AS_SUPERCLASS" />
   </Match>
 
+  <Match>
+    <Class name="org.apache.knox.gateway.performance.test.PerformanceTestConfiguration" />
+    <Bug pattern="PATH_TRAVERSAL_IN" />
+  </Match>
 </FindBugsFilter>
diff --git a/gateway-performance-test/pom.xml b/gateway-performance-test/pom.xml
new file mode 100644
index 0000000..d7dac8c
--- /dev/null
+++ b/gateway-performance-test/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.knox</groupId>
+        <artifactId>gateway</artifactId>
+        <version>1.5.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>gateway-performance-test</artifactId>
+    <name>gateway-performance-test</name>
+    <description>A test framework to measure the Knox Gateway's performance</description>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-i18n</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-i18n-logging-log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-shell</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-util-common</artifactId>
+        </dependency>
+
+        <dependency>
+           <groupId>com.fasterxml.jackson.core</groupId>
+           <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>com.fasterxml.jackson.dataformat</groupId>
+           <artifactId>jackson-dataformat-yaml</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>commons-io</groupId>
+           <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>javax.servlet</groupId>
+           <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>org.apache.commons</groupId>
+           <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>org.apache.commons</groupId>
+           <artifactId>commons-math3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+           <groupId>org.apache.httpcomponents</groupId>
+           <artifactId>httpcore</artifactId>
+        </dependency>
+    </dependencies>
+    <profiles>
+        <profile>
+             <id>gateway-performance-test</id>
+             <build>
+                 <plugins>
+                     <plugin>
+                         <groupId>org.codehaus.mojo</groupId>
+                         <artifactId>exec-maven-plugin</artifactId>
+                         <version>${exec-maven-plugin.version}</version>
+                         <executions>
+                             <execution>
+                                <id>run_gateway_performance_test</id>
+                                <phase>test</phase>
+                                <goals>
+                                  <goal>java</goal>
+                                </goals>
+                                <configuration>
+                                    <mainClass>org.apache.knox.gateway.performance.test.PerformanceTestRunner</mainClass>
+                                    <arguments>
+                                        <argument>${basedir}/src/test/resources/performance.test.configuration.properties</argument>
+                                    </arguments>
+                                    <systemProperties>
+                                        <systemProperty>
+                                            <key>perf.test.report.generation.target.folder</key>
+                                            <value>${basedir}/target/testResults</value>
+                                        </systemProperty>
+                                        <systemProperty>
+                                            <key>log4j.configuration</key>
+                                            <value>file:${basedir}/src/test/resources/performanceTest-log4j.properties</value>
+                                        </systemProperty>
+                                    </systemProperties>
+                                </configuration>
+                             </execution>
+                         </executions>
+                     </plugin>
+                 </plugins>
+             </build>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java
new file mode 100644
index 0000000..6754a0f
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ExecutorServiceUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class ExecutorServiceUtils {
+
+  public static void shutdownAndAwaitTermination(ExecutorService executorService, long timeout, TimeUnit timeUnit) {
+    executorService.shutdown(); // Disable new tasks from being submitted
+    try {
+      // Wait a while for existing tasks to terminate
+      if (!executorService.awaitTermination(timeout, timeUnit)) {
+        executorService.shutdownNow(); // Cancel currently executing tasks
+        // Wait a while for tasks to respond to being cancelled
+        if (!executorService.awaitTermination(timeout, timeUnit)) {
+          System.err.println("Pool did not terminate");
+        }
+      }
+    } catch (InterruptedException ie) {
+      // (Re-)Cancel if current thread also interrupted
+      executorService.shutdownNow();
+      // Preserve interrupt status
+      Thread.currentThread().interrupt();
+    }
+  }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java
new file mode 100644
index 0000000..6149605
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestConfiguration.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+
+import static org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner.USE_CASE_NAME;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class PerformanceTestConfiguration {
+
+  private static final String URL_PATH_SEPARATOR = "/";
+  private static final String PERF_TEST_PREFIX = "perf.test.";
+  private static final String GATEWAY_PREFIX = PERF_TEST_PREFIX + "gateway.";
+  private static final String PARAM_GATEWAY_URL_PROTOROL = GATEWAY_PREFIX + "url.protocol";
+  private static final String DEFAULT_GATEWAY_URL_PROTOCOL = "https";
+  private static final String PARAM_GATEWAY_URL_HOST = GATEWAY_PREFIX + "url.host";
+  private static final String DEFAULT_GATEWAY_URL_HOST = "localhost";
+  private static final String PARAM_GATEWAY_URL_PORT = GATEWAY_PREFIX + "url.port";
+  private static final String DEFAULT_GATEWAY_URL_PORT = "8443";
+  private static final String GATEWAY_URL_TEMPLATE = "%s://%s:%d";
+  private static final String PARAM_GATEWAY_JMX_PORT = GATEWAY_PREFIX + "jmx.port";
+  private static final String DEFAULT_GATEWAY_JMX_PORT = "8888";
+  private static final String GATEWAY_JMX_URL_TEMPLATE = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
+  private static final String PARAM_GATEWAY_PATH = GATEWAY_PREFIX + "path";
+  private static final String DEFAULT_GATEWAY_PATH = "gateway";
+  private static final String PARAM_GATEWAY_USER = GATEWAY_PREFIX + "user";
+  private static final String DEFAULT_GATEWAY_USER = "guest";
+  private static final String PARAM_GATEWAY_PW = GATEWAY_PREFIX + "pw";
+  private static final String DEFAULT_GATEWAY_PW = "guest-password";
+
+  private static final String REPORT_GENERATION_PREFIX = PERF_TEST_PREFIX + "report.generation.";
+  private static final String PARAM_REPORT_GENERATION_PERIOD = REPORT_GENERATION_PREFIX + "periodInSecs";
+  private static final String DEFAULT_REPORT_GENERATION_PERIOD = "60";
+  private static final String PARAM_REPORT_GENERATION_TARGET_FOLDER = REPORT_GENERATION_PREFIX + "target.folder";
+  private static final String PARAM_ENABLED_POSTFIX = ".enabled";
+  private static final String USE_CASE_PREFIX = PERF_TEST_PREFIX + "usecase.";
+  private static final String PARAM_USE_CASE_TOPOLOGY_POSTFIX = ".topology";
+
+  private final Properties configuration = new Properties();
+  private final Map<String, Map<String, String>> defaultUseCaseMap;
+
+  PerformanceTestConfiguration(String configFileLocation) throws IOException {
+    final Path configFilePath = Paths.get(configFileLocation);
+    try (Reader configFileReader = Files.newBufferedReader(configFilePath, StandardCharsets.UTF_8)) {
+      configuration.load(configFileReader);
+    }
+    final Map<String, String> knoxTokenDefaultTopologies = new HashMap<>();
+    knoxTokenDefaultTopologies.put("gateway", "sandbox");
+    knoxTokenDefaultTopologies.put("tokenbased", "tokenbased");
+    defaultUseCaseMap = new HashMap<>();
+    defaultUseCaseMap.put(USE_CASE_NAME, knoxTokenDefaultTopologies);
+  }
+
+  /* Gateway connection */
+
+  public String getGatewayUrl() {
+    final String protocol = configuration.getProperty(PARAM_GATEWAY_URL_PROTOROL, DEFAULT_GATEWAY_URL_PROTOCOL);
+    final String host = configuration.getProperty(PARAM_GATEWAY_URL_HOST, DEFAULT_GATEWAY_URL_HOST);
+    final int port = Integer.parseInt(configuration.getProperty(PARAM_GATEWAY_URL_PORT, DEFAULT_GATEWAY_URL_PORT));
+    return String.format(Locale.ROOT, GATEWAY_URL_TEMPLATE, protocol, host, port);
+  }
+
+  public String getGatewayJmxUrl() {
+    final String host = configuration.getProperty(PARAM_GATEWAY_URL_HOST, DEFAULT_GATEWAY_URL_HOST);
+    final int port = Integer.parseInt(configuration.getProperty(PARAM_GATEWAY_JMX_PORT, DEFAULT_GATEWAY_JMX_PORT));
+    return String.format(Locale.ROOT, GATEWAY_JMX_URL_TEMPLATE, host, port);
+  }
+
+  public String getGatewayPath() {
+    return configuration.getProperty(PARAM_GATEWAY_PATH, DEFAULT_GATEWAY_PATH);
+  }
+
+  public String getGatewayUser() {
+    return configuration.getProperty(PARAM_GATEWAY_USER, DEFAULT_GATEWAY_USER);
+  }
+
+  public String getGatewayPassword() {
+    return configuration.getProperty(PARAM_GATEWAY_PW, DEFAULT_GATEWAY_PW);
+  }
+
+  /* Reporting */
+
+  public long getReportGenerationPeriod() {
+    return Long.parseLong(configuration.getProperty(PARAM_REPORT_GENERATION_PERIOD, DEFAULT_REPORT_GENERATION_PERIOD));
+  }
+
+  public String getReportingTargetFolder() {
+    final String targetFolder = System.getProperty(PARAM_REPORT_GENERATION_TARGET_FOLDER);
+    return StringUtils.isBlank(targetFolder) ? configuration.getProperty(PARAM_REPORT_GENERATION_TARGET_FOLDER) : targetFolder;
+  }
+
+  public boolean isReportingEngineEnabled(String engineType) {
+    return Boolean.parseBoolean(configuration.getProperty(REPORT_GENERATION_PREFIX + engineType + PARAM_ENABLED_POSTFIX, "false"));
+  }
+
+  /* Use case */
+
+  public boolean isUseCaseEnabled(String useCase) {
+    return Boolean.parseBoolean(configuration.getProperty(USE_CASE_PREFIX + useCase + PARAM_ENABLED_POSTFIX, "false"));
+  }
+
+  public String getUseCaseTopology(String useCase, String topologyType) {
+    return configuration.getProperty(USE_CASE_PREFIX + useCase + PARAM_USE_CASE_TOPOLOGY_POSTFIX + topologyType, defaultUseCaseMap.get(useCase).get(topologyType));
+  }
+
+  public String getUseCaseUrl(String useCase, String topologyType) {
+    return getGatewayUrl() + URL_PATH_SEPARATOR + getGatewayPath() + URL_PATH_SEPARATOR + getUseCaseTopology(useCase, topologyType);
+  }
+
+  public String getUseCaseParam(String useCase, String param) {
+    return configuration.getProperty(USE_CASE_PREFIX + useCase + "." + param);
+  }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java
new file mode 100644
index 0000000..2e4b5f4
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestLifeCyleListener.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+public interface PerformanceTestLifeCyleListener {
+
+  void onFinish();
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java
new file mode 100644
index 0000000..4cbe5b0
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestMessages.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+import org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenAction;
+
+@Messages(logger = "org.apache.knox.gateway.performance.test")
+public interface PerformanceTestMessages {
+
+  @Message(level = MessageLevel.INFO, text = "Running Knox Token Workers on {0} threads ...")
+  void runKnoxTokenWorkers(int numOfThreads);
+
+  @Message(level = MessageLevel.INFO, text = "I am a Knox Token {0} thread")
+  void knoxTokenWorkerName(KnoxTokenAction action);
+
+  @Message(level = MessageLevel.INFO, text = "Knox token worker thread finished")
+  void finishKnoxTokenWorker();
+
+  @Message(level = MessageLevel.ERROR, text = "Failed to run {0} Knox token worker: {1}")
+  void failedToRunKnoxTokenWorker(KnoxTokenAction action, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+  @Message(level = MessageLevel.ERROR, text = "Failed to execute {0} Knox token action: {1}")
+  void failedToExecuteKnoxTokenAction(KnoxTokenAction action, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+  @Message(level = MessageLevel.INFO, text = "Sleeping {0} seconds...")
+  void sleep(long sleepTime);
+
+  @Message(level = MessageLevel.INFO, text = "Acquiring Knox token...")
+  void acquireKnoxToken();
+
+  @Message(level = MessageLevel.INFO, text = "Acquired Knox token")
+  void acquiredKnoxToken();
+
+  @Message(level = MessageLevel.INFO, text = "Renewing Knox token {0}")
+  void renewKnoxToken(String knoxToken);
+
+  @Message(level = MessageLevel.INFO, text = "Renewed Knox token: {0}")
+  void renewedKnoxToken(String expiration);
+
+  @Message(level = MessageLevel.INFO, text = "Failed to renew Knox token: {0}")
+  void failedToRenewKnoxToken(String error);
+
+  @Message(level = MessageLevel.INFO, text = "There is no token to be renewed yet")
+  void nothingToRenew();
+
+  @Message(level = MessageLevel.INFO, text = "Using Knox token {0}")
+  void useKnoxToken(String knoxToken);
+
+  @Message(level = MessageLevel.INFO, text = "Failed to use Knox token: {0}")
+  void failedToUseKnoxToken(String error);
+
+  @Message(level = MessageLevel.INFO, text = "There is no token to be used yet")
+  void nothingToUse();
+
+  @Message(level = MessageLevel.ERROR, text = "Error while generating {0} report: {1}")
+  void failedToGenerateReport(String reportType, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+  @Message(level = MessageLevel.INFO, text = "Metrics reporter is shut down")
+  void shutDownMetricsReporter();
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java
new file mode 100644
index 0000000..48caadd
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/PerformanceTestRunner.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import org.apache.knox.gateway.performance.test.reporting.GatewayMetricsReporter;
+
+public class PerformanceTestRunner {
+
+  public static void main(String[] args) throws Exception {
+    final PerformanceTestConfiguration configuration = new PerformanceTestConfiguration(args[0]);
+    final ResponseTimeCache responseTimeCache = new ResponseTimeCache();
+    final GatewayMetricsReporter metricsReporter = new GatewayMetricsReporter(configuration, responseTimeCache);
+    metricsReporter.start();
+    final List<PerformanceTestLifeCyleListener> lifeCyleListeners = Arrays.asList(metricsReporter);
+    for (UseCaseRunner useCaseRunner : getUseCaseRunners()) {
+      if (configuration.isUseCaseEnabled(useCaseRunner.getUseCaseName())) {
+        useCaseRunner.setResponseTimeCache(responseTimeCache);
+        useCaseRunner.execute(configuration, lifeCyleListeners);
+      }
+    }
+  }
+
+  private static Set<UseCaseRunner> getUseCaseRunners() {
+    final Set<UseCaseRunner> useCaseRunners = new HashSet<>();
+    ServiceLoader.load(UseCaseRunner.class).forEach((useCaseRunner) -> useCaseRunners.add(useCaseRunner));
+    return useCaseRunners;
+  }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java
new file mode 100644
index 0000000..a309699
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/ResponseTimeCache.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+
+public class ResponseTimeCache {
+
+  private final Queue<Long> acquireResponseTimes = new ConcurrentLinkedQueue<>();
+  private final Queue<Long> renewResponseTimes = new ConcurrentLinkedQueue<>();
+
+  public void saveAcquireResponseTime(long getResponseTime) {
+    acquireResponseTimes.offer(getResponseTime);
+  }
+
+  public void saveRenewResponseTime(long renewResponseTime) {
+    renewResponseTimes.offer(renewResponseTime);
+  }
+
+  public List<Long> listAcquireResponseTimes() {
+    return this.acquireResponseTimes.stream().collect(Collectors.toList());
+  }
+
+  public List<Long> listRenewResponseTimes() {
+    return this.renewResponseTimes.stream().collect(Collectors.toList());
+  }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java
new file mode 100644
index 0000000..8cda4c4
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/UseCaseRunner.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test;
+
+import java.util.List;
+
+public interface UseCaseRunner {
+
+  String getUseCaseName();
+
+  void setResponseTimeCache(ResponseTimeCache responseTimeCache);
+
+  void execute(PerformanceTestConfiguration configuration, List<PerformanceTestLifeCyleListener> lifeCyleListeners);
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java
new file mode 100644
index 0000000..147894d
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenAction.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.knoxtoken;
+
+public enum KnoxTokenAction {
+  ACQUIRE, RENEW, USE_TOKEN;
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java
new file mode 100644
index 0000000..6f96cee
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenCache.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.knoxtoken;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+class KnoxTokenCache {
+  private static final int CAPACITY = 500;
+  private final Queue<String> knoxTokens = new ConcurrentLinkedQueue<>();
+
+  void saveKnoxToken(String knoxToken) {
+    cleanIfNecessary();
+    this.knoxTokens.offer(knoxToken);
+  }
+
+  private void cleanIfNecessary() {
+    if (knoxTokens.size() == CAPACITY) {
+      knoxTokens.clear();
+    }
+  }
+
+  String getKnoxToken() {
+    return knoxTokens.poll();
+  }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java
new file mode 100644
index 0000000..1bc979b
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenUseCaseRunner.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.knoxtoken;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.ExecutorServiceUtils;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestLifeCyleListener;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+import org.apache.knox.gateway.performance.test.UseCaseRunner;
+
+public class KnoxTokenUseCaseRunner implements UseCaseRunner {
+
+  public static final String USE_CASE_NAME = "knoxtoken";
+
+  private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+  private static final String PARAM_NUMBER_OF_THREADS = "numOfThreads";
+
+  private final KnoxTokenCache knoxTokenCache = new KnoxTokenCache();
+  private ResponseTimeCache responseTimeCache;
+  private ExecutorService pool;
+
+  @Override
+  public void setResponseTimeCache(ResponseTimeCache responseTimeCache) {
+    this.responseTimeCache = responseTimeCache;
+  }
+
+  @Override
+  public void execute(PerformanceTestConfiguration configuration, List<PerformanceTestLifeCyleListener> lifeCyleListeners) {
+    final ThreadFactory tfthreadFactory = new BasicThreadFactory.Builder().namingPattern("KnoxTokenUseCaseRunner-%d").build();
+    final int numberOfThreads = Integer.parseInt(configuration.getUseCaseParam(getUseCaseName(), PARAM_NUMBER_OF_THREADS));
+    LOG.runKnoxTokenWorkers(numberOfThreads);
+    pool = Executors.newFixedThreadPool(numberOfThreads + 2, tfthreadFactory);
+    try {
+      for (int i = 0; i < numberOfThreads; i++) {
+        pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.ACQUIRE, knoxTokenCache, responseTimeCache));
+      }
+
+      // 2 other jobs to renew and use qcquired Knox Tokens
+      pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.RENEW, knoxTokenCache, responseTimeCache));
+      pool.submit(new KnoxTokenWorkerThread(configuration, KnoxTokenAction.USE_TOKEN, knoxTokenCache, responseTimeCache));
+    } catch (Exception ex) {
+      ExecutorServiceUtils.shutdownAndAwaitTermination(pool, 10, TimeUnit.SECONDS);
+    } finally {
+      final long testDuration = Long.parseLong(configuration.getUseCaseParam(getUseCaseName(), KnoxTokenWorkerThread.PARAM_DURATION_IN_SECONDS));
+      final long upperBound = Long.parseLong(configuration.getUseCaseParam(getUseCaseName(), KnoxTokenWorkerThread.PARAM_REQUEST_DELAY_UPPERBOUND));
+      ExecutorServiceUtils.shutdownAndAwaitTermination(pool, testDuration + upperBound + 10, TimeUnit.SECONDS);
+      lifeCyleListeners.forEach(lifeCyleListener -> lifeCyleListener.onFinish());
+    }
+  }
+
+  @Override
+  public String getUseCaseName() {
+    return USE_CASE_NAME;
+  }
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java
new file mode 100644
index 0000000..ae3a91b
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/knoxtoken/KnoxTokenWorkerThread.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.knoxtoken;
+
+
+import static org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner.USE_CASE_NAME;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.shell.ErrorResponse;
+import org.apache.knox.gateway.shell.KnoxSession;
+import org.apache.knox.gateway.shell.KnoxShellException;
+import org.apache.knox.gateway.shell.hdfs.Hdfs;
+import org.apache.knox.gateway.shell.knox.token.Get;
+import org.apache.knox.gateway.shell.knox.token.Token;
+import org.apache.knox.gateway.shell.knox.token.TokenLifecycleResponse;
+import org.apache.knox.gateway.util.JsonUtils;
+
+@SuppressWarnings("PMD.DoNotUseThreads")
+public class KnoxTokenWorkerThread implements Runnable {
+  private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+  static final String PARAM_DURATION_IN_SECONDS = "testDurationInSecs";
+  private static final String PARAM_REQUEST_DELAY_LOWERBOUND = "requestDelayLowerBoundInSecs";
+  static final String PARAM_REQUEST_DELAY_UPPERBOUND = "requestDelayUpperBoundInSecs";
+
+  private final PerformanceTestConfiguration configuration;
+  private final KnoxTokenAction action;
+  private final KnoxTokenCache knoxTokenCache;
+  private final ResponseTimeCache responseTimeCache;
+  private Instant startTime;
+
+  public KnoxTokenWorkerThread(PerformanceTestConfiguration configuration, KnoxTokenAction action, KnoxTokenCache knoxTokenCache, ResponseTimeCache responseTimeCache) {
+    this.configuration = configuration;
+    this.action = action;
+    this.knoxTokenCache = knoxTokenCache;
+    this.responseTimeCache = responseTimeCache;
+  }
+
+  @Override
+  public void run() {
+    try {
+      LOG.knoxTokenWorkerName(action);
+      final KnoxSession gatewayKnoxSession = KnoxSession.login(configuration.getUseCaseUrl(USE_CASE_NAME, "gateway"), configuration.getGatewayUser(),
+          configuration.getGatewayPassword());
+      final KnoxSession tokenBasedKnoxSession = KnoxSession.login(configuration.getUseCaseUrl(USE_CASE_NAME, "tokenbased"), configuration.getGatewayUser(),
+          configuration.getGatewayPassword());
+      final long testDuration = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_DURATION_IN_SECONDS));
+      final long lowerBound = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_REQUEST_DELAY_LOWERBOUND));
+      final long upperBound = Long.parseLong(configuration.getUseCaseParam(USE_CASE_NAME, PARAM_REQUEST_DELAY_UPPERBOUND));
+      this.startTime = Instant.now();
+      int requestCount = 0;
+      while (shouldRun(testDuration)) {
+        executeAction(gatewayKnoxSession, tokenBasedKnoxSession);
+
+        if (requestCount > 0) {
+          TimeUnit.SECONDS.sleep(calculateSleepTime(lowerBound, upperBound));
+        }
+        requestCount++;
+      }
+    } catch (Exception e) {
+      LOG.failedToRunKnoxTokenWorker(action, e.getMessage(), e);
+    } finally {
+      LOG.finishKnoxTokenWorker();
+    }
+  }
+
+  private void executeAction(final KnoxSession gatewayKnoxSession, final KnoxSession tokenBasedKnoxSession) {
+    try {
+      switch (this.action) {
+      case ACQUIRE:
+        knoxTokenCache.saveKnoxToken(acquireKnoxToken(gatewayKnoxSession));
+        break;
+      case RENEW:
+        renewKnoxToken(gatewayKnoxSession);
+        break;
+      case USE_TOKEN:
+        useKnoxToken(tokenBasedKnoxSession);
+        break;
+      default:
+        // NOP
+        break;
+      }
+    } catch (Exception e) {
+      LOG.failedToExecuteKnoxTokenAction(action, e.getMessage(), e);
+    }
+  }
+
+  private boolean shouldRun(long testDuration) {
+    final long timeElapsed = Duration.between(startTime, Instant.now()).getSeconds();
+    final boolean shouldRun = timeElapsed < testDuration;
+    return shouldRun;
+  }
+
+  private long calculateSleepTime(long lowerBound, long upperBound) {
+    final long sleepTime = ThreadLocalRandom.current().nextLong(KnoxTokenAction.ACQUIRE == this.action ? lowerBound : lowerBound * 2,
+        KnoxTokenAction.ACQUIRE == this.action ? upperBound : upperBound * 2);
+    LOG.sleep(sleepTime);
+    return sleepTime;
+  }
+
+  private String acquireKnoxToken(KnoxSession knoxSession) throws IOException {
+    LOG.acquireKnoxToken();
+    long getStart = System.currentTimeMillis();
+    final Get.Response getTokenResponse = Token.get(knoxSession).now();
+    final long getResponseTime = System.currentTimeMillis() - getStart;
+    LOG.acquiredKnoxToken();
+    responseTimeCache.saveAcquireResponseTime(getResponseTime);
+    final Map<String, String> tokenAsMap = JsonUtils.getMapFromJsonString(getTokenResponse.getString());
+    return tokenAsMap.get("access_token");
+  }
+
+  private void renewKnoxToken(KnoxSession knoxSession) throws Exception {
+    final String knoxTokenToRenew = this.knoxTokenCache.getKnoxToken();
+    if (knoxTokenToRenew != null) {
+      LOG.renewKnoxToken(TokenUtils.getTokenDisplayText(knoxTokenToRenew));
+      final long renewStart = System.currentTimeMillis();
+      final TokenLifecycleResponse renewResponse = Token.renew(knoxSession, knoxTokenToRenew).now();
+      final long renewResponseTime = System.currentTimeMillis() - renewStart;
+      responseTimeCache.saveRenewResponseTime(renewResponseTime);
+      final Map<String, String> map = JsonUtils.getMapFromJsonString(renewResponse.getString());
+      boolean renewed = Boolean.parseBoolean(map.get("renewed"));
+      if (renewed) {
+        LOG.renewedKnoxToken(Instant.ofEpochMilli(Long.parseLong(map.get("expires"))).toString());
+      } else {
+        LOG.failedToRenewKnoxToken(map.get("error"));
+      }
+    } else {
+      LOG.nothingToRenew();
+    }
+  }
+
+  private void useKnoxToken(KnoxSession knoxSession) {
+    try {
+      final String knoxToken = this.knoxTokenCache.getKnoxToken();
+      if (knoxToken != null) {
+        LOG.useKnoxToken(TokenUtils.getTokenDisplayText(knoxToken));
+        Hdfs.ls(knoxSession).knoxToken(knoxToken).now();
+      } else {
+        LOG.nothingToUse();
+      }
+    } catch (KnoxShellException e) {
+      final ErrorResponse errorResponse = (ErrorResponse) e.getCause();
+      final int errorCode = errorResponse.getResponse().getStatusLine().getStatusCode();
+      // if unauthorized -> it's a failure (every other error code is irrelevant here)
+      if (HttpServletResponse.SC_UNAUTHORIZED == errorCode) {
+        LOG.failedToUseKnoxToken(e.getMessage());
+      }
+    }
+  }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java
new file mode 100644
index 0000000..dd6b44f
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/AbstractReportEngine.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+
+abstract class AbstractReportEngine implements ReportEngine {
+  private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+  private final DateFormat REPORT_FILE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.ROOT);
+  private final PerformanceTestConfiguration configuration;
+  private final Path reportFolderPath;
+
+  AbstractReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+    this.configuration = configuration;
+    this.reportFolderPath = Paths.get(configuration.getReportingTargetFolder(), getType());
+    if (!reportFolderPath.toFile().exists()) {
+      Files.createDirectories(reportFolderPath);
+    }
+  }
+
+  @Override
+  public void generateReport(String reportName, Map<String, Object> reportMaterial) {
+    if (configuration.isReportingEngineEnabled(getType())) {
+      String reportFile = reportName + "." + REPORT_FILE_DATE_FORMAT.format(new Date()) + "." + getType();
+      try {
+        FileUtils.writeStringToFile(reportFolderPath.resolve(reportFile).toFile(), getContent(reportMaterial), StandardCharsets.UTF_8);
+      } catch (Exception e) {
+        LOG.failedToGenerateReport(getType(), e.getMessage(), e);
+      }
+    }
+  }
+
+  public abstract String getContent(Map<String, Object> reportMaterial) throws Exception;
+
+  public abstract String getType();
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java
new file mode 100644
index 0000000..ee5d69e
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/GatewayMetricsReporter.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.performance.test.ExecutorServiceUtils;
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.performance.test.PerformanceTestLifeCyleListener;
+import org.apache.knox.gateway.performance.test.PerformanceTestMessages;
+import org.apache.knox.gateway.performance.test.ResponseTimeCache;
+
+public class GatewayMetricsReporter implements PerformanceTestLifeCyleListener {
+  private static final PerformanceTestMessages LOG = MessagesFactory.get(PerformanceTestMessages.class);
+  private static final String TOKEN_STATE_STATISTICS_OBJECT_NAME = "metrics:type=Statistics,name=TokenStateService";
+  private static final String TIMERS_OBJECT_NAME = "metrics:type=timers,name=client*";
+  private static final String HEAP_GAUGES_OBJECT_NAME = "metrics:type=gauges,name=heap*";
+  private final PerformanceTestConfiguration configuration;
+  private final ResponseTimeCache responseTimeCache;
+  private final JMXConnector jmxConnector;
+  private final ScheduledExecutorService executor;
+  private final Map<String, String> objectNamesToQuery;
+  private final List<ReportEngine> reportEngines;
+
+  public GatewayMetricsReporter(PerformanceTestConfiguration configuration, ResponseTimeCache responseTimeCache) throws IOException {
+    this.configuration = configuration;
+    this.responseTimeCache = responseTimeCache;
+    final JMXServiceURL serviceUrl = new JMXServiceURL(configuration.getGatewayJmxUrl());
+    jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
+    final ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("GatewayMetricsReporter-%d").build();
+    this.executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
+    this.objectNamesToQuery = new HashMap<>();
+    objectNamesToQuery.put("tokenStateStatistics", TOKEN_STATE_STATISTICS_OBJECT_NAME);
+    objectNamesToQuery.put("timers", TIMERS_OBJECT_NAME);
+    objectNamesToQuery.put("heapGauges", HEAP_GAUGES_OBJECT_NAME);
+    this.reportEngines = Arrays.asList(new JsonReportEngine(configuration), new YamlReportEngine(configuration));
+  }
+
+  public void start() throws IOException {
+    final MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
+    executor.scheduleAtFixedRate(() -> generateReports(mbeanServerConnection), configuration.getReportGenerationPeriod(), configuration.getReportGenerationPeriod(),
+        TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void onFinish() {
+    ExecutorServiceUtils.shutdownAndAwaitTermination(executor, 10, TimeUnit.SECONDS);
+    try {
+      jmxConnector.close();
+    } catch (IOException e) {
+      // NOP
+    }
+    LOG.shutDownMetricsReporter();
+  }
+
+  private void generateReports(MBeanServerConnection mbeanConn) {
+    printResponseTimes();
+    printMetrics(mbeanConn);
+  }
+
+  private void printResponseTimes() {
+    final Map<String, Object> responseTimesMap = new HashMap<>();
+    setReponseTimesMetrics(responseTimesMap, responseTimeCache.listAcquireResponseTimes(), "acquireResponseTimes");
+    setReponseTimesMetrics(responseTimesMap, responseTimeCache.listRenewResponseTimes(), "renewResponseTimes");
+    for (ReportEngine reportEngine : reportEngines) {
+      reportEngine.generateReport("responseTimes", responseTimesMap);
+    }
+  }
+
+  private void setReponseTimesMetrics(Map<String, Object> responseTimesMap, List<Long> responseTimes, String metricsName) {
+    final Map<String, Object> statistics = new HashMap<>();
+    statistics.put("_data", responseTimes);
+    final double[] doubleArrayResponseTimes = responseTimes.stream().mapToDouble(responseTime -> responseTime).toArray();
+    statistics.put("max", StatUtils.max(doubleArrayResponseTimes));
+    statistics.put("min", StatUtils.min(doubleArrayResponseTimes));
+    statistics.put("mean", StatUtils.mean(doubleArrayResponseTimes));
+    statistics.put("mode", StatUtils.mode(doubleArrayResponseTimes));
+    statistics.put("geometricMean", StatUtils.geometricMean(doubleArrayResponseTimes));
+    responseTimesMap.put(metricsName, statistics);
+  }
+
+  private void printMetrics(MBeanServerConnection mbeanConn) {
+    try {
+      for (Map.Entry<String, String> objectNameEntry : objectNamesToQuery.entrySet()) {
+        Map<String, Object> metrics = new HashMap<>();
+        for (ObjectName bean : mbeanConn.queryNames(ObjectName.getInstance(objectNameEntry.getValue()), null)) {
+          metrics.put(bean.getCanonicalName(), getAttributes(mbeanConn, bean, mbeanConn.getMBeanInfo(bean)));
+        }
+        for (ReportEngine reportEngine : reportEngines) {
+          reportEngine.generateReport(objectNameEntry.getKey(), metrics);
+        }
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  private Map<String, Object> getAttributes(MBeanServerConnection mbeanConn, ObjectName bean, MBeanInfo mbeanInfo) throws Exception {
+    final Map<String, Object> attributeMap = new HashMap<>();
+    for (MBeanAttributeInfo mbeanAttribute : mbeanInfo.getAttributes()) {
+      String attributeName = mbeanAttribute.getName();
+      Object attributeValue = mbeanConn.getAttribute(bean, attributeName);
+      attributeMap.put(attributeName, attributeValue);
+    }
+    return attributeMap;
+  }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java
new file mode 100644
index 0000000..d9f9fda
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/JsonReportEngine.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+import org.apache.knox.gateway.util.JsonUtils;
+
+class JsonReportEngine extends AbstractReportEngine {
+
+  private static final String TYPE = "json";
+
+  JsonReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+    super(configuration);
+  }
+
+  @Override
+  public String getContent(Map<String, Object> reportMaterial) throws Exception {
+    return JsonUtils.renderAsJsonString(reportMaterial);
+  }
+
+  @Override
+  public String getType() {
+    return TYPE;
+  }
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java
new file mode 100644
index 0000000..7c498bd
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/ReportEngine.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.reporting;
+
+import java.util.Map;
+
+public interface ReportEngine {
+
+  void generateReport(String reportName, Map<String, Object> reportMaterial);
+
+}
diff --git a/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java
new file mode 100644
index 0000000..168fd26
--- /dev/null
+++ b/gateway-performance-test/src/main/java/org/apache/knox/gateway/performance/test/reporting/YamlReportEngine.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.performance.test.reporting;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.knox.gateway.performance.test.PerformanceTestConfiguration;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+class YamlReportEngine extends AbstractReportEngine {
+
+  private static final String TYPE = "yaml";
+  private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+
+  YamlReportEngine(PerformanceTestConfiguration configuration) throws IOException {
+    super(configuration);
+  }
+
+  @Override
+  public String getContent(Map<String, Object> reportMaterial) throws Exception {
+      return mapper.writeValueAsString(reportMaterial);
+  }
+
+  @Override
+  public String getType() {
+    return TYPE;
+  }
+
+}
diff --git a/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner b/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner
new file mode 100644
index 0000000..af9e34f
--- /dev/null
+++ b/gateway-performance-test/src/main/resources/META-INF/services/org.apache.knox.gateway.performance.test.UseCaseRunner
@@ -0,0 +1,19 @@
+##########################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##########################################################################
+
+org.apache.knox.gateway.performance.test.knoxtoken.KnoxTokenUseCaseRunner
\ No newline at end of file
diff --git a/gateway-performance-test/src/test/resources/performance.test.configuration.properties b/gateway-performance-test/src/test/resources/performance.test.configuration.properties
new file mode 100644
index 0000000..43ff695
--- /dev/null
+++ b/gateway-performance-test/src/test/resources/performance.test.configuration.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Gateway connection related properties
+perf.test.gateway.url.protocol=https
+perf.test.gateway.url.host=localhost
+perf.test.gateway.url.port=8443
+perf.test.gateway.jmx.port=8888
+
+# report generation related properties
+perf.test.report.generation.periodInSecs=30
+perf.test.report.generation.json.enabled=true
+perf.test.report.generation.yaml.enabled=true
+
+# Knox Token use case related properties
+perf.test.usecase.knoxtoken.enabled=true
+perf.test.usecase.knoxtoken.topology.gateway=sandbox
+perf.test.usecase.knoxtoken.topology.tokenbased=tokenbased
+perf.test.usecase.knoxtoken.numOfThreads=3
+perf.test.usecase.knoxtoken.testDurationInSecs=60
+perf.test.usecase.knoxtoken.requestDelayLowerBoundInSecs=5
+perf.test.usecase.knoxtoken.requestDelayUpperBoundInSecs=10
\ No newline at end of file
diff --git a/gateway-performance-test/src/test/resources/performanceTest-log4j.properties b/gateway-performance-test/src/test/resources/performanceTest-log4j.properties
new file mode 100644
index 0000000..3fa6534
--- /dev/null
+++ b/gateway-performance-test/src/test/resources/performanceTest-log4j.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+app.log.dir=gateway-performance-test/target/logs
+app.log.file=performanceTest.log
+
+log4j.rootLogger=ERROR, stdout, drfa
+
+log4j.logger.org.apache.knox.gateway.performance.test=INFO
+log4j.logger.org.apache.http.client=INFO
+log4j.logger.org.apache.http.impl.client=INFO
+log4j.logger.org.apache.http.impl.conn=INFO
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} - [%t] - %m%n
+
+log4j.appender.drfa=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.drfa.File=${app.log.dir}/${app.log.file}
+log4j.appender.drfa.DatePattern=.yyyy-MM-dd
+log4j.appender.drfa.layout=org.apache.log4j.PatternLayout
+log4j.appender.drfa.layout.ConversionPattern=%d{ISO8601} - [%t] - %m%n
diff --git a/gateway-release/pom.xml b/gateway-release/pom.xml
index 32f1203..4271b4c 100644
--- a/gateway-release/pom.xml
+++ b/gateway-release/pom.xml
@@ -449,5 +449,9 @@
             <groupId>org.apache.knox</groupId>
             <artifactId>knox-homepage-ui</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-service-metadata</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
index 546d53d..559ac10 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
@@ -69,7 +69,7 @@ import javax.crypto.spec.SecretKeySpec;
 
 public class DefaultKeystoreService implements KeystoreService {
   private static final String DN_TEMPLATE = "CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
-  private static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
+  public static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
   private static final String CREDENTIALS_STORE_TYPE = "JCEKS";
   private static final String CERT_GEN_MODE = "hadoop.gateway.cert.gen.mode";
   private static final String CERT_GEN_MODE_LOCALHOST = "localhost";
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java
new file mode 100644
index 0000000..4085762
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/TokenStateServiceStatistics.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.services.token;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import javax.management.StandardMBean;
+
+import org.apache.knox.gateway.services.security.token.TokenStateServiceStatisticsMBean;
+
+public class TokenStateServiceStatistics extends StandardMBean implements TokenStateServiceStatisticsMBean {
+
+  public enum KeystoreInteraction {
+    SAVE_ALIAS("saveAlias"), REMOVE_ALIAS("removeAlias"), GET_PASSWORD("getPassword"), GET_ALIAS("getAlias");
+
+    private final String key;
+
+    KeystoreInteraction(String key) {
+      this.key = key;
+    }
+
+    public String getKey() {
+      return key;
+    }
+  };
+
+  private final AtomicInteger numberOfTokensAdded = new AtomicInteger(0);
+  private final AtomicInteger numberOfTokensRenewed = new AtomicInteger(0);
+  private final Map<String, AtomicInteger> keystoreInteractions = new ConcurrentHashMap<>();
+  private final AtomicLong gatewayCredentialsFileSize = new AtomicLong(0L);
+
+  public TokenStateServiceStatistics() {
+    super(TokenStateServiceStatisticsMBean.class, false);
+  }
+
+  public void addToken() {
+    this.numberOfTokensAdded.incrementAndGet();
+  }
+
+  public void renewToken() {
+    this.numberOfTokensRenewed.incrementAndGet();
+  }
+
+  public void interactKeystore(KeystoreInteraction keystoreInteraction) {
+    this.keystoreInteractions.putIfAbsent(keystoreInteraction.getKey(), new AtomicInteger(0));
+    this.keystoreInteractions.get(keystoreInteraction.getKey()).incrementAndGet();
+  }
+
+  public void setGatewayCredentialsFileSize(long gatewayCredentialsFileSize) {
+    this.gatewayCredentialsFileSize.set(gatewayCredentialsFileSize);
+  }
+
+  @Override
+  public int getNumberOfTokensAdded() {
+    return this.numberOfTokensAdded.get();
+  }
+
+  @Override
+  public int getNumberOfTokensRenewed() {
+    return this.numberOfTokensRenewed.get();
+  }
+
+  @Override
+  public Map<String, Integer> getKeystoreInteractions() {
+    return this.keystoreInteractions.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().get()));
+  }
+
+  @Override
+  public long getGatewayCredentialsFileSize() {
+    return this.gatewayCredentialsFileSize.get();
+  }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
index 8b44336..598bd12 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
@@ -20,12 +20,16 @@ import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService;
 import org.apache.knox.gateway.services.security.token.UnknownTokenException;
 import org.apache.knox.gateway.services.token.state.JournalEntry;
 import org.apache.knox.gateway.services.token.state.TokenStateJournal;
+import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
 import org.apache.knox.gateway.services.token.impl.state.TokenStateJournalFactory;
 
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -55,6 +59,8 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
 
   private TokenStateJournal journal;
 
+  private Path gatewayCredentialsFilePath;
+
   public void setAliasService(AliasService aliasService) {
     this.aliasService = aliasService;
   }
@@ -99,6 +105,11 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     if (statePersistenceInterval > 0) {
       statePersistenceScheduler = Executors.newScheduledThreadPool(1);
     }
+
+    if (tokenStateServiceStatistics != null) {
+      this.gatewayCredentialsFilePath = Paths.get(config.getGatewayKeystoreDir()).resolve(AliasService.NO_CLUSTER_NAME + DefaultKeystoreService.CREDENTIALS_SUFFIX);
+      tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+    }
   }
 
   @Override
@@ -151,6 +162,10 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
 
       try {
         aliasService.addAliasesForCluster(AliasService.NO_CLUSTER_NAME, aliases);
+        if (tokenStateServiceStatistics != null) {
+          tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.SAVE_ALIAS);
+          tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+        }
         for (String tokenId : tokenIds) {
           log.createdTokenStateAliases(tokenId);
           // After the aliases have been successfully persisted, remove their associated state from the journal
@@ -202,9 +217,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     // If there is no result from the in-memory collection, proceed to check the alias service
     if (result < 1L) {
       try {
-        char[] maxLifetimeStr =
-                aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME,
-                        tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
+        char[] maxLifetimeStr = getPasswordUsingAliasService(tokenId + TOKEN_MAX_LIFETIME_POSTFIX);
         if (maxLifetimeStr != null) {
           result = Long.parseLong(new String(maxLifetimeStr));
         }
@@ -215,6 +228,14 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     return result;
   }
 
+  private char[] getPasswordUsingAliasService(String tokenId) throws AliasServiceException {
+    char[] password = aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
+    if (tokenStateServiceStatistics != null) {
+      tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.GET_PASSWORD);
+    }
+    return password;
+  }
+
   @Override
   public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
     // Check the in-memory collection first, to avoid costly keystore access when possible
@@ -233,7 +254,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     // If there is no associated state in the in-memory cache, proceed to check the alias service
     long expiration = 0;
     try {
-      char[] expStr = aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
+      char[] expStr = getPasswordUsingAliasService(tokenId);
       if (expStr == null) {
         throw new UnknownTokenException(tokenId);
       }
@@ -256,7 +277,7 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     // If it's not in the cache, then check the underlying alias
     if (isUnknown) {
       try {
-        isUnknown = (aliasService.getPasswordFromAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId) == null);
+        isUnknown = (getPasswordUsingAliasService(tokenId) == null);
       } catch (AliasServiceException e) {
         log.errorAccessingTokenState(tokenId, e);
       }
@@ -296,6 +317,10 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
       log.removingTokenStateAliases();
       try {
         aliasService.removeAliasesForCluster(AliasService.NO_CLUSTER_NAME, aliasesToRemove);
+        if (tokenStateServiceStatistics != null) {
+          tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
+          tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+        }
         for (String tokenId : tokenIds) {
           log.removedTokenStateAliases(tokenId);
         }
@@ -313,23 +338,28 @@ public class AliasBasedTokenStateService extends DefaultTokenStateService {
     try {
       aliasService.removeAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId);
       aliasService.addAliasForCluster(AliasService.NO_CLUSTER_NAME, tokenId, String.valueOf(expiration));
+      if (tokenStateServiceStatistics != null) {
+        tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.REMOVE_ALIAS);
+        tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.SAVE_ALIAS);
+        tokenStateServiceStatistics.setGatewayCredentialsFileSize(this.gatewayCredentialsFilePath.toFile().length());
+      }
     } catch (AliasServiceException e) {
       log.failedToUpdateTokenExpiration(tokenId, e);
     }
   }
 
+
   @Override
   protected List<String> getTokens() {
     List<String> tokenIds = null;
 
     try {
       List<String> allAliases = aliasService.getAliasesForCluster(AliasService.NO_CLUSTER_NAME);
-
-      // Filter for the token state aliases, and extract the token ID
-      tokenIds = allAliases.stream()
-                           .filter(a -> a.contains(TOKEN_MAX_LIFETIME_POSTFIX))
-                           .map(a -> a.substring(0, a.indexOf(TOKEN_MAX_LIFETIME_POSTFIX)))
-                           .collect(Collectors.toList());
+      if (tokenStateServiceStatistics != null) {
+        tokenStateServiceStatistics.interactKeystore(TokenStateServiceStatistics.KeystoreInteraction.GET_ALIAS);
+      }
+      // Filter for the token state aliases (exclude aliases ending with TOKEN_MAX_LIFETIME_POSTFIX)
+      tokenIds = allAliases.stream().filter(alias -> !alias.endsWith(TOKEN_MAX_LIFETIME_POSTFIX)).collect(Collectors.toList());
     } catch (AliasServiceException e) {
       log.errorAccessingTokenState(e);
     }
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
index 2361269..850ac07 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
@@ -16,15 +16,7 @@
  */
 package org.apache.knox.gateway.services.token.impl;
 
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.token.TokenStateService;
-import org.apache.knox.gateway.services.security.token.TokenUtils;
-import org.apache.knox.gateway.services.security.token.UnknownTokenException;
-import org.apache.knox.gateway.services.security.token.impl.JWT;
-import org.apache.knox.gateway.services.security.token.impl.JWTToken;
-
+import java.lang.management.ManagementFactory;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.HashMap;
@@ -37,6 +29,22 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.token.TokenStateService;
+import org.apache.knox.gateway.services.security.token.TokenUtils;
+import org.apache.knox.gateway.services.security.token.UnknownTokenException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
+import org.apache.knox.gateway.services.token.TokenStateServiceStatistics;
+
 /**
  * In-Memory authentication token state management implementation.
  */
@@ -65,12 +73,24 @@ public class DefaultTokenStateService implements TokenStateService {
 
   private final ScheduledExecutorService evictionScheduler = Executors.newScheduledThreadPool(1);
 
+  //token state MBean to store statistics (only initialized and used if JMX reporting is enabled)
+  protected TokenStateServiceStatistics tokenStateServiceStatistics;
+
 
   @Override
   public void init(final GatewayConfig config, final Map<String, String> options) throws ServiceLifecycleException {
     tokenEvictionInterval = config.getKnoxTokenEvictionInterval();
     tokenEvictionGracePeriod = config.getKnoxTokenEvictionGracePeriod();
     permissiveValidationEnabled = config.isKnoxTokenPermissiveValidationEnabled();
+    if (config.isMetricsEnabled() && config.isJmxMetricsReportingEnabled()) {
+      try {
+        tokenStateServiceStatistics = new TokenStateServiceStatistics();
+        final ObjectName objectName = ObjectName.getInstance("metrics:type=Statistics,name=TokenStateService");
+        ManagementFactory.getPlatformMBeanServer().registerMBean(tokenStateServiceStatistics, objectName);
+      } catch (MalformedObjectNameException | InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
+        throw new ServiceLifecycleException("Could not register token state service MBean", e);
+      }
+    }
   }
 
   @Override
@@ -122,6 +142,9 @@ public class DefaultTokenStateService implements TokenStateService {
     }
     setMaxLifetime(tokenId, issueTime, maxLifetimeDuration);
     log.addedToken(tokenId, getTimestampDisplay(expiration));
+    if (tokenStateServiceStatistics != null) {
+      tokenStateServiceStatistics.addToken();
+    }
   }
 
   @Override
@@ -154,17 +177,16 @@ public class DefaultTokenStateService implements TokenStateService {
 
   @Override
   public long getTokenExpiration(String tokenId, boolean validate) throws UnknownTokenException {
-    long expiration;
-
     if (validate) {
       validateToken(tokenId);
     }
 
+    long expiration = -1;
     synchronized (tokenExpirations) {
-      if (!tokenExpirations.containsKey(tokenId)) {
-        throw new UnknownTokenException(tokenId);
-      }
-      expiration = tokenExpirations.get(tokenId);
+      expiration = tokenExpirations.getOrDefault(tokenId, -1L);
+    }
+    if (expiration == -1) {
+      throw new UnknownTokenException(tokenId);
     }
 
     return expiration;
@@ -199,6 +221,9 @@ public class DefaultTokenStateService implements TokenStateService {
       expiration = System.currentTimeMillis() + renewInterval;
       updateExpiration(tokenId, expiration);
       log.renewedToken(tokenId, getTimestampDisplay(expiration));
+      if (tokenStateServiceStatistics != null) {
+        tokenStateServiceStatistics.renewToken();
+      }
     } else {
       log.renewalLimitExceeded(tokenId);
       throw new IllegalArgumentException("The renewal limit for the token has been exceeded");
diff --git a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
index ba2df30..e158919 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/AbstractRequest.java
@@ -37,6 +37,7 @@ public abstract class AbstractRequest<T> {
   private static final String PARAMETER_NAME_DOAS = "doAs";
 
   private final KnoxSession session;
+  private String knoxToken;
 
   private final String doAsUser;
 
@@ -53,8 +54,16 @@ public abstract class AbstractRequest<T> {
     return session;
   }
 
+  public AbstractRequest<T> knoxToken(String knoxToken) {
+    this.knoxToken = knoxToken;
+    return this;
+  }
+
   protected CloseableHttpResponse execute(HttpRequest request ) throws IOException {
     addHeaders(request, session.getHeaders());
+    if (knoxToken != null) {
+      request.addHeader("Authorization", "Bearer " + knoxToken);
+    }
     return session.executeNow( request );
   }
 
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java
new file mode 100644
index 0000000..ce2537e
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateServiceStatisticsMBean.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.services.security.token;
+
+import java.util.Map;
+
+public interface TokenStateServiceStatisticsMBean {
+
+  int getNumberOfTokensAdded();
+
+  int getNumberOfTokensRenewed();
+
+  Map<String, Integer> getKeystoreInteractions();
+
+  long getGatewayCredentialsFileSize();
+}
diff --git a/pom.xml b/pom.xml
index 0370b7a..373cd87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
         <module>gateway-spi</module>
         <module>gateway-discovery-ambari</module>
         <module>gateway-discovery-cm</module>
+        <module>gateway-performance-test</module>
         <module>gateway-server</module>
         <module>gateway-server-launcher</module>
         <module>gateway-server-xforwarded-filter</module>
@@ -853,6 +854,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.knox</groupId>
+                <artifactId>gateway-performance-test</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.knox</groupId>
                 <artifactId>gateway-spi</artifactId>
                 <version>${project.version}</version>
             </dependency>