* More tools are listed at the end of this presentation.
** Uses goja, not V8 or Node, and doesn't have a global event loop yet.
*** I've used this on a project significantly more real than the example in this presentation, so that's a big reason we're looking at it today.
Yes, I should've used const instead of let everywhere.
import http from "k6/http"; import {check, fail, sleep} from "k6"; import {Trend} from "k6/metrics"; import {Normal} from [some gist URL]; // Browserified AndreasMadsen/distributions
// baseURL e.g. http://my-load-test-system.local/
const [baseURL, clientId, clientSecret] =
open('./config.txt').split("\n"),
// start on the second line of the doc, one per line
emails = open("./emails.csv").split("\n").slice(1),
pCorrectCredentials = 0.8, pRetryAfterFailedCreds = 0.5, pAbandonAfterHomeLoad = 0.15, pAddChallenge = 0.05, pAddAnotherActivity = 0.05, pIncludeChallengeDuration = 0.5, pIncludeChallengeMileage = 0.5, // start with larger units for more accurate // approximation of what challenges look like challengeMinHalfHours = 1, challengeMaxHalfHours = 80, challengeMinTenMiles = 1, challengeMaxTenMiles = 20, activitySpeed = new Normal(15, 3), activityMinSeconds = 180, activityMaxSeconds = 10800,
challengeThinkTime = new Normal(30, 10), activityThinkTime = new Normal(30, 10), secondActivityThinkTime = new Normal(10, 3),
challengeListResponseTime
= new Trend("challenge_list_response_time"),
activityListResponseTime
= new Trend("activity_list_response_time"),
userProfileResponseTime
= new Trend("user_profile_response_time");
export default function () {
let isIncorrectLogin = Math.random() >
pCorrectCredentials,
email = emails[getRandomInt(0, emails.length)];
let resLogin = http.post(baseURL + "oauth/token", {
"client_id": clientId,
"client_secret": clientSecret,
"grant_type": "password",
"username": email,
"password": isIncorrectLogin ? "seekrit" : "secret",
}, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
if (isIncorrectLogin) {
check(resLogin, {
"invalid login caught": (res) => res.status === 401
}) || fail("no 401 on invalid login");
if (Math.random() > pRetryAfterFailedCreds) {
return; // abandon on incorrect login
}
// log in the correct way this time
resLogin = http.post(baseURL + "oauth/token", {
"client_id": clientId,
// ...snip...
}
check(resLogin, { "login succeeded": (res) => res.status === 200 && res.json().access_token !== undefined, }) || fail("failed to log in");
let params = { headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " +
resLogin.json().access_token
}}, makeGet = function (path) {
return {
method: "GET", url: baseURL + path, params: params
};
};
let homeScreenResponses = http.batch({
"me": makeGet("api/me"),
"challenges": makeGet("api/me/challenges"),
"activities": makeGet("api/me/activities")
});
check(homeScreenResponses["me"], {"User profile loaded":
(res) => res.json().email === email})
|| fail("user profile email did not match");
check(homeScreenResponses["challenges"], {"Challenges list loaded":
(res) => res.status === 200})
|| fail("challenges list GET failed");
check(homeScreenResponses["activities"], {"Activities list loaded":
(res) => res.status === 200})
|| fail("activities list GET failed");
activityListResponseTime
.add(homeScreenResponses["activities"].timings.duration);
challengeListResponseTime
.add(homeScreenResponses["challenges"].timings.duration);
userProfileResponseTime
.add(homeScreenResponses["me"].timings.duration);
let pNextAction = Math.random();
if (pNextAction > (1 - pAbandonAfterHomeLoad)) {
return; // abandon here
} else if (pNextAction >
(1 - pAbandonAfterHomeLoad - pAddChallenge)){
// think time before creating challenge
sleep(fromDist(challengeThinkTime));
let startMonth = getRandomInt(1, 3),
endMonth = startMonth + getRandomInt(1, 2),
challengeRes = http.post(baseURL + "api/challenges", JSON.stringify({
"name": "Test Challenge",
"starts_at": "2020-0" + startMonth + "-01 00:00:00",
"ends_at": "2020-" + (endMonth >= 10
? endMonth : ("0" + endMonth)) + "-01 00:00:00",
"duration": Math.random() > pIncludeChallengeDuration ? null
: secondsToTime(
getRandomInt(challengeMinHalfHours, challengeMaxHalfHours) * 1800),
"distance_miles": Math.random() > pIncludeChallengeMileage ? null
: getRandomInt(challengeMinTenMiles, challengeMaxTenMiles) * 10
}), params);
check(challengeRes, {"challenge was created":
(res) => res.status === 201 && res.json().id
}) || fail("challenge create failed");
let challengeListRes = http.get(baseURL + "api/me/challenges", params);
check(challengeListRes, {
"challenge is in user challenge list": (res) => {
let json = res.json();
for (let i = 0; i < json.created.length; i++)
if (json.created[i].id === challengeRes.json().id)
return true;
return false;
}
}) || fail("challenge was not in user challenge list");
k6 is working on it...slowly...
It's Always DNS™