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 { createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7 let server: PeerTubeServer
12 .option('-o, --outfile [outfile]', 'Outfile')
13 .option('--grep [string]', 'Filter tests you want to execute')
14 .description('Run API REST benchmark')
17 const options = program.opts()
19 const outfile = options.outfile
22 .catch(err => console.error(err))
24 if (server) return killallServers([ server ])
27 function buildAuthorizationHeader () {
29 Authorization: 'Bearer ' + server.accessToken
33 function buildAPHeader () {
35 Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
39 async function run () {
40 console.log('Preparing server...')
46 title: 'AP - account peertube',
47 path: '/accounts/peertube',
48 headers: buildAPHeader(),
49 expecter: (body, status) => {
50 return status === 200 && body.startsWith('{"type":')
55 path: '/videos/watch/' + video.uuid,
56 headers: buildAPHeader(),
57 expecter: (body, status) => {
58 return status === 200 && body.startsWith('{"type":"Video"')
62 title: 'Misc - webfinger peertube',
63 path: '/.well-known/webfinger?resource=acct:peertube@' + server.host,
64 expecter: (body, status) => {
65 return status === 200 && body.startsWith('{"subject":')
69 title: 'API - unread notifications',
70 path: '/api/v1/users/me/notifications?start=0&count=0&unread=true',
71 headers: buildAuthorizationHeader(),
72 expecter: (_body, status) => {
78 path: '/api/v1/users/me',
79 headers: buildAuthorizationHeader(),
80 expecter: (body, status) => {
81 return status === 200 && body.startsWith('{"id":')
85 title: 'API - videos list',
86 path: '/api/v1/videos',
87 expecter: (body, status) => {
88 return status === 200 && body.startsWith('{"total":10')
92 title: 'API - video get',
93 path: '/api/v1/videos/' + video.uuid,
94 expecter: (body, status) => {
95 return status === 200 && body.startsWith('{"id":')
99 title: 'API - video captions',
100 path: '/api/v1/videos/' + video.uuid + '/captions',
101 expecter: (body, status) => {
102 return status === 200 && body.startsWith('{"total":4')
106 title: 'API - video threads',
107 path: '/api/v1/videos/' + video.uuid + '/comment-threads',
108 expecter: (body, status) => {
109 return status === 200 && body.startsWith('{"total":10')
113 title: 'API - video replies',
114 path: '/api/v1/videos/' + video.uuid + '/comment-threads/' + threadId,
115 expecter: (body, status) => {
116 return status === 200 && body.startsWith('{"comment":{')
120 title: 'HTML - video watch',
121 path: '/videos/watch/' + video.uuid,
122 expecter: (body, status) => {
123 return status === 200 && body.includes('<title>my super')
127 title: 'HTML - video embed',
128 path: '/videos/embed/' + video.uuid,
129 expecter: (body, status) => {
130 return status === 200 && body.includes('embed')
134 title: 'HTML - homepage',
136 expecter: (_body, status) => {
137 return status === 200
141 title: 'API - config',
142 path: '/api/v1/config',
143 expecter: (body, status) => {
144 return status === 200 && body.startsWith('{"client":')
148 if (!options.grep) return true
150 return t.title.includes(options.grep)
153 const finalResult: any[] = []
155 for (const test of tests) {
156 console.log('Running against %s.', test.path)
157 const testResult = await runBenchmark(test)
159 Object.assign(testResult, { title: test.title, path: test.path })
160 finalResult.push(testResult)
162 console.log(printResult(testResult))
165 if (outfile) await writeJson(outfile, finalResult)
168 function runBenchmark (options: {
170 headers?: { [ id: string ]: string }
173 const { path, expecter, headers } = options
175 return new Promise((res, rej) => {
177 url: server.url + path,
184 onResponse: (status, body) => {
185 if (expecter(body, status) !== true) {
186 console.error('Expected result failed.', { body, status })
187 throw new Error('Invalid expectation')
192 }, (err, result) => {
193 if (err) return rej(err)
200 async function prepare () {
201 server = await createSingleServer(1, {
208 await setAccessTokensToServers([ server ])
211 name: 'my super video',
216 privacy: VideoPrivacy.PUBLIC,
217 support: 'please give me a coffee',
218 description: 'my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3',
219 tags: [ 'tag1', 'tag2', 'tag3' ]
222 for (let i = 0; i < 10; i++) {
223 await server.videos.upload({ attributes: { ...attributes, name: 'my super video ' + i } })
226 const { data } = await server.videos.list()
227 video = data.find(v => v.name === 'my super video 1')
229 for (let i = 0; i < 10; i++) {
230 const text = 'my super first comment'
231 const created = await server.comments.createThread({ videoId: video.id, text })
232 threadId = created.id
234 const text1 = 'my super answer to thread 1'
235 const child = await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text1 })
237 const text2 = 'my super answer to answer of thread 1'
238 await server.comments.addReply({ videoId: video.id, toCommentId: child.id, text: text2 })
240 const text3 = 'my second answer to thread 1'
241 await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text3 })
244 for (const caption of [ 'ar', 'fr', 'en', 'zh' ]) {
245 await server.captions.add({
248 fixture: 'subtitle-good2.vtt'
252 return { server, video, threadId }