Histogram.spec.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const _1 = require(".");
  4. const JsHistogram_1 = require("./JsHistogram");
  5. const Histogram_1 = require("./Histogram");
  6. const Int32Histogram_1 = require("./Int32Histogram");
  7. const wasm_1 = require("./wasm");
  8. class HistogramForTests extends JsHistogram_1.default {
  9. //constructor() {}
  10. clearCounts() { }
  11. incrementCountAtIndex(index) { }
  12. incrementTotalCount() { }
  13. addToTotalCount(value) { }
  14. setTotalCount(totalCount) { }
  15. resize(newHighestTrackableValue) {
  16. this.establishSize(newHighestTrackableValue);
  17. }
  18. addToCountAtIndex(index, value) { }
  19. setCountAtIndex(index, value) { }
  20. getTotalCount() {
  21. return 0;
  22. }
  23. getCountAtIndex(index) {
  24. return 0;
  25. }
  26. _getEstimatedFootprintInBytes() {
  27. return 42;
  28. }
  29. copyCorrectedForCoordinatedOmission(expectedIntervalBetweenValueSamples) {
  30. return this;
  31. }
  32. }
  33. describe("Histogram initialization", () => {
  34. let histogram;
  35. beforeEach(() => {
  36. histogram = new HistogramForTests(1, Number.MAX_SAFE_INTEGER, 3);
  37. });
  38. it("should set sub bucket size", () => {
  39. expect(histogram.subBucketCount).toBe(2048);
  40. });
  41. it("should set resize to false when max value specified", () => {
  42. expect(histogram.autoResize).toBe(false);
  43. });
  44. it("should compute counts array length", () => {
  45. expect(histogram.countsArrayLength).toBe(45056);
  46. });
  47. it("should compute bucket count", () => {
  48. expect(histogram.bucketCount).toBe(43);
  49. });
  50. it("should set min non zero value", () => {
  51. expect(histogram.minNonZeroValue).toBe(Number.MAX_SAFE_INTEGER);
  52. });
  53. it("should set max value", () => {
  54. expect(histogram.maxValue).toBe(0);
  55. });
  56. });
  57. describe("Histogram recording values", () => {
  58. it("should compute count index when value in first bucket", () => {
  59. // given
  60. const histogram = new HistogramForTests(1, Number.MAX_SAFE_INTEGER, 3);
  61. // when
  62. const index = histogram.countsArrayIndex(2000); // 2000 < 2048
  63. expect(index).toBe(2000);
  64. });
  65. it("should compute count index when value outside first bucket", () => {
  66. // given
  67. const histogram = new HistogramForTests(1, Number.MAX_SAFE_INTEGER, 3);
  68. // when
  69. const index = histogram.countsArrayIndex(2050); // 2050 > 2048
  70. // then
  71. expect(index).toBe(2049);
  72. });
  73. it("should compute count index taking into account lowest discernible value", () => {
  74. // given
  75. const histogram = new HistogramForTests(2000, Number.MAX_SAFE_INTEGER, 2);
  76. // when
  77. const index = histogram.countsArrayIndex(16000);
  78. // then
  79. expect(index).toBe(15);
  80. });
  81. it("should compute count index of a big value taking into account lowest discernible value", () => {
  82. // given
  83. const histogram = new HistogramForTests(2000, Number.MAX_SAFE_INTEGER, 2);
  84. // when
  85. const bigValue = Number.MAX_SAFE_INTEGER - 1;
  86. const index = histogram.countsArrayIndex(bigValue);
  87. // then
  88. expect(index).toBe(4735);
  89. });
  90. it("should update min non zero value", () => {
  91. // given
  92. const histogram = new HistogramForTests(1, Number.MAX_SAFE_INTEGER, 3);
  93. // when
  94. histogram.recordValue(123);
  95. // then
  96. expect(histogram.minNonZeroValue).toBe(123);
  97. });
  98. it("should update max value", () => {
  99. // given
  100. const histogram = new HistogramForTests(1, Number.MAX_SAFE_INTEGER, 3);
  101. // when
  102. histogram.recordValue(123);
  103. // then
  104. expect(histogram.maxValue).toBe(123);
  105. });
  106. it("should throw an error when value bigger than highest trackable value", () => {
  107. // given
  108. const histogram = new HistogramForTests(1, 4096, 3);
  109. // when then
  110. expect(() => histogram.recordValue(9000)).toThrowError();
  111. });
  112. it("should not throw an error when autoresize enable and value bigger than highest trackable value", () => {
  113. // given
  114. const histogram = new HistogramForTests(1, 4096, 3);
  115. histogram.autoResize = true;
  116. // when then
  117. expect(() => histogram.recordValue(9000)).not.toThrowError();
  118. });
  119. it("should increase counts array size when recording value bigger than highest trackable value", () => {
  120. // given
  121. const histogram = new HistogramForTests(1, 4096, 3);
  122. histogram.autoResize = true;
  123. // when
  124. histogram.recordValue(9000);
  125. // then
  126. expect(histogram.highestTrackableValue).toBeGreaterThan(9000);
  127. });
  128. });
  129. describe("Histogram computing statistics", () => {
  130. const histogram = new Int32Histogram_1.default(1, Number.MAX_SAFE_INTEGER, 3);
  131. it("should compute mean value", () => {
  132. // given
  133. histogram.reset();
  134. // when
  135. histogram.recordValue(25);
  136. histogram.recordValue(50);
  137. histogram.recordValue(75);
  138. // then
  139. expect(histogram.mean).toBe(50);
  140. });
  141. it("should compute standard deviation", () => {
  142. // given
  143. histogram.reset();
  144. // when
  145. histogram.recordValue(25);
  146. histogram.recordValue(50);
  147. histogram.recordValue(75);
  148. // then
  149. expect(histogram.stdDeviation).toBeGreaterThan(20.4124);
  150. expect(histogram.stdDeviation).toBeLessThan(20.4125);
  151. });
  152. it("should compute percentile distribution", () => {
  153. // given
  154. histogram.reset();
  155. // when
  156. histogram.recordValue(25);
  157. histogram.recordValue(50);
  158. histogram.recordValue(75);
  159. // then
  160. const expectedResult = ` Value Percentile TotalCount 1/(1-Percentile)
  161. 25.000 0.000000000000 1 1.00
  162. 25.000 0.100000000000 1 1.11
  163. 25.000 0.200000000000 1 1.25
  164. 25.000 0.300000000000 1 1.43
  165. 50.000 0.400000000000 2 1.67
  166. 50.000 0.500000000000 2 2.00
  167. 50.000 0.550000000000 2 2.22
  168. 50.000 0.600000000000 2 2.50
  169. 50.000 0.650000000000 2 2.86
  170. 75.000 0.700000000000 3 3.33
  171. 75.000 1.000000000000 3
  172. #[Mean = 50.000, StdDeviation = 20.412]
  173. #[Max = 75.000, Total count = 3]
  174. #[Buckets = 43, SubBuckets = 2048]
  175. `;
  176. expect(histogram.outputPercentileDistribution()).toBe(expectedResult);
  177. });
  178. it("should compute percentile distribution in csv format", () => {
  179. // given
  180. histogram.reset();
  181. // when
  182. histogram.recordValue(25);
  183. histogram.recordValue(50);
  184. histogram.recordValue(75);
  185. // then
  186. const expectedResult = `"Value","Percentile","TotalCount","1/(1-Percentile)"
  187. 25.000,0.000000000000,1,1.00
  188. 25.000,0.100000000000,1,1.11
  189. 25.000,0.200000000000,1,1.25
  190. 25.000,0.300000000000,1,1.43
  191. 50.000,0.400000000000,2,1.67
  192. 50.000,0.500000000000,2,2.00
  193. 50.000,0.550000000000,2,2.22
  194. 50.000,0.600000000000,2,2.50
  195. 50.000,0.650000000000,2,2.86
  196. 75.000,0.700000000000,3,3.33
  197. 75.000,1.000000000000,3,Infinity
  198. `;
  199. expect(histogram.outputPercentileDistribution(undefined, undefined, true)).toBe(expectedResult);
  200. });
  201. it("should compute percentile distribution in JSON format with rounding according to number of significant digits", () => {
  202. // given
  203. histogram.reset();
  204. // when
  205. histogram.recordValue(25042);
  206. histogram.recordValue(50042);
  207. histogram.recordValue(75042);
  208. // then
  209. const { summary } = histogram;
  210. expect(summary.p50).toEqual(50000);
  211. });
  212. });
  213. describe("Histogram correcting coordinated omissions", () => {
  214. const histogram = new Int32Histogram_1.default(1, Number.MAX_SAFE_INTEGER, 3);
  215. it("should generate additional values when recording", () => {
  216. // given
  217. histogram.reset();
  218. // when
  219. histogram.recordValueWithExpectedInterval(207, 100);
  220. // then
  221. expect(histogram.totalCount).toBe(2);
  222. expect(histogram.minNonZeroValue).toBe(107);
  223. expect(histogram.maxValue).toBe(207);
  224. });
  225. it("should generate additional values when correcting after recording", () => {
  226. // given
  227. histogram.reset();
  228. histogram.recordValue(207);
  229. histogram.recordValue(207);
  230. // when
  231. const correctedHistogram = histogram.copyCorrectedForCoordinatedOmission(100);
  232. // then
  233. expect(correctedHistogram.totalCount).toBe(4);
  234. expect(correctedHistogram.minNonZeroValue).toBe(107);
  235. expect(correctedHistogram.maxValue).toBe(207);
  236. });
  237. it("should not generate additional values when correcting after recording", () => {
  238. // given
  239. histogram.reset();
  240. histogram.recordValue(207);
  241. histogram.recordValue(207);
  242. // when
  243. const correctedHistogram = histogram.copyCorrectedForCoordinatedOmission(1000);
  244. // then
  245. expect(correctedHistogram.totalCount).toBe(2);
  246. expect(correctedHistogram.minNonZeroValue).toBe(207);
  247. expect(correctedHistogram.maxValue).toBe(207);
  248. });
  249. });
  250. describe("WASM Histogram not initialized", () => {
  251. it("should throw a clear error message", () => {
  252. expect(() => _1.build({ useWebAssembly: true })).toThrow("WebAssembly is not ready yet");
  253. expect(() => wasm_1.WasmHistogram.build()).toThrow("WebAssembly is not ready yet");
  254. expect(() => wasm_1.WasmHistogram.decode(null)).toThrow("WebAssembly is not ready yet");
  255. });
  256. });
  257. describe("WASM Histogram not happy path", () => {
  258. beforeEach(wasm_1.initWebAssemblySync);
  259. it("should throw a clear error message when used after destroy", () => {
  260. const destroyedHistogram = _1.build({ useWebAssembly: true });
  261. destroyedHistogram.destroy();
  262. expect(() => destroyedHistogram.recordValue(42)).toThrow("Cannot use a destroyed histogram");
  263. });
  264. it("should not crash when displayed after destroy", () => {
  265. const destroyedHistogram = _1.build({ useWebAssembly: true });
  266. destroyedHistogram.destroy();
  267. expect(destroyedHistogram + "").toEqual("Destroyed WASM histogram");
  268. });
  269. it("should throw a clear error message when added to a JS regular Histogram", () => {
  270. const wasmHistogram = _1.build({ useWebAssembly: true });
  271. const jsHistogram = _1.build({ useWebAssembly: false });
  272. expect(() => jsHistogram.add(wasmHistogram)).toThrow("Cannot add a WASM histogram to a regular JS histogram");
  273. });
  274. it("should throw a clear error message when trying to add a JS regular Histogram", () => {
  275. const wasmHistogram = _1.build({ useWebAssembly: true });
  276. const jsHistogram = _1.build({ useWebAssembly: false });
  277. expect(() => wasmHistogram.add(jsHistogram)).toThrow("Cannot add a regular JS histogram to a WASM histogram");
  278. });
  279. it("should throw a clear error message when substracted to a JS regular Histogram", () => {
  280. const wasmHistogram = _1.build({ useWebAssembly: true });
  281. const jsHistogram = _1.build({ useWebAssembly: false });
  282. expect(() => jsHistogram.subtract(wasmHistogram)).toThrow("Cannot subtract a WASM histogram to a regular JS histogram");
  283. });
  284. it("should throw a clear error message when trying to add a JS regular Histogram", () => {
  285. const wasmHistogram = _1.build({ useWebAssembly: true });
  286. const jsHistogram = _1.build({ useWebAssembly: false });
  287. expect(() => wasmHistogram.subtract(jsHistogram)).toThrow("Cannot subtract a regular JS histogram to a WASM histogram");
  288. });
  289. });
  290. describe("WASM estimated memory footprint", () => {
  291. let wasmHistogram;
  292. beforeAll(wasm_1.initWebAssembly);
  293. afterEach(() => wasmHistogram.destroy());
  294. it("should be a little bit more than js footprint for packed histograms", () => {
  295. wasmHistogram = _1.build({ useWebAssembly: true, bitBucketSize: "packed" });
  296. expect(wasmHistogram.estimatedFootprintInBytes).toBeGreaterThan(_1.build({ bitBucketSize: "packed" }).estimatedFootprintInBytes);
  297. });
  298. });
  299. describe("WASM Histogram correcting coordinated omissions", () => {
  300. let histogram;
  301. beforeAll(wasm_1.initWebAssembly);
  302. beforeEach(() => {
  303. histogram = _1.build({ useWebAssembly: true });
  304. });
  305. afterEach(() => histogram.destroy());
  306. it("should generate additional values when recording", () => {
  307. // given
  308. histogram.reset();
  309. // when
  310. histogram.recordValueWithExpectedInterval(207, 100);
  311. // then
  312. expect(histogram.totalCount).toBe(2);
  313. expect(histogram.minNonZeroValue).toBe(107);
  314. expect(histogram.maxValue).toBe(207);
  315. });
  316. it("should generate additional values when correcting after recording", () => {
  317. // given
  318. histogram.reset();
  319. histogram.recordValue(207);
  320. histogram.recordValue(207);
  321. // when
  322. const correctedHistogram = histogram.copyCorrectedForCoordinatedOmission(100);
  323. // then
  324. expect(correctedHistogram.totalCount).toBe(4);
  325. expect(correctedHistogram.minNonZeroValue).toBe(107);
  326. expect(correctedHistogram.maxValue).toBe(207);
  327. });
  328. it("should not generate additional values when correcting after recording", () => {
  329. // given
  330. histogram.reset();
  331. histogram.recordValue(207);
  332. histogram.recordValue(207);
  333. // when
  334. const correctedHistogram = histogram.copyCorrectedForCoordinatedOmission(1000);
  335. // then
  336. expect(correctedHistogram.totalCount).toBe(2);
  337. expect(correctedHistogram.minNonZeroValue).toBe(207);
  338. expect(correctedHistogram.maxValue).toBe(207);
  339. });
  340. });
  341. describe("Histogram add & substract", () => {
  342. beforeAll(wasm_1.initWebAssembly);
  343. it("should add histograms of same size", () => {
  344. // given
  345. const histogram = new Int32Histogram_1.default(1, Number.MAX_SAFE_INTEGER, 2);
  346. const histogram2 = new Int32Histogram_1.default(1, Number.MAX_SAFE_INTEGER, 2);
  347. histogram.recordValue(42);
  348. histogram2.recordValue(158);
  349. // when
  350. histogram.add(histogram2);
  351. // then
  352. expect(histogram.totalCount).toBe(2);
  353. expect(histogram.mean).toBe(100);
  354. });
  355. it("should add histograms of different sizes & precisions", () => {
  356. // given
  357. const histogram = _1.build({
  358. lowestDiscernibleValue: 1,
  359. highestTrackableValue: 1024,
  360. autoResize: true,
  361. numberOfSignificantValueDigits: 2,
  362. bitBucketSize: "packed",
  363. useWebAssembly: true,
  364. });
  365. const histogram2 = _1.build({
  366. lowestDiscernibleValue: 1,
  367. highestTrackableValue: 1024,
  368. autoResize: true,
  369. numberOfSignificantValueDigits: 3,
  370. bitBucketSize: 32,
  371. useWebAssembly: true,
  372. });
  373. //histogram2.autoResize = true;
  374. histogram.recordValue(42000);
  375. histogram2.recordValue(1000);
  376. // when
  377. histogram.add(histogram2);
  378. // then
  379. expect(histogram.totalCount).toBe(2);
  380. expect(Math.floor(histogram.mean / 100)).toBe(215);
  381. });
  382. it("should add histograms of different sizes", () => {
  383. // given
  384. const histogram = new Int32Histogram_1.default(1, Number.MAX_SAFE_INTEGER, 2);
  385. const histogram2 = new Int32Histogram_1.default(1, 1024, 2);
  386. histogram2.autoResize = true;
  387. histogram.recordValue(42000);
  388. histogram2.recordValue(1000);
  389. // when
  390. histogram.add(histogram2);
  391. // then
  392. expect(histogram.totalCount).toBe(2);
  393. expect(Math.floor(histogram.mean / 100)).toBe(215);
  394. });
  395. it("should be equal when another histogram of lower precision is added then subtracted", () => {
  396. // given
  397. const histogram = _1.build({ numberOfSignificantValueDigits: 5 });
  398. const histogram2 = _1.build({ numberOfSignificantValueDigits: 3 });
  399. histogram.recordValue(100);
  400. histogram2.recordValue(42000);
  401. // when
  402. const before = histogram.summary;
  403. histogram.add(histogram2);
  404. histogram.subtract(histogram2);
  405. // then
  406. expect(histogram.summary).toStrictEqual(before);
  407. });
  408. it("should update percentiles when another histogram of same characteristics is substracted", () => {
  409. // given
  410. const histogram = _1.build({ numberOfSignificantValueDigits: 3 });
  411. const histogram2 = _1.build({ numberOfSignificantValueDigits: 3 });
  412. histogram.recordValueWithCount(100, 2);
  413. histogram2.recordValueWithCount(100, 1);
  414. histogram.recordValueWithCount(200, 2);
  415. histogram2.recordValueWithCount(200, 1);
  416. histogram.recordValueWithCount(300, 2);
  417. histogram2.recordValueWithCount(300, 1);
  418. // when
  419. histogram.subtract(histogram2);
  420. // then
  421. expect(histogram.getValueAtPercentile(50)).toBe(200);
  422. });
  423. });
  424. describe("Histogram clearing support", () => {
  425. beforeAll(wasm_1.initWebAssembly);
  426. it("should reset data in order to reuse histogram", () => {
  427. // given
  428. const histogram = _1.build({
  429. lowestDiscernibleValue: 1,
  430. highestTrackableValue: Number.MAX_SAFE_INTEGER,
  431. numberOfSignificantValueDigits: 5,
  432. useWebAssembly: true,
  433. });
  434. histogram.startTimeStampMsec = 42;
  435. histogram.endTimeStampMsec = 56;
  436. histogram.tag = "blabla";
  437. histogram.recordValue(1000);
  438. // when
  439. histogram.reset();
  440. // then
  441. expect(histogram.totalCount).toBe(0);
  442. expect(histogram.startTimeStampMsec).toBe(0);
  443. expect(histogram.endTimeStampMsec).toBe(0);
  444. expect(histogram.tag).toBe(Histogram_1.NO_TAG);
  445. expect(histogram.maxValue).toBe(0);
  446. expect(histogram.minNonZeroValue).toBeGreaterThan(Number.MAX_SAFE_INTEGER);
  447. expect(histogram.getValueAtPercentile(99.999)).toBe(0);
  448. });
  449. it("should behave as new when reseted", () => {
  450. // given
  451. const histogram = _1.build({
  452. lowestDiscernibleValue: 1,
  453. highestTrackableValue: 15000,
  454. numberOfSignificantValueDigits: 2,
  455. });
  456. const histogram2 = _1.build({
  457. lowestDiscernibleValue: 1,
  458. highestTrackableValue: 15000,
  459. numberOfSignificantValueDigits: 2,
  460. });
  461. histogram.recordValue(1);
  462. histogram.recordValue(100);
  463. histogram.recordValue(2000);
  464. histogram.reset();
  465. // when
  466. histogram.recordValue(1000);
  467. histogram2.recordValue(1000);
  468. // then
  469. expect(histogram.mean).toBe(histogram2.mean);
  470. });
  471. });
  472. //# sourceMappingURL=Histogram.spec.js.map