techempower.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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 = () => just.print('pg.close')
  162. sock.start(err => {
  163. if (err) return just.error(err.stack)
  164. sock.authenticate(err => {
  165. if (err) return just.error(err.stack)
  166. onPGAuth(sock).catch(err => just.error(err.stack))
  167. })
  168. })
  169. }
  170. const HEADER = '<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>'
  171. const FOOTER = '</table></body></html>'
  172. const S1 = '<tr><td>'
  173. const S2 = '</td><td>'
  174. const S3 = '</td></tr>'
  175. function getHTML (rows) {
  176. let html = HEADER
  177. for (const row of rows) {
  178. html += (S1 + row[0] + S2 + row[1] + S3)
  179. }
  180. return html + FOOTER
  181. }
  182. function sortByMessage (a, b) {
  183. if (a[1] > b[1]) return 1
  184. if (a[1] < b[1]) return -1
  185. return 0
  186. }
  187. const cache = {}
  188. function onHTTPConnect (sock) {
  189. stats.conn++
  190. const client = clients[sock.fd % clients.length]
  191. const rbuf = new ArrayBuffer(4096)
  192. const parser = createParser(rbuf)
  193. const { getWorldById, updateWorldById, allFortunes, updateWorldById20, updateWorldById15, updateWorldById10, updateWorldById5, getCachedWorldById } = client
  194. const message = { message: 'Hello, World!' }
  195. const text = 'Hello, World!'
  196. const extra = [0, 'Additional fortune added at request time.']
  197. const updateQueries = {
  198. 5: updateWorldById5,
  199. 10: updateWorldById10,
  200. 15: updateWorldById15,
  201. 20: updateWorldById20
  202. }
  203. const results = []
  204. let queries = 0
  205. let updates = 0
  206. function onUpdateMulti () {
  207. stats.qps++
  208. const json = JSON.stringify(results)
  209. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  210. stats.rps++
  211. }
  212. function onUpdateSingle () {
  213. stats.qps++
  214. updates++
  215. if (results.length === updates) {
  216. const json = JSON.stringify(results)
  217. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  218. stats.rps++
  219. }
  220. }
  221. function onUpdates () {
  222. stats.qps++
  223. const [id, randomNumber] = getWorldById.getRows()[0]
  224. results.push({ id, randomNumber })
  225. if (results.length === queries) {
  226. const query = updateQueries[queries]
  227. if (query) {
  228. let i = 0
  229. for (const row of results) {
  230. row.randomNumber = Math.ceil(Math.random() * 10000)
  231. query.params[i++] = row.id
  232. query.params[i++] = row.randomNumber
  233. }
  234. query.call(onUpdateMulti)
  235. return
  236. }
  237. updates = 0
  238. for (const row of results) {
  239. row.randomNumber = Math.ceil(Math.random() * 10000)
  240. updateWorldById.params[0] = row.id
  241. updateWorldById.params[1] = row.randomNumber
  242. updateWorldById.append(onUpdateSingle)
  243. }
  244. updateWorldById.send()
  245. }
  246. }
  247. function handleUpdates (qs) {
  248. const [, val] = qs.split('=')
  249. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  250. results.length = 0
  251. for (let i = 1; i < queries; i++) {
  252. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  253. getWorldById.append(onUpdates, (i % 20 === 0))
  254. }
  255. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  256. getWorldById.append(onUpdates)
  257. getWorldById.send()
  258. }
  259. function onMulti () {
  260. stats.qps++
  261. const [id, randomNumber] = getWorldById.getRows()[0]
  262. results.push({ id, randomNumber })
  263. if (results.length === queries) {
  264. const json = JSON.stringify(results)
  265. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  266. stats.rps++
  267. queries = 0
  268. }
  269. }
  270. function handleMulti (qs) {
  271. const [, val] = qs.split('=')
  272. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  273. results.length = 0
  274. for (let i = 1; i < queries; i++) {
  275. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  276. getWorldById.append(onMulti, (i % 20 === 0))
  277. }
  278. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  279. getWorldById.append(onMulti)
  280. getWorldById.send()
  281. }
  282. function onCached () {
  283. stats.qps++
  284. const row = getCachedWorldById.getRows()[0]
  285. const [id, randomNumber] = row
  286. const world = { id, randomNumber }
  287. cache[id] = world
  288. results.push(world)
  289. if (results.length === queries) {
  290. const json = JSON.stringify(results)
  291. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  292. stats.rps++
  293. queries = 0
  294. results.length = 0
  295. }
  296. }
  297. function handleCached (qs) {
  298. const [, val] = qs.split('=')
  299. queries = Math.min(parseInt(val || 1, 10), 500) || 1
  300. for (let i = 1; i < queries; i++) {
  301. const id = Math.ceil(Math.random() * 10000)
  302. const row = cache[id]
  303. if (row) {
  304. results.push(row)
  305. } else {
  306. getCachedWorldById.params[0] = id
  307. getCachedWorldById.append(onCached, (i % 20 === 0))
  308. }
  309. }
  310. const id = Math.ceil(Math.random() * 10000)
  311. const row = cache[id]
  312. if (row) {
  313. results.push(row)
  314. } else {
  315. getCachedWorldById.params[0] = id
  316. getCachedWorldById.append(onCached)
  317. }
  318. if (results.length === queries) {
  319. const json = JSON.stringify(results)
  320. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  321. stats.rps++
  322. queries = 0
  323. results.length = 0
  324. return
  325. }
  326. getCachedWorldById.send()
  327. }
  328. function onFortunes () {
  329. stats.qps++
  330. const html = getHTML([extra, ...allFortunes.getRows()].sort(sortByMessage))
  331. sock.writeString(`${rHTML}${utf8Length(html)}${END}${html}`)
  332. stats.rps++
  333. }
  334. function onSingle () {
  335. stats.qps++
  336. const [id, randomNumber] = getWorldById.getRows()[0]
  337. const json = sDB({ id, randomNumber })
  338. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  339. stats.rps++
  340. }
  341. const queryPath = '/query'
  342. const updatePath = '/update'
  343. const cachePath = '/cached-world'
  344. const pathSep = '?'
  345. const END = '\r\n\r\n'
  346. const handlers = {
  347. '/json': () => {
  348. const json = sJSON(message)
  349. sock.writeString(`${rJSON}${json.length}${END}${json}`)
  350. stats.rps++
  351. },
  352. '/fortunes': () => {
  353. allFortunes.call(onFortunes)
  354. },
  355. '/db': () => {
  356. getWorldById.params[0] = Math.ceil(Math.random() * 10000)
  357. getWorldById.call(onSingle)
  358. },
  359. '/plaintext': () => {
  360. sock.writeString(`${rTEXT}${text.length}${END}${text}`)
  361. stats.rps++
  362. },
  363. default: url => {
  364. const [path, qs] = url.split(pathSep)
  365. if (path === queryPath) {
  366. handleMulti(qs)
  367. return
  368. }
  369. if (path === updatePath) {
  370. handleUpdates(qs)
  371. return
  372. }
  373. if (path === cachePath) {
  374. handleCached(qs)
  375. return
  376. }
  377. sock.writeString(r404)
  378. stats.rps++
  379. }
  380. }
  381. parser.onRequests = count => {
  382. if (count > 1) {
  383. sock.writeString(`${rTEXT}${text.length}${END}${text}`.repeat(count))
  384. stats.rps += count
  385. return
  386. }
  387. const url = parser.url(0)
  388. const handler = (handlers[url] || handlers.default)
  389. handler(url)
  390. }
  391. sock.onData = bytes => parser.parse(bytes)
  392. sock.onClose = () => {
  393. stats.conn--
  394. parser.free()
  395. }
  396. return parser.buffer
  397. }
  398. function onPGReady () {
  399. microtasks = false
  400. server.listen()
  401. }
  402. const { utf8Length } = just.sys
  403. const poolSize = parseInt(just.env().PGPOOL || just.sys.cpus, 10)
  404. const server = createServer('0.0.0.0', 8080)
  405. server.onConnect = onHTTPConnect
  406. const { INT4OID, VARCHAROID } = constants.fieldTypes
  407. const clients = []
  408. const tfb = {
  409. hostname: 'tfb-database',
  410. port: 5432,
  411. user: 'benchmarkdbuser',
  412. pass: 'benchmarkdbpass',
  413. database: 'hello_world'
  414. }
  415. const stats = start()
  416. let i = poolSize
  417. const sJSON = sjs({ message: attr('string') })
  418. const sDB = sjs({ id: attr('number'), randomNumber: attr('number') })
  419. const sQUERY = sjs(attr('array', sjs({ id: attr('number'), randomNumber: attr('number') })))
  420. while (i--) connect(tfb, onPGConnect)
  421. const { loop } = just.factory
  422. let microtasks = true
  423. 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: `
  424. let rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
  425. let rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
  426. 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`
  427. just.setInterval(() => {
  428. rHTML = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: `
  429. rTEXT = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: text/plain\r\nContent-Length: `
  430. rJSON = `HTTP/1.1 200 OK\r\nServer: j\r\nDate: ${stats.time}\r\nContent-Type: application/json\r\nContent-Length: `
  431. 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`
  432. }, 100)
  433. while (1) {
  434. loop.poll(10)
  435. if (microtasks) just.sys.runMicroTasks()
  436. }