Просмотр исходного кода

user auth token load tests using k6.io

Marcus Efraimsson 7 лет назад
Родитель
Сommit
6454de74e4

+ 69 - 0
devenv/docker/loadtest/README.md

@@ -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 - 0
devenv/docker/loadtest/auth_token_test.js

@@ -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 - 0
devenv/docker/loadtest/modules/client.js

@@ -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 - 0
devenv/docker/loadtest/modules/util.js

@@ -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 - 0
devenv/docker/loadtest/run.sh

@@ -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 "$@"