Skip to content

Commit 0e12ce4

Browse files
committed
add scenario 8
1 parent bc54c46 commit 0e12ce4

4 files changed

Lines changed: 795 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)