techempower.js 12 KB

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