1 | import { BINARY } from "./generated-wasm";
|
---|
2 | import Histogram, {
|
---|
3 | NO_TAG,
|
---|
4 | BitBucketSize,
|
---|
5 | toSummary,
|
---|
6 | HistogramSummary
|
---|
7 | } from "../Histogram";
|
---|
8 | // @ts-ignore
|
---|
9 | import * as base64 from "base64-js";
|
---|
10 | // @ts-ignore
|
---|
11 | import * as pako from "pako";
|
---|
12 | // @ts-ignore
|
---|
13 | import * as loader from "@assemblyscript/loader";
|
---|
14 | import { BuildRequest } from "../HistogramBuilder";
|
---|
15 |
|
---|
16 | const isNode = typeof process !== "undefined" && process.version;
|
---|
17 | // @ts-ignore
|
---|
18 | const isWorker = typeof importScripts === "function";
|
---|
19 | export const webAssemblyAvailable = (() => {
|
---|
20 | let available = false;
|
---|
21 | if (isNode) {
|
---|
22 | // nodejs
|
---|
23 | available = "WebAssembly" in global;
|
---|
24 | } else {
|
---|
25 | // browser
|
---|
26 | // @ts-ignore
|
---|
27 | available = isWorker || "WebAssembly" in window;
|
---|
28 | }
|
---|
29 | return available;
|
---|
30 | })();
|
---|
31 |
|
---|
32 | let wasm: any = undefined;
|
---|
33 |
|
---|
34 | export const initWebAssembly = async (): Promise<void> => {
|
---|
35 | if (!webAssemblyAvailable) {
|
---|
36 | throw new Error("WebAssembly not available here!");
|
---|
37 | }
|
---|
38 | if (!!wasm) {
|
---|
39 | return;
|
---|
40 | }
|
---|
41 | return loader
|
---|
42 | .instantiate(pako.inflate(base64.toByteArray(BINARY)))
|
---|
43 | .then((w: any) => (wasm = w.exports || w));
|
---|
44 | };
|
---|
45 |
|
---|
46 | export const initWebAssemblySync = () => {
|
---|
47 | if (!!wasm) {
|
---|
48 | return;
|
---|
49 | }
|
---|
50 | const w = loader.instantiateSync(pako.inflate(base64.toByteArray(BINARY)));
|
---|
51 | wasm = w.exports || w;
|
---|
52 | };
|
---|
53 |
|
---|
54 | export const webAssemblyReady = () => !!wasm;
|
---|
55 |
|
---|
56 | const defaultRequest: BuildRequest = {
|
---|
57 | bitBucketSize: 32,
|
---|
58 | autoResize: true,
|
---|
59 | lowestDiscernibleValue: 1,
|
---|
60 | highestTrackableValue: 2,
|
---|
61 | numberOfSignificantValueDigits: 3
|
---|
62 | };
|
---|
63 |
|
---|
64 | const remoteHistogramClassFor = (size?: BitBucketSize) =>
|
---|
65 | size === "packed" ? "PackedHistogram" : `Histogram${size}`;
|
---|
66 |
|
---|
67 | const destroyedWasmHistogram = new Proxy(
|
---|
68 | {},
|
---|
69 | {
|
---|
70 | get: function(obj, prop) {
|
---|
71 | throw new Error("Cannot use a destroyed histogram");
|
---|
72 | }
|
---|
73 | }
|
---|
74 | );
|
---|
75 |
|
---|
76 | export class WasmHistogram implements Histogram {
|
---|
77 | tag: string;
|
---|
78 |
|
---|
79 | constructor(
|
---|
80 | private _wasmHistogram: any,
|
---|
81 | private _remoteHistogramClass: string
|
---|
82 | ) {
|
---|
83 | this.tag = NO_TAG;
|
---|
84 | }
|
---|
85 |
|
---|
86 | static build(request: BuildRequest = defaultRequest) {
|
---|
87 | if (!webAssemblyReady()) {
|
---|
88 | throw new Error("WebAssembly is not ready yet!");
|
---|
89 | }
|
---|
90 | const parameters = Object.assign({}, defaultRequest, request);
|
---|
91 | const remoteHistogramClass = remoteHistogramClassFor(
|
---|
92 | parameters.bitBucketSize
|
---|
93 | );
|
---|
94 | return new WasmHistogram(
|
---|
95 | new wasm[remoteHistogramClass](
|
---|
96 | parameters.lowestDiscernibleValue,
|
---|
97 | parameters.highestTrackableValue,
|
---|
98 | parameters.numberOfSignificantValueDigits,
|
---|
99 | parameters.autoResize
|
---|
100 | ),
|
---|
101 | remoteHistogramClass
|
---|
102 | );
|
---|
103 | }
|
---|
104 |
|
---|
105 | static decode(
|
---|
106 | data: Uint8Array,
|
---|
107 | bitBucketSize: 8 | 16 | 32 | 64 | "packed" = 32,
|
---|
108 | minBarForHighestTrackableValue: number = 0
|
---|
109 | ): Histogram {
|
---|
110 | if (!webAssemblyReady()) {
|
---|
111 | throw new Error("WebAssembly is not ready yet!");
|
---|
112 | }
|
---|
113 | const remoteHistogramClass = remoteHistogramClassFor(bitBucketSize);
|
---|
114 | const decodeFunc = `decode${remoteHistogramClass}`;
|
---|
115 | const ptrArr = wasm.__retain(wasm.__allocArray(wasm.UINT8ARRAY_ID, data));
|
---|
116 | const wasmHistogram = new WasmHistogram(
|
---|
117 | wasm[remoteHistogramClass].wrap(
|
---|
118 | wasm[decodeFunc](ptrArr, minBarForHighestTrackableValue)
|
---|
119 | ),
|
---|
120 | remoteHistogramClass
|
---|
121 | );
|
---|
122 | wasm.__release(ptrArr);
|
---|
123 | return wasmHistogram;
|
---|
124 | }
|
---|
125 |
|
---|
126 | public get numberOfSignificantValueDigits(): number {
|
---|
127 | return this._wasmHistogram.numberOfSignificantValueDigits;
|
---|
128 | }
|
---|
129 |
|
---|
130 | public get autoResize(): boolean {
|
---|
131 | return !!this._wasmHistogram.autoResize;
|
---|
132 | }
|
---|
133 |
|
---|
134 | public set autoResize(resize: boolean) {
|
---|
135 | this._wasmHistogram.autoResize = resize;
|
---|
136 | }
|
---|
137 |
|
---|
138 | public get highestTrackableValue(): number {
|
---|
139 | return this._wasmHistogram.highestTrackableValue;
|
---|
140 | }
|
---|
141 |
|
---|
142 | public set highestTrackableValue(value: number) {
|
---|
143 | this._wasmHistogram.highestTrackableValue = value;
|
---|
144 | }
|
---|
145 |
|
---|
146 | public get startTimeStampMsec(): number {
|
---|
147 | return this._wasmHistogram.startTimeStampMsec;
|
---|
148 | }
|
---|
149 |
|
---|
150 | public set startTimeStampMsec(value: number) {
|
---|
151 | this._wasmHistogram.startTimeStampMsec = value;
|
---|
152 | }
|
---|
153 |
|
---|
154 | public get endTimeStampMsec(): number {
|
---|
155 | return this._wasmHistogram.endTimeStampMsec;
|
---|
156 | }
|
---|
157 |
|
---|
158 | public set endTimeStampMsec(value: number) {
|
---|
159 | this._wasmHistogram.endTimeStampMsec = value;
|
---|
160 | }
|
---|
161 |
|
---|
162 | public get totalCount(): number {
|
---|
163 | return this._wasmHistogram.totalCount;
|
---|
164 | }
|
---|
165 | public get stdDeviation(): number {
|
---|
166 | return this._wasmHistogram.stdDeviation;
|
---|
167 | }
|
---|
168 | public get mean(): number {
|
---|
169 | return this._wasmHistogram.mean;
|
---|
170 | }
|
---|
171 | public get estimatedFootprintInBytes(): number {
|
---|
172 | return 192 + this._wasmHistogram.estimatedFootprintInBytes;
|
---|
173 | }
|
---|
174 |
|
---|
175 | public get minNonZeroValue(): number {
|
---|
176 | return this._wasmHistogram.minNonZeroValue;
|
---|
177 | }
|
---|
178 | public get maxValue(): number {
|
---|
179 | return this._wasmHistogram.maxValue;
|
---|
180 | }
|
---|
181 |
|
---|
182 | recordValue(value: number) {
|
---|
183 | this._wasmHistogram.recordValue(value);
|
---|
184 | }
|
---|
185 |
|
---|
186 | recordValueWithCount(value: number, count: number): void {
|
---|
187 | this._wasmHistogram.recordValueWithCount(value, count);
|
---|
188 | }
|
---|
189 |
|
---|
190 | recordValueWithExpectedInterval(
|
---|
191 | value: number,
|
---|
192 | expectedIntervalBetweenValueSamples: number
|
---|
193 | ) {
|
---|
194 | this._wasmHistogram.recordValueWithExpectedInterval(
|
---|
195 | value,
|
---|
196 | expectedIntervalBetweenValueSamples
|
---|
197 | );
|
---|
198 | }
|
---|
199 |
|
---|
200 | getValueAtPercentile(percentile: number): number {
|
---|
201 | return this._wasmHistogram.getValueAtPercentile(percentile);
|
---|
202 | }
|
---|
203 |
|
---|
204 | outputPercentileDistribution(
|
---|
205 | percentileTicksPerHalfDistance = 5,
|
---|
206 | outputValueUnitScalingRatio = 1,
|
---|
207 | useCsvFormat = false
|
---|
208 | ): string {
|
---|
209 | // TODO csv
|
---|
210 | if (useCsvFormat) {
|
---|
211 | throw new Error("CSV output not supported by wasm histograms");
|
---|
212 | }
|
---|
213 | return wasm.__getString(
|
---|
214 | this._wasmHistogram.outputPercentileDistribution(
|
---|
215 | percentileTicksPerHalfDistance,
|
---|
216 | outputValueUnitScalingRatio
|
---|
217 | )
|
---|
218 | );
|
---|
219 | }
|
---|
220 |
|
---|
221 | private isDestroyed() {
|
---|
222 | return this._wasmHistogram === destroyedWasmHistogram;
|
---|
223 | }
|
---|
224 |
|
---|
225 | get summary(): HistogramSummary {
|
---|
226 | return toSummary(this);
|
---|
227 | }
|
---|
228 |
|
---|
229 | toJSON(): HistogramSummary {
|
---|
230 | return this.summary;
|
---|
231 | }
|
---|
232 |
|
---|
233 | toString() {
|
---|
234 | if (this.isDestroyed()) {
|
---|
235 | return "Destroyed WASM histogram";
|
---|
236 | }
|
---|
237 | return `WASM ${this._remoteHistogramClass} ${JSON.stringify(
|
---|
238 | this,
|
---|
239 | null,
|
---|
240 | 2
|
---|
241 | )}`;
|
---|
242 | }
|
---|
243 |
|
---|
244 | inspect() {
|
---|
245 | return this.toString();
|
---|
246 | }
|
---|
247 |
|
---|
248 | [Symbol.for("nodejs.util.inspect.custom")]() {
|
---|
249 | return this.toString();
|
---|
250 | }
|
---|
251 |
|
---|
252 | addWhileCorrectingForCoordinatedOmission(
|
---|
253 | otherHistogram: WasmHistogram,
|
---|
254 | expectedIntervalBetweenValueSamples: number
|
---|
255 | ): void {
|
---|
256 | this._wasmHistogram.addWhileCorrectingForCoordinatedOmission(
|
---|
257 | otherHistogram,
|
---|
258 | expectedIntervalBetweenValueSamples
|
---|
259 | );
|
---|
260 | }
|
---|
261 |
|
---|
262 | copyCorrectedForCoordinatedOmission(
|
---|
263 | expectedIntervalBetweenValueSamples: number
|
---|
264 | ): WasmHistogram {
|
---|
265 | return new WasmHistogram(
|
---|
266 | wasm[this._remoteHistogramClass].wrap(
|
---|
267 | this._wasmHistogram.copyCorrectedForCoordinatedOmission(
|
---|
268 | expectedIntervalBetweenValueSamples
|
---|
269 | )
|
---|
270 | ),
|
---|
271 | this._remoteHistogramClass
|
---|
272 | );
|
---|
273 | }
|
---|
274 |
|
---|
275 | add(otherHistogram: WasmHistogram): void {
|
---|
276 | if (!(otherHistogram instanceof WasmHistogram)) {
|
---|
277 | // should be impossible to be in this situation but actually
|
---|
278 | // TypeScript has some flaws...
|
---|
279 | throw new Error("Cannot add a regular JS histogram to a WASM histogram");
|
---|
280 | }
|
---|
281 | this._wasmHistogram[`add${otherHistogram._remoteHistogramClass}`](
|
---|
282 | otherHistogram._wasmHistogram
|
---|
283 | );
|
---|
284 | }
|
---|
285 |
|
---|
286 | subtract(otherHistogram: WasmHistogram): void {
|
---|
287 | if (!(otherHistogram instanceof WasmHistogram)) {
|
---|
288 | // should be impossible to be in this situation but actually
|
---|
289 | // TypeScript has some flaws...
|
---|
290 | throw new Error(
|
---|
291 | "Cannot subtract a regular JS histogram to a WASM histogram"
|
---|
292 | );
|
---|
293 | }
|
---|
294 | this._wasmHistogram[`subtract${otherHistogram._remoteHistogramClass}`](
|
---|
295 | otherHistogram._wasmHistogram
|
---|
296 | );
|
---|
297 | }
|
---|
298 |
|
---|
299 | encode(): Uint8Array {
|
---|
300 | const ptrArray = this._wasmHistogram.encode();
|
---|
301 | const array = wasm.__getUint8Array(ptrArray);
|
---|
302 | wasm.__release(ptrArray);
|
---|
303 | return array;
|
---|
304 | }
|
---|
305 |
|
---|
306 | reset(): void {
|
---|
307 | this.tag = NO_TAG;
|
---|
308 | this._wasmHistogram.reset();
|
---|
309 | }
|
---|
310 |
|
---|
311 | destroy(): void {
|
---|
312 | wasm.__release(this._wasmHistogram);
|
---|
313 | this._wasmHistogram = destroyedWasmHistogram;
|
---|
314 | }
|
---|
315 | }
|
---|