util.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { existsSync, readFile, writeFile } from 'fs';
  2. import { resolve } from 'path';
  3. import { get } from 'https';
  4. import { suite } from 'uvu';
  5. import { performance } from 'perf_hooks';
  6. import { Worker } from 'worker_threads';
  7. const testFilesRaw = {
  8. basic: Buffer.from('Hello world!'),
  9. text: 'https://www.gutenberg.org/files/2701/old/moby10b.txt',
  10. smallImage: 'https://www.hlevkin.com/TestImages/new/Rainier.bmp',
  11. image: 'https://www.hlevkin.com/TestImages/new/Maltese.bmp',
  12. largeImage: 'https://www.hlevkin.com/TestImages/new/Sunrise.bmp'
  13. };
  14. export type TestFile = keyof typeof testFilesRaw;
  15. const testFilesPromise = (async () => {
  16. let res = {} as Record<TestFile, Buffer>;
  17. for (const name in testFilesRaw) {
  18. let data = testFilesRaw[name];
  19. if (typeof data == 'string') {
  20. const fn = resolve(__dirname, 'data', name);
  21. if (!existsSync(fn)) {
  22. console.log('Downloading ' + data + '...');
  23. data = await new Promise((r, re) => get(data as string, res => {
  24. const len = +res.headers['content-length'];
  25. const buf = Buffer.allocUnsafe(len);
  26. let i = 0;
  27. res.on('data', chunk => {
  28. buf.set(chunk, i);
  29. console.log((100 * (i += chunk.length) / len).toFixed(1) + '%\x1B[1A');
  30. });
  31. res.on('error', re);
  32. res.on('end', () => {
  33. console.log('Complete');
  34. writeFile(fn, buf, () => r(buf));
  35. });
  36. }));
  37. } else {
  38. data = await new Promise((res, rej) =>
  39. readFile(fn, (err, buf) => err ? rej(err) : res(buf))
  40. );
  41. }
  42. }
  43. res[name] = data as Buffer;
  44. }
  45. return res;
  46. })();
  47. export type TestHandler = (file: Buffer, name: string, resetTimer: () => void) => unknown | Promise<unknown>;
  48. export const testSuites = async <T extends Record<string, TestHandler>>(suites: T) => {
  49. const perf = {} as Record<keyof T, Promise<Record<TestFile, number>>>;
  50. for (const k in suites) {
  51. perf[k] = new Promise(async setPerf => {
  52. const ste = suite(k);
  53. let testFiles: typeof testFilesPromise extends Promise<infer T> ? T : never;
  54. ste.before(() => testFilesPromise.then(v => {
  55. testFiles = v;
  56. }));
  57. const localPerf: Record<string, number> = {};
  58. for (const name in testFilesRaw) {
  59. ste(name, async () => {
  60. let ts = performance.now();
  61. await suites[k](testFiles[name], name, () => {
  62. ts = performance.now();
  63. });
  64. localPerf[name] = performance.now() - ts;
  65. });
  66. }
  67. ste.after(() => {
  68. setPerf(localPerf);
  69. });
  70. ste.run();
  71. })
  72. }
  73. const resolvedPerf = {} as Record<keyof T, Record<TestFile, number>>;
  74. for (const k in suites) resolvedPerf[k] = await perf[k];
  75. return resolvedPerf;
  76. }
  77. // create worker string
  78. const cws = (pkg: string, method: string = '_cjsDefault') => `
  79. const ${method == '_cjsDefault' ? method : `{ ${method} }`} = require('${pkg}');
  80. const { Worker, workerData, parentPort } = require('worker_threads');
  81. try {
  82. const buf = ${method}(...(Array.isArray(workerData) ? workerData : [workerData]));
  83. parentPort.postMessage(buf, [buf.buffer]);
  84. } catch (err) {
  85. parentPort.postMessage({ err });
  86. }
  87. `;
  88. // create callback worker string
  89. const cbws = (pkg: string, method: string, alias = 'run') => `
  90. const { ${method}: ${alias} } = require('${pkg}');
  91. const { Worker, workerData, parentPort } = require('worker_threads');
  92. ${alias}(...(Array.isArray(workerData) ? workerData : [workerData]), (err, buf) => {
  93. if (err) parentPort.postMessage({ err });
  94. else parentPort.postMessage(buf, [buf.buffer]);
  95. });
  96. `;
  97. export type Workerized = (workerData: Uint8Array | [Uint8Array, {}], transferable?: ArrayBuffer[]) => WorkerizedResult;
  98. export interface WorkerizedResult extends PromiseLike<Uint8Array> {
  99. timeout(ms: number): void;
  100. };
  101. // Worker creator
  102. const wc = (pkg: string, method?: string, cb = false): Workerized => {
  103. const str = cb ? cbws(pkg, method) : cws(pkg, method);
  104. return (workerData, transferable) => {
  105. const worker = new Worker(str, {
  106. eval: true,
  107. workerData,
  108. transferList: transferable
  109. });
  110. let terminated = false;
  111. return {
  112. timeout(ms: number) {
  113. const tm = setTimeout(() => {
  114. worker.terminate();
  115. terminated = true;
  116. }, ms);
  117. worker.once('message', () => clearTimeout(tm));
  118. },
  119. then(res, rej) {
  120. return new Promise((res, rej) => {
  121. worker
  122. .once('message', msg => {
  123. if (msg.err) rej(msg.err);
  124. res(msg);
  125. })
  126. .once('error', rej)
  127. .once('exit', code => {
  128. if (terminated) rej(new Error('Timed out'));
  129. else if (code !== 0) rej(new Error('Exited with status code ' + code));
  130. });
  131. }).then(res, rej);
  132. }
  133. };
  134. }
  135. }
  136. const fflate = resolve(__dirname, '..');
  137. export const workers = {
  138. fflate: {
  139. deflate: wc(fflate, 'deflate'),
  140. inflate: wc(fflate, 'inflate'),
  141. gzip: wc(fflate, 'gzip'),
  142. gunzip: wc(fflate, 'gunzip'),
  143. zlib: wc(fflate, 'zlib'),
  144. unzlib: wc(fflate, 'unzlib')
  145. },
  146. pako: {
  147. deflate: wc('pako', 'deflateRaw'),
  148. inflate: wc('pako', 'inflateRaw'),
  149. gzip: wc('pako', 'gzip'),
  150. gunzip: wc('pako', 'ungzip'),
  151. zlib: wc('pako', 'deflate'),
  152. unzlib: wc('pako', 'inflate')
  153. },
  154. uzip: {
  155. deflate: wc('uzip', 'deflateRaw'),
  156. inflate: wc('uzip', 'inflateRaw')
  157. },
  158. tinyInflate: {
  159. inflate: wc('tiny-inflate')
  160. },
  161. zlib: {
  162. deflate: wc('zlib', 'deflateRaw', true),
  163. inflate: wc('zlib', 'inflateRaw', true),
  164. gzip: wc('zlib', 'gzip', true),
  165. gunzip: wc('zlib', 'gunzip', true),
  166. zlib: wc('zlib', 'deflate', true),
  167. unzlib: wc('zlib', 'inflate', true)
  168. }
  169. };
  170. export const bClone = (buf: Buffer) => {
  171. const clone = Buffer.allocUnsafe(buf.length);
  172. clone.set(buf);
  173. return clone;
  174. }