Skip to content

Commit 78641ee

Browse files
committed
add scenario 10
1 parent 3d85077 commit 78641ee

2 files changed

Lines changed: 389 additions & 0 deletions

File tree

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
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 = '/v3/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+
'email': []
194+
};
195+
196+
let randomSuffix = Math.floor(Math.random() * 1_000_000_001);
197+
for (var i = 0; i < emailCount; ++i) {
198+
data.email.push(`test${randomSuffix}${i}@example.com`);
199+
}
200+
201+
return data;
202+
}
203+
204+
function send(data, auth) {
205+
var options = {};
206+
if (auth) {
207+
options.headers = {
208+
'Authorization': `Bearer ${clientKey}`
209+
};
210+
}
211+
212+
return http.post(`${baseUrl}${data.endpoint}`, data.requestBody, options);
213+
}
214+
215+
function execute(data, auth) {
216+
var response = send(data, auth);
217+
218+
check(response, {
219+
'status is 200': r => r.status === 200,
220+
});
221+
}
222+
223+
async function encryptEnvelope(envelope, clientSecret) {
224+
const rawKey = encoding.b64decode(clientSecret);
225+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
226+
"encrypt",
227+
"decrypt",
228+
]);
229+
230+
const iv = crypto.getRandomValues(new Uint8Array(12));
231+
232+
const ciphertext = new Uint8Array(await crypto.subtle.encrypt(
233+
{
234+
name: "AES-GCM",
235+
iv: iv,
236+
},
237+
key,
238+
envelope
239+
));
240+
241+
const result = new Uint8Array(+(1 + iv.length + ciphertext.length));
242+
243+
// The version of the envelope format.
244+
result[0] = 1;
245+
246+
result.set(iv, 1);
247+
248+
// The tag is at the end of ciphertext.
249+
result.set(ciphertext, 1 + iv.length);
250+
251+
return result;
252+
}
253+
254+
async function decryptEnvelope(envelope, clientSecret) {
255+
const rawKey = encoding.b64decode(clientSecret);
256+
const rawData = encoding.b64decode(envelope);
257+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", true, [
258+
"encrypt",
259+
"decrypt",
260+
]);
261+
const length = rawData.byteLength;
262+
const iv = rawData.slice(0, 12);
263+
264+
const decrypted = await crypto.subtle.decrypt(
265+
{
266+
name: "AES-GCM",
267+
iv: iv,
268+
tagLength: 128
269+
},
270+
key,
271+
rawData.slice(12)
272+
);
273+
274+
275+
const decryptedResponse = String.fromCharCode.apply(String, new Uint8Array(decrypted.slice(16)));
276+
const response = JSON.parse(decryptedResponse);
277+
278+
return response;
279+
}
280+
281+
function getEnvelopeWithTimestamp(timestampArray, obj) {
282+
var randomBytes = new Uint8Array(8);
283+
crypto.getRandomValues(randomBytes);
284+
285+
var payload = stringToUint8Array(JSON.stringify(obj));
286+
287+
var envelope = new Uint8Array(timestampArray.length + randomBytes.length + payload.length);
288+
envelope.set(timestampArray);
289+
envelope.set(randomBytes, timestampArray.length);
290+
envelope.set(payload, timestampArray.length + randomBytes.length);
291+
292+
return envelope;
293+
294+
}
295+
function getEnvelope(obj) {
296+
var timestampArr = new Uint8Array(getTimestamp());
297+
return getEnvelopeWithTimestamp(timestampArr, obj);
298+
}
299+
300+
function getTimestamp() {
301+
const now = Date.now();
302+
return getTimestampFromTime(now);
303+
}
304+
305+
function getTimestampFromTime(time) {
306+
const res = new ArrayBuffer(8);
307+
const { hi, lo } = Get32BitPartsBE(time);
308+
const view = new DataView(res);
309+
view.setUint32(0, hi, false);
310+
view.setUint32(4, lo, false);
311+
return res;
312+
}
313+
314+
// http://anuchandy.blogspot.com/2015/03/javascript-how-to-extract-lower-32-bit.html
315+
function Get32BitPartsBE(bigNumber) {
316+
if (bigNumber > 9007199254740991) {
317+
// Max int that JavaScript can represent is 2^53.
318+
throw new Error('The 64-bit value is too big to be represented in JS :' + bigNumber);
319+
}
320+
321+
var bigNumberAsBinaryStr = bigNumber.toString(2);
322+
// Convert the above binary str to 64 bit (actually 52 bit will work) by padding zeros in the left
323+
var bigNumberAsBinaryStr2 = '';
324+
for (var i = 0; i < 64 - bigNumberAsBinaryStr.length; i++) {
325+
bigNumberAsBinaryStr2 += '0';
326+
};
327+
328+
bigNumberAsBinaryStr2 += bigNumberAsBinaryStr;
329+
330+
return {
331+
hi: parseInt(bigNumberAsBinaryStr2.substring(0, 32), 2),
332+
lo: parseInt(bigNumberAsBinaryStr2.substring(32), 2),
333+
};
334+
}
335+
336+
function stringToUint8Array(str) {
337+
const buffer = new ArrayBuffer(str.length);
338+
const view = new Uint8Array(buffer);
339+
for (var i = 0; i < str.length; i++) {
340+
view[i] = str.charCodeAt(i);
341+
}
342+
return view;
343+
}
344+
345+
async function createReq(obj) {
346+
var envelope = getEnvelope(obj);
347+
return encoding.b64encode((await encryptEnvelope(envelope, clientSecret)).buffer);
348+
};
349+
350+
async function generateRequestWithTime(obj) {
351+
var time = Date.now();
352+
var timestampArr = new Uint8Array(getTimestampFromTime(time));
353+
var requestBody = await createReqWithTimestamp(timestampArr, obj);
354+
var element = {
355+
time: time,
356+
requestBody: requestBody
357+
};
358+
359+
return element;
360+
}
361+
362+
async function generateTokenGenerateRequestWithTime() {
363+
let randomSuffix = Math.floor(Math.random() * 1_000_000_001);
364+
let requestData = { 'optout_check': 1, 'email': `test${randomSuffix}@example.com` };
365+
return await generateRequestWithTime(requestData);
366+
}
367+
368+
async function generateIdentityMapRequestWithTime(emailCount) {
369+
let data = generateIdentityMapRequest(emailCount);
370+
return await generateRequestWithTime(data);
371+
}
372+
373+
const generateSinceTimestampStr = () => {
374+
var date = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 /* 2 days ago */);
375+
var year = date.getFullYear();
376+
var month = (date.getMonth() + 1).toString().padStart(2, '0');
377+
var day = date.getDate().toString().padStart(2, '0');
378+
379+
return `${year}-${month}-${day}T00:00:00`;
380+
};
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-v3.js $COMMENT

0 commit comments

Comments
 (0)