Skip to content

Commit f42716c

Browse files
committed
add scenario 12
1 parent 23ea5f5 commit f42716c

2 files changed

Lines changed: 391 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)