Skip to content

Commit 5c45a85

Browse files
committed
add v2 scenario 10
1 parent 78641ee commit 5c45a85

2 files changed

Lines changed: 390 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)