Skip to content

Commit 40a9cb5

Browse files
committed
add v2 scenario 9
1 parent 2e6fe57 commit 40a9cb5

2 files changed

Lines changed: 383 additions & 0 deletions

File tree

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
import encoding from 'k6/encoding';
2+
import { check } from 'k6';
3+
import http from 'k6/http';
4+
5+
const baseUrl = __ENV.OPERATOR_URL;
6+
const clientSecret = __ENV.CLIENT_SECRET;
7+
const clientKey = __ENV.CLIENT_KEY;
8+
9+
const generateRPS = 25000;
10+
const refreshRPS = 25000;
11+
12+
// Low RPS for identity map to mimic real prod traffic.
13+
// DIIs are generated fresh on every request (no 45s caching) so each call
14+
// exercises a unique set of identifiers.
15+
const identityMapRPS = 3;
16+
17+
const warmUpTime = '10m'
18+
const testDuration = '20m'
19+
20+
export const options = {
21+
insecureSkipTLSVerify: true,
22+
noConnectionReuse: false,
23+
scenarios: {
24+
// Warmup scenarios
25+
tokenGenerateWarmup: {
26+
executor: 'ramping-arrival-rate',
27+
exec: 'tokenGenerate',
28+
timeUnit: '1s',
29+
preAllocatedVUs: 200,
30+
maxVUs: 400,
31+
stages: [
32+
{ duration: warmUpTime, target: generateRPS}
33+
],
34+
},
35+
tokenRefreshWarmup: {
36+
executor: 'ramping-arrival-rate',
37+
exec: 'tokenRefresh',
38+
timeUnit: '1s',
39+
preAllocatedVUs: 200,
40+
maxVUs: 400,
41+
stages: [
42+
{ duration: warmUpTime, target: refreshRPS}
43+
],
44+
},
45+
identityMapWarmup: {
46+
executor: 'ramping-arrival-rate',
47+
exec: 'identityMap',
48+
timeUnit: '1s',
49+
preAllocatedVUs: 5,
50+
maxVUs: 10,
51+
stages: [
52+
{ duration: warmUpTime, target: identityMapRPS}
53+
],
54+
},
55+
// Actual testing scenarios
56+
tokenGenerate: {
57+
executor: 'constant-arrival-rate',
58+
exec: 'tokenGenerate',
59+
rate: generateRPS,
60+
timeUnit: '1s',
61+
preAllocatedVUs: 200,
62+
maxVUs: 400,
63+
duration: testDuration,
64+
gracefulStop: '0s',
65+
startTime: warmUpTime,
66+
},
67+
tokenRefresh: {
68+
executor: 'constant-arrival-rate',
69+
exec: 'tokenRefresh',
70+
rate: refreshRPS,
71+
timeUnit: '1s',
72+
preAllocatedVUs: 200,
73+
maxVUs: 400,
74+
duration: testDuration,
75+
gracefulStop: '0s',
76+
startTime: warmUpTime,
77+
},
78+
identityMap: {
79+
executor: 'constant-arrival-rate',
80+
exec: 'identityMap',
81+
rate: identityMapRPS,
82+
timeUnit: '1s',
83+
preAllocatedVUs: 5,
84+
maxVUs: 10,
85+
duration: testDuration,
86+
gracefulStop: '0s',
87+
startTime: warmUpTime,
88+
},
89+
},
90+
// So we get count in the summary, to demonstrate different metrics are different
91+
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'count'],
92+
thresholds: {
93+
// Intentionally empty. We'll programatically define our bogus
94+
// thresholds (to generate the sub-metrics) below. In your real-world
95+
// load test, you can add any real threshoulds you want here.
96+
}
97+
};
98+
99+
// https://community.k6.io/t/multiple-scenarios-metrics-per-each/1314/3
100+
for (let key in options.scenarios) {
101+
// Each scenario automaticall tags the metrics it generates with its own name
102+
let thresholdName = `http_req_duration{scenario:${key}}`;
103+
// Check to prevent us from overwriting a threshold that already exists
104+
if (!options.thresholds[thresholdName]) {
105+
options.thresholds[thresholdName] = [];
106+
}
107+
// 'max>=0' is a bogus condition that will always be fulfilled
108+
options.thresholds[thresholdName].push('max>=0');
109+
}
110+
111+
export async function setup() {
112+
var token = await generateRefreshRequest();
113+
return {
114+
tokenGenerate: null,
115+
refreshToken: token
116+
};
117+
118+
async function generateRefreshRequest() {
119+
let randomSuffix = Math.floor(Math.random() * 1_000_000_001);
120+
let request = await createReq( {'optout_check': 1, 'email': `test${randomSuffix}@example.com`});
121+
var requestData = {
122+
endpoint: '/v2/token/generate',
123+
requestBody: request,
124+
}
125+
let response = await send(requestData, clientKey);
126+
let decrypt = await decryptEnvelope(response.body, clientSecret)
127+
return decrypt.body.refresh_token;
128+
};
129+
}
130+
131+
export function handleSummary(data) {
132+
return {
133+
'summary.json': JSON.stringify(data),
134+
}
135+
}
136+
137+
// Scenarios
138+
export async function tokenGenerate(data) {
139+
const endpoint = '/v2/token/generate';
140+
if (data.tokenGenerate == null) {
141+
var newData = await generateTokenGenerateRequestWithTime();
142+
data.tokenGenerate = newData;
143+
} else if (data.tokenGenerate.time < (Date.now() - 45000)) {
144+
data.tokenGenerate = await generateTokenGenerateRequestWithTime();
145+
}
146+
147+
var requestBody = data.tokenGenerate.requestBody;
148+
var tokenGenerateData = {
149+
endpoint: endpoint,
150+
requestBody: requestBody,
151+
}
152+
153+
execute(tokenGenerateData, true);
154+
}
155+
156+
export function tokenRefresh(data) {
157+
var requestBody = data.refreshToken;
158+
var refreshData = {
159+
endpoint: '/v2/token/refresh',
160+
requestBody: requestBody
161+
}
162+
163+
execute(refreshData, false);
164+
}
165+
166+
export async function identityMap() {
167+
// No caching: generate a fresh encrypted request with unique DIIs on every call.
168+
const requestData = await generateIdentityMapRequestWithTime(5000);
169+
170+
var identityMapData = {
171+
endpoint: '/v2/identity/map',
172+
requestBody: requestData.requestBody,
173+
}
174+
175+
execute(identityMapData, true);
176+
}
177+
178+
// Helpers
179+
async function createReqWithTimestamp(timestampArr, obj) {
180+
var envelope = getEnvelopeWithTimestamp(timestampArr, obj);
181+
return encoding.b64encode((await encryptEnvelope(envelope, clientSecret)).buffer);
182+
}
183+
184+
function generateIdentityMapRequest(emailCount) {
185+
var data = {
186+
'optout_check': 1,
187+
'email': []
188+
};
189+
190+
let randomSuffix = Math.floor(Math.random() * 1_000_000_001);
191+
for (var i = 0; i < emailCount; ++i) {
192+
data.email.push(`test${randomSuffix}${i}@example.com`);
193+
}
194+
195+
return data;
196+
}
197+
198+
function send(data, auth) {
199+
var options = {};
200+
if (auth) {
201+
options.headers = {
202+
'Authorization': `Bearer ${clientKey}`
203+
};
204+
}
205+
206+
return http.post(`${baseUrl}${data.endpoint}`, data.requestBody, options);
207+
}
208+
209+
function execute(data, auth) {
210+
var response = send(data, auth);
211+
212+
check(response, {
213+
'status is 200': r => r.status === 200,
214+
});
215+
}
216+
217+
async function encryptEnvelope(envelope, clientSecret) {
218+
const rawKey = encoding.b64decode(clientSecret);
219+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
220+
"encrypt",
221+
"decrypt",
222+
]);
223+
224+
const iv = crypto.getRandomValues(new Uint8Array(12));
225+
226+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt(
227+
{
228+
name: "AES-GCM",
229+
iv: iv,
230+
},
231+
key,
232+
envelope
233+
));
234+
235+
const result = new Uint8Array(+(1 + iv.length + ciphertext.length));
236+
237+
// The version of the envelope format.
238+
result[0] = 1;
239+
240+
result.set(iv, 1);
241+
242+
// The tag is at the end of ciphertext.
243+
result.set(ciphertext, 1 + iv.length);
244+
245+
return result;
246+
}
247+
248+
async function decryptEnvelope(envelope, clientSecret) {
249+
const rawKey = encoding.b64decode(clientSecret);
250+
const rawData = encoding.b64decode(envelope);
251+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
252+
"encrypt",
253+
"decrypt",
254+
]);
255+
const length = rawData.byteLength;
256+
const iv = rawData.slice(0, 12);
257+
258+
const decrypted = await crypto.subtle.decrypt(
259+
{
260+
name: "AES-GCM",
261+
iv: iv,
262+
tagLength: 128
263+
},
264+
key,
265+
rawData.slice(12)
266+
);
267+
268+
269+
const decryptedResponse = String.fromCharCode.apply(String, new Uint8Array(decrypted.slice(16)));
270+
const response = JSON.parse(decryptedResponse);
271+
272+
return response;
273+
}
274+
275+
function getEnvelopeWithTimestamp(timestampArray, obj) {
276+
var randomBytes = new Uint8Array(8);
277+
crypto.getRandomValues(randomBytes);
278+
279+
var payload = stringToUint8Array(JSON.stringify(obj));
280+
281+
var envelope = new Uint8Array(timestampArray.length + randomBytes.length + payload.length);
282+
envelope.set(timestampArray);
283+
envelope.set(randomBytes, timestampArray.length);
284+
envelope.set(payload, timestampArray.length + randomBytes.length);
285+
286+
return envelope;
287+
288+
}
289+
function getEnvelope(obj) {
290+
var timestampArr = new Uint8Array(getTimestamp());
291+
return getEnvelopeWithTimestamp(timestampArr, obj);
292+
}
293+
294+
function getTimestamp() {
295+
const now = Date.now();
296+
return getTimestampFromTime(now);
297+
}
298+
299+
function getTimestampFromTime(time) {
300+
const res = new ArrayBuffer(8);
301+
const { hi, lo } = Get32BitPartsBE(time);
302+
const view = new DataView(res);
303+
view.setUint32(0, hi, false);
304+
view.setUint32(4, lo, false);
305+
return res;
306+
}
307+
308+
// http://anuchandy.blogspot.com/2015/03/javascript-how-to-extract-lower-32-bit.html
309+
function Get32BitPartsBE(bigNumber) {
310+
if (bigNumber > 9007199254740991) {
311+
// Max int that JavaScript can represent is 2^53.
312+
throw new Error('The 64-bit value is too big to be represented in JS :' + bigNumber);
313+
}
314+
315+
var bigNumberAsBinaryStr = bigNumber.toString(2);
316+
// Convert the above binary str to 64 bit (actually 52 bit will work) by padding zeros in the left
317+
var bigNumberAsBinaryStr2 = '';
318+
for (var i = 0; i < 64 - bigNumberAsBinaryStr.length; i++) {
319+
bigNumberAsBinaryStr2 += '0';
320+
};
321+
322+
bigNumberAsBinaryStr2 += bigNumberAsBinaryStr;
323+
324+
return {
325+
hi: parseInt(bigNumberAsBinaryStr2.substring(0, 32), 2),
326+
lo: parseInt(bigNumberAsBinaryStr2.substring(32), 2),
327+
};
328+
}
329+
330+
function stringToUint8Array(str) {
331+
const buffer = new ArrayBuffer(str.length);
332+
const view = new Uint8Array(buffer);
333+
for (var i = 0; i < str.length; i++) {
334+
view[i] = str.charCodeAt(i);
335+
}
336+
return view;
337+
}
338+
339+
async function createReq(obj) {
340+
var envelope = getEnvelope(obj);
341+
return encoding.b64encode((await encryptEnvelope(envelope, clientSecret)).buffer);
342+
};
343+
344+
async function generateRequestWithTime(obj) {
345+
var time = Date.now();
346+
var timestampArr = new Uint8Array(getTimestampFromTime(time));
347+
var requestBody = await createReqWithTimestamp(timestampArr, obj);
348+
var element = {
349+
time: time,
350+
requestBody: requestBody
351+
};
352+
353+
return element;
354+
}
355+
356+
async function generateTokenGenerateRequestWithTime() {
357+
let randomSuffix = Math.floor(Math.random() * 1_000_000_001);
358+
let requestData = { 'optout_check': 1, 'email': `test${randomSuffix}@example.com` };
359+
return await generateRequestWithTime(requestData);
360+
}
361+
362+
async function generateIdentityMapRequestWithTime(emailCount) {
363+
let data = generateIdentityMapRequest(emailCount);
364+
return await generateRequestWithTime(data);
365+
}
366+
367+
const generateSinceTimestampStr = () => {
368+
var date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 /* 2 days ago */);
369+
var year = date.getFullYear();
370+
var month = (date.getMonth() + 1).toString().padStart(2, '0');
371+
var day = date.getDate().toString().padStart(2, '0');
372+
373+
return `${year}-${month}-${day}T00:00:00`;
374+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
COMMENT=$1
4+
5+
if [ "$#" -ne 1 ]; then
6+
COMMENT=$( date '+%F_%H:%M:%S' )
7+
fi
8+
9+
./start-named-test.sh k6-token-generate-refresh-identitymap-scenario-9.js $COMMENT

0 commit comments

Comments
 (0)