cvat/tests/perf/scripts/libs/api/tus.js

159 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright (C) CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import http from 'k6/http';
import encoding from 'k6/encoding';
import { validateResponse } from '../../utils/validation.js';
import { BASE_URL } from '../../variables/constants.js';
const MAX_REQUEST_SIZE = 50 * 1024 * 1024; // 50 MB threshold
function tusUploadInit(token, taskId, filePath, fileSize) {
const res = http.post(`${BASE_URL}/tasks/${taskId}/data/`, null,
{
headers: {
Authorization: `Token ${token}`,
'Upload-Length': `${fileSize}`,
'Upload-Metadata': `filename ${encoding.b64encode(filePath)}`,
},
},
);
validateResponse(res, 201, 'Task data Upload-Start');
return res;
}
function tusUploadFinish(token, taskId, finishOpts, fileName) {
const url = `${BASE_URL}/tasks/${taskId}/data/?file=${fileName}`;
const res = http.post(url, JSON.stringify(finishOpts), {
headers: {
Authorization: `Token ${token}`,
'Upload-Finish': '',
'Content-Type': 'application/json',
},
});
validateResponse(res, 202, 'Task data Upload-Finish');
return res;
}
/**
* Uploads a local file to a CVAT task via TUS protocol. Uses a single batch.
* @param {string} token
* @param {string} taskId
* @param {string} fileName
*/
export function tusUploadFile(token, taskId, fileName, fileData) {
const fileSize = fileData.byteLength;
// 1. Upload-Init
let res = tusUploadInit(token, taskId, fileName, fileSize);
// 2. Create upload URL (CVAT sends Location header)
const uploadUrl = res.headers.Location;
if (!uploadUrl) {
throw new Error('CVAT did not return upload URL in Location header');
}
// 3. Send file chunks (well send it in one go here)
res = http.patch(uploadUrl, fileData, {
headers: {
Authorization: `Token ${token}`,
'Upload-Offset': '0',
'Content-Type': 'application/offset+octet-stream',
},
});
validateResponse(res, 204, 'Task data Upload-Chunk');
tusUploadFinish(token, taskId, { image_quality: 70 }, fileName);
}
// Single large file with TUS (Upload-Length + PATCH)
function tusUploadSingleFile(token, taskId, file) {
const size = file.bytes.byteLength;
let res = tusUploadInit(token, taskId, file.name, size);
const uploadUrl = res.headers.Location;
let offset = 0;
const CHUNK_SIZE = 10 * 1024 * 1024;
while (offset < size) {
const end = Math.min(offset + CHUNK_SIZE, size);
const chunk = file.bytes.slice(offset, end);
res = http.patch(uploadUrl, chunk, {
headers: {
Authorization: `Token ${token}`,
'Tus-Resumable': '1.0.0',
'Upload-Offset': String(offset),
'Content-Type': 'application/offset+octet-stream',
},
});
validateResponse(res, 204, 'Upload Patch');
offset = Number(res.headers['Upload-Offset']);
}
}
function splitFilesByRequests(files) {
const bulk = [];
const separate = [];
let total = 0;
for (const f of files) {
if (f.bytes.byteLength > MAX_REQUEST_SIZE) {
separate.push(f);
} else {
bulk.push(f);
}
total += f.bytes.byteLength;
}
const groups = [];
let current = [];
let currentSize = 0;
for (const f of bulk) {
if (currentSize + f.bytes.byteLength > MAX_REQUEST_SIZE) {
groups.push({ files: current, size: currentSize });
current = [];
currentSize = 0;
}
current.push(f);
currentSize += f.bytes.byteLength;
}
if (current.length) groups.push({ files: current, size: currentSize });
return { groups, separate, total };
}
function tusUploadFiles(
token,
taskId,
files, // [{ name: "a.jpg", bytes: ArrayBuffer }, ...]
finishOpts, // { image_quality, sorting_method, ... }
) {
const url = `${BASE_URL}/tasks/${taskId}/data`;
const { groups, separate } = splitFilesByRequests(files);
// If sorting method is predefined -> pass upload_file_order
if (
finishOpts.sorting_method &&
String(finishOpts.sorting_method).toLowerCase() === 'predefined'
) {
finishOpts.upload_file_order = files.map((f) => f.name);
}
// ---- Bulk files via Upload-Multiple
for (const { files: group } of groups) {
const formData = {};
group.forEach((f, i) => {
formData[`client_files[${i}]`] = http.file(f.bytes, f.name);
});
formData.image_quality = finishOpts.image_quality;
const res = http.post(url, formData, {
headers: {
Authorization: `Token ${token}`,
'Upload-Multiple': '',
},
});
validateResponse(res, 200, 'Upload-Multiple');
}
// ---- Large files via TUS
for (const f of separate) {
tusUploadSingleFile(token, taskId, f);
}
tusUploadFinish(token, taskId, finishOpts);
}
export default { tusUploadFiles, tusUploadFile };