benchmark.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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'
  6. let servers: PeerTubeServer[]
  7. // First server
  8. let server: PeerTubeServer
  9. let video: Video
  10. let threadId: number
  11. program
  12. .option('-o, --outfile [outfile]', 'Outfile')
  13. .option('--grep [string]', 'Filter tests you want to execute')
  14. .description('Run API REST benchmark')
  15. .parse(process.argv)
  16. const options = program.opts()
  17. const outfile = options.outfile
  18. run()
  19. .catch(err => console.error(err))
  20. .finally(() => {
  21. if (servers) return killallServers(servers)
  22. })
  23. function buildAuthorizationHeader () {
  24. return {
  25. Authorization: 'Bearer ' + server.accessToken
  26. }
  27. }
  28. function buildAPHeader () {
  29. return {
  30. Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
  31. }
  32. }
  33. function buildJSONHeader () {
  34. return {
  35. 'Content-Type': 'application/json'
  36. }
  37. }
  38. async function run () {
  39. console.log('Preparing server...')
  40. await prepare()
  41. const tests = [
  42. {
  43. title: 'AP - account peertube',
  44. path: '/accounts/peertube',
  45. headers: buildAPHeader(),
  46. expecter: (body, status) => {
  47. return status === 200 && body.startsWith('{"@context":')
  48. }
  49. },
  50. {
  51. title: 'AP - video',
  52. path: '/videos/watch/' + video.uuid,
  53. headers: buildAPHeader(),
  54. expecter: (body, status) => {
  55. return status === 200 && body.startsWith('{"@context":')
  56. }
  57. },
  58. {
  59. title: 'Misc - webfinger peertube',
  60. path: '/.well-known/webfinger?resource=acct:peertube@' + server.host,
  61. expecter: (body, status) => {
  62. return status === 200 && body.startsWith('{"subject":')
  63. }
  64. },
  65. {
  66. title: 'API - unread notifications',
  67. path: '/api/v1/users/me/notifications?start=0&count=0&unread=true',
  68. headers: buildAuthorizationHeader(),
  69. expecter: (_body, status) => {
  70. return status === 200
  71. }
  72. },
  73. {
  74. title: 'API - me',
  75. path: '/api/v1/users/me',
  76. headers: buildAuthorizationHeader(),
  77. expecter: (body, status) => {
  78. return status === 200 && body.startsWith('{"id":')
  79. }
  80. },
  81. {
  82. title: 'API - videos list',
  83. path: '/api/v1/videos',
  84. expecter: (body, status) => {
  85. return status === 200 && body.startsWith('{"total":10')
  86. }
  87. },
  88. {
  89. title: 'API - video get',
  90. path: '/api/v1/videos/' + video.uuid,
  91. expecter: (body, status) => {
  92. return status === 200 && body.startsWith('{"id":')
  93. }
  94. },
  95. {
  96. title: 'API - video captions',
  97. path: '/api/v1/videos/' + video.uuid + '/captions',
  98. expecter: (body, status) => {
  99. return status === 200 && body.startsWith('{"total":4')
  100. }
  101. },
  102. {
  103. title: 'API - video threads',
  104. path: '/api/v1/videos/' + video.uuid + '/comment-threads',
  105. expecter: (body, status) => {
  106. return status === 200 && body.startsWith('{"total":10')
  107. }
  108. },
  109. {
  110. title: 'API - video replies',
  111. path: '/api/v1/videos/' + video.uuid + '/comment-threads/' + threadId,
  112. expecter: (body, status) => {
  113. return status === 200 && body.startsWith('{"comment":{')
  114. }
  115. },
  116. {
  117. title: 'HTML - video watch',
  118. path: '/videos/watch/' + video.uuid,
  119. expecter: (body, status) => {
  120. return status === 200 && body.includes('<title>my super')
  121. }
  122. },
  123. {
  124. title: 'HTML - video embed',
  125. path: '/videos/embed/' + video.uuid,
  126. expecter: (body, status) => {
  127. return status === 200 && body.includes('embed')
  128. }
  129. },
  130. {
  131. title: 'HTML - homepage',
  132. path: '/',
  133. expecter: (_body, status) => {
  134. return status === 200
  135. }
  136. },
  137. {
  138. title: 'API - config',
  139. path: '/api/v1/config',
  140. expecter: (body, status) => {
  141. return status === 200 && body.startsWith('{"client":')
  142. }
  143. },
  144. {
  145. title: 'API - views with token',
  146. method: 'PUT',
  147. headers: {
  148. ...buildAuthorizationHeader(),
  149. ...buildJSONHeader()
  150. },
  151. body: JSON.stringify({ currentTime: 2 }),
  152. path: '/api/v1/videos/' + video.uuid + '/views',
  153. expecter: (body, status) => {
  154. return status === 204
  155. }
  156. },
  157. {
  158. title: 'API - views without token',
  159. method: 'POST',
  160. headers: buildJSONHeader(),
  161. body: JSON.stringify({ currentTime: 2 }),
  162. path: '/api/v1/videos/' + video.uuid + '/views',
  163. expecter: (body, status) => {
  164. return status === 204
  165. }
  166. }
  167. ].filter(t => {
  168. if (!options.grep) return true
  169. return t.title.includes(options.grep)
  170. })
  171. const finalResult: any[] = []
  172. for (const test of tests) {
  173. console.log('Running against %s.', test.path)
  174. const testResult = await runBenchmark(test)
  175. Object.assign(testResult, { title: test.title, path: test.path })
  176. finalResult.push(testResult)
  177. console.log(printResult(testResult))
  178. }
  179. if (outfile) await writeJson(outfile, finalResult)
  180. }
  181. function runBenchmark (options: {
  182. path: string
  183. method?: string
  184. body?: string
  185. headers?: { [ id: string ]: string }
  186. expecter: Function
  187. }) {
  188. const { method = 'GET', path, body, expecter, headers } = options
  189. return new Promise((res, rej) => {
  190. autocannon({
  191. url: server.url + path,
  192. method: method,
  193. body,
  194. connections: 20,
  195. headers,
  196. pipelining: 1,
  197. duration: 10,
  198. requests: [
  199. {
  200. onResponse: (status, body) => {
  201. if (expecter(body, status) !== true) {
  202. console.error('Expected result failed.', { body, status })
  203. throw new Error('Invalid expectation')
  204. }
  205. }
  206. }
  207. ]
  208. }, (err, result) => {
  209. if (err) return rej(err)
  210. return res(result)
  211. })
  212. })
  213. }
  214. async function prepare () {
  215. servers = await createMultipleServers(3, {
  216. rates_limit: {
  217. api: {
  218. max: 5_000_000
  219. }
  220. }
  221. })
  222. server = servers[0]
  223. await setAccessTokensToServers(servers)
  224. await doubleFollow(servers[0], servers[1])
  225. await doubleFollow(servers[0], servers[2])
  226. const attributes = {
  227. name: 'my super video',
  228. category: 2,
  229. nsfw: true,
  230. licence: 6,
  231. language: 'fr',
  232. privacy: VideoPrivacy.PUBLIC,
  233. support: 'please give me a coffee',
  234. description: 'my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3',
  235. tags: [ 'tag1', 'tag2', 'tag3' ]
  236. }
  237. for (let i = 0; i < 10; i++) {
  238. await server.videos.upload({ attributes: { ...attributes, name: 'my super video ' + i } })
  239. }
  240. const { data } = await server.videos.list()
  241. video = data.find(v => v.name === 'my super video 1')
  242. for (let i = 0; i < 10; i++) {
  243. const text = 'my super first comment'
  244. const created = await server.comments.createThread({ videoId: video.id, text })
  245. threadId = created.id
  246. const text1 = 'my super answer to thread 1'
  247. const child = await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text1 })
  248. const text2 = 'my super answer to answer of thread 1'
  249. await server.comments.addReply({ videoId: video.id, toCommentId: child.id, text: text2 })
  250. const text3 = 'my second answer to thread 1'
  251. await server.comments.addReply({ videoId: video.id, toCommentId: threadId, text: text3 })
  252. }
  253. for (const caption of [ 'ar', 'fr', 'en', 'zh' ]) {
  254. await server.captions.add({
  255. language: caption,
  256. videoId: video.id,
  257. fixture: 'subtitle-good2.vtt'
  258. })
  259. }
  260. }