1 import autocannon, { printResult } from 'autocannon'
2 import { program } from 'commander'
3 import { writeJson } from 'fs-extra'
4 import { Video, VideoPrivacy } from '@shared/models'
5 import { createMultipleServers, doubleFollow, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7 let servers: PeerTubeServer[]
9 let server: PeerTubeServer
14 .option('-o, --outfile [outfile]', 'Outfile')
15 .option('--grep [string]', 'Filter tests you want to execute')
16 .description('Run API REST benchmark')
19 const options = program.opts()
21 const outfile = options.outfile
24 .catch(err => console.error(err))
26 if (servers) return killallServers(servers)
29 function buildAuthorizationHeader () {
31 Authorization: 'Bearer ' + server.accessToken
35 function buildAPHeader () {
37 Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
41 function buildJSONHeader () {
43 'Content-Type': 'application/json'
47 async function run () {
48 console.log('Preparing server...')
54 title: 'AP - account peertube',
55 path: '/accounts/peertube',
56 headers: buildAPHeader(),
57 expecter: (body, status) => {
58 return status === 200 && body.startsWith('{"@context":')
63 path: '/videos/watch/' + video.uuid,
64 headers: buildAPHeader(),
65 expecter: (body, status) => {
66 return status === 200 && body.startsWith('{"@context":')
70 title: 'Misc - webfinger peertube',
71 path: '/.well-known/webfinger?resource=acct:peertube@' + server.host,
72 expecter: (body, status) => {
73 return status === 200 && body.startsWith('{"subject":')
77 title: 'API - unread notifications',
78 path: '/api/v1/users/me/notifications?start=0&count=0&unread=true',
79 headers: buildAuthorizationHeader(),
80 expecter: (_body, status) => {
86 path: '/api/v1/users/me',
87 headers: buildAuthorizationHeader(),
88 expecter: (body, status) => {
89 return status === 200 && body.startsWith('{"id":')
93 title: 'API - videos list',
94 path: '/api/v1/videos',
95 expecter: (body, status) => {
96 return status === 200 && body.startsWith('{"total":10')
100 title: 'API - video get',
101 path: '/api/v1/videos/' + video.uuid,
102 expecter: (body, status) => {
103 return status === 200 && body.startsWith('{"id":')
107 title: 'API - video captions',
108 path: '/api/v1/videos/' + video.uuid + '/captions',
109 expecter: (body, status) => {
110 return status === 200 && body.startsWith('{"total":4')
114 title: 'API - video threads',
115 path: '/api/v1/videos/' + video.uuid + '/comment-threads',
116 expecter: (body, status) => {
117 return status === 200 && body.startsWith('{"total":10')
121 title: 'API - video replies',
122 path: '/api/v1/videos/' + video.uuid + '/comment-threads/' + threadId,
123 expecter: (body, status) => {
124 return status === 200 && body.startsWith('{"comment":{')
128 title: 'HTML - video watch',
129 path: '/videos/watch/' + video.uuid,
130 expecter: (body, status) => {
131 return status === 200 && body.includes('<title>my super')
135 title: 'HTML - video embed',
136 path: '/videos/embed/' + video.uuid,
137 expecter: (body, status) => {
138 return status === 200 && body.includes('embed')
142 title: 'HTML - homepage',
144 expecter: (_body, status) => {
145 return status === 200
149 title: 'API - config',
150 path: '/api/v1/config',
151 expecter: (body, status) => {
152 return status === 200 && body.startsWith('{"client":')
156 title: 'API - views with token',
159 ...buildAuthorizationHeader(),
162 body: JSON.stringify({ currentTime: 2 }),
163 path: '/api/v1/videos/' + video.uuid + '/views',
164 expecter: (body, status) => {
165 return status === 204
169 title: 'API - views without token',
171 headers: buildJSONHeader(),
172 body: JSON.stringify({ currentTime: 2 }),
173 path: '/api/v1/videos/' + video.uuid + '/views',
174 expecter: (body, status) => {
175 return status === 204
179 if (!options.grep) return true
181 return t.title.includes(options.grep)
184 const finalResult: any[] = []
186 for (const test of tests) {
187 console.log('Running against %s.', test.path)
188 const testResult = await runBenchmark(test)
190 Object.assign(testResult, { title: test.title, path: test.path })
191 finalResult.push(testResult)
193 console.log(printResult(testResult))
196 if (outfile) await writeJson(outfile, finalResult)
199 function runBenchmark (options: {
203 headers?: { [ id: string ]: string }
206 const { method = 'GET', path, body, expecter, headers } = options
208 return new Promise((res, rej) => {
210 url: server.url + path,
219 onResponse: (status, body) => {
220 if (expecter(body, status) !== true) {
221 console.error('Expected result failed.', { body, status })
222 throw new Error('Invalid expectation')
227 }, (err, result) => {
228 if (err) return rej(err)
235 async function prepare () {
236 servers = await createMultipleServers(3, {
245 await setAccessTokensToServers(servers)
246 await doubleFollow(servers[0], servers[1])
247 await doubleFollow(servers[0], servers[2])
250 name: 'my super video',
255 privacy: VideoPrivacy.PUBLIC,
256 support: 'please give me a coffee',
257 description: 'my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3',
258 tags: [ 'tag1', 'tag2', 'tag3' ]
261 for (let i = 0; i < 10; i++) {
262 await server.videos.upload({ attributes: { ...attributes, name: 'my super video ' + i } })
265 const { data } = await server.videos.list()
266 video = data.find(v => v.name === 'my super video 1')
268 for (let i = 0; i < 10; i++) {
269 const text = 'my super first comment'
270 const created = await server.comments.createThread({ videoId: video.id, text })
271 threadId = created.id
273 const text1 = 'my super answer to thread 1'
274 const child = await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text1 })
276 const text2 = 'my super answer to answer of thread 1'
277 await server.comments.addReply({ videoId: video.id, toCommentId: child.id, text: text2 })
279 const text3 = 'my second answer to thread 1'
280 await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text3 })
283 for (const caption of [ 'ar', 'fr', 'en', 'zh' ]) {
284 await server.captions.add({
287 fixture: 'subtitle-good2.vtt'