From 99ce0a36e687105a1c6a082f1a8294b4dafd0a85 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Mon, 16 Jun 2014 18:37:33 +0200
Subject: [PATCH 1/8] Refactoring current performance test
---
pom.xml | 4 +-
testsuite/integration/pom.xml | 4 +-
testsuite/performance/README.md | 33 ++++++++-------
testsuite/performance/pom.xml | 40 ++++++++++++++++++-
.../BaseJMeterPerformanceTest.java | 33 ++++++++++++++-
.../performance/ReadUsersWorker.java | 4 +-
.../src/test/jmeter/keycloak_perf_test.jmx | 6 +--
.../src/test/jmeter/system.properties | 12 ++++--
8 files changed, 107 insertions(+), 29 deletions(-)
diff --git a/pom.xml b/pom.xml
index 66927cb8ce..9d6f31113c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -360,7 +360,7 @@
org.apache.jmeter
ApacheJMeter_java
- 2.9
+ 2.10
dom4j
@@ -513,7 +513,7 @@
com.lazerycode.jmeter
jmeter-maven-plugin
- 1.8.1
+ 1.9.0
com.lazerycode.jmeter
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index a1d7c50c19..052f6b1945 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -2,10 +2,10 @@
- keycloak-parent
+ keycloak-testsuite-pom
org.keycloak
1.0-beta-4-SNAPSHOT
- ../../pom.xml
+ ../pom.xml
4.0.0
diff --git a/testsuite/performance/README.md b/testsuite/performance/README.md
index 4e6a842eb6..1969b87d18 100644
--- a/testsuite/performance/README.md
+++ b/testsuite/performance/README.md
@@ -1,19 +1,16 @@
Configuration of performance test
=================================
-- At this moment it's src/test/jmeter/keycloak_perf_test.jmx where you can configure among other things:
--- "ThreadGroup.num_threads" -- number of worker threads
--- "LoopController.loops" -- Number of loops per each thread.
-
- src/test/jmeter/system.properties -- System properties including configuration of providers. Allow to specify:
+-- Number of worker threads and loops to be used by JMeter performance test
-- which model to use
-- which test to run
-- configuration of individual tests. Properties for each test documented in the file
Running performance tests
=========================
-cd KEYCLOAK_HOME/testsuite
-mvn clean install -DskipTests=true -Pperformance-tests
+cd KEYCLOAK_HOME/testsuite/performance
+mvn clean verify -DskipTests=true -Pperformance-tests
Results:
- Log is in: testsuite/performance/target/jmeter/logs/keycloak_perf_test.jmx.log
@@ -22,18 +19,26 @@ Results:
Example for running test
========================
-Run:
-mvn clean install -DskipTests=true -Pperformance-tests
+1) Run:
+mvn clean verify -DskipTests=true -Pperformance-tests
with OOTB configuration (Assumption is mongo running on 27017 as it's using mongo by default). This will create 10 new realms.
-Then change keycloak_perf_test.jmx to have
- "ThreadGroup.num_threads" to 10 and
- "LoopController.loops" to 100
-And change "keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.CreateUsersWorker" in system.properties
+2) Then change src/test/jmeter/system.properties to have
+ "Tkeycloak.jmeter.numThreads" to 10 and
+ "keycloak.jmeter.loops" to 100
+ "keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.CreateUsersWorker"
Then run again:
-mvn clean install -DskipTests=true -Pperformance-tests
-This will create 1000 new users (10 worker threads and each worker doing 100 iterations. Each worker is creating users in separate realm)
+mvn clean verify -DskipTests=true -Pperformance-tests
+This will create 1000 new users (10 worker threads and each worker doing 100 iterations. Each worker is creating users in separate realm. So 100 users like "user1", "user2", ... "user100" in each realm)
+
+3) Then change src/test/jmeter/system.properties to have
+ "keycloak.perf.workerClass" to "org.keycloak.testsuite.performance.ReadUsersWorker"
+
+Then run again:
+mvn clean verify -DskipTests=true -Pperformance-tests
+ This will read all 1000 previously created users and each user is read 5 times. There are 1000 iterations in total and each iteration is doing 5 read users.
+
TODO: Easier configuration without need to edit config files, more user friendly, easier to configure and run test
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index 6dbe26e2ba..1ba9c0fc8c 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -3,10 +3,10 @@
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">
- keycloak-parent
+ keycloak-testsuite-pom
org.keycloak
1.0-beta-4-SNAPSHOT
- ../../pom.xml
+ ../pom.xml
4.0.0
@@ -73,9 +73,31 @@
net.iharder
base64
+
+ org.bouncycastle
+ bcprov-jdk16
+
org.apache.jmeter
ApacheJMeter_java
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcmail-jdk15on
+
+
+ org.bouncycastle
+ bcmail-jdk15
+
+
+ org.apache.tika
+ tika-parsers
+
+
@@ -161,6 +183,10 @@
org.slf4j
slf4j-simple
+
+ commons-io
+ commons-io
+
@@ -168,6 +194,16 @@
jboss-logging
${jboss.logging.version}
+
+ commons-io
+ commons-io
+ 2.4
+
+
+ org.bouncycastle
+ bcprov-jdk16
+ ${bouncycastle.version}
+
+
+ org.jboss.resteasy
+ jaxrs-api
+ ${resteasy.version.latest}
+
+
+ org.jboss.resteasy
+ resteasy-jaxrs
+ ${resteasy.version.latest}
+
+
+ log4j
+ log4j
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ org.jboss.resteasy
+ resteasy-client
+ ${resteasy.version.latest}
+
+
+ org.jboss.resteasy
+ resteasy-crypto
+ ${resteasy.version.latest}
+
+
+ org.jboss.resteasy
+ resteasy-multipart-provider
+ ${resteasy.version.latest}
+
+
+ org.jboss.resteasy
+ resteasy-jackson-provider
+ ${resteasy.version.latest}
+
+
+ org.jboss.resteasy
+ resteasy-undertow
+ ${resteasy.version.latest}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${maven.compiler.target}
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ ${project.basedir}
+
+
+
+
+
+
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
new file mode 100644
index 0000000000..55cc44f9b2
--- /dev/null
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -0,0 +1,87 @@
+package org.keycloak.testsuite.performance.web;
+
+import java.io.InputStream;
+
+import javax.servlet.DispatcherType;
+
+import io.undertow.servlet.Servlets;
+import io.undertow.servlet.api.DeploymentInfo;
+import io.undertow.servlet.api.FilterInfo;
+import io.undertow.servlet.api.ServletInfo;
+import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
+import org.jboss.resteasy.spi.ResteasyDeployment;
+import org.keycloak.services.filters.ClientConnectionFilter;
+import org.keycloak.services.filters.KeycloakSessionServletFilter;
+import org.keycloak.test.tools.KeycloakTestApplication;
+import org.keycloak.testutils.KeycloakServer;
+
+/**
+ * @author Marek Posolda
+ */
+public class KeycloakPerfServer {
+
+ private KeycloakServer keycloakServer;
+
+ public static void main(String[] args) throws Throwable {
+ KeycloakServer keycloakServer = KeycloakServer.bootstrapKeycloakServer(args);
+ System.out.println("Keycloak server bootstrapped");
+
+ ProviderSessionFactoryHolder.setProviderSessionFactory(keycloakServer.getProviderSessionFactory());
+ new KeycloakPerfServer(keycloakServer).start();
+ }
+
+ public KeycloakPerfServer(KeycloakServer keycloakServer) {
+ this.keycloakServer = keycloakServer;
+ }
+
+ public void start() {
+ importPerfRealm();
+ deployPerfTools();
+ deployPerfApp();
+ }
+
+ protected void importPerfRealm() {
+ InputStream perfRealmStream = KeycloakPerfServer.class.getClassLoader().getResourceAsStream("perfrealm.json");
+ keycloakServer.importRealm(perfRealmStream);
+ }
+
+ protected void deployPerfTools() {
+ ResteasyDeployment deployment = new ResteasyDeployment();
+ deployment.setApplicationClass(KeycloakToolsApplication.class.getName());
+
+ UndertowJaxrsServer server = keycloakServer.getServer();
+
+ DeploymentInfo di = server.undertowDeployment(deployment, "");
+ di.setClassLoader(KeycloakTestApplication.class.getClassLoader());
+ di.setContextPath("/keycloak-tools");
+ di.setDeploymentName("KeycloakTools");
+
+ FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
+ di.addFilter(filter);
+ di.addFilterUrlMapping("SessionFilter", "/perf/*", DispatcherType.REQUEST);
+
+ FilterInfo connectionFilter = Servlets.filter("ClientConnectionFilter", ClientConnectionFilter.class);
+ di.addFilter(connectionFilter);
+ di.addFilterUrlMapping("ClientConnectionFilter", "/perf/*", DispatcherType.REQUEST);
+
+ server.deploy(di);
+
+ System.out.println("Keycloak tools deployed");
+ }
+
+ protected void deployPerfApp() {
+ DeploymentInfo deploymentInfo = new DeploymentInfo();
+ deploymentInfo.setClassLoader(getClass().getClassLoader());
+ deploymentInfo.setDeploymentName("PerfApp");
+ deploymentInfo.setContextPath("/perf-app");
+
+ ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
+ servlet.addMapping("/*");
+
+ deploymentInfo.addServlet(servlet);
+
+ keycloakServer.getServer().deploy(deploymentInfo);
+
+ System.out.println("PerfApp deployed");
+ }
+}
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java
new file mode 100644
index 0000000000..94798d9b45
--- /dev/null
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java
@@ -0,0 +1,42 @@
+package org.keycloak.testsuite.performance.web;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+
+import org.jboss.resteasy.core.Dispatcher;
+import org.keycloak.provider.ProviderSessionFactory;
+import org.keycloak.test.tools.PerfTools;
+
+/**
+ * Modified version of {@link org.keycloak.test.tools.KeycloakTestApplication}, which shares ProviderSessionFactory with KeycloakApplication
+ *
+ * @author Marek Posolda
+ */
+public class KeycloakToolsApplication extends Application {
+
+ protected ProviderSessionFactory providerSessionFactory;
+ protected Set> classes = new HashSet>();
+ protected Set
+
+
+
\ No newline at end of file
diff --git a/testsuite/performance-web/src/main/resources/perf-app-resources/index.html b/testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html
similarity index 100%
rename from testsuite/performance-web/src/main/resources/perf-app-resources/index.html
rename to testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html
From 4ebc575afcf9f3e7abe00bd67fcd43ee95632393 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Thu, 26 Jun 2014 10:16:34 +0200
Subject: [PATCH 4/8] Change perf test to parse accessToken and refreshToken in
Servlet. Support for logout
---
.../performance/web/KeycloakPerfServer.java | 2 +-
.../performance/web/PerfAppServlet.java | 56 +++++++--
.../resources/perf-app-resources/index.ftl | 28 ++---
.../perf-app-resources/js-console.html | 110 ------------------
.../src/main/resources/perfrealm.json | 2 +-
5 files changed, 65 insertions(+), 133 deletions(-)
delete mode 100644 testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
index b15101955a..9a64680c52 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -78,7 +78,7 @@ public class KeycloakPerfServer {
deploymentInfo.setContextPath("/perf-app");
ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
- servlet.addMapping("/perf-servlet");
+ servlet.addMapping("/perf-servlet/*");
deploymentInfo.addServlet(servlet);
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
index 6de11f9b45..a781b31218 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
@@ -15,8 +15,10 @@ import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
+import org.keycloak.adapters.AdapterConstants;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
+import org.keycloak.util.Time;
/**
* @author Marek Posolda
@@ -43,6 +45,7 @@ public class PerfAppServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
String action = req.getParameter("action");
+ String actionDone = null;
if (action != null) {
if (action.equals("code")) {
@@ -50,8 +53,10 @@ public class PerfAppServlet extends HttpServlet {
return;
} else if (action.equals("exchangeCode")) {
exchangeCodeForToken(req, resp);
+ actionDone = "Token retrieved";
} else if (action.equals("refresh")) {
refreshToken(req, resp);
+ actionDone = "Token refreshed";
} else if (action.equals("logout")) {
logoutRedirect(req, resp);
return;
@@ -61,13 +66,22 @@ public class PerfAppServlet extends HttpServlet {
String code = req.getParameter("code");
if (code != null) {
req.getSession().setAttribute("code", code);
+ actionDone = "Code retrieved";
}
- String freemarkerRedirect = freemarkerRedirect(req, resp);
+ String freemarkerRedirect = freemarkerRedirect(req, resp, actionDone);
resp.getWriter().println(freemarkerRedirect);
resp.getWriter().flush();
}
+ @Override
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ if (req.getRequestURI().endsWith(AdapterConstants.K_LOGOUT)) {
+ // System.out.println("Logout callback triggered");
+ resp.setStatus(204);
+ }
+ }
+
protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String loginUrl = oauthClient.getLoginFormUrl();
resp.sendRedirect(loginUrl);
@@ -77,32 +91,60 @@ public class PerfAppServlet extends HttpServlet {
String code = (String)req.getSession().getAttribute("code");
OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");
- String accessToken = atResponse.getAccessToken();
- String refreshToken = atResponse.getRefreshToken();
- req.getSession().setAttribute("accessToken", accessToken);
- req.getSession().setAttribute("refreshToken", refreshToken);
+ updateTokensInSession(req, atResponse);
}
protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String refreshToken = (String)req.getSession().getAttribute("refreshToken");
OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");
+ updateTokensInSession(req, atResponse);
+ }
+
+ private void updateTokensInSession(HttpServletRequest req, OAuthClient.AccessTokenResponse atResponse) {
String accessToken = atResponse.getAccessToken();
- refreshToken = atResponse.getRefreshToken();
+ String refreshToken = atResponse.getRefreshToken();
+ AccessToken accessTokenParsed = oauthClient.verifyToken(accessToken);
+ RefreshToken refreshTokenParsed = oauthClient.verifyRefreshToken(refreshToken);
req.getSession().setAttribute("accessToken", accessToken);
req.getSession().setAttribute("refreshToken", refreshToken);
+ req.getSession().setAttribute("accessTokenParsed", accessTokenParsed);
+ req.getSession().setAttribute("refreshTokenParsed", refreshTokenParsed);
}
protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ String sessionState = null;
+ AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
+ if (accessTokenParsed != null) {
+ sessionState = accessTokenParsed.getSessionState();
+ }
+ // Invalidate http session
+ req.getSession(false).invalidate();
+
+ String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), sessionState);
+ resp.sendRedirect(logoutURL);
}
- private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp, String actionDone) throws ServletException, IOException {
+ AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
+ RefreshToken refreshTokenParsed = (RefreshToken)req.getSession().getAttribute("refreshTokenParsed");
+
Map attributes = new HashMap();
attributes.put("requestURI", req.getRequestURI());
attributes.put("code", req.getSession().getAttribute("code"));
attributes.put("accessToken", req.getSession().getAttribute("accessToken"));
attributes.put("refreshToken", req.getSession().getAttribute("refreshToken"));
+ attributes.put("accessTokenParsed", accessTokenParsed);
+ attributes.put("refreshTokenParsed", refreshTokenParsed);
+ attributes.put("actionDone", actionDone);
+
+ if (accessTokenParsed != null) {
+ attributes.put("accessTokenExpiration", Time.toDate(accessTokenParsed.getExpiration()).toString());
+ }
+ if (refreshTokenParsed != null) {
+ attributes.put("refreshTokenExpiration", Time.toDate(refreshTokenParsed.getExpiration()).toString());
+ }
try {
Writer out = new StringWriter();
diff --git a/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
index b8e92eb2f9..74af2bece7 100644
--- a/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
+++ b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
@@ -19,30 +19,30 @@
<#if code??>
- Code: ${code}
+ Code Available
+ Code=${code}
#if>
<#if accessToken??>
- accessToken: ${accessToken}
-
-
+ Access Token Available
+ AccessToken=${accessToken}
+ Username=${accessTokenParsed.preferredUsername}
+ SessionState=${accessTokenParsed.sessionState}
+ Expiration=${accessTokenExpiration}
-
-
#if>
<#if refreshToken??>
- refreshToken: ${refreshToken}
-
-
+ Refresh token available
+ RefreshToken=${refreshToken}
+ Expiration=${refreshTokenExpiration}
+ #if>
-
+ <#if actionDone??>
+ RequestAction=${actionDone}
+
#if>
diff --git a/testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html b/testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html
deleted file mode 100644
index c5b7c532c1..0000000000
--- a/testsuite/performance-web/src/main/resources/perf-app-resources/js-console.html
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Result
-
-
-Events
-
-
-
-
-
-
\ No newline at end of file
diff --git a/testsuite/performance-web/src/main/resources/perfrealm.json b/testsuite/performance-web/src/main/resources/perfrealm.json
index aaa7fc46c6..2dadcc2be6 100644
--- a/testsuite/performance-web/src/main/resources/perfrealm.json
+++ b/testsuite/performance-web/src/main/resources/perfrealm.json
@@ -59,7 +59,7 @@
"redirectUris": [
"http://localhost:8081/perf-app/*"
],
- "adminUrl": "http://localhost:8081/perf-app/logout",
+ "adminUrl": "http://localhost:8081/perf-app/perf-servlet",
"secret": "password"
}
],
From 9ebe2ff45e278fd8f6c088e11f965ee485b82108 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Thu, 26 Jun 2014 15:35:58 +0200
Subject: [PATCH 5/8] Added support for get-users-count and
create-available-users into PerfTools
---
testsuite/performance-web/README.md | 13 ++++++++
.../src/main/resources/perfrealm.json | 8 ++---
.../org/keycloak/test/tools/PerfTools.java | 30 +++++++++++++++++++
3 files changed, 47 insertions(+), 4 deletions(-)
create mode 100644 testsuite/performance-web/README.md
diff --git a/testsuite/performance-web/README.md b/testsuite/performance-web/README.md
new file mode 100644
index 0000000000..9db534f718
--- /dev/null
+++ b/testsuite/performance-web/README.md
@@ -0,0 +1,13 @@
+Adding users
+------------
+
+Adding 1000 new users (will start from last added user, so you don't need to explicitly check how many users to create are needed:
+http://localhost:8081/keycloak-tools/perf/perf-realm/create-available-users?prefix=user&count=1000&batch=100&roles=user
+
+Checking users count:
+http://localhost:8081/keycloak-tools/perf/perf-realm/get-users-count?prefix=user
+
+Switching to Mongo
+------------------
+Start with: (TODO)
+-Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.db=keycloak-perf
\ No newline at end of file
diff --git a/testsuite/performance-web/src/main/resources/perfrealm.json b/testsuite/performance-web/src/main/resources/perfrealm.json
index 2dadcc2be6..1fab9a52b4 100644
--- a/testsuite/performance-web/src/main/resources/perfrealm.json
+++ b/testsuite/performance-web/src/main/resources/perfrealm.json
@@ -16,9 +16,9 @@
},
"users" : [
{
- "username" : "test-user@localhost",
+ "username" : "test@localhost",
"enabled": true,
- "email" : "test-user@localhost",
+ "email" : "test@localhost",
"credentials" : [
{ "type" : "password",
"value" : "password" }
@@ -37,7 +37,7 @@
],
"roleMappings": [
{
- "username": "test-user@localhost",
+ "username": "test@localhost",
"roles": ["user"]
}
],
@@ -92,7 +92,7 @@
"applicationRoleMappings": {
"perf-app": [
{
- "username": "test-user@localhost",
+ "username": "test@localhost",
"roles": ["customer-user"]
}
]
diff --git a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
index 2e08ed77e9..60a5baabfd 100644
--- a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
+++ b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
@@ -17,9 +17,12 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
+
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@@ -99,6 +102,33 @@ public class PerfTools {
}
}
+
+ @GET
+ @Path("{realm}/get-users-count")
+ public Response getUsersCountReq(@PathParam("realm") String realmName, @QueryParam("prefix") String prefix) {
+ int usersCount = getUsersCount(realmName, prefix);
+ return Response.ok(String.valueOf(usersCount)).build();
+ }
+
+ // Same as createUsers, but dynamically compute "start" (Next available user)
+ @GET
+ @Path("{realm}/create-available-users")
+ public Response createAvailableUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count, @QueryParam("batch") Integer batch, @QueryParam("prefix") String prefix, @QueryParam("roles") String roles) throws InterruptedException {
+ int start = getUsersCount(realmName, prefix);
+ return createUsers(realmName, count, batch, start, prefix, roles);
+ }
+
+ private int getUsersCount(String realmName, String prefix) {
+ RealmModel realm = session.getRealmByName(realmName);
+
+ // TODO: method for count on model
+ if (prefix == null) {
+ return realm.getUsers().size();
+ } else {
+ return realm.searchForUser(prefix).size();
+ }
+ }
+
@GET
@Path("export")
public void export(@QueryParam("dir") String dir) {
From de774a305887327a42737fcf287f0a5332aaf064 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Thu, 26 Jun 2014 18:02:53 +0200
Subject: [PATCH 6/8] Add classes classifier to performance-tools war, so
classes can be reused in performance-web module
---
testsuite/performance-web/pom.xml | 2 +-
testsuite/tools/pom.xml | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/testsuite/performance-web/pom.xml b/testsuite/performance-web/pom.xml
index 8cf6f19b75..dd696dbf95 100644
--- a/testsuite/performance-web/pom.xml
+++ b/testsuite/performance-web/pom.xml
@@ -28,7 +28,7 @@
org.keycloak
keycloak-testsuite-tools
${project.version}
- war
+ classes
diff --git a/testsuite/tools/pom.xml b/testsuite/tools/pom.xml
index 3d3d2c3256..c630624317 100755
--- a/testsuite/tools/pom.xml
+++ b/testsuite/tools/pom.xml
@@ -296,6 +296,12 @@
keycloak-tools
+
+ maven-war-plugin
+
+ true
+
+
org.jboss.as.plugins
jboss-as-maven-plugin
From b6fd58e644cc9e9503d7b0ee7e5dcc5dadf12247 Mon Sep 17 00:00:00 2001
From: mposolda
Date: Fri, 27 Jun 2014 12:20:15 +0200
Subject: [PATCH 7/8] Support for configuring number of worker threads in
undertow
---
.../keycloak/testutils/KeycloakServer.java | 18 +++++++++++++++-
.../performance/web/KeycloakPerfServer.java | 3 +--
.../web/KeycloakSessionFactoryHolder.java | 21 +++++++++++++++++++
.../web/KeycloakToolsApplication.java | 10 ++++-----
.../web/ProviderSessionFactoryHolder.java | 21 -------------------
.../org/keycloak/test/tools/PerfTools.java | 4 ++--
6 files changed, 46 insertions(+), 31 deletions(-)
create mode 100644 testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakSessionFactoryHolder.java
delete mode 100644 testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/ProviderSessionFactoryHolder.java
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
index 6f2aaf9536..c401f0a349 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
@@ -59,6 +59,7 @@ public class KeycloakServer {
public static class KeycloakServerConfig {
private String host = "localhost";
private int port = 8081;
+ private int workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8;
private String resourcesHome;
public String getHost() {
@@ -84,6 +85,14 @@ public class KeycloakServer {
public void setResourcesHome(String resourcesHome) {
this.resourcesHome = resourcesHome;
}
+
+ public int getWorkerThreads() {
+ return workerThreads;
+ }
+
+ public void setWorkerThreads(int workerThreads) {
+ this.workerThreads = workerThreads;
+ }
}
public static T loadJson(InputStream is, Class type) {
@@ -141,6 +150,10 @@ public class KeycloakServer {
config.setResourcesHome(dir.getAbsolutePath());
}
+ if (System.getProperties().containsKey("undertowWorkerThreads")) {
+ int undertowWorkerThreads = Integer.parseInt(System.getProperty("undertowWorkerThreads"));
+ config.setWorkerThreads(undertowWorkerThreads);
+ }
final KeycloakServer keycloak = new KeycloakServer(config);
keycloak.sysout = true;
@@ -244,7 +257,10 @@ public class KeycloakServer {
ResteasyDeployment deployment = new ResteasyDeployment();
deployment.setApplicationClass(KeycloakApplication.class.getName());
- Builder builder = Undertow.builder().addListener(config.getPort(), config.getHost());
+ Builder builder = Undertow.builder()
+ .addHttpListener(config.getPort(), config.getHost())
+ .setWorkerThreads(config.getWorkerThreads())
+ .setIoThreads(config.getWorkerThreads() / 8);
server = new UndertowJaxrsServer().start(builder);
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
index 9a64680c52..2ad2fa2459 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -8,7 +8,6 @@ import io.undertow.server.handlers.resource.ClassPathResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
-import io.undertow.servlet.api.MimeMapping;
import io.undertow.servlet.api.ServletInfo;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
@@ -28,7 +27,7 @@ public class KeycloakPerfServer {
KeycloakServer keycloakServer = KeycloakServer.bootstrapKeycloakServer(args);
System.out.println("Keycloak server bootstrapped");
- ProviderSessionFactoryHolder.setProviderSessionFactory(keycloakServer.getProviderSessionFactory());
+ KeycloakSessionFactoryHolder.setKeycloakSessionFactory(keycloakServer.getSessionFactory());
new KeycloakPerfServer(keycloakServer).start();
}
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakSessionFactoryHolder.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakSessionFactoryHolder.java
new file mode 100644
index 0000000000..adc3e19716
--- /dev/null
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakSessionFactoryHolder.java
@@ -0,0 +1,21 @@
+package org.keycloak.testsuite.performance.web;
+
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * Static holder to allow sharing ProviderSessionFactory among different JAX-RS applications
+ *
+ * @author Marek Posolda
+ */
+public class KeycloakSessionFactoryHolder {
+
+ private static KeycloakSessionFactory keycloakSessionFactory;
+
+ public static KeycloakSessionFactory getKeycloakSessionFactory() {
+ return keycloakSessionFactory;
+ }
+
+ public static void setKeycloakSessionFactory(KeycloakSessionFactory keycloakSessionFactory) {
+ KeycloakSessionFactoryHolder.keycloakSessionFactory = keycloakSessionFactory;
+ }
+}
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java
index 94798d9b45..e0612c1b39 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakToolsApplication.java
@@ -8,7 +8,7 @@ import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import org.jboss.resteasy.core.Dispatcher;
-import org.keycloak.provider.ProviderSessionFactory;
+import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.test.tools.PerfTools;
/**
@@ -18,14 +18,14 @@ import org.keycloak.test.tools.PerfTools;
*/
public class KeycloakToolsApplication extends Application {
- protected ProviderSessionFactory providerSessionFactory;
+ protected KeycloakSessionFactory keycloakSessionFactory;
protected Set> classes = new HashSet>();
protected Set singletons = new HashSet();
public KeycloakToolsApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
- this.providerSessionFactory = ProviderSessionFactoryHolder.getProviderSessionFactory();
- context.setAttribute(ProviderSessionFactory.class.getName(), this.providerSessionFactory);
- singletons.add(new PerfTools(providerSessionFactory));
+ this.keycloakSessionFactory = KeycloakSessionFactoryHolder.getKeycloakSessionFactory();
+ context.setAttribute(KeycloakSessionFactory.class.getName(), this.keycloakSessionFactory);
+ singletons.add(new PerfTools(keycloakSessionFactory));
}
@Override
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/ProviderSessionFactoryHolder.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/ProviderSessionFactoryHolder.java
deleted file mode 100644
index ea1420bbd1..0000000000
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/ProviderSessionFactoryHolder.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.keycloak.testsuite.performance.web;
-
-import org.keycloak.provider.ProviderSessionFactory;
-
-/**
- * Static holder to allow sharing ProviderSessionFactory among different JAX-RS applications
- *
- * @author Marek Posolda
- */
-public class ProviderSessionFactoryHolder {
-
- private static ProviderSessionFactory providerSessionFactory;
-
- public static ProviderSessionFactory getProviderSessionFactory() {
- return providerSessionFactory;
- }
-
- public static void setProviderSessionFactory(ProviderSessionFactory providerSessionFactory) {
- ProviderSessionFactoryHolder.providerSessionFactory = providerSessionFactory;
- }
-}
diff --git a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
index 60a5baabfd..32a1fdcae6 100644
--- a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
+++ b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java
@@ -113,9 +113,9 @@ public class PerfTools {
// Same as createUsers, but dynamically compute "start" (Next available user)
@GET
@Path("{realm}/create-available-users")
- public Response createAvailableUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count, @QueryParam("batch") Integer batch, @QueryParam("prefix") String prefix, @QueryParam("roles") String roles) throws InterruptedException {
+ public void createAvailableUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count, @QueryParam("batch") Integer batch, @QueryParam("prefix") String prefix, @QueryParam("roles") String roles) throws InterruptedException {
int start = getUsersCount(realmName, prefix);
- return createUsers(realmName, count, batch, start, prefix, roles);
+ createUsers(realmName, count, batch, start, prefix, roles);
}
private int getUsersCount(String realmName, String prefix) {
From cb9f1590910060281e57e8bba77594ac21971b7f Mon Sep 17 00:00:00 2001
From: mposolda
Date: Fri, 27 Jun 2014 18:14:42 +0200
Subject: [PATCH 8/8] Added JMeter. Added KeycloakPerfServer into maven.
---
testsuite/performance-web/README.md | 109 ++++-
testsuite/performance-web/pom.xml | 128 ++++++
.../performance/web/KeycloakPerfServer.java | 7 +
.../performance/web/PerfAppServlet.java | 8 +-
.../src/test/jmeter/jmeter.properties | 20 +
.../test/jmeter/keycloak_web_perf_test.jmx | 387 ++++++++++++++++++
6 files changed, 644 insertions(+), 15 deletions(-)
create mode 100644 testsuite/performance-web/src/test/jmeter/jmeter.properties
create mode 100644 testsuite/performance-web/src/test/jmeter/keycloak_web_perf_test.jmx
diff --git a/testsuite/performance-web/README.md b/testsuite/performance-web/README.md
index 9db534f718..950e90cc12 100644
--- a/testsuite/performance-web/README.md
+++ b/testsuite/performance-web/README.md
@@ -1,13 +1,106 @@
+Keycloak Web Performance Testsuite
+==================================
+To run web performance testsuite, you need to:
+1) Run Keycloak server
+2) Add some users into your Keycloak
+3) Run JMeter performance tests
+
+Keycloak server
+---------------
+In this project you can run:
+
+```shell
+mvn exec:java -Pkeycloak-perf-server
+````
+
+which will execute embedded Undertow server with:
+ * Keycloak server
+ * Performance-tools for mass adding of new users
+ * Simple web application for testing performance
+It will also automatically import realm "perf-realm" into Keycloak from file src/main/resources/perfrealm.json
+
+Note that by default it will use in-memory H2 database, which means that all changes (for example all added users) are discarded after server restart. For performance testing, it's recommended to use some database like PostgreSQL or MySQL
+
+To run server with PostgreSQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
+```shell
+mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:postgresql://localhost:5432/keycloak_perf -Dhibernate.connection.driver_class=org.postgresql.Driver -Dhibernate.connection.username=postgres -Dhibernate.connection.password=postgres -Dhibernate.connection.autocommit=false
+````
+
+To run server with MySQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
+```shell
+mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:mysql://localhost/keycloak_perf -Dhibernate.connection.driver_class=com.mysql.jdbc.Driver -Dhibernate.connection.username=portal -Dhibernate.connection.password=portal -Dhibernate.connection.autocommit=false
+````
+
+To run server with Mongo you may use command like this (change host,port,dbName and credentials according your DB configuration):
+```shell
+mvn exec:java -Pkeycloak-perf-server -Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.db=keycloak-perf
+````
+
+To enable cache, you can add additional property:
+```shell
+-Dkeycloak.model.cache.provider=simple
+````
+
Adding users
-------------
+-----------------
-Adding 1000 new users (will start from last added user, so you don't need to explicitly check how many users to create are needed:
-http://localhost:8081/keycloak-tools/perf/perf-realm/create-available-users?prefix=user&count=1000&batch=100&roles=user
+Performance test is using users with prefix "user" (so users like "user-0", "user-1", ... , "user-123456" etc). So you first need to add some of these users into your database.
-Checking users count:
+For checking users count, you can open this URL:
+```shell
http://localhost:8081/keycloak-tools/perf/perf-realm/get-users-count?prefix=user
+````
-Switching to Mongo
-------------------
-Start with: (TODO)
--Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.db=keycloak-perf
\ No newline at end of file
+For adding 10000 new users into your database (will start from last added user, so you don't need to explicitly check how many users to create are needed:
+```shell
+http://localhost:8081/keycloak-tools/perf/perf-realm/create-available-users?prefix=user&count=10000&batch=100&roles=user
+````
+
+Seeing progress of job for creating users
+```shell
+http://localhost:8081/keycloak-tools/perf/jobs
+````
+
+Note that with default H2 are all data automatically cleared after server restart. So it's recommended to use different DB like PostgreSQL or Mongo.
+
+
+Execute performance test
+------------------------
+
+When server is started and some users are created, you can run performance test. It's possible to run it from Command line with:
+
+```shell
+mvn verify -Pperformance-test
+````
+
+By default, test is using Keycloak on localhost:8081 and 50 concurrent clients (threads) and each client doing 50 test iterations. Each iterations is:
+- Login user into KC and retrieve code
+- Exchange code for accessToken
+- Refresh token 2 times
+- Logout user
+
+Each client is using separate username, so actually you need at least 50 users created into your DB (users "user-0", "user-1", ... "user-49" . See above)
+
+ATM it's possible to adjust behaviour with properties:
+* host --- Keycloak host ("localhost" by default)
+* port --- Keycloak port ("8081" by default)
+* userPrefix --- prefix of users ("user" by default)
+* concurrentUsers --- Number of concurrent clients (50 by default). RampUp time is configured to start 5 new users each second, so with 50 users are all clients started in like 10 seconds.
+* iterationsPerUser --- Number of iterations per each client (50 by default). So by default we have 50*50 = 2500 iterations in total per whole test
+* refreshTokenRequestsPerIteration --- Number of refresh token iterations (2 by default)
+
+You can change configuration by adding some properties into command line for example to start just with 10 concurrent clients and 10 iterations per client (so just 100 test iterations) you can use:
+```shell
+mvn verify -Pperformance-test -DconcurrentUsers=10 -DiterationsPerUser=10
+````
+
+After triggering test are results in file target/jmeter/results/aggregatedRequests-durations--keycloak_web_perf_test.html
+
+Execute performance test from JMeter GUI
+----------------------------------------
+You can run
+```shell
+mvn jmeter:gui
+````
+
+and then open file src/test/jmeter/keycloak_web_perf_test.jmx and trigger test from JMeter GUI. It may be good as you can see the progress of whole test during execution.
diff --git a/testsuite/performance-web/pom.xml b/testsuite/performance-web/pom.xml
index dd696dbf95..e2d61db79a 100644
--- a/testsuite/performance-web/pom.xml
+++ b/testsuite/performance-web/pom.xml
@@ -88,6 +88,30 @@
resteasy-undertow
${resteasy.version.latest}
+
+
+ org.apache.jmeter
+ ApacheJMeter_java
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcmail-jdk15on
+
+
+ org.bouncycastle
+ bcmail-jdk15
+
+
+ org.apache.tika
+ tika-parsers
+
+
+
+
@@ -117,4 +141,108 @@
+
+
+
+ keycloak-perf-server
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ org.keycloak.testsuite.performance.web.KeycloakPerfServer
+
+
+
+
+
+
+
+ performance-test
+
+
+ localhost
+ 8081
+ user
+ 50
+ 50
+ 2
+
+
+
+
+
+
+ com.lazerycode.jmeter
+ jmeter-maven-plugin
+
+ false
+
+ ${host}
+ ${port}
+ ${userPrefix}
+ ${concurrentUsers}
+ ${iterationsPerUser}
+ ${refreshTokenRequestsPerIteration}
+
+
+
+
+ jmeter-tests
+ verify
+
+ jmeter
+
+
+
+
+
+
+
+
+ com.lazerycode.jmeter
+ jmeter-analysis-maven-plugin
+
+
+ jmeter-tests-analyze
+ verify
+
+ analyze
+
+
+
+ ${project.build.directory}/jmeter/results
+ false
+
+
+
+ aggregatedRequests
+ * request
+
+
+ codes
+ **/perf-app/perf-servlet?code=*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
index 2ad2fa2459..5dde02ee22 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -24,6 +24,13 @@ public class KeycloakPerfServer {
private KeycloakServer keycloakServer;
public static void main(String[] args) throws Throwable {
+ // TODO: should be better than programmatic setup here, but don't copy persistence.xml again...
+ System.setProperty("hibernate.hbm2ddl.auto", "update");
+
+ if (System.getProperty("undertowWorkerThreads") == null) {
+ System.setProperty("undertowWorkerThreads", "256");
+ }
+
KeycloakServer keycloakServer = KeycloakServer.bootstrapKeycloakServer(args);
System.out.println("Keycloak server bootstrapped");
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
index a781b31218..52cf1fa10b 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
@@ -113,16 +113,10 @@ public class PerfAppServlet extends HttpServlet {
}
protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String sessionState = null;
- AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
- if (accessTokenParsed != null) {
- sessionState = accessTokenParsed.getSessionState();
- }
-
// Invalidate http session
req.getSession(false).invalidate();
- String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), sessionState);
+ String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), null);
resp.sendRedirect(logoutURL);
}
diff --git a/testsuite/performance-web/src/test/jmeter/jmeter.properties b/testsuite/performance-web/src/test/jmeter/jmeter.properties
new file mode 100644
index 0000000000..a024bb5a2d
--- /dev/null
+++ b/testsuite/performance-web/src/test/jmeter/jmeter.properties
@@ -0,0 +1,20 @@
+#Thu Mar 07 18:46:04 BRT 2013
+not_in_menu=HTML Parameter Mask,HTTP User Parameter Modifier
+xml.parser=org.apache.xerces.parsers.SAXParser
+cookies=cookies
+wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser
+HTTPResponse.parsers=htmlParser wmlParser
+remote_hosts=127.0.0.1
+system.properties=system.properties
+beanshell.server.file=../extras/startup.bsh
+log_level.jmeter.junit=DEBUG
+sampleresult.timestamp.start=true
+jmeter.laf.mac=System
+log_level.jorphan=INFO
+classfinder.functions.contain=.functions.
+user.properties=user.properties
+wmlParser.types=text/vnd.wap.wml
+log_level.jmeter=DEBUG
+classfinder.functions.notContain=.gui.
+htmlParser.types=text/html application/xhtml+xml application/xml text/xml
+upgrade_properties=/bin/upgrade.properties
diff --git a/testsuite/performance-web/src/test/jmeter/keycloak_web_perf_test.jmx b/testsuite/performance-web/src/test/jmeter/keycloak_web_perf_test.jmx
new file mode 100644
index 0000000000..07074aa998
--- /dev/null
+++ b/testsuite/performance-web/src/test/jmeter/keycloak_web_perf_test.jmx
@@ -0,0 +1,387 @@
+
+
+
+
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+ host
+ ${__P(host, localhost)}
+ =
+
+
+ port
+ ${__P(port, 8081)}
+ =
+
+
+ userPrefix
+ ${__P(userPrefix, user)}
+ =
+
+
+ concurrentUsers
+ ${__P(concurrentUsers, 50)}
+ =
+
+
+ iterationsPerUser
+ ${__P(iterationsPerUser, 50)}
+ =
+
+
+ refreshTokenRequestsPerIteration
+ ${__P(refreshTokenRequestsPerIteration, 2)}
+ =
+
+
+
+
+
+
+ true
+ org.apache.jmeter.protocol.http.control.HC4CookieHandler
+
+
+
+ continue
+
+ false
+ ${iterationsPerUser}
+
+ ${concurrentUsers}
+ ${__javaScript(${concurrentUsers} / 5)}
+ 1403769177000
+ 1403769177000
+ false
+
+
+
+
+
+
+ username
+
+
+
+ user-${__javaScript(${__threadNum}-1)}
+
+
+ false
+
+
+
+
+
+
+ ${host}
+ ${port}
+
+
+
+
+
+ 4
+
+
+
+
+ 200
+
+ Check that status is 200 in each HTTP response
+ Assertion.response_code
+ false
+ 8
+
+
+
+
+
+
+ false
+ code
+ =
+ true
+ action
+
+
+
+
+
+
+
+
+
+ /perf-app/perf-servlet
+ GET
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ Log in to perf-realm
+
+ Assertion.response_data
+ false
+ 2
+
+
+
+
+
+
+
+ false
+ ${username}
+ =
+ true
+ username
+
+
+ false
+ password
+ =
+ true
+ password
+
+
+
+
+
+
+
+
+
+ /auth/realms/perf-realm/tokens/auth/request/login?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fperf-app%2Fperf-servlet&state=123&client_id=perf-app
+ POST
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ RequestAction=Code retrieved
+
+ Assertion.response_data
+ false
+ 2
+
+
+
+ false
+ code
+ Code=([\w\.]+)
+ $1$
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /perf-app/perf-servlet?action=exchangeCode
+ GET
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ RequestAction=Token retrieved
+ Username=${username}
+
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+ true
+ ${refreshTokenRequestsPerIteration}
+
+
+
+
+
+
+
+
+
+
+
+
+ /perf-app/perf-servlet?action=refresh
+ GET
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ RequestAction=Token refreshed
+
+ Assertion.response_data
+ false
+ 16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /perf-app/perf-servlet?action=logout
+ GET
+ true
+ false
+ true
+ false
+ false
+
+
+
+
+
+ RequestAction=
+ Username=
+
+ Assertion.response_data
+ false
+ 20
+
+
+
+
+
+ false
+
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+
+
+
+
+
+
+ false
+
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+
+
+
+
+
+ true
+ true
+ true
+
+ true
+ true
+ true
+ true
+ false
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ 0
+ true
+
+
+
+
+
+
+
+