JsHistogram.encoding.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.doDecode = exports.decompress = exports.inflate = exports.deflate = void 0;
  4. /*
  5. * This is a TypeScript port of the original Java version, which was written by
  6. * Gil Tene as described in
  7. * https://github.com/HdrHistogram/HdrHistogram
  8. * and released to the public domain, as explained at
  9. * http://creativecommons.org/publicdomain/zero/1.0/
  10. */
  11. // @ts-ignore
  12. const base64 = require("base64-js");
  13. // @ts-ignore
  14. const pako = require("pako");
  15. const JsHistogram_1 = require("./JsHistogram");
  16. const ByteBuffer_1 = require("./ByteBuffer");
  17. const JsHistogramFactory_1 = require("./JsHistogramFactory");
  18. const ZigZagEncoding_1 = require("./ZigZagEncoding");
  19. const { max } = Math;
  20. const V2EncodingCookieBase = 0x1c849303;
  21. const V2CompressedEncodingCookieBase = 0x1c849304;
  22. const V2maxWordSizeInBytes = 9; // LEB128-64b9B + ZigZag require up to 9 bytes per word
  23. const encodingCookie = V2EncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding
  24. const compressedEncodingCookie = V2CompressedEncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding
  25. function fillBufferFromCountsArray(self, buffer) {
  26. const countsLimit = self.countsArrayIndex(self.maxValue) + 1;
  27. let srcIndex = 0;
  28. while (srcIndex < countsLimit) {
  29. // V2 encoding format uses a ZigZag LEB128-64b9B encoded long. Positive values are counts,
  30. // while negative values indicate a repeat zero counts.
  31. const count = self.getCountAtIndex(srcIndex++);
  32. if (count < 0) {
  33. throw new Error("Cannot encode histogram containing negative counts (" +
  34. count +
  35. ") at index " +
  36. srcIndex +
  37. ", corresponding the value range [" +
  38. self.lowestEquivalentValue(self.valueFromIndex(srcIndex)) +
  39. "," +
  40. self.nextNonEquivalentValue(self.valueFromIndex(srcIndex)) +
  41. ")");
  42. }
  43. // Count trailing 0s (which follow this count):
  44. let zerosCount = 0;
  45. if (count == 0) {
  46. zerosCount = 1;
  47. while (srcIndex < countsLimit && self.getCountAtIndex(srcIndex) == 0) {
  48. zerosCount++;
  49. srcIndex++;
  50. }
  51. }
  52. if (zerosCount > 1) {
  53. ZigZagEncoding_1.default.encode(buffer, -zerosCount);
  54. }
  55. else {
  56. ZigZagEncoding_1.default.encode(buffer, count);
  57. }
  58. }
  59. }
  60. /**
  61. * Encode this histogram into a ByteBuffer
  62. * @param self this histogram
  63. * @param buffer The buffer to encode into
  64. * @return The number of bytes written to the buffer
  65. */
  66. function encodeIntoByteBuffer(self, buffer) {
  67. const initialPosition = buffer.position;
  68. buffer.putInt32(encodingCookie);
  69. buffer.putInt32(0); // Placeholder for payload length in bytes.
  70. buffer.putInt32(1);
  71. buffer.putInt32(self.numberOfSignificantValueDigits);
  72. buffer.putInt64(self.lowestDiscernibleValue);
  73. buffer.putInt64(self.highestTrackableValue);
  74. buffer.putInt64(1);
  75. const payloadStartPosition = buffer.position;
  76. fillBufferFromCountsArray(self, buffer);
  77. const backupIndex = buffer.position;
  78. buffer.position = initialPosition + 4;
  79. buffer.putInt32(backupIndex - payloadStartPosition); // Record the payload length
  80. buffer.position = backupIndex;
  81. return backupIndex - initialPosition;
  82. }
  83. function fillCountsArrayFromSourceBuffer(self, sourceBuffer, lengthInBytes, wordSizeInBytes) {
  84. if (wordSizeInBytes != 2 &&
  85. wordSizeInBytes != 4 &&
  86. wordSizeInBytes != 8 &&
  87. wordSizeInBytes != V2maxWordSizeInBytes) {
  88. throw new Error("word size must be 2, 4, 8, or V2maxWordSizeInBytes (" +
  89. V2maxWordSizeInBytes +
  90. ") bytes");
  91. }
  92. let dstIndex = 0;
  93. const endPosition = sourceBuffer.position + lengthInBytes;
  94. while (sourceBuffer.position < endPosition) {
  95. let zerosCount = 0;
  96. let count = ZigZagEncoding_1.default.decode(sourceBuffer);
  97. if (count < 0) {
  98. zerosCount = -count;
  99. dstIndex += zerosCount; // No need to set zeros in array. Just skip them.
  100. }
  101. else {
  102. self.setCountAtIndex(dstIndex++, count);
  103. }
  104. }
  105. return dstIndex; // this is the destination length
  106. }
  107. function getCookieBase(cookie) {
  108. return cookie & ~0xf0;
  109. }
  110. function getWordSizeInBytesFromCookie(cookie) {
  111. if (getCookieBase(cookie) == V2EncodingCookieBase ||
  112. getCookieBase(cookie) == V2CompressedEncodingCookieBase) {
  113. return V2maxWordSizeInBytes;
  114. }
  115. const sizeByte = (cookie & 0xf0) >> 4;
  116. return sizeByte & 0xe;
  117. }
  118. function findDeflateFunction() {
  119. try {
  120. return eval('require("zlib").deflateSync');
  121. }
  122. catch (error) {
  123. return !!pako ? pako.deflate : () => { throw new Error('pako library is mandatory for encoding/deconding on the browser side'); };
  124. }
  125. }
  126. function findInflateFunction() {
  127. try {
  128. return eval('require("zlib").inflateSync');
  129. }
  130. catch (error) {
  131. return !!pako ? pako.inflate : () => { throw new Error('pako library is mandatory for encoding/deconding on the browser side'); };
  132. }
  133. }
  134. exports.deflate = findDeflateFunction();
  135. exports.inflate = findInflateFunction();
  136. function decompress(data) {
  137. const buffer = new ByteBuffer_1.default(data);
  138. const initialTargetPosition = buffer.position;
  139. const cookie = buffer.getInt32();
  140. if ((cookie & ~0xf0) !== V2CompressedEncodingCookieBase) {
  141. throw new Error("Encoding not supported, only V2 is supported");
  142. }
  143. const lengthOfCompressedContents = buffer.getInt32();
  144. const uncompressedBuffer = exports.inflate(buffer.data.slice(initialTargetPosition + 8, initialTargetPosition + 8 + lengthOfCompressedContents));
  145. return uncompressedBuffer;
  146. }
  147. exports.decompress = decompress;
  148. function doDecode(data, bitBucketSize = 32, minBarForHighestTrackableValue = 0) {
  149. const buffer = new ByteBuffer_1.default(data);
  150. const cookie = buffer.getInt32();
  151. let payloadLengthInBytes;
  152. let numberOfSignificantValueDigits;
  153. let lowestTrackableUnitValue;
  154. let highestTrackableValue;
  155. if (getCookieBase(cookie) === V2EncodingCookieBase) {
  156. if (getWordSizeInBytesFromCookie(cookie) != V2maxWordSizeInBytes) {
  157. throw new Error("The buffer does not contain a Histogram (no valid cookie found)");
  158. }
  159. payloadLengthInBytes = buffer.getInt32();
  160. buffer.getInt32(); // normalizingIndexOffset not used
  161. numberOfSignificantValueDigits = buffer.getInt32();
  162. lowestTrackableUnitValue = buffer.getInt64();
  163. highestTrackableValue = buffer.getInt64();
  164. buffer.getInt64(); // integerToDoubleValueConversionRatio not used
  165. }
  166. else {
  167. throw new Error("The buffer does not contain a Histogram (no valid V2 encoding cookie found)");
  168. }
  169. highestTrackableValue = max(highestTrackableValue, minBarForHighestTrackableValue);
  170. const histogramConstr = JsHistogramFactory_1.constructorFromBucketSize(bitBucketSize);
  171. const histogram = new histogramConstr(lowestTrackableUnitValue, highestTrackableValue, numberOfSignificantValueDigits);
  172. const filledLength = fillCountsArrayFromSourceBuffer(histogram, buffer, payloadLengthInBytes, V2maxWordSizeInBytes);
  173. histogram.establishInternalTackingValues(filledLength);
  174. return histogram;
  175. }
  176. exports.doDecode = doDecode;
  177. function doEncodeIntoCompressedBase64(compressionLevel) {
  178. const compressionOptions = compressionLevel
  179. ? { level: compressionLevel }
  180. : {};
  181. const self = this;
  182. const targetBuffer = ByteBuffer_1.default.allocate();
  183. targetBuffer.putInt32(compressedEncodingCookie);
  184. const intermediateUncompressedByteBuffer = ByteBuffer_1.default.allocate();
  185. const uncompressedLength = encodeIntoByteBuffer(self, intermediateUncompressedByteBuffer);
  186. const data = intermediateUncompressedByteBuffer.data.slice(0, uncompressedLength);
  187. const compressedData = exports.deflate(data, compressionOptions);
  188. targetBuffer.putInt32(compressedData.byteLength);
  189. targetBuffer.putArray(compressedData);
  190. return base64.fromByteArray(targetBuffer.data);
  191. }
  192. JsHistogram_1.JsHistogram.decode = doDecode;
  193. JsHistogram_1.JsHistogram.prototype.encodeIntoCompressedBase64 = doEncodeIntoCompressedBase64;
  194. //# sourceMappingURL=JsHistogram.encoding.js.map