techempower.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. const { connect, constants } = require('lib/connection.js')
  2. const { createServer } = require('lib/tcp.js')
  3. const { createParser } = require('lib/http.js')
  4. const { start } = require('lib/stats.js')
  5. const { sjs, attr } = require('lib/stringify.js')
  6. function compile (sock, query) {
  7. return new Promise((resolve, reject) => {
  8. const result = sock.compile(query, err => {
  9. if (err) return reject(err)
  10. resolve(result)
  11. })
  12. })
  13. }
  14. async function onPGAuth (sock) {
  15. sock.getWorldById = await compile(sock, {
  16. formats: [{ format: 1, oid: INT4OID }],
  17. sql: 'select id, randomNumber from World where id = $1',
  18. fields: [{ format: 1, oid: INT4OID }],
  19. name: 's1',
  20. portal: '',
  21. maxRows: 0,
  22. params: [1]
  23. })
  24. sock.allFortunes = await compile(sock, {
  25. formats: [],
  26. sql: 'select * from Fortune',
  27. fields: [{ format: 1, oid: INT4OID }, { format: 0, oid: VARCHAROID }],
  28. name: 's2',
  29. portal: '',
  30. maxRows: 0,
  31. htmlEscape: true,
  32. params: []
  33. })
  34. sock.updateWorldById = await compile(sock, {
  35. formats: [{ format: 1, oid: INT4OID }],
  36. sql: 'update World set randomNumber = $2 where id = $1',
  37. fields: [],
  38. name: 's3',
  39. portal: '',
  40. maxRows: 0,
  41. params: [1, 1]
  42. })
  43. // TODO: we could actually build these on the fly for any number of updates
  44. sock.updateWorldById20 = await compile(sock, {
  45. formats: [{ format: 1, oid: INT4OID }],
  46. sql: `update world set randomnumber = CASE id
  47. when $1 then $2
  48. when $3 then $4
  49. when $5 then $6
  50. when $7 then $8
  51. when $9 then $10
  52. when $11 then $12
  53. when $13 then $14
  54. when $15 then $16
  55. when $17 then $18
  56. when $19 then $20
  57. when $21 then $22
  58. when $23 then $24
  59. when $25 then $26
  60. when $27 then $28
  61. when $29 then $30
  62. when $31 then $32
  63. when $33 then $34
  64. when $35 then $36
  65. when $37 then $38
  66. when $39 then $40
  67. else randomnumber
  68. end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19,$21,$23,$25,$27,$29,$31,$33,$35,$37,$39)
  69. `,
  70. fields: [],
  71. name: 's4',
  72. portal: '',
  73. maxRows: 0,
  74. params: Array(40).fill(0)
  75. })
  76. sock.updateWorldById15 = await compile(sock, {
  77. formats: [{ format: 1, oid: INT4OID }],
  78. sql: `update world set randomnumber = CASE id
  79. when $1 then $2
  80. when $3 then $4
  81. when $5 then $6
  82. when $7 then $8
  83. when $9 then $10
  84. when $11 then $12
  85. when $13 then $14
  86. when $15 then $16
  87. when $17 then $18
  88. when $19 then $20
  89. when $21 then $22
  90. when $23 then $24
  91. when $25 then $26
  92. when $27 then $28
  93. when $29 then $30
  94. else randomnumber
  95. end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19,$21,$23,$25,$27,$29)
  96. `,
  97. fields: [],
  98. name: 's5',
  99. portal: '',
  100. maxRows: 0,
  101. params: Array(30).fill(0)
  102. })
  103. sock.updateWorldById10 = await compile(sock, {
  104. formats: [{ format: 1, oid: INT4OID }],
  105. sql: `update world set randomnumber = CASE id
  106. when $1 then $2
  107. when $3 then $4
  108. when $5 then $6
  109. when $7 then $8
  110. when $9 then $10
  111. when $11 then $12
  112. when $13 then $14
  113. when $15 then $16
  114. when $17 then $18
  115. when $19 then $20
  116. else randomnumber
  117. end where id in ($1,$3,$5,$7,$9,$11,$13,$15,$17,$19)
  118. `,
  119. fields: [],
  120. name: 's6',
  121. portal: '',
  122. maxRows: 0,
  123. params: Array(20).fill(0)
  124. })
  125. sock.updateWorldById5 = await compile(sock, {
  126. formats: [{ format: 1, oid: INT4OID }],
  127. sql: `update world set randomnumber = CASE id
  128. when $1 then $2
  129. when $3 then $4
  130. when $5 then $6
  131. when $7 then $8
  132. when $9 then $10
  133. else randomnumber
  134. end where id in ($1,$3,$5,$7,$9)
  135. `,
  136. fields: [],
  137. name: 's7',
  138. portal: '',
  139. maxRows: 0,
  140. params: Array(10).fill(0)
  141. })
  142. sock.getCachedWorldById = await compile(sock, {
  143. formats: [{ format: 1, oid: INT4OID }],
  144. sql: 'select id, randomNumber from World where id = $1',
  145. fields: [{ format: 1, oid: INT4OID }],
  146. name: 's8',
  147. portal: '',
  148. maxRows: 0,
  149. params: [1]
  150. })
  151. clients.push(sock)
  152. stats.clients = clients.length
  153. if (clients.length === poolSize) onPGReady()
  154. }
  155. function onPGConnect (err, sock) {
  156. if (err) {
  157. just.error(err.stack)
  158. just.setTimeout(() => connect(tfb, onPGConnect), 1000)
  159. return
  160. }
  161. sock.onClose = () => {
  162. // todo: remove from pool and reconnect?
  163. just.error('pg.close')
  164. }
  165. sock.start(err => {
  166. if (err) return just.error(err.stack)
  167. sock.authenticate(err => {
  168. if (err) return just.error(err.stack)
  169. onPGAuth(sock).catch(err => just.error(err.stack))
  170. })
  171. })
  172. }
  173. const HEADER = '<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>'
  174. const FOOTER = '</table></body></html>'
  175. const S1 = '<tr><td>'
  176. const S2 = '</td><td>'
  177. const S3 = '</td></tr>'
  178. function getHTML (rows) {
  179. let html = HEADER
  180. for (const row of rows) {
  181. html += (S1 + row[0] + S2 + row[1] + S3)
  182. }
  183. return html + FOOTER
  184. }
  185. function sortByMessage (a, b) {
  186. if (a[1] > b[1]) return 1
  187. if (a[1] < b[1]) return -1
  188. return 0
  189. }
  190. const cache = {}
  191. function onHTTPConnect (sock) {
  192. stats.conn++
  193. const client = clients[sock.fd % clients.length]
  194. const rbuf = new ArrayBuffer(4096)
  195. const parser = createParser(rbuf)
  196. const { getWorldById, updateWorldById, allFortunes, updateWorldById20, updateWorldById15, updateWorldById10, updateWorldById5, getCachedWorldById } = client
  197. const message = { message: 'Hello, World!' }
  198. const text = 'Hello, World!'
  199. const extra = [0, 'Additional fortune added at request time.']
  200. const updateQueries = {
  201. 5: updateWorldById5,
  202. 10: updateWorldById10,
  203. 15: updateWorldById15,
  204. 20: updateWorldById20
  205. }
  206. const results = []
  207. let queries = 0
  208. let updates = 0
  209. function onUpdateMulti () {
  210. stats.qps++
  211. const json = JSON.stringify(results)
  212. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  213. stats.rps++
  214. }
  215. function onUpdateSingle () {
  216. stats.qps++
  217. updates++
  218. if (results.length === updates) {
  219. const json = JSON.stringify(results)
  220. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  221. stats.rps++
  222. }
  223. }
  224. function onUpdates () {
  225. stats.qps++
  226. const [id, randomNumber] = getWorldById.getRows()[0]
  227. results.push({ id, randomNumber })
  228. if (results.length === queries) {
  229. const query = updateQueries[queries]
  230. if (query) {
  231. let i = 0
  232. for (const row of results) {
  233. row.randomNumber = Math.ceil(Math.random() * 10000)
  234. query.params[i++] = row.id
  235. query.params[i++] = row.randomNumber
  236. }
  237. query.call(onUpdateMulti)
  238. return
  239. }
  240. updates = 0
  241. for (const row of results) {
  242. row.randomNumber = Math.ceil(Math.random() * 10000)
  243. updateWorldById.params[0] = row.id
  244. updateWorldById.params[1] = row.randomNumber
  245. updateWorldById.append(onUpdateSingle)
  246. }
  247. updateWorldById.send()
  248. }
  249. }
  250. function handleUpdates (qs) {
  251. const [, val] = qs.split('=')
  252. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  253. results.length = 0
  254. for (let i = 1; i < queries; i++) {
  255. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  256. getWorldById.append(onUpdates, (i % 20 === 0))
  257. }
  258. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  259. getWorldById.append(onUpdates)
  260. getWorldById.send()
  261. }
  262. function onMulti () {
  263. stats.qps++
  264. const [id, randomNumber] = getWorldById.getRows()[0]
  265. results.push({ id, randomNumber })
  266. if (results.length === queries) {
  267. const json = JSON.stringify(results)
  268. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  269. stats.rps++
  270. queries = 0
  271. }
  272. }
  273. function handleMulti (qs) {
  274. const [, val] = qs.split('=')
  275. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  276. results.length = 0
  277. for (let i = 1; i < queries; i++) {
  278. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  279. getWorldById.append(onMulti, (i % 20 === 0))
  280. }
  281. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  282. getWorldById.append(onMulti)
  283. getWorldById.send()
  284. }
  285. function onCached () {
  286. stats.qps++
  287. const row = getCachedWorldById.getRows()[0]
  288. const [id, randomNumber] = row
  289. const world = { id, randomNumber }
  290. cache[id] = world
  291. results.push(world)
  292. if (results.length === queries) {
  293. const json = JSON.stringify(results)
  294. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  295. stats.rps++
  296. queries = 0
  297. results.length = 0
  298. }
  299. }
  300. function handleCached (qs) {
  301. const [, val] = qs.split('=')
  302. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  303. for (let i = 1; i < queries; i++) {
  304. const id = Math.ceil(Math.random() * 10000)
  305. const row = cache[id]
  306. if (row) {
  307. results.push(row)
  308. } else {
  309. getCachedWorldById.params[0] = id
  310. getCachedWorldById.append(onCached, (i % 20 === 0))
  311. }
  312. }
  313. const id = Math.ceil(Math.random() * 10000)
  314. const row = cache[id]
  315. if (row) {
  316. results.push(row)
  317. } else {
  318. getCachedWorldById.params[0] = id
  319. getCachedWorldById.append(onCached)
  320. }
  321. if (results.length === queries) {
  322. const json = JSON.stringify(results)
  323. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  324. stats.rps++
  325. queries = 0
  326. results.length = 0
  327. return
  328. }
  329. getCachedWorldById.send()
  330. }
  331. function onFortunes () {
  332. stats.qps++
  333. const html = getHTML([extra, ...allFortunes.getRows()].sort(sortByMessage))
  334. sock.writeString(`${rHTML}${utf8Length(html)}${END}${html}`)
  335. stats.rps++
  336. }
  337. function onSingle () {
  338. stats.qps++
  339. const [id, randomNumber] = getWorldById.getRows()[0]
  340. const json = sDB({ id, randomNumber })
  341. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  342. stats.rps++
  343. }
  344. const queryPath = '/query'
  345. const updatePath = '/update'
  346. const cachePath = '/cached-world'
  347. const pathSep = '?'
  348. const END = '\r\n\r\n'
  349. const handlers = {
  350. '/json': () => {
  351. const json = sJSON(message)
  352. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  353. stats.rps++
  354. },
  355. '/fortunes': () => {
  356. allFortunes.call(onFortunes)
  357. },
  358. '/db': () => {
  359. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  360. getWorldById.call(onSingle)
  361. },
  362. '/plaintext': () => {
  363. sock.writeString(`${rTEXT}${text.length}${END}${text}`)
  364. stats.rps++
  365. },
  366. default: url => {
  367. const [path, qs] = url.split(pathSep)
  368. if (path === queryPath) {
  369. handleMulti(qs)
  370. return
  371. }
  372. if (path === updatePath) {
  373. handleUpdates(qs)
  374. return
  375. }
  376. if (path === cachePath) {
  377. handleCached(qs)
  378. return
  379. }
  380. sock.writeString(r404)
  381. stats.rps++
  382. }
  383. }
  384. parser.onRequests = count => {
  385. if (count > 1) {
  386. sock.writeString(`${rTEXT}${text.length}${END}${text}`.repeat(count))
  387. stats.rps += count
  388. return
  389. }
  390. const url = parser.url(0)
  391. const handler = (handlers[url] || handlers.default)
  392. handler(url)
  393. }
  394. sock.onData = bytes => parser.parse(bytes)
  395. sock.onClose = () => {
  396. stats.conn--
  397. parser.free()
  398. }
  399. return parser.buffer
  400. }
  401. function onPGReady () {
  402. microtasks = false
  403. server.listen()
  404. }
  405. const { utf8Length } = just.sys
  406. const poolSize = parseInt(just.env().PGPOOL || just.sys.cpus, 10)
  407. const server = createServer('0.0.0.0', 8080)
  408. server.onConnect = onHTTPConnect
  409. const { INT4OID, VARCHAROID } = constants.fieldTypes
  410. const clients = []
  411. const tfb = {
  412. hostname: 'tfb-database',
  413. port: 5432,
  414. user: 'benchmarkdbuser',
  415. pass: 'benchmarkdbpass',
  416. database: 'hello_world'
  417. }
  418. const stats = start()
  419. let i = poolSize
  420. const sJSON = sjs({ message: attr('string') })
  421. const sDB = sjs({ id: attr('number'), randomNumber: attr('number') })
  422. while (i--) connect(tfb, onPGConnect)
  423. const { loop } = just.factory
  424. let microtasks = true
  425. let rHTML = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: `
  426. let rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
  427. let rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
  428. let r404 = `HTTP/1.1 404 Not Found\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n`
  429. just.setInterval(() => {
  430. rHTML = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: `
  431. rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
  432. rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
  433. r404 = `HTTP/1.1 404 Not Found\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: 0\r\n\r\n`
  434. }, 100)
  435. while (1) {
  436. loop.poll(-1)
  437. if (microtasks) just.sys.runMicroTasks()
  438. }