diff --git a/scenarios/grpc_car.js b/scenarios/grpc_car.js new file mode 100644 index 0000000..59cc96c --- /dev/null +++ b/scenarios/grpc_car.js @@ -0,0 +1,198 @@ +import datagen from 'k6/x/frostfs/datagen'; +import native from 'k6/x/frostfs/native'; +import logging from 'k6/x/frostfs/logging'; +import registry from 'k6/x/frostfs/registry'; +import { SharedArray } from 'k6/data'; +import { sleep } from 'k6'; +import { textSummary } from './libs/k6-summary-0.0.2.js'; +import { parseEnv } from './libs/env-parser.js'; +import { uuidv4 } from './libs/k6-utils-1.4.0.js'; + +parseEnv(); + +const obj_list = new SharedArray('obj_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).objects; +}); + +const container_list = new SharedArray('container_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).containers; +}); + +const read_size = JSON.parse(open(__ENV.PREGEN_JSON)).obj_size; +const summary_json = __ENV.SUMMARY_JSON || "/tmp/summary.json"; + +// Select random gRPC endpoint for current VU +const grpc_endpoints = __ENV.GRPC_ENDPOINTS.split(','); +const grpc_endpoint = grpc_endpoints[Math.floor(Math.random() * grpc_endpoints.length)]; +const grpc_client = native.connect(grpc_endpoint, '', __ENV.DIAL_TIMEOUT ? parseInt(__ENV.DIAL_TIMEOUT) : 5, __ENV.STREAM_TIMEOUT ? parseInt(__ENV.STREAM_TIMEOUT) : 60); +const log = logging.new().withField("endpoint", grpc_endpoint); + +const registry_enabled = !!__ENV.REGISTRY_FILE; +const obj_registry = registry_enabled ? registry.open(__ENV.REGISTRY_FILE) : undefined; + +const duration = __ENV.DURATION; + +const delete_age = __ENV.DELETE_AGE ? parseInt(__ENV.DELETE_AGE) : undefined; +let obj_to_delete_selector = undefined; +if (registry_enabled && delete_age) { + obj_to_delete_selector = registry.getSelector( + __ENV.REGISTRY_FILE, + "obj_to_delete", + __ENV.SELECTION_SIZE ? parseInt(__ENV.SELECTION_SIZE) : 0, + { + status: "created", + age: delete_age, + } + ); +} + + +const generator = datagen.generator(1024 * parseInt(__ENV.WRITE_OBJ_SIZE)); + +const scenarios = {}; + +const time_unit = __ENV.TIME_UNIT || '1s'; +const pre_alloc_write_vus = parseInt(__ENV.PRE_ALLOC_WRITERS || '0'); +const max_write_vus = parseInt(__ENV.MAX_WRITERS || pre_alloc_write_vus); +const write_rate = parseInt(__ENV.WRITE_RATE || '0'); +if (write_rate > 0) { + scenarios.write = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_write_vus, + maxVUs: max_write_vus, + rate: write_rate, + timeUnit: time_unit, + exec: 'obj_write', + gracefulStop: '5s', + }; +} + +const pre_alloc_read_vus = parseInt(__ENV.PRE_ALLOC_READERS || '0'); +const max_read_vus = parseInt(__ENV.MAX_READERS || pre_alloc_read_vus); +const read_rate = parseInt(__ENV.READ_RATE || '0'); +if (read_rate > 0) { + scenarios.read = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_write_vus, + maxVUs: max_read_vus, + rate: read_rate, + timeUnit: time_unit, + exec: 'obj_read', + gracefulStop: '5s', + }; +} + +const pre_alloc_delete_vus = parseInt(__ENV.PRE_ALLOC_DELETERS || '0'); +const max_delete_vus = parseInt(__ENV.MAX_DELETERS || pre_alloc_write_vus); +const delete_rate = parseInt(__ENV.DELETE_RATE || '0'); +if (delete_rate > 0) { + if (!obj_to_delete_selector) { + throw new Error('Positive DELETE worker number without a proper object selector'); + } + + scenarios.delete = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_delete_vus, + maxVUs: max_delete_vus, + rate: delete_rate, + timeUnit: time_unit, + exec: 'obj_delete', + gracefulStop: '5s', + }; +} + +export const options = { + scenarios, + setupTimeout: '5s', +}; + +export function setup() { + const total_pre_allocated_vu_count = pre_alloc_write_vus + pre_alloc_read_vus + pre_alloc_delete_vus; + const total_max_vu_count = max_read_vus + max_write_vus + max_delete_vus + + console.log(`Pregenerated containers: ${container_list.length}`); + console.log(`Pregenerated read object size: ${read_size}`); + console.log(`Pregenerated total objects: ${obj_list.length}`); + console.log(`Pre allocated reading VUs: ${pre_alloc_read_vus}`); + console.log(`Pre allocated writing VUs: ${pre_alloc_write_vus}`); + console.log(`Pre allocated deleting VUs: ${pre_alloc_delete_vus}`); + console.log(`Total pre allocated VUs: ${total_pre_allocated_vu_count}`); + console.log(`Max reading VUs: ${max_read_vus}`); + console.log(`Max writing VUs: ${max_write_vus}`); + console.log(`Max deleting VUs: ${max_delete_vus}`); + console.log(`Total max VUs: ${total_max_vu_count}`); + console.log(`Time unit: ${time_unit}`); + console.log(`Read rate: ${read_rate}`); + console.log(`Writing rate: ${write_rate}`); + console.log(`Delete rate: ${delete_rate}`); +} + +export function teardown(data) { + if (obj_registry) { + obj_registry.close(); + } +} + +export function handleSummary(data) { + return { + 'stdout': textSummary(data, { indent: ' ', enableColors: false }), + [summary_json]: JSON.stringify(data), + }; +} + +export function obj_write() { + if (__ENV.SLEEP_WRITE) { + sleep(__ENV.SLEEP_WRITE); + } + + const headers = { + unique_header: uuidv4() + }; + const container = container_list[Math.floor(Math.random() * container_list.length)]; + + const { payload, hash } = generator.genPayload(registry_enabled); + const resp = grpc_client.put(container, headers, payload); + if (!resp.success) { + log.withField("cid", container).error(resp.error); + return; + } + + if (obj_registry) { + obj_registry.addObject(container, resp.object_id, "", "", hash); + } +} + +export function obj_read() { + if (__ENV.SLEEP_READ) { + sleep(__ENV.SLEEP_READ); + } + + const obj = obj_list[Math.floor(Math.random() * obj_list.length)]; + const resp = grpc_client.get(obj.container, obj.object) + if (!resp.success) { + log.withFields({cid: obj.container, oid: obj.object}).error(resp.error); + } +} + +export function obj_delete() { + if (__ENV.SLEEP_DELETE) { + sleep(__ENV.SLEEP_DELETE); + } + + const obj = obj_to_delete_selector.nextObject(); + if (!obj) { + return; + } + + const resp = grpc_client.delete(obj.c_id, obj.o_id); + if (!resp.success) { + // Log errors except (2052 - object already deleted) + log.withFields({cid: obj.c_id, oid: obj.o_id}).error(resp.error); + return; + } + + obj_registry.deleteObject(obj.id); +} diff --git a/scenarios/s3_car.js b/scenarios/s3_car.js new file mode 100644 index 0000000..4d46a45 --- /dev/null +++ b/scenarios/s3_car.js @@ -0,0 +1,197 @@ +import datagen from 'k6/x/frostfs/datagen'; +import logging from 'k6/x/frostfs/logging'; +import registry from 'k6/x/frostfs/registry'; +import s3 from 'k6/x/frostfs/s3'; +import { SharedArray } from 'k6/data'; +import { sleep } from 'k6'; +import { textSummary } from './libs/k6-summary-0.0.2.js'; +import { parseEnv } from './libs/env-parser.js'; +import { uuidv4 } from './libs/k6-utils-1.4.0.js'; + +parseEnv(); + +const obj_list = new SharedArray('obj_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).objects; +}); + +const bucket_list = new SharedArray('bucket_list', function () { + return JSON.parse(open(__ENV.PREGEN_JSON)).buckets; +}); + +const read_size = JSON.parse(open(__ENV.PREGEN_JSON)).obj_size; +const summary_json = __ENV.SUMMARY_JSON || "/tmp/summary.json"; + +// Select random S3 endpoint for current VU +const s3_endpoints = __ENV.S3_ENDPOINTS.split(','); +const s3_endpoint = s3_endpoints[Math.floor(Math.random() * s3_endpoints.length)]; +const s3_client = s3.connect(`http://${s3_endpoint}`); +const log = logging.new().withField("endpoint", s3_endpoint); + +const registry_enabled = !!__ENV.REGISTRY_FILE; +const obj_registry = registry_enabled ? registry.open(__ENV.REGISTRY_FILE) : undefined; + +const duration = __ENV.DURATION; + +const delete_age = __ENV.DELETE_AGE ? parseInt(__ENV.DELETE_AGE) : undefined; +let obj_to_delete_selector = undefined; +if (registry_enabled && delete_age) { + obj_to_delete_selector = registry.getSelector( + __ENV.REGISTRY_FILE, + "obj_to_delete", + __ENV.SELECTION_SIZE ? parseInt(__ENV.SELECTION_SIZE) : 0, + { + status: "created", + age: delete_age, + } + ); +} + +const generator = datagen.generator(1024 * parseInt(__ENV.WRITE_OBJ_SIZE)); + +const scenarios = {}; + +const time_unit = __ENV.TIME_UNIT || '1s'; +const pre_alloc_write_vus = parseInt(__ENV.PRE_ALLOC_WRITERS || '0'); +const max_write_vus = parseInt(__ENV.MAX_WRITERS || pre_alloc_write_vus); +const write_rate = parseInt(__ENV.WRITE_RATE || '0'); +if (write_rate > 0) { + scenarios.write = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_write_vus, + maxVUs: max_write_vus, + rate: write_rate, + timeUnit: time_unit, + exec: 'obj_write', + gracefulStop: '5s', + }; +} + + +const pre_alloc_read_vus = parseInt(__ENV.PRE_ALLOC_READERS || '0'); +const max_read_vus = parseInt(__ENV.MAX_READERS || pre_alloc_read_vus); +const read_rate = parseInt(__ENV.READ_RATE || '0'); +if (read_rate > 0) { + scenarios.read = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_write_vus, + maxVUs: max_read_vus, + rate: read_rate, + timeUnit: time_unit, + exec: 'obj_read', + gracefulStop: '5s', + }; +} + + +const pre_alloc_delete_vus = parseInt(__ENV.PRE_ALLOC_DELETERS || '0'); +const max_delete_vus = parseInt(__ENV.MAX_DELETERS || pre_alloc_write_vus); +const delete_rate = parseInt(__ENV.DELETE_RATE || '0'); +if (delete_rate > 0) { + if (!obj_to_delete_selector) { + throw new Error('Positive DELETE worker number without a proper object selector'); + } + + scenarios.delete = { + executor: 'constant-arrival-rate', + duration: `${duration}s`, + preAllocatedVUs: pre_alloc_delete_vus, + maxVUs: max_delete_vus, + rate: delete_rate, + timeUnit: time_unit, + exec: 'obj_delete', + gracefulStop: '5s', + }; +} + +export const options = { + scenarios, + setupTimeout: '5s', +}; + +export function setup() { + const total_pre_allocated_vu_count = pre_alloc_write_vus + pre_alloc_read_vus + pre_alloc_delete_vus; + const total_max_vu_count = max_read_vus + max_write_vus + max_delete_vus + + console.log(`Pregenerated buckets: ${bucket_list.length}`); + console.log(`Pregenerated read object size: ${read_size}`); + console.log(`Pregenerated total objects: ${obj_list.length}`); + console.log(`Pre allocated reading VUs: ${pre_alloc_read_vus}`); + console.log(`Pre allocated writing VUs: ${pre_alloc_write_vus}`); + console.log(`Pre allocated deleting VUs: ${pre_alloc_delete_vus}`); + console.log(`Total pre allocated VUs: ${total_pre_allocated_vu_count}`); + console.log(`Max reading VUs: ${max_read_vus}`); + console.log(`Max writing VUs: ${max_write_vus}`); + console.log(`Max deleting VUs: ${max_delete_vus}`); + console.log(`Total max VUs: ${total_max_vu_count}`); + console.log(`Time unit: ${time_unit}`); + console.log(`Read rate: ${read_rate}`); + console.log(`Writing rate: ${write_rate}`); + console.log(`Delete rate: ${delete_rate}`); +} + +export function teardown(data) { + if (obj_registry) { + obj_registry.close(); + } +} + +export function handleSummary(data) { + return { + 'stdout': textSummary(data, { indent: ' ', enableColors: false }), + [summary_json]: JSON.stringify(data), + }; + } + +export function obj_write() { + if (__ENV.SLEEP_WRITE) { + sleep(__ENV.SLEEP_WRITE); + } + + const key = __ENV.OBJ_NAME || uuidv4(); + const bucket = bucket_list[Math.floor(Math.random() * bucket_list.length)]; + + const { payload, hash } = generator.genPayload(registry_enabled); + const resp = s3_client.put(bucket, key, payload); + if (!resp.success) { + log.withFields({bucket: bucket, key: key}).error(resp.error); + return; + } + + if (obj_registry) { + obj_registry.addObject("", "", bucket, key, hash); + } +} + +export function obj_read() { + if (__ENV.SLEEP_READ) { + sleep(__ENV.SLEEP_READ); + } + + const obj = obj_list[Math.floor(Math.random() * obj_list.length)]; + + const resp = s3_client.get(obj.bucket, obj.object); + if (!resp.success) { + log.withFields({bucket: obj.bucket, key: obj.object}).error(resp.error); + } +} + +export function obj_delete() { + if (__ENV.SLEEP_DELETE) { + sleep(__ENV.SLEEP_DELETE); + } + + const obj = obj_to_delete_selector.nextObject(); + if (!obj) { + return; + } + + const resp = s3_client.delete(obj.s3_bucket, obj.s3_key); + if (!resp.success) { + log.withFields({bucket: obj.s3_bucket, key: obj.s3_key, op: "DELETE"}).error(resp.error); + return; + } + + obj_registry.deleteObject(obj.id); +}