socket-function 0.9.3 → 0.9.5
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/.eslintrc.js +50 -50
- package/SocketFunction.ts +280 -280
- package/SocketFunctionTypes.ts +90 -90
- package/hot/HotReloadController.ts +105 -105
- package/mobx/UrlParam.ts +39 -39
- package/mobx/observer.tsx +49 -49
- package/mobx/promiseToObservable.tsx +41 -41
- package/package.json +1 -1
- package/require/CSSShim.ts +19 -19
- package/require/RequireController.ts +252 -252
- package/require/buffer.js +2368 -2368
- package/require/compileFlags.ts +44 -44
- package/require/require.html +13 -13
- package/require/require.js +464 -462
- package/spec.txt +115 -115
- package/src/CallFactory.ts +389 -389
- package/src/JSONLACKS/JSONLACKS.generated.js +17 -17
- package/src/JSONLACKS/JSONLACKS.pegjs +247 -247
- package/src/JSONLACKS/JSONLACKS.ts +441 -429
- package/src/args.ts +21 -21
- package/src/batching.ts +177 -170
- package/src/caching.ts +359 -318
- package/src/callHTTPHandler.ts +203 -203
- package/src/callManager.ts +134 -134
- package/src/certStore.ts +29 -29
- package/src/fixLargeNetworkCalls.ts +8 -8
- package/src/formatting/colors.ts +78 -78
- package/src/formatting/format.ts +160 -160
- package/src/formatting/logColors.ts +17 -17
- package/src/misc.ts +315 -302
- package/src/nodeCache.ts +92 -92
- package/src/nodeProxy.ts +54 -54
- package/src/profiling/getOwnTime.ts +107 -142
- package/src/profiling/measure.ts +289 -273
- package/src/profiling/stats.ts +212 -212
- package/src/profiling/tcpLagProxy.ts +63 -63
- package/src/storagePath.ts +10 -10
- package/src/tlsParsing.ts +96 -96
- package/src/types.ts +8 -8
- package/src/webSocketServer.ts +254 -250
- package/test/client.css +2 -2
- package/test/client.ts +46 -46
- package/test/server.ts +43 -43
- package/test/shared.ts +52 -52
- package/tsconfig.json +26 -26
package/src/nodeCache.ts
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import { CallFactory, createCallFactory } from "./CallFactory";
|
|
2
|
-
import { MaybePromise } from "./types";
|
|
3
|
-
import { lazy } from "./caching";
|
|
4
|
-
import { SocketFunction } from "../SocketFunction";
|
|
5
|
-
|
|
6
|
-
// TODO: Add CallInstanceFactory.isClosed, so nodeCache can clean up old entries.
|
|
7
|
-
// This is only needed for memory management, and not for correctness. Entries never
|
|
8
|
-
// need to be refreshed, because NetworkLocation.listeningPorts shouldn't really change.
|
|
9
|
-
// Either we will have listeningPorts and re-establish the connection, or we won't, and
|
|
10
|
-
// then it is a client, in which case we cannot re-establish the connection (and we just
|
|
11
|
-
// have to wait for the client to re-establish it). AND, if the listeningPorts change from
|
|
12
|
-
// a value to a new value... then they should be obtained using connect() anyway,
|
|
13
|
-
// and so whatever way the user got the NetworkLocation to begin with, they should use again.
|
|
14
|
-
|
|
15
|
-
export function getNodeId(domain: string, port: number): string {
|
|
16
|
-
// NOTE: As domains are never reused, this doesn't need any randomness
|
|
17
|
-
return `${domain}:${port}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** A nodeId not available for reconnecting. */
|
|
21
|
-
export function getClientNodeId(address: string): string {
|
|
22
|
-
return `client:${address}:${Date.now()}:${Math.random()}`;
|
|
23
|
-
}
|
|
24
|
-
export function isClientNodeId(nodeId: string): boolean {
|
|
25
|
-
return nodeId.startsWith("client:");
|
|
26
|
-
}
|
|
27
|
-
/** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
|
|
28
|
-
* but we should always have an address).
|
|
29
|
-
* - Rarely used, as for logging you can just log the nodeId. ALSO, it isn't sufficient to reconnect, as the port is also needed!
|
|
30
|
-
* */
|
|
31
|
-
export function getNodeIdIP(nodeId: string): string {
|
|
32
|
-
if (isClientNodeId(nodeId)) {
|
|
33
|
-
return nodeId.split(":")[1];
|
|
34
|
-
}
|
|
35
|
-
return getNodeIdLocation(nodeId)!.address;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
|
|
39
|
-
if (isClientNodeId(nodeId)) {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
let [address, port] = nodeId.split(":");
|
|
43
|
-
return { address, port: parseInt(port) };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function getNodeIdDomain(nodeId: string): string {
|
|
47
|
-
let location = getNodeIdLocation(nodeId);
|
|
48
|
-
if (!location) {
|
|
49
|
-
throw new Error(`Cannot get domain from nodeId, which is only usable as a client. NodeId: ${JSON.stringify(nodeId)}`);
|
|
50
|
-
}
|
|
51
|
-
return new URL(location.address).hostname.split(".").slice(-2).join(".");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// NOTE: CallFactory turns into an actual CallFactory when registerNodeClient is called
|
|
55
|
-
// nodeId =>
|
|
56
|
-
const nodeCache = new Map<string, MaybePromise<CallFactory>>();
|
|
57
|
-
|
|
58
|
-
// NOTE: Should be called directly inside call factory constructor whenever
|
|
59
|
-
// their nodeId changes (and on construction).
|
|
60
|
-
export function registerNodeClient(callFactory: CallFactory) {
|
|
61
|
-
nodeCache.set(callFactory.nodeId, callFactory);
|
|
62
|
-
startCleanupLoop();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getCreateCallFactory(nodeId: string): MaybePromise<CallFactory> {
|
|
66
|
-
let callFactory = nodeCache.get(nodeId);
|
|
67
|
-
if (callFactory === undefined) {
|
|
68
|
-
callFactory = createCallFactory(undefined, nodeId);
|
|
69
|
-
nodeCache.set(nodeId, callFactory);
|
|
70
|
-
}
|
|
71
|
-
return callFactory;
|
|
72
|
-
}
|
|
73
|
-
export function getCallFactory(nodeId: string): MaybePromise<CallFactory | undefined> {
|
|
74
|
-
return nodeCache.get(nodeId);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const startCleanupLoop = lazy(() => {
|
|
78
|
-
(async () => {
|
|
79
|
-
while (true) {
|
|
80
|
-
for (let [key, value] of Array.from(nodeCache.entries())) {
|
|
81
|
-
let factory = value;
|
|
82
|
-
if (!(factory instanceof Promise)) {
|
|
83
|
-
if (factory.closedForever) {
|
|
84
|
-
nodeCache.delete(key);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
await new Promise(resolve => setTimeout(resolve, 1000 * 60 * 5));
|
|
89
|
-
}
|
|
90
|
-
})().catch(e => {
|
|
91
|
-
console.error(`nodeCache cleanup loop failed, ${e.stack}`);
|
|
92
|
-
});
|
|
1
|
+
import { CallFactory, createCallFactory } from "./CallFactory";
|
|
2
|
+
import { MaybePromise } from "./types";
|
|
3
|
+
import { lazy } from "./caching";
|
|
4
|
+
import { SocketFunction } from "../SocketFunction";
|
|
5
|
+
|
|
6
|
+
// TODO: Add CallInstanceFactory.isClosed, so nodeCache can clean up old entries.
|
|
7
|
+
// This is only needed for memory management, and not for correctness. Entries never
|
|
8
|
+
// need to be refreshed, because NetworkLocation.listeningPorts shouldn't really change.
|
|
9
|
+
// Either we will have listeningPorts and re-establish the connection, or we won't, and
|
|
10
|
+
// then it is a client, in which case we cannot re-establish the connection (and we just
|
|
11
|
+
// have to wait for the client to re-establish it). AND, if the listeningPorts change from
|
|
12
|
+
// a value to a new value... then they should be obtained using connect() anyway,
|
|
13
|
+
// and so whatever way the user got the NetworkLocation to begin with, they should use again.
|
|
14
|
+
|
|
15
|
+
export function getNodeId(domain: string, port: number): string {
|
|
16
|
+
// NOTE: As domains are never reused, this doesn't need any randomness
|
|
17
|
+
return `${domain}:${port}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A nodeId not available for reconnecting. */
|
|
21
|
+
export function getClientNodeId(address: string): string {
|
|
22
|
+
return `client:${address}:${Date.now()}:${Math.random()}`;
|
|
23
|
+
}
|
|
24
|
+
export function isClientNodeId(nodeId: string): boolean {
|
|
25
|
+
return nodeId.startsWith("client:");
|
|
26
|
+
}
|
|
27
|
+
/** Will always be available, even if getNodeIdLocation is not (as we don't always have the port,
|
|
28
|
+
* but we should always have an address).
|
|
29
|
+
* - Rarely used, as for logging you can just log the nodeId. ALSO, it isn't sufficient to reconnect, as the port is also needed!
|
|
30
|
+
* */
|
|
31
|
+
export function getNodeIdIP(nodeId: string): string {
|
|
32
|
+
if (isClientNodeId(nodeId)) {
|
|
33
|
+
return nodeId.split(":")[1];
|
|
34
|
+
}
|
|
35
|
+
return getNodeIdLocation(nodeId)!.address;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getNodeIdLocation(nodeId: string): { address: string, port: number; } | undefined {
|
|
39
|
+
if (isClientNodeId(nodeId)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
let [address, port] = nodeId.split(":");
|
|
43
|
+
return { address, port: parseInt(port) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getNodeIdDomain(nodeId: string): string {
|
|
47
|
+
let location = getNodeIdLocation(nodeId);
|
|
48
|
+
if (!location) {
|
|
49
|
+
throw new Error(`Cannot get domain from nodeId, which is only usable as a client. NodeId: ${JSON.stringify(nodeId)}`);
|
|
50
|
+
}
|
|
51
|
+
return new URL(location.address).hostname.split(".").slice(-2).join(".");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// NOTE: CallFactory turns into an actual CallFactory when registerNodeClient is called
|
|
55
|
+
// nodeId =>
|
|
56
|
+
const nodeCache = new Map<string, MaybePromise<CallFactory>>();
|
|
57
|
+
|
|
58
|
+
// NOTE: Should be called directly inside call factory constructor whenever
|
|
59
|
+
// their nodeId changes (and on construction).
|
|
60
|
+
export function registerNodeClient(callFactory: CallFactory) {
|
|
61
|
+
nodeCache.set(callFactory.nodeId, callFactory);
|
|
62
|
+
startCleanupLoop();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getCreateCallFactory(nodeId: string): MaybePromise<CallFactory> {
|
|
66
|
+
let callFactory = nodeCache.get(nodeId);
|
|
67
|
+
if (callFactory === undefined) {
|
|
68
|
+
callFactory = createCallFactory(undefined, nodeId);
|
|
69
|
+
nodeCache.set(nodeId, callFactory);
|
|
70
|
+
}
|
|
71
|
+
return callFactory;
|
|
72
|
+
}
|
|
73
|
+
export function getCallFactory(nodeId: string): MaybePromise<CallFactory | undefined> {
|
|
74
|
+
return nodeCache.get(nodeId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const startCleanupLoop = lazy(() => {
|
|
78
|
+
(async () => {
|
|
79
|
+
while (true) {
|
|
80
|
+
for (let [key, value] of Array.from(nodeCache.entries())) {
|
|
81
|
+
let factory = value;
|
|
82
|
+
if (!(factory instanceof Promise)) {
|
|
83
|
+
if (factory.closedForever) {
|
|
84
|
+
nodeCache.delete(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * 60 * 5));
|
|
89
|
+
}
|
|
90
|
+
})().catch(e => {
|
|
91
|
+
console.error(`nodeCache cleanup loop failed, ${e.stack}`);
|
|
92
|
+
});
|
|
93
93
|
});
|
package/src/nodeProxy.ts
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { lazy } from "./caching";
|
|
2
|
-
import { FullCallType, SocketExposedInterface, SocketInternalInterface } from "../SocketFunctionTypes";
|
|
3
|
-
|
|
4
|
-
type CallProxyType = {
|
|
5
|
-
[nodeId: string]: SocketInternalInterface;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export const getCallObj = Symbol.for("getCallObj");
|
|
9
|
-
|
|
10
|
-
let proxyCache = new Map<string, CallProxyType>();
|
|
11
|
-
export function getCallProxy(id: string, callback: (callType: FullCallType) => Promise<unknown>): CallProxyType {
|
|
12
|
-
let value = proxyCache.get(id);
|
|
13
|
-
if (!value) {
|
|
14
|
-
let nodeCache = new Map<string, CallProxyType[""]>();
|
|
15
|
-
value = new Proxy(Object.create(null), {
|
|
16
|
-
get(target, nodeId) {
|
|
17
|
-
if (typeof nodeId !== "string") return undefined;
|
|
18
|
-
let nodeProxy = nodeCache.get(nodeId);
|
|
19
|
-
if (!nodeProxy) {
|
|
20
|
-
nodeProxy = new Proxy(Object.create(null), {
|
|
21
|
-
get(target, functionName) {
|
|
22
|
-
if (typeof functionName !== "string") return undefined;
|
|
23
|
-
return Object.assign(
|
|
24
|
-
(...args: unknown[]) => {
|
|
25
|
-
let call: FullCallType = {
|
|
26
|
-
classGuid: id,
|
|
27
|
-
nodeId,
|
|
28
|
-
functionName,
|
|
29
|
-
args,
|
|
30
|
-
};
|
|
31
|
-
return callback(call);
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
[getCallObj]: (...args: unknown[]) => {
|
|
35
|
-
let call: FullCallType = {
|
|
36
|
-
classGuid: id,
|
|
37
|
-
nodeId,
|
|
38
|
-
functionName,
|
|
39
|
-
args,
|
|
40
|
-
};
|
|
41
|
-
return call;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}) as CallProxyType[""];
|
|
47
|
-
nodeCache.set(nodeId, nodeProxy);
|
|
48
|
-
}
|
|
49
|
-
return nodeProxy;
|
|
50
|
-
},
|
|
51
|
-
}) as CallProxyType;
|
|
52
|
-
proxyCache.set(id, value);
|
|
53
|
-
}
|
|
54
|
-
return value;
|
|
1
|
+
import { lazy } from "./caching";
|
|
2
|
+
import { FullCallType, SocketExposedInterface, SocketInternalInterface } from "../SocketFunctionTypes";
|
|
3
|
+
|
|
4
|
+
type CallProxyType = {
|
|
5
|
+
[nodeId: string]: SocketInternalInterface;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getCallObj = Symbol.for("getCallObj");
|
|
9
|
+
|
|
10
|
+
let proxyCache = new Map<string, CallProxyType>();
|
|
11
|
+
export function getCallProxy(id: string, callback: (callType: FullCallType) => Promise<unknown>): CallProxyType {
|
|
12
|
+
let value = proxyCache.get(id);
|
|
13
|
+
if (!value) {
|
|
14
|
+
let nodeCache = new Map<string, CallProxyType[""]>();
|
|
15
|
+
value = new Proxy(Object.create(null), {
|
|
16
|
+
get(target, nodeId) {
|
|
17
|
+
if (typeof nodeId !== "string") return undefined;
|
|
18
|
+
let nodeProxy = nodeCache.get(nodeId);
|
|
19
|
+
if (!nodeProxy) {
|
|
20
|
+
nodeProxy = new Proxy(Object.create(null), {
|
|
21
|
+
get(target, functionName) {
|
|
22
|
+
if (typeof functionName !== "string") return undefined;
|
|
23
|
+
return Object.assign(
|
|
24
|
+
(...args: unknown[]) => {
|
|
25
|
+
let call: FullCallType = {
|
|
26
|
+
classGuid: id,
|
|
27
|
+
nodeId,
|
|
28
|
+
functionName,
|
|
29
|
+
args,
|
|
30
|
+
};
|
|
31
|
+
return callback(call);
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
[getCallObj]: (...args: unknown[]) => {
|
|
35
|
+
let call: FullCallType = {
|
|
36
|
+
classGuid: id,
|
|
37
|
+
nodeId,
|
|
38
|
+
functionName,
|
|
39
|
+
args,
|
|
40
|
+
};
|
|
41
|
+
return call;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}) as CallProxyType[""];
|
|
47
|
+
nodeCache.set(nodeId, nodeProxy);
|
|
48
|
+
}
|
|
49
|
+
return nodeProxy;
|
|
50
|
+
},
|
|
51
|
+
}) as CallProxyType;
|
|
52
|
+
proxyCache.set(id, value);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
55
|
}
|
|
@@ -1,143 +1,108 @@
|
|
|
1
|
-
import debugbreak from "debugbreak";
|
|
2
|
-
// TODO: We could probably make this an optional / dev dependency, to allow
|
|
3
|
-
// for use on machines without the ability to compile?
|
|
4
|
-
import { now } from "rdtsc-now";
|
|
5
|
-
|
|
6
|
-
export type OwnTimeObj = {
|
|
7
|
-
name: string;
|
|
8
|
-
time: number;
|
|
9
|
-
ownTime: number;
|
|
10
|
-
};
|
|
11
|
-
type OwnTimeObjInternal = OwnTimeObj & {
|
|
12
|
-
lastStartTime: number;
|
|
13
|
-
firstStartTime: number;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Our parent is now the last open call
|
|
109
|
-
pendingCallTime = obj.parent;
|
|
110
|
-
if (pendingCallTime) {
|
|
111
|
-
// Resume our parent ownTime counting
|
|
112
|
-
pendingCallTime.lastStartTime = time;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (obj.child && obj.parent) {
|
|
116
|
-
obj.child.parent = obj.parent;
|
|
117
|
-
obj.parent.child = obj.child;
|
|
118
|
-
}
|
|
119
|
-
obj.parent = undefined;
|
|
120
|
-
obj.child = undefined;
|
|
121
|
-
|
|
122
|
-
obj.time += addMeasureOverheadTime;
|
|
123
|
-
obj.ownTime += addMeasureOverheadTime;
|
|
124
|
-
|
|
125
|
-
onTime(obj);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let isAsync = false;
|
|
129
|
-
try {
|
|
130
|
-
let result = code();
|
|
131
|
-
if (result && typeof result === "object" && result instanceof Promise) {
|
|
132
|
-
isAsync = true;
|
|
133
|
-
return result.finally(() => {
|
|
134
|
-
finish();
|
|
135
|
-
}) as any;
|
|
136
|
-
}
|
|
137
|
-
return result;
|
|
138
|
-
} finally {
|
|
139
|
-
if (!isAsync) {
|
|
140
|
-
finish();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
1
|
+
import debugbreak from "debugbreak";
|
|
2
|
+
// TODO: We could probably make this an optional / dev dependency, to allow
|
|
3
|
+
// for use on machines without the ability to compile?
|
|
4
|
+
import { now } from "rdtsc-now";
|
|
5
|
+
|
|
6
|
+
export type OwnTimeObj = {
|
|
7
|
+
name: string;
|
|
8
|
+
time: number;
|
|
9
|
+
ownTime: number;
|
|
10
|
+
};
|
|
11
|
+
export type OwnTimeObjInternal = OwnTimeObj & {
|
|
12
|
+
lastStartTime: number;
|
|
13
|
+
firstStartTime: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let openTimes: OwnTimeObjInternal[] = [];
|
|
17
|
+
|
|
18
|
+
export function getOpenTimesBase(): OwnTimeObjInternal[] {
|
|
19
|
+
return openTimes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
(global as any).pendingOwnCallTime = openTimes;
|
|
23
|
+
|
|
24
|
+
// NOTE: This overhead time is actually mostly for aggregate time, but it is needed,
|
|
25
|
+
// otherwise we consistently underestimate the time spent.
|
|
26
|
+
// ALSO! This forces high count lines to be at the top of the aggregate time list, which is really important!
|
|
27
|
+
// NOTE: The overhead time greatly varies, but even if it only takes 100ns, if 10X of that
|
|
28
|
+
// is significant, you are probably spending too much timing profiling anyway!
|
|
29
|
+
export const measureOverheadTime = 500 / 1000 / 1000;
|
|
30
|
+
// We internally add, because of where we measure time, there is time spent before we grab the
|
|
31
|
+
// current time, and after we record the last time, that is lost, but should be added.
|
|
32
|
+
let addMeasureOverheadTime = 0;
|
|
33
|
+
{
|
|
34
|
+
// NOTE: This is going to vary considerably. I assume because sometimes we are on a core
|
|
35
|
+
// that is free, and other times we are on a core that is hyperthreading with another hardware
|
|
36
|
+
// thread. This really hurts us because our timing uses rdtsc, which really hates hyper threading,
|
|
37
|
+
// and can easily get 50% slower because of it.
|
|
38
|
+
let results: number[] = [];
|
|
39
|
+
for (let j = 0; j < 10; j++) {
|
|
40
|
+
const measureCount = 1000 * 10;
|
|
41
|
+
let time = now();
|
|
42
|
+
for (let i = 0; i < measureCount; i++) {
|
|
43
|
+
getOwnTime("test", () => { }, () => { });
|
|
44
|
+
}
|
|
45
|
+
time = now() - time;
|
|
46
|
+
let overhead = time / measureCount;
|
|
47
|
+
results.push(overhead);
|
|
48
|
+
}
|
|
49
|
+
results.sort((a, b) => a - b);
|
|
50
|
+
addMeasureOverheadTime = results[results.length / 2];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TIMING: About 60ns, of which 40ns is just now() calls.
|
|
54
|
+
// If async is closer to 300ns.
|
|
55
|
+
export function getOwnTime<T>(
|
|
56
|
+
name: string,
|
|
57
|
+
code: () => T,
|
|
58
|
+
onTime: (obj: OwnTimeObj) => void
|
|
59
|
+
): T {
|
|
60
|
+
let time = now();
|
|
61
|
+
let obj: OwnTimeObjInternal = {
|
|
62
|
+
name,
|
|
63
|
+
time: 0,
|
|
64
|
+
ownTime: 0,
|
|
65
|
+
firstStartTime: time,
|
|
66
|
+
lastStartTime: time,
|
|
67
|
+
};
|
|
68
|
+
let prevOwnTime = openTimes[openTimes.length - 1];
|
|
69
|
+
if (prevOwnTime) {
|
|
70
|
+
prevOwnTime.ownTime += time - prevOwnTime.lastStartTime;
|
|
71
|
+
}
|
|
72
|
+
openTimes.push(obj);
|
|
73
|
+
|
|
74
|
+
function finish() {
|
|
75
|
+
let time = now();
|
|
76
|
+
obj.time = time - obj.firstStartTime;
|
|
77
|
+
if (obj === openTimes[openTimes.length - 1]) {
|
|
78
|
+
obj.ownTime += time - obj.lastStartTime;
|
|
79
|
+
let newOwnTime = openTimes[openTimes.length - 2];
|
|
80
|
+
if (newOwnTime) {
|
|
81
|
+
newOwnTime.lastStartTime = time;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let index = openTimes.indexOf(obj);
|
|
85
|
+
openTimes.splice(index, 1);
|
|
86
|
+
|
|
87
|
+
obj.time += addMeasureOverheadTime;
|
|
88
|
+
obj.ownTime += addMeasureOverheadTime;
|
|
89
|
+
|
|
90
|
+
onTime(obj);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let isAsync = false;
|
|
94
|
+
try {
|
|
95
|
+
let result = code();
|
|
96
|
+
if (result && typeof result === "object" && result instanceof Promise) {
|
|
97
|
+
isAsync = true;
|
|
98
|
+
return result.finally(() => {
|
|
99
|
+
finish();
|
|
100
|
+
}) as any;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
} finally {
|
|
104
|
+
if (!isAsync) {
|
|
105
|
+
finish();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
143
108
|
}
|