util.ts 6.2 KB

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