socket-function 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -262,7 +262,7 @@ declare module "socket-function/mobx/UrlParam" {
262
262
  declare module "socket-function/mobx/observer" {
263
263
  import type preact from "preact";
264
264
  import { Reaction } from "mobx";
265
- export declare function observer<T extends {
265
+ /** @deprecated Use the version from sliftutils instead. */ export declare function observer<T extends {
266
266
  new (...args: any[]): {
267
267
  render(): preact.ComponentChild;
268
268
  forceUpdate(callback?: () => void): void;
@@ -528,6 +528,23 @@ declare module "socket-function/src/JSONLACKS/JSONLACKS.generated.js" {
528
528
  export function parse(text: string): unknown;
529
529
  }
530
530
 
531
+ declare module "socket-function/src/Zip" {
532
+ /// <reference types="node" />
533
+ /// <reference types="node" />
534
+ import { MaybePromise } from "socket-function/src/types";
535
+ export declare class Zip {
536
+ static gzip(buffer: Buffer, level?: number): Promise<Buffer>;
537
+ static gzipSync(buffer: Buffer, level?: number): Buffer;
538
+ static gunzip(buffer: Buffer): MaybePromise<Buffer>;
539
+ static gunzipAsyncBase(buffer: Buffer): Promise<Buffer>;
540
+ static gunzipUntracked(buffer: Buffer): Promise<Buffer>;
541
+ static gunzipSync(buffer: Buffer): Buffer;
542
+ private static gunzipUntrackedSync;
543
+ static gunzipBatch(buffers: Buffer[]): Promise<Buffer[]>;
544
+ }
545
+
546
+ }
547
+
531
548
  declare module "socket-function/src/args" {
532
549
  export declare const getArgs: {
533
550
  (): {
@@ -1042,6 +1059,7 @@ declare module "socket-function/src/profiling/measure" {
1042
1059
  entries: {
1043
1060
  [name: string]: ProfileEntry;
1044
1061
  };
1062
+ creator: string;
1045
1063
  }
1046
1064
  export declare function createMeasureProfile(): MeasureProfile;
1047
1065
  export declare function addToMeasureProfile(base: MeasureProfile, other: MeasureProfile): void;
@@ -1,6 +1,6 @@
1
1
  import type preact from "preact";
2
2
  import { Reaction } from "mobx";
3
- export declare function observer<T extends {
3
+ /** @deprecated Use the version from sliftutils instead. */ export declare function observer<T extends {
4
4
  new (...args: any[]): {
5
5
  render(): preact.ComponentChild;
6
6
  forceUpdate(callback?: () => void): void;
package/mobx/observer.tsx CHANGED
@@ -2,13 +2,14 @@ import type preact from "preact";
2
2
  import { setFlag } from "../require/compileFlags";
3
3
 
4
4
  import { observable, Reaction } from "mobx";
5
+ import { measureBlock } from "socket-function/src/profiling/measure";
5
6
 
6
7
  setFlag(require, "mobx", "allowclient", true);
7
8
  setFlag(require, "preact", "allowclient", true);
8
9
 
9
10
  let globalConstructOrder = 0;
10
11
 
11
- export function observer<
12
+ /** @deprecated Use the version from sliftutils instead. */export function observer<
12
13
  T extends {
13
14
  new(...args: any[]): {
14
15
  render(): preact.ComponentChild;
@@ -42,7 +43,9 @@ export function observer<
42
43
  render() {
43
44
  let output: preact.ComponentChild;
44
45
  this.reaction.track(() => {
45
- output = super.render();
46
+ measureBlock(() => {
47
+ output = super.render();
48
+ }, `${name}|render`);
46
49
  });
47
50
  return output;
48
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -659,7 +659,7 @@ export function requireMain() {
659
659
  // NOTE: All on one line, so we don't break sourcemaps by TOO much. We could also parse
660
660
  // the sourcemap and adjust it, but... it is much easier to just not change the line counts.
661
661
  return eval(
662
- `(function ${debugName}(exports, require, module, __filename, __dirname, importDynamic) {${contents}\n })`
662
+ `(function ${debugName}(exports, require, module, __filename, __dirname, importDynamic) {"use strict";${contents}\n })`
663
663
  );
664
664
  }
665
665
 
@@ -15,8 +15,9 @@ import { formatNumber, formatTime } from "./formatting/format";
15
15
  import zlib from "zlib";
16
16
  import pako from "pako";
17
17
  import { setFlag } from "../require/compileFlags";
18
- import { measureFnc, measureWrap } from "./profiling/measure";
18
+ import { measureFnc, measureWrap, registerMeasureInfo } from "./profiling/measure";
19
19
  import { MaybePromise } from "./types";
20
+ import { Zip } from "./Zip";
20
21
  setFlag(require, "pako", "allowclient", true);
21
22
 
22
23
  // NOTE: If it is too low, and too many servers disconnect, we can easily spend 100% of our time
@@ -158,9 +159,13 @@ export async function createCallFactory(
158
159
  let data: Buffer[];
159
160
  let originalArgs = call.args;
160
161
  let time = Date.now();
162
+ let sendStats: CompressionStats = {
163
+ uncompressedSize: 0,
164
+ compressedSize: 0,
165
+ };
161
166
  try {
162
167
  if (shouldCompressCall(fullCall)) {
163
- fullCall.args = await compressObj(fullCall.args) as any;
168
+ fullCall.args = await compressObj(fullCall.args, sendStats) as any;
164
169
  fullCall.isArgsCompressed = true;
165
170
  }
166
171
  let dataMaybePromise = SocketFunction.WIRE_SERIALIZER.serialize(fullCall);
@@ -169,6 +174,15 @@ export async function createCallFactory(
169
174
  } else {
170
175
  data = dataMaybePromise;
171
176
  }
177
+ if (!sendStats.compressedSize) {
178
+ let totalSize = 0;
179
+ for (let d of data) {
180
+ totalSize += d.length;
181
+ }
182
+ sendStats.uncompressedSize = totalSize;
183
+ sendStats.compressedSize = totalSize;
184
+ }
185
+ addSendStats(sendStats);
172
186
  } catch (e: any) {
173
187
  throw new Error(`Error serializing data for call ${call.classGuid}.${call.functionName}\n${e.stack}`);
174
188
  }
@@ -475,6 +489,15 @@ export async function createCallFactory(
475
489
  callback(resultSize);
476
490
  }
477
491
 
492
+ let receiveStats: CompressionStats = {
493
+ uncompressedSize: resultSize,
494
+ compressedSize: resultSize,
495
+ };
496
+ let sendStats: CompressionStats = {
497
+ uncompressedSize: 0,
498
+ compressedSize: 0,
499
+ };
500
+
478
501
  if (call.isReturn) {
479
502
  let callbackObj = pendingCalls.get(call.seqNum);
480
503
  if (parseTime > SocketFunction.WIRE_WARN_TIME) {
@@ -489,13 +512,13 @@ export async function createCallFactory(
489
512
  console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\tRETURN\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
490
513
  }
491
514
  if (call.isResultCompressed) {
492
- call.result = await decompressObj(call.result as Buffer);
515
+ call.result = await decompressObj(call.result as Buffer, receiveStats);
493
516
  call.isResultCompressed = false;
494
517
  }
495
518
  callbackObj.callback(call);
496
519
  } else {
497
520
  if (call.isArgsCompressed) {
498
- call.args = await decompressObj(call.args as any as Buffer) as any;
521
+ call.args = await decompressObj(call.args as any as Buffer, sendStats) as any;
499
522
  call.isArgsCompressed = false;
500
523
  }
501
524
  if (call.functionName === "changeIdentity") {
@@ -592,7 +615,7 @@ export async function createCallFactory(
592
615
  console.log(`DUR\t${(formatTime(timeTaken)).padEnd(6, " ")}\tFINISH\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
593
616
  }
594
617
  if (shouldCompressCall(call)) {
595
- response.result = await compressObj(response.result) as any;
618
+ response.result = await compressObj(response.result, sendStats) as any;
596
619
  response.isResultCompressed = true;
597
620
  }
598
621
  } catch (e: any) {
@@ -611,11 +634,17 @@ export async function createCallFactory(
611
634
  }
612
635
  }
613
636
 
637
+ let size = 0;
638
+
614
639
  if (response.result instanceof Buffer) {
615
640
  let { result, ...remaining } = response;
641
+ size = result.length;
616
642
  await sendWithHeader([result], { type: "Buffer", bufferCount: 1, metadata: remaining });
617
643
  } else if (Array.isArray(response.result) && response.result.every(x => x instanceof Buffer)) {
618
644
  let { result, ...remaining } = response;
645
+ for (let r of result) {
646
+ size += r.length;
647
+ }
619
648
  await sendWithHeader(result, { type: "Buffer[]", bufferCount: result.length, metadata: remaining });
620
649
  } else {
621
650
  const LIMIT = getCallFlags(call)?.responseLimit || SocketFunction.MAX_MESSAGE_SIZE * 1.5;
@@ -630,9 +659,21 @@ export async function createCallFactory(
630
659
  };
631
660
  result = await SocketFunction.WIRE_SERIALIZER.serialize(response);
632
661
  }
662
+ for (let r of result) {
663
+ size += r.length;
664
+ }
633
665
  await send(result);
634
666
  }
667
+
668
+ // If we have no size, then it's probably uncompressed
669
+ if (!sendStats.compressedSize) {
670
+ sendStats.compressedSize = size;
671
+ sendStats.uncompressedSize = size;
672
+ }
635
673
  }
674
+
675
+ addSendStats(sendStats);
676
+ addReceiveStats(receiveStats);
636
677
  }
637
678
 
638
679
  let clientsideSerial = runInSerial(async <T>(val: Promise<T>) => val);
@@ -717,63 +758,49 @@ export async function createCallFactory(
717
758
  return callFactory;
718
759
  }
719
760
 
720
- async function doStream(stream: GenericTransformStream, buffer: Buffer): Promise<Buffer> {
721
- let reader = stream.readable.getReader();
722
- let writer = stream.writable.getWriter();
723
- let writePromise = writer.write(buffer);
724
- let closePromise = writer.close();
725
-
726
- let outputBuffers: Buffer[] = [];
727
- while (true) {
728
- let { value, done } = await reader.read();
729
- if (done) {
730
- await writePromise;
731
- await closePromise;
732
- return Buffer.concat(outputBuffers);
733
- }
734
- outputBuffers.push(Buffer.from(value));
735
- }
736
- }
737
- async function unzipBase(buffer: Buffer): Promise<Buffer> {
738
- if (isNode()) {
739
- return new Promise((resolve, reject) => {
740
- zlib.gunzip(buffer, (err: any, result: Buffer) => {
741
- if (err) reject(err);
742
- else resolve(result);
743
- });
744
- });
745
- } else {
746
- // NOTE: pako seems to be faster, at least clientside.
747
- // TIMING: 700ms vs 1200ms
748
- // - This might just be faster for small files.
749
- return Buffer.from(pako.inflate(buffer));
750
- // @ts-ignore
751
- // return await doStream(new DecompressionStream("gzip"), buffer);
752
- }
761
+
762
+
763
+ let uncompressedSent = 0;
764
+ let compressedSent = 0;
765
+ let uncompressedReceived = 0;
766
+ let compressedReceived = 0;
767
+
768
+ let sendCount = 0;
769
+ let receiveCount = 0;
770
+
771
+ function addSendStats(stats: CompressionStats) {
772
+ uncompressedSent += stats.uncompressedSize;
773
+ compressedSent += stats.compressedSize;
774
+ sendCount++;
753
775
  }
754
- async function zipBase(buffer: Buffer, level?: number): Promise<Buffer> {
755
- if (isNode()) {
756
- return new Promise((resolve, reject) => {
757
- zlib.gzip(buffer, { level }, (err: any, result: Buffer) => {
758
- if (err) reject(err);
759
- else resolve(result);
760
- });
761
- });
762
- } else {
763
- // @ts-ignore
764
- return await doStream(new CompressionStream("gzip"), buffer);
765
- }
776
+ function addReceiveStats(stats: CompressionStats) {
777
+ uncompressedReceived += stats.uncompressedSize;
778
+ compressedReceived += stats.compressedSize;
779
+ receiveCount++;
766
780
  }
781
+ // Register this late as I don't want it to appear before the memory register info, which is use more useful than the network one.
782
+ setImmediate(() => {
783
+ registerMeasureInfo(() => `NET => ${formatNumber(compressedSent)}B (${formatNumber(uncompressedSent)}B/${formatNumber(sendCount)}) / <= ${formatNumber(compressedReceived)}B (${formatNumber(uncompressedReceived)}B/${formatNumber(receiveCount)})`);
784
+ });
767
785
 
768
- const compressObj = measureWrap(async function wireCallCompress(obj: unknown): Promise<Buffer> {
786
+ type CompressionStats = {
787
+ uncompressedSize: number;
788
+ compressedSize: number;
789
+ };
790
+
791
+ const compressObj = measureWrap(async function wireCallCompress(obj: unknown, stats: CompressionStats): Promise<Buffer> {
769
792
  let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
770
793
  let lengthBuffer = Buffer.from((new Float64Array(buffers.map(x => x.length))).buffer);
771
794
  let buffer = Buffer.concat([lengthBuffer, ...buffers]);
772
- let result = await zipBase(buffer);
795
+ stats.uncompressedSize += buffer.length;
796
+ let result = await Zip.gzip(buffer);
797
+ stats.compressedSize += result.length;
773
798
  return result;
774
799
  });
775
- const decompressObj = measureWrap(async function wireCallDecompress(obj: Buffer): Promise<unknown> {
776
- let buffer = await unzipBase(obj);
800
+ // Assumes the caller already added the obj.length to both the uncompressedSize and compressedSize, and is just looking to update the uncompressedSize to be larger according to the size after we uncompress
801
+ const decompressObj = measureWrap(async function wireCallDecompress(obj: Buffer, stats: CompressionStats): Promise<unknown> {
802
+ let buffer = await Zip.gunzip(obj);
803
+ stats.uncompressedSize += buffer.length - obj.length;
777
804
  let lengthBuffer = buffer.slice(0, 8);
778
805
  let lengths = new Float64Array(lengthBuffer.buffer, lengthBuffer.byteOffset, lengthBuffer.byteLength / 8);
779
806
  let buffers: Buffer[] = [];
@@ -782,5 +809,6 @@ const decompressObj = measureWrap(async function wireCallDecompress(obj: Buffer)
782
809
  buffers.push(buffer.slice(offset, offset + length));
783
810
  offset += length;
784
811
  }
785
- return await SocketFunction.WIRE_SERIALIZER.deserialize(buffers);
812
+ let result = await SocketFunction.WIRE_SERIALIZER.deserialize(buffers);
813
+ return result;
786
814
  });
package/src/Zip.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { MaybePromise } from "./types";
4
+ export declare class Zip {
5
+ static gzip(buffer: Buffer, level?: number): Promise<Buffer>;
6
+ static gzipSync(buffer: Buffer, level?: number): Buffer;
7
+ static gunzip(buffer: Buffer): MaybePromise<Buffer>;
8
+ static gunzipAsyncBase(buffer: Buffer): Promise<Buffer>;
9
+ static gunzipUntracked(buffer: Buffer): Promise<Buffer>;
10
+ static gunzipSync(buffer: Buffer): Buffer;
11
+ private static gunzipUntrackedSync;
12
+ static gunzipBatch(buffers: Buffer[]): Promise<Buffer[]>;
13
+ }
package/src/Zip.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { isNode } from "./misc";
2
+ import { measureFnc } from "./profiling/measure";
3
+ import zlib from "zlib";
4
+ import * as pako from "pako";
5
+
6
+ import { setFlag } from "../require/compileFlags";
7
+ import { MaybePromise } from "./types";
8
+ setFlag(require, "pako", "allowclient", true);
9
+
10
+ const SYNC_THRESHOLD_BYTES = 100_000_000;
11
+
12
+ export class Zip {
13
+ @measureFnc
14
+ public static async gzip(buffer: Buffer, level?: number): Promise<Buffer> {
15
+ if (isNode()) {
16
+ return new Promise((resolve, reject) => {
17
+ zlib.gzip(buffer, { level }, (err: any, result: Buffer) => {
18
+ if (err) reject(err);
19
+ else resolve(result);
20
+ });
21
+ });
22
+ } else {
23
+ // @ts-ignore
24
+ return await doStream(new CompressionStream("gzip"), buffer);
25
+ }
26
+ }
27
+ @measureFnc
28
+ public static gzipSync(buffer: Buffer, level?: number): Buffer {
29
+ if (isNode()) {
30
+ return Buffer.from(zlib.gzipSync(buffer, { level }));
31
+ }
32
+ return Buffer.from(pako.deflate(buffer));
33
+ }
34
+ public static gunzip(buffer: Buffer): MaybePromise<Buffer> {
35
+ // Switch to the synchronous version if the buffer is small. This is a lot faster in Node.js and clientside.
36
+ // - On tests of random small amounts of data, this seems to be up to 7X faster (on node). However, on non-random data, on the actual data we're using, it seems to be almost 50 times faster. So... definitely worth it...
37
+ if (buffer.length < SYNC_THRESHOLD_BYTES) {
38
+ let time = Date.now();
39
+ let result = Zip.gunzipSync(buffer);
40
+ let duration = Date.now() - time;
41
+ if (duration > 50) {
42
+ // Wait, so we don't lock up the main thread. And if we already wait it 50ms, then waiting for one frame is marginal, even client-side.
43
+ return ((async () => {
44
+ await new Promise(resolve => setTimeout(resolve, 0));
45
+ return result;
46
+ }))();
47
+ }
48
+ return result;
49
+ }
50
+ return Zip.gunzipAsyncBase(buffer);
51
+ }
52
+ @measureFnc
53
+ public static async gunzipAsyncBase(buffer: Buffer): Promise<Buffer> {
54
+ return Zip.gunzipUntracked(buffer);
55
+ }
56
+ // A base function, so we can avoid instrumentation for batch calls
57
+ public static async gunzipUntracked(buffer: Buffer): Promise<Buffer> {
58
+ if (isNode()) {
59
+ return await new Promise<Buffer>((resolve, reject) => {
60
+ zlib.gunzip(buffer, (err: any, result: Buffer) => {
61
+ if (err) reject(err);
62
+ else resolve(result);
63
+ });
64
+ });
65
+ }
66
+ return await doStream(new DecompressionStream("gzip"), buffer);
67
+ }
68
+
69
+ @measureFnc
70
+ public static gunzipSync(buffer: Buffer): Buffer {
71
+ return this.gunzipUntrackedSync(buffer);
72
+ }
73
+ private static gunzipUntrackedSync(buffer: Buffer): Buffer {
74
+ if (isNode()) {
75
+ return Buffer.from(zlib.gunzipSync(buffer));
76
+ }
77
+ return Buffer.from(pako.inflate(buffer));
78
+ }
79
+
80
+ @measureFnc
81
+ public static async gunzipBatch(buffers: Buffer[]): Promise<Buffer[]> {
82
+ let time = Date.now();
83
+ buffers = await Promise.all(buffers.map(x => {
84
+ if (x.length < SYNC_THRESHOLD_BYTES) {
85
+ return this.gunzipUntrackedSync(x);
86
+ }
87
+ return this.gunzipUntracked(x);
88
+ }));
89
+ time = Date.now() - time;
90
+ let totalSize = buffers.reduce((acc, buffer) => acc + buffer.length, 0);
91
+ //console.log(`Gunzip ${formatNumber(totalSize)}B at ${formatNumber(totalSize / time * 1000)}B/s`);
92
+ return buffers;
93
+ }
94
+ }
95
+
96
+ async function doStream(stream: GenericTransformStream, buffer: Buffer): Promise<Buffer> {
97
+ let reader = stream.readable.getReader();
98
+ let writer = stream.writable.getWriter();
99
+ let writePromise = writer.write(buffer);
100
+ let closePromise = writer.close();
101
+
102
+ let outputBuffers: Buffer[] = [];
103
+ while (true) {
104
+ let { value, done } = await reader.read();
105
+ if (done) {
106
+ await writePromise;
107
+ await closePromise;
108
+ return Buffer.concat(outputBuffers);
109
+ }
110
+ outputBuffers.push(Buffer.from(value));
111
+ }
112
+
113
+ }
@@ -47,6 +47,7 @@ export interface MeasureProfile {
47
47
  entries: {
48
48
  [name: string]: ProfileEntry;
49
49
  };
50
+ creator: string;
50
51
  }
51
52
  export declare function createMeasureProfile(): MeasureProfile;
52
53
  export declare function addToMeasureProfile(base: MeasureProfile, other: MeasureProfile): void;
@@ -100,6 +100,7 @@ export function startMeasure(): {
100
100
  startTime: now,
101
101
  endTime: now,
102
102
  entries: Object.create(null),
103
+ creator: new Error().stack || "",
103
104
  };
104
105
  let openAtStart = new Set(getOpenTimesBase());
105
106
 
@@ -316,6 +317,7 @@ export interface MeasureProfile {
316
317
  entries: {
317
318
  [name: string]: ProfileEntry;
318
319
  };
320
+ creator: string;
319
321
  }
320
322
  export function createMeasureProfile(): MeasureProfile {
321
323
  let now = Date.now();
@@ -323,6 +325,7 @@ export function createMeasureProfile(): MeasureProfile {
323
325
  startTime: now,
324
326
  endTime: now,
325
327
  entries: Object.create(null),
328
+ creator: new Error().stack || "",
326
329
  };
327
330
  }
328
331