mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
user auth token load tests using k6.io
This commit is contained in:
parent
d6edaa1328
commit
6454de74e4
69
devenv/docker/loadtest/README.md
Normal file
69
devenv/docker/loadtest/README.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Grafana load test
|
||||||
|
|
||||||
|
Runs load tests and checks using [k6](https://k6.io/).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Docker
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Run load test for 15 minutes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Run load test for custom duration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./run.sh -d 10s
|
||||||
|
```
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
/\ |‾‾| /‾‾/ /‾/
|
||||||
|
/\ / \ | |_/ / / /
|
||||||
|
/ \/ \ | | / ‾‾\
|
||||||
|
/ \ | |‾\ \ | (_) |
|
||||||
|
/ __________ \ |__| \__\ \___/ .io
|
||||||
|
|
||||||
|
execution: local
|
||||||
|
output: -
|
||||||
|
script: src/auth_token_test.js
|
||||||
|
|
||||||
|
duration: 10s, iterations: -
|
||||||
|
vus: 2, max: 2
|
||||||
|
|
||||||
|
done [==========================================================] 10s / 10s
|
||||||
|
|
||||||
|
█ user auth token test
|
||||||
|
|
||||||
|
█ user authenticates thru ui with username and password
|
||||||
|
|
||||||
|
✓ response status is 200
|
||||||
|
✓ response has cookie 'grafana_session' with 32 characters
|
||||||
|
|
||||||
|
█ batch tsdb requests
|
||||||
|
|
||||||
|
✓ response status is 200
|
||||||
|
|
||||||
|
checks.....................: 100.00% ✓ 364 ✗ 0
|
||||||
|
data_received..............: 4.0 MB 402 kB/s
|
||||||
|
data_sent..................: 120 kB 12 kB/s
|
||||||
|
group_duration.............: avg=84.95ms min=31.49ms med=90.28ms max=120.08ms p(90)=118.15ms p(95)=118.47ms
|
||||||
|
http_req_blocked...........: avg=1.63ms min=2.18µs med=1.1ms max=10.94ms p(90)=3.34ms p(95)=4.28ms
|
||||||
|
http_req_connecting........: avg=1.37ms min=0s med=902.58µs max=10.47ms p(90)=2.95ms p(95)=3.82ms
|
||||||
|
http_req_duration..........: avg=58.61ms min=3.86ms med=60.49ms max=114.21ms p(90)=92.61ms p(95)=100.17ms
|
||||||
|
http_req_receiving.........: avg=36µs min=9.78µs med=31.17µs max=234.69µs p(90)=61.58µs p(95)=72.95µs
|
||||||
|
http_req_sending...........: avg=361.51µs min=19.57µs med=181.38µs max=10.56ms p(90)=642.88µs p(95)=845.28µs
|
||||||
|
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
|
||||||
|
http_req_waiting...........: avg=58.22ms min=3.8ms med=59.7ms max=114.09ms p(90)=92.45ms p(95)=100.02ms
|
||||||
|
http_reqs..................: 382 38.199516/s
|
||||||
|
iteration_duration.........: avg=975.79ms min=7.98µs med=1.08s max=1.11s p(90)=1.09s p(95)=1.11s
|
||||||
|
iterations.................: 18 1.799977/s
|
||||||
|
vus........................: 2 min=2 max=2
|
||||||
|
vus_max....................: 2 min=2 max=2
|
||||||
|
```
|
67
devenv/docker/loadtest/auth_token_test.js
Normal file
67
devenv/docker/loadtest/auth_token_test.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { sleep, check, group } from 'k6';
|
||||||
|
import { createClient, createBasicAuthClient } from './modules/client.js';
|
||||||
|
import { createTestOrgIfNotExists, createTestdataDatasourceIfNotExists } from './modules/util.js';
|
||||||
|
|
||||||
|
export let options = {
|
||||||
|
noCookiesReset: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint = __ENV.URL || 'http://localhost:3000'
|
||||||
|
const client = createClient(endpoint)
|
||||||
|
|
||||||
|
export const setup = () => {
|
||||||
|
const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin');
|
||||||
|
createTestOrgIfNotExists(basicAuthClient);
|
||||||
|
const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient);
|
||||||
|
return {datasourceId: datasourceId};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (data) => {
|
||||||
|
group("user auth token test", () => {
|
||||||
|
if (__ITER === 0) {
|
||||||
|
group("user authenticates thru ui with username and password", () => {
|
||||||
|
let res = client.ui.login('admin', 'admin');
|
||||||
|
|
||||||
|
check(res, {
|
||||||
|
'response status is 200': (r) => r.status === 200,
|
||||||
|
'response has cookie \'grafana_session\' with 32 characters': (r) => r.cookies.grafana_session[0].value.length === 32,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__ITER !== 0) {
|
||||||
|
group("batch tsdb requests", () => {
|
||||||
|
const batchCount = 20;
|
||||||
|
const requests = [];
|
||||||
|
const payload = {
|
||||||
|
from: '1547765247624',
|
||||||
|
to: '1547768847624',
|
||||||
|
queries: [{
|
||||||
|
refId: 'A',
|
||||||
|
scenarioId: 'random_walk',
|
||||||
|
intervalMs: 10000,
|
||||||
|
maxDataPoints: 433,
|
||||||
|
datasourceId: data.datasourceId,
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
requests.push({ method: 'GET', url: '/api/annotations?dashboardId=2074&from=1548078832772&to=1548082432772' });
|
||||||
|
|
||||||
|
for (let n = 0; n < batchCount; n++) {
|
||||||
|
requests.push({ method: 'POST', url: '/api/tsdb/query', body: payload });
|
||||||
|
}
|
||||||
|
|
||||||
|
let responses = client.batch(requests);
|
||||||
|
for (let n = 0; n < batchCount; n++) {
|
||||||
|
check(responses[n], {
|
||||||
|
'response status is 200': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const teardown = (data) => {}
|
183
devenv/docker/loadtest/modules/client.js
Normal file
183
devenv/docker/loadtest/modules/client.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import http from "k6/http";
|
||||||
|
import encoding from 'k6/encoding';
|
||||||
|
|
||||||
|
export const UIEndpoint = class UIEndpoint {
|
||||||
|
constructor(httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
login(username, pwd) {
|
||||||
|
const payload = { user: username, password: pwd };
|
||||||
|
return this.httpClient.formPost('/login', payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DatasourcesEndpoint = class DatasourcesEndpoint {
|
||||||
|
constructor(httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
getById(id) {
|
||||||
|
return this.httpClient.get(`/datasources/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByName(name) {
|
||||||
|
return this.httpClient.get(`/datasources/name/${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(payload) {
|
||||||
|
return this.httpClient.post(`/datasources`, JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id) {
|
||||||
|
return this.httpClient.delete(`/datasources/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrganizationsEndpoint = class OrganizationsEndpoint {
|
||||||
|
constructor(httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
getById(id) {
|
||||||
|
return this.httpClient.get(`/orgs/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByName(name) {
|
||||||
|
return this.httpClient.get(`/orgs/name/${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(name) {
|
||||||
|
let payload = {
|
||||||
|
name: name,
|
||||||
|
};
|
||||||
|
return this.httpClient.post(`/orgs`, JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id) {
|
||||||
|
return this.httpClient.delete(`/orgs/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GrafanaClient = class GrafanaClient {
|
||||||
|
constructor(httpClient) {
|
||||||
|
httpClient.onBeforeRequest = this.onBeforeRequest;
|
||||||
|
this.raw = httpClient;
|
||||||
|
this.ui = new UIEndpoint(httpClient);
|
||||||
|
this.orgs = new OrganizationsEndpoint(httpClient.withUrl('/api'));
|
||||||
|
this.datasources = new DatasourcesEndpoint(httpClient.withUrl('/api'));
|
||||||
|
}
|
||||||
|
|
||||||
|
batch(requests) {
|
||||||
|
return this.raw.batch(requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
withOrgId(orgId) {
|
||||||
|
this.orgId = orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeRequest(params) {
|
||||||
|
if (this.orgId && this.orgId > 0) {
|
||||||
|
params = params.headers || {};
|
||||||
|
params.headers["X-Grafana-Org-Id"] = this.orgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BaseClient = class BaseClient {
|
||||||
|
constructor(url, subUrl) {
|
||||||
|
if (url.endsWith('/')) {
|
||||||
|
url = url.substring(0, url.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subUrl.endsWith('/')) {
|
||||||
|
subUrl = subUrl.substring(0, subUrl.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.url = url + subUrl;
|
||||||
|
this.onBeforeRequest = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
withUrl(subUrl) {
|
||||||
|
return new BaseClient(this.url, subUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeRequest(params) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get(url, params) {
|
||||||
|
params = params || {};
|
||||||
|
this.beforeRequest(params);
|
||||||
|
this.onBeforeRequest(params);
|
||||||
|
return http.get(this.url + url, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
formPost(url, body, params) {
|
||||||
|
params = params || {};
|
||||||
|
this.beforeRequest(params);
|
||||||
|
this.onBeforeRequest(params);
|
||||||
|
return http.post(this.url + url, body, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
post(url, body, params) {
|
||||||
|
params = params || {};
|
||||||
|
params.headers = params.headers || {};
|
||||||
|
params.headers['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
this.beforeRequest(params);
|
||||||
|
this.onBeforeRequest(params);
|
||||||
|
return http.post(this.url + url, body, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(url, params) {
|
||||||
|
params = params || {};
|
||||||
|
this.beforeRequest(params);
|
||||||
|
this.onBeforeRequest(params);
|
||||||
|
return http.del(this.url + url, null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch(requests) {
|
||||||
|
for (let n = 0; n < requests.length; n++) {
|
||||||
|
let params = requests[n].params || {};
|
||||||
|
params.headers = params.headers || {};
|
||||||
|
params.headers['Content-Type'] = 'application/json';
|
||||||
|
this.beforeRequest(params);
|
||||||
|
this.onBeforeRequest(params);
|
||||||
|
requests[n].params = params;
|
||||||
|
requests[n].url = this.url + requests[n].url;
|
||||||
|
if (requests[n].body) {
|
||||||
|
requests[n].body = JSON.stringify(requests[n].body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.batch(requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BasicAuthClient extends BaseClient {
|
||||||
|
constructor(url, subUrl, username, password) {
|
||||||
|
super(url, subUrl);
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
withUrl(subUrl) {
|
||||||
|
return new BasicAuthClient(this.url, subUrl, this.username, this.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeRequest(params) {
|
||||||
|
params = params || {};
|
||||||
|
params.headers = params.headers || {};
|
||||||
|
let token = `${this.username}:${this.password}`;
|
||||||
|
params.headers['Authorization'] = `Basic ${encoding.b64encode(token)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createClient = (url) => {
|
||||||
|
return new GrafanaClient(new BaseClient(url, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createBasicAuthClient = (url, username, password) => {
|
||||||
|
return new GrafanaClient(new BasicAuthClient(url, '', username, password));
|
||||||
|
}
|
30
devenv/docker/loadtest/modules/util.js
Normal file
30
devenv/docker/loadtest/modules/util.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export const createTestOrgIfNotExists = (client) => {
|
||||||
|
let res = client.orgs.getByName('k6');
|
||||||
|
if (res.status === 404) {
|
||||||
|
res = client.orgs.create('k6');
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('Expected 200 response status when creating org');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.withOrgId(res.json().orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTestdataDatasourceIfNotExists = (client) => {
|
||||||
|
const payload = {
|
||||||
|
access: 'proxy',
|
||||||
|
isDefault: false,
|
||||||
|
name: 'k6-testdata',
|
||||||
|
type: 'testdata',
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = client.datasources.getByName(payload.name);
|
||||||
|
if (res.status === 404) {
|
||||||
|
res = client.datasources.create(payload);
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('Expected 200 response status when creating datasource');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json().id;
|
||||||
|
}
|
20
devenv/docker/loadtest/run.sh
Executable file
20
devenv/docker/loadtest/run.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#/bin/bash
|
||||||
|
|
||||||
|
PWD=$(pwd)
|
||||||
|
|
||||||
|
run() {
|
||||||
|
duration='15m'
|
||||||
|
|
||||||
|
while getopts ":d:" o; do
|
||||||
|
case "${o}" in
|
||||||
|
d)
|
||||||
|
duration=${OPTARG}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
docker run -t --network=host -v $PWD:/src --rm -i loadimpact/k6:master run --vus 2 --duration $duration src/auth_token_test.js
|
||||||
|
}
|
||||||
|
|
||||||
|
run "$@"
|
Loading…
Reference in New Issue
Block a user