-
Notifications
You must be signed in to change notification settings - Fork 0
/
uploads.js
163 lines (143 loc) · 4.68 KB
/
uploads.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* eslint-disable import/no-unresolved */
import http from 'k6/http'
import { check, fail } from 'k6'
import { randomBytes } from 'k6/crypto'
// Configuration
// -------------
// Upload creation endpoint
const ENDPOINT = 'https://tusd.tusdemo.net/files/'
// Size of a single upload in bytes
const UPLOAD_LENGTH = 1024 * 1024
// Number of bytes to upload in a single request. If it's <= UPLOAD_LENGTH, a single request
// is used to transfer the data. Otherwise, multiple requests are used.
const REQUEST_PAYLOAD_SIZE = UPLOAD_LENGTH
// If true, the upload data will be included in the upload creation request. Otherwise,
// an empty upload creation request is issued.
const CREATION_WITH_DATA = false
// If true, a HEAD request will be sent after each PATCH request, simulating a complete upload
// resumption.
const RETRIEVE_OFFSET_BETWEEN_REQUESTS = false
// Number of concurrent simulated users
const VIRTUAL_USERS = 2
// Number of sequential uploads per simulated user
const UPLOADS_PER_VIRTUAL_USER = 10
/** @type Uint8Array[] */
const PAYLOADS = []
for (let offset = 0; offset < UPLOAD_LENGTH;) {
const payloadSize = Math.min(UPLOAD_LENGTH - offset, REQUEST_PAYLOAD_SIZE)
const payload = new Uint8Array(randomBytes(payloadSize))
PAYLOADS.push(payload)
offset += payloadSize
}
export const options = {
scenarios: {
contacts: {
executor : 'per-vu-iterations',
vus : VIRTUAL_USERS,
iterations : UPLOADS_PER_VIRTUAL_USER,
maxDuration: '30s',
},
},
}
/**
* @param {string} endpoint
* @param {Uint8Array?} payload
* @param {Boolean} completesUpload
*/
function uploadCreation (endpoint, payload = null, completesUpload = false) {
const body = payload !== null ? payload.buffer : null
/** @type Record<string, string> */
const headers = {
'Upload-Complete' : completesUpload ? '?1' : '?0',
'Upload-Draft-Interop-Version': '5',
}
const res = http.post(endpoint, body, {
headers,
})
if (
!check(res, {
'response code was 201' : (r) => r.status === 201,
'response includes upload URL' : (r) => (r.headers['Location'] || '').length > 0,
'response includes upload offset': (r) => payload == null
|| r.headers['Upload-Offset'] === `${payload.length}`,
})
) {
fail('upload creation failed')
}
// TODO: Potentially merge with creation URL, if the upload URL is not absolute
const uploadUrl = res.headers['Location']
const offset = payload !== null ? parseInt(res.headers['Upload-Offset'], 10) : 0
return { uploadUrl, offset }
}
/**
* @param {string} uploadUrl
* @param {number} offset
* @param {Uint8Array} payload
* @param {Boolean} completesUpload
*/
function uploadAppend (uploadUrl, offset, payload, completesUpload) {
// We must pass the ArrayBuffer, not the typed array to `http.patch` as a body.
const res = http.patch(uploadUrl, payload.buffer, {
headers: {
'Upload-Offset' : `${offset}`,
'Upload-Complete' : completesUpload ? '?1' : '?0',
'Upload-Draft-Interop-Version': '5',
},
})
if (
!check(res, {
'response code was 204' : (r) => r.status === 204,
'response includes upload offset': (r) => r.headers['Upload-Offset'] === `${offset + payload.length}`,
})
) {
fail('upload appending failed')
}
const newOffset = offset + payload.length
return newOffset
}
/**
* @param {string} uploadUrl
* @param {number} expectedOffset
*/
function offsetRetrieve (uploadUrl, expectedOffset) {
const res = http.head(uploadUrl, {
headers: {
'Upload-Draft-Interop-Version': '5',
},
})
if (
!check(res, {
'response code was 204' : (r) => r.status === 200,
'response includes upload offset': (r) => r.headers['Upload-Offset'] === `${expectedOffset}`,
})
) {
fail('offset retrieve failed')
}
}
// The function that defines VU logic.
//
// See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more
// about authoring k6 scripts.
//
export default function run () {
// Shallow copy of payloads
const payloads = [...PAYLOADS]
const upload = uploadCreation(ENDPOINT, CREATION_WITH_DATA ? payloads.shift() : null, CREATION_WITH_DATA && PAYLOADS.length === 1)
const { uploadUrl } = upload
let { offset } = upload
for (const payload of payloads) {
offset = uploadAppend(uploadUrl, offset, payload, payloads.indexOf(payload) === payloads.length - 1)
if (RETRIEVE_OFFSET_BETWEEN_REQUESTS) {
offsetRetrieve(uploadUrl, offset)
}
}
if (
!check(offset, {
'offset matches upload length': (o) => o === UPLOAD_LENGTH,
})
) {
if (offset !== UPLOAD_LENGTH) {
fail('upload was not completed')
}
}
}