├── setup-jest.js ├── .vscode ├── settings.json └── launch.json ├── assembly ├── __tests__ │ ├── as-pect.d.ts │ ├── ulp.spec.ts │ ├── PackedArray.spec.ts │ ├── formatters.spec.ts │ ├── encoding.spec.ts │ ├── ZigZagEncoding.spec.ts │ └── ByteBuffer.spec.ts ├── tsconfig.json ├── ulp.ts ├── formatters.ts ├── AbstractHistogramBase.ts ├── ByteBuffer.ts ├── HistogramIterationValue.ts ├── ZigZagEncoding.ts ├── RecordedValuesIterator.ts ├── encoding.ts └── index.ts ├── test_files ├── bug-whitespace.hlog ├── tagged-Log.logV2.hlog ├── jHiccup-no-header-2.0.7S.logV2.hlog ├── jHiccup-2.0.7S.logV2.hlog └── jHiccup-with-basetime-2.0.7S.logV2.hlog ├── .travis.yml ├── babel.config.js ├── release.sh ├── src ├── ulp.spec.ts ├── EncodableHistogram.ts ├── packedarray │ ├── ResizeError.ts │ ├── PackedArray.fc.spec.ts │ ├── PackedArray.spec.ts │ └── PackedArray.ts ├── ulp.ts ├── Int8Histogram.ts ├── Int16Histogram.ts ├── Int32Histogram.ts ├── Float64Histogram.ts ├── ZigZagEncoding.fc.spec.ts ├── log.spec.ts ├── JsHistogramFactory.ts ├── index.spec.ts ├── formatters.ts ├── RecordedValuesIterator.spec.ts ├── RecordedValuesIterator.ts ├── index.ts ├── HistogramLogWriter.spec.ts ├── bench │ ├── histogram-json-percentile.ts │ ├── histogram-percentile.ts │ ├── histogram-distribution.ts │ ├── histogram-data-access.ts │ ├── histogram-decoding.ts │ ├── histogram-add.ts │ └── histogram-data-access-co.ts ├── ByteBuffer.ts ├── HistogramBuilder.ts ├── formatters.spec.ts ├── TypedArrayHistogram.spec.ts ├── PackedHistogram.spec.ts ├── HistogramIterationValue.ts ├── ZigZagEncoding.spec.ts ├── TypedArrayHistogram.ts ├── HistogramLogWriter.ts ├── encoding.ts ├── ByteBuffer.spec.ts ├── PercentileIterator.ts ├── PackedHistogram.ts ├── ZigZagEncoding.ts ├── JsHistogramIterator.ts ├── Histogram.fc.spec.ts ├── Recorder.spec.ts └── HistogramLogReader.ts ├── jest.config.js ├── tsconfig.json ├── stryker.conf.js ├── base64.js ├── wallaby.js ├── rollup.config.js ├── .gitignore ├── LICENSE ├── as-pect.config.js └── package.json /setup-jest.js: -------------------------------------------------------------------------------- 1 | global.TextDecoder = require("util").TextDecoder; -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /assembly/__tests__/as-pect.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test_files/bug-whitespace.hlog: -------------------------------------------------------------------------------- 1 | Tag=bo-global-http,1527510686.313,40.807,0.000,HISTFAAAABx4nJNpmSzMwMDACMXMDBDAyCD/HwzgfAB/wwdn -------------------------------------------------------------------------------- /assembly/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../node_modules/assemblyscript/std/assembly.json", 3 | "include": ["./**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "node" 5 | script: 6 | - npm run asbuild 7 | - npm run build 8 | - npm run astest:ci 9 | - npm run test 10 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = { 3 | presets: [ 4 | ["@babel/preset-env", { targets: { node: "current" } }], 5 | "@babel/preset-typescript", 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -Rf release 3 | mkdir release 4 | cp README.md release/README.md 5 | cp -R node_modules release/node_modules 6 | cp -R src release/src 7 | cp *.json release/ 8 | cp webpack.config.js release/webpack.config.js 9 | cd release 10 | yarn prepare-publish 11 | -------------------------------------------------------------------------------- /src/ulp.spec.ts: -------------------------------------------------------------------------------- 1 | import ulp from "./ulp"; 2 | 3 | describe("math ulp helper", () => { 4 | it("should compute ulp of integer", () => { 5 | expect(ulp(1)).toBe(2.220446049250313e-16); 6 | }); 7 | 8 | it("should compute ulp of floating point number", () => { 9 | expect(ulp(0.000333)).toBe(5.421010862427522e-20); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/EncodableHistogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | export abstract class EncodableHistogram {} 10 | -------------------------------------------------------------------------------- /src/packedarray/ResizeError.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | export class ResizeError { 9 | constructor(public newSize: number) {} 10 | } 11 | -------------------------------------------------------------------------------- /src/ulp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | const ulp = (x: number) => Math.pow(2, Math.floor(Math.log2(x)) - 52); 10 | 11 | export default ulp; 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFiles: ['../setup-jest.js'], 3 | transform: { 4 | ".+\\.ts?$": "ts-jest", 5 | ".+\\.js?$": "babel-jest", 6 | }, 7 | transformIgnorePatterns: [ 8 | '/node_modules/(?!@assemblyscript\/loader)' 9 | ], 10 | rootDir: "src", 11 | globals: { 12 | "ts-jest": { 13 | diagnostics: true, 14 | tsConfig: "./tsconfig.json", 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /assembly/ulp.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | const ulp = (x: f64): f64 => 10 | Math.abs(x - reinterpret(reinterpret(x) ^ 1)); 11 | 12 | export default ulp; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./", 4 | "declaration": false, 5 | "sourceMap": false, 6 | "noImplicitAny": true, 7 | "strictNullChecks": true, 8 | "module": "commonjs", 9 | "target": "es6", 10 | "typeRoots": ["./node_modules/@types"], 11 | "lib": ["es2017"], 12 | "types": ["node", "jest"] 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules", "dist", "assembly"] 16 | } 17 | -------------------------------------------------------------------------------- /stryker.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | mutate: [ 4 | "src/**/*.ts", 5 | "!src/**/*.spec.ts" 6 | ], 7 | testFramework: 'mocha', 8 | testRunner: 'mocha', 9 | mutator: "typescript", 10 | transpilers: [ "typescript" ], 11 | reporter: ["clear-text", "progress", "html"], 12 | tsconfigFile: 'tsconfig.json', 13 | coverageAnalysis: "all", 14 | maxConcurrentTestRunners: 3, 15 | }); 16 | }; -------------------------------------------------------------------------------- /base64.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const zlib = require("zlib"); 3 | 4 | const src = process.argv[2]; 5 | if (!src || !src.length) { 6 | process.stderr.write("missing input file"); 7 | process.exit(1); 8 | } 9 | 10 | try { 11 | const raw = fs.readFileSync(src); 12 | const encoded = zlib 13 | .deflateSync(Buffer.from(raw), { level: zlib.constants.Z_BEST_COMPRESSION }) 14 | .toString("base64"); 15 | process.stdout.write(encoded); 16 | } catch (e) { 17 | process.stderr.write(`error encoding: ${e.message}`); 18 | process.exit(1); 19 | } 20 | -------------------------------------------------------------------------------- /assembly/__tests__/ulp.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import ulp from "../ulp"; 10 | 11 | describe("math ulp helper", () => { 12 | it("should compute ulp of integer", () => { 13 | expect(ulp(1)).toBe(2.220446049250313e-16); 14 | }); 15 | 16 | it("should compute ulp of floating point number", () => { 17 | expect(ulp(0.000333)).toBe(5.421010862427522e-20); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | module.exports = function(wallaby) { 2 | //process.env.NODE_PATH += path.delimiter + wallaby.projectCacheDir; 3 | 4 | return { 5 | files: [ 6 | // Source code 7 | { pattern: "src/**/*.ts", load: false }, 8 | { pattern: "src/**/*.js", load: false }, 9 | { pattern: "test_files/*.hlog", load: false }, 10 | { pattern: "src/**/*spec.ts", ignore: true }, 11 | ], 12 | 13 | tests: [ 14 | // Unit tests 15 | { pattern: "src/**/*spec.ts" }, 16 | { pattern: "src/**/*.fc.spec.ts", ignore: true }, 17 | ], 18 | 19 | env: { type: "node" }, 20 | 21 | testFramework: "jest", 22 | 23 | debug: true, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/index.js", 15 | "preLaunchTask": "tsc: build - tsconfig.json", 16 | "outFiles": [ 17 | "${workspaceFolder}//**/*.js" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/Int8Histogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import TypedArrayHistogram from "./TypedArrayHistogram"; 9 | 10 | class Int8Histogram extends TypedArrayHistogram { 11 | constructor( 12 | lowestDiscernibleValue: number, 13 | highestTrackableValue: number, 14 | numberOfSignificantValueDigits: number 15 | ) { 16 | super( 17 | Uint8Array, 18 | lowestDiscernibleValue, 19 | highestTrackableValue, 20 | numberOfSignificantValueDigits 21 | ); 22 | } 23 | } 24 | 25 | export default Int8Histogram; 26 | -------------------------------------------------------------------------------- /src/Int16Histogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import TypedArrayHistogram from "./TypedArrayHistogram"; 9 | 10 | class Int16Histogram extends TypedArrayHistogram { 11 | constructor( 12 | lowestDiscernibleValue: number, 13 | highestTrackableValue: number, 14 | numberOfSignificantValueDigits: number 15 | ) { 16 | super( 17 | Uint16Array, 18 | lowestDiscernibleValue, 19 | highestTrackableValue, 20 | numberOfSignificantValueDigits 21 | ); 22 | } 23 | } 24 | 25 | export default Int16Histogram; 26 | -------------------------------------------------------------------------------- /src/Int32Histogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import TypedArrayHistogram from "./TypedArrayHistogram"; 9 | 10 | class Int32Histogram extends TypedArrayHistogram { 11 | constructor( 12 | lowestDiscernibleValue: number, 13 | highestTrackableValue: number, 14 | numberOfSignificantValueDigits: number 15 | ) { 16 | super( 17 | Uint32Array, 18 | lowestDiscernibleValue, 19 | highestTrackableValue, 20 | numberOfSignificantValueDigits 21 | ); 22 | } 23 | } 24 | 25 | export default Int32Histogram; 26 | -------------------------------------------------------------------------------- /src/Float64Histogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import TypedArrayHistogram from "./TypedArrayHistogram"; 9 | 10 | class Float64Histogram extends TypedArrayHistogram { 11 | constructor( 12 | lowestDiscernibleValue: number, 13 | highestTrackableValue: number, 14 | numberOfSignificantValueDigits: number 15 | ) { 16 | super( 17 | Float64Array, 18 | lowestDiscernibleValue, 19 | highestTrackableValue, 20 | numberOfSignificantValueDigits 21 | ); 22 | } 23 | } 24 | 25 | export default Float64Histogram; 26 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-node-resolve"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | import typescript from "rollup-plugin-typescript"; 4 | import { terser } from "rollup-plugin-terser"; 5 | import pkg from "./package.json"; 6 | 7 | export default [ 8 | // browser-friendly UMD build 9 | { 10 | input: "src/index.ts", 11 | external: ["pako"], 12 | output: { 13 | name: "hdr", 14 | file: pkg.browser, 15 | format: "umd", 16 | globals: { 17 | pako: "pako", 18 | }, 19 | }, 20 | plugins: [ 21 | resolve(), // so Rollup can find `base64...` 22 | commonjs(), // so Rollup can convert `base64` to an ES module 23 | typescript(), // so Rollup can convert TypeScript to JavaScript 24 | terser(), 25 | ], 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /src/ZigZagEncoding.fc.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fc from "fast-check"; 2 | import ByteBuffer from "./ByteBuffer"; 3 | import ZigZagEncoding from "./ZigZagEncoding"; 4 | 5 | const runFromStryker = __dirname.includes("stryker"); 6 | 7 | const runnerOptions = { 8 | numRuns: runFromStryker ? 10 : 1000, 9 | }; 10 | 11 | describe("Zig Zag Encoding", () => { 12 | it("should get the same number after an encoding & decoding", () => { 13 | const buffer = ByteBuffer.allocate(8); 14 | fc.assert( 15 | fc.property(fc.nat(Number.MAX_SAFE_INTEGER), (number) => { 16 | buffer.resetPosition(); 17 | ZigZagEncoding.encode(buffer, number); 18 | buffer.resetPosition(); 19 | const result = ZigZagEncoding.decode(buffer); 20 | return number === result; 21 | }), 22 | runnerOptions 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/log.spec.ts: -------------------------------------------------------------------------------- 1 | import { build, HistogramLogReader, HistogramLogWriter } from "."; 2 | 3 | describe("Logs", () => { 4 | it("should give same result after been written then read", () => { 5 | // given 6 | let buffer = ""; 7 | const writer = new HistogramLogWriter((content) => { 8 | buffer += content; 9 | }); 10 | writer.outputLogFormatVersion(); 11 | writer.outputStartTime(12345000); 12 | writer.outputLegend(); 13 | const inputHistogram = build(); 14 | inputHistogram.recordValue(42); 15 | // when 16 | writer.outputIntervalHistogram(inputHistogram, 12345042, 1234056, 1); 17 | const reader = new HistogramLogReader(buffer); 18 | const outputHistogram = reader.nextIntervalHistogram(); 19 | // then 20 | expect(outputHistogram).not.toBeNull(); 21 | // @ts-ignore 22 | expect(outputHistogram.mean).toBe(inputHistogram.mean); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | .stryker-tmp 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | dist 41 | 42 | # coverage reports 43 | reports 44 | 45 | # benchmark result 46 | benchmark 47 | 48 | # generated wasm base64 module 49 | generated*.ts 50 | -------------------------------------------------------------------------------- /src/JsHistogramFactory.ts: -------------------------------------------------------------------------------- 1 | import { BitBucketSize } from "./Histogram"; 2 | import PackedHistogram from "./PackedHistogram"; 3 | import Int8Histogram from "./Int8Histogram"; 4 | import Int16Histogram from "./Int16Histogram"; 5 | import Int32Histogram from "./Int32Histogram"; 6 | import Float64Histogram from "./Float64Histogram"; 7 | import JsHistogram from "./JsHistogram"; 8 | 9 | export interface JsHistogramConstructor { 10 | new ( 11 | lowestDiscernibleValue: number, 12 | highestTrackableValue: number, 13 | numberOfSignificantValueDigits: number 14 | ): JsHistogram; 15 | } 16 | 17 | export function constructorFromBucketSize( 18 | bitBucketSize: BitBucketSize 19 | ): JsHistogramConstructor { 20 | switch (bitBucketSize) { 21 | case "packed": 22 | return PackedHistogram; 23 | case 8: 24 | return Int8Histogram; 25 | case 16: 26 | return Int16Histogram; 27 | case 32: 28 | return Int32Histogram; 29 | case 64: 30 | return Float64Histogram; 31 | default: 32 | throw new Error("Incorrect parameter bitBucketSize"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import * as hdr from "./index"; 9 | 10 | describe("Histogram builder", () => { 11 | it("should build histogram with default values", () => { 12 | // given 13 | // when 14 | const histogram = hdr.build(); 15 | // then 16 | expect(histogram).not.toBeNull(); 17 | expect(histogram.autoResize).toBe(true); 18 | expect(histogram.highestTrackableValue).toBe(2); 19 | }); 20 | 21 | it("should build histogram with custom parameters", () => { 22 | // given 23 | // when 24 | const histogram = hdr.build({ 25 | bitBucketSize: 32, 26 | numberOfSignificantValueDigits: 2, 27 | }); 28 | const expectedHistogram = new hdr.Int32Histogram(1, 2, 2); 29 | expectedHistogram.autoResize = true; 30 | 31 | histogram.recordValue(12345); 32 | expectedHistogram.recordValue(12345); 33 | 34 | // then 35 | expect(histogram.mean).toBe(expectedHistogram.mean); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/formatters.ts: -------------------------------------------------------------------------------- 1 | const leftPadding = (size: number) => { 2 | return (input: string) => { 3 | if (input.length < size) { 4 | return " ".repeat(size - input.length) + input; 5 | } 6 | return input; 7 | }; 8 | }; 9 | 10 | export const integerFormatter = (size: number) => { 11 | const padding = leftPadding(size); 12 | return (integer: number) => padding("" + integer); 13 | }; 14 | 15 | const { floor, log10, pow } = Math; 16 | const numberOfDigits = (n: number) => floor(log10(n) + 1); 17 | 18 | export const keepSignificantDigits = (digits: number) => (value: number) => { 19 | const valueDigits = numberOfDigits(value); 20 | if (valueDigits > digits) { 21 | const extraDigits = valueDigits - digits; 22 | const magnitude = pow(10, extraDigits); 23 | return value - (value % magnitude); 24 | } 25 | return value; 26 | }; 27 | 28 | export const floatFormatter = (size: number, fractionDigits: number) => { 29 | const numberFormatter = new Intl.NumberFormat("en-US", { 30 | maximumFractionDigits: fractionDigits, 31 | minimumFractionDigits: fractionDigits, 32 | useGrouping: false, 33 | }); 34 | 35 | const padding = leftPadding(size); 36 | 37 | return (float: number) => padding(numberFormatter.format(float)); 38 | }; 39 | -------------------------------------------------------------------------------- /assembly/__tests__/PackedArray.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import { PackedArray } from "../packedarray/PackedArray"; 10 | 11 | describe("Packed Array", () => { 12 | it("should store a byte without extending array", () => { 13 | // given 14 | const packed = new PackedArray(1024); 15 | // when 16 | packed.set(42, 123); 17 | // then 18 | expect(packed.get(42)).toBe(123); 19 | }); 20 | 21 | it("should resize array when storing data", () => { 22 | // given 23 | const array = new PackedArray(1024, 16); 24 | 25 | // when 26 | array.set(12, u64.MAX_VALUE); 27 | 28 | // then 29 | const storedData = array.get(12); 30 | expect(storedData).toBe(u64.MAX_VALUE); 31 | }); 32 | it("should store a big number", () => { 33 | // given 34 | const array = new PackedArray(45056, 16); 35 | 36 | // when 37 | array.set(32768, 1); 38 | 39 | // then 40 | const storedData = array.get(32768); 41 | expect(storedData).toBe(1); 42 | const storedDataAt0 = array.get(0); 43 | expect(storedDataAt0).toBe(0); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/RecordedValuesIterator.spec.ts: -------------------------------------------------------------------------------- 1 | import RecordedValuesIterator from "./RecordedValuesIterator"; 2 | import Histogram from "./Int32Histogram"; 3 | 4 | describe("Recorded Values Iterator", () => { 5 | it("should iterate to recorded value", () => { 6 | // given 7 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 2); 8 | histogram.recordValue(123); 9 | const iterator = new RecordedValuesIterator(histogram); 10 | // when 11 | const iterationValue = iterator.next(); 12 | // then 13 | expect(iterator.hasNext()).toBe(false); 14 | expect(iterationValue.totalCountToThisValue).toBe(1); 15 | expect(iterationValue.totalValueToThisValue).toBe(123); 16 | }); 17 | 18 | it("should iterate to all recorded values", () => { 19 | // given 20 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 2); 21 | histogram.recordValue(1); 22 | histogram.recordValue(300); 23 | histogram.recordValue(3000); 24 | const iterator = new RecordedValuesIterator(histogram); 25 | // when 26 | const values: number[] = []; 27 | while (iterator.hasNext()) { 28 | values.push(iterator.next().valueIteratedTo); 29 | } 30 | // then 31 | expect(values).toHaveLength(3); 32 | expect(values[0]).toBe(1); 33 | expect(values[1]).toBeGreaterThan(300); 34 | expect(values[2]).toBeGreaterThan(3000); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2016, Alexandre Victoor 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/packedarray/PackedArray.fc.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import * as fc from "fast-check"; 9 | import { PackedArray } from "./PackedArray"; 10 | 11 | const runFromStryker = __dirname.includes("stryker"); 12 | 13 | const runnerOptions = { 14 | numRuns: runFromStryker ? 10 : 1000, 15 | verbose: true 16 | }; 17 | 18 | describe("Packed array", () => { 19 | it("should store data as a regular sparse array", () => { 20 | const SIZE = 1000; 21 | fc.assert( 22 | fc.property(arbData(SIZE), entries => { 23 | const packedArray = new PackedArray(SIZE + 1); 24 | const sparseArray = new Array(); 25 | entries.forEach(([index, value]) => packedArray.add(index, value)); 26 | entries.forEach(([index, value]) => { 27 | if (sparseArray[index]) { 28 | sparseArray[index] = sparseArray[index] + value; 29 | } else { 30 | sparseArray[index] = value; 31 | } 32 | }); 33 | return entries.every( 34 | ([index]) => sparseArray[index] === packedArray.get(index) 35 | ); 36 | }), 37 | runnerOptions 38 | ); 39 | }); 40 | }); 41 | 42 | const arbData = (size: number) => 43 | fc.array(fc.tuple(fc.integer(1, size), fc.integer(1, 100000000)), 1, size); 44 | -------------------------------------------------------------------------------- /src/RecordedValuesIterator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import JsHistogram from "./JsHistogram"; 10 | import JsHistogramIterator from "./JsHistogramIterator"; 11 | 12 | /** 13 | * Used for iterating through all recorded histogram values using the finest granularity steps supported by the 14 | * underlying representation. The iteration steps through all non-zero recorded value counts, and terminates when 15 | * all recorded histogram values are exhausted. 16 | */ 17 | class RecordedValuesIterator extends JsHistogramIterator { 18 | visitedIndex: number; 19 | 20 | /** 21 | * @param histogram The histogram this iterator will operate on 22 | */ 23 | constructor(histogram: JsHistogram) { 24 | super(); 25 | this.doReset(histogram); 26 | } 27 | 28 | /** 29 | * Reset iterator for re-use in a fresh iteration over the same histogram data set. 30 | */ 31 | public reset() { 32 | this.doReset(this.histogram); 33 | } 34 | 35 | private doReset(histogram: JsHistogram) { 36 | super.resetIterator(histogram); 37 | this.visitedIndex = -1; 38 | } 39 | 40 | incrementIterationLevel() { 41 | this.visitedIndex = this.currentIndex; 42 | } 43 | 44 | reachedIterationLevel() { 45 | const currentCount = this.histogram.getCountAtIndex(this.currentIndex); 46 | return currentCount != 0 && this.visitedIndex !== this.currentIndex; 47 | } 48 | } 49 | 50 | export default RecordedValuesIterator; 51 | -------------------------------------------------------------------------------- /assembly/formatters.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | const leftPadding = (size: i32, input: string): string => { 10 | if (input.length < size) { 11 | input.padStart(size - input.length); 12 | return " ".repeat(size - input.length) + input; 13 | } 14 | return input; 15 | }; 16 | 17 | export const integerFormatter = (size: i32, integer: u64): string => { 18 | return leftPadding(size, integer.toString()); 19 | }; 20 | 21 | export class IntegerFormatter { 22 | constructor(private size: i32) {} 23 | 24 | format(integer: u64): string { 25 | return leftPadding(this.size, integer.toString()); 26 | } 27 | } 28 | 29 | export class FloatFormatter { 30 | constructor(private size: i32, private fractionDigits: i32) {} 31 | 32 | format(float: f64): string { 33 | const intergerPart = Math.floor(float); 34 | const digits = Math.pow(10, this.fractionDigits); 35 | const floatPart = ( 36 | Math.round(float * digits - intergerPart * digits) 37 | ); 38 | let floatPartString = floatPart.toString(); 39 | if (floatPartString.length < this.fractionDigits) { 40 | floatPartString += "0".repeat( 41 | this.fractionDigits - floatPartString.length 42 | ); 43 | } 44 | 45 | let result = intergerPart.toString() + "." + floatPartString; 46 | if (result.length < this.size) { 47 | result = " ".repeat(this.size - result.length) + result; 48 | } 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import ByteBuffer from "./ByteBuffer"; 9 | import { 10 | decodeFromCompressedBase64, 11 | encodeIntoCompressedBase64, 12 | } from "./encoding"; 13 | import type Histogram from "./Histogram"; 14 | import type { HistogramSummary, BitBucketSize } from "./Histogram"; 15 | import Float64Histogram from "./Float64Histogram"; 16 | import HistogramLogReader, { listTags } from "./HistogramLogReader"; 17 | import HistogramLogWriter from "./HistogramLogWriter"; 18 | import Int16Histogram from "./Int16Histogram"; 19 | import Int32Histogram from "./Int32Histogram"; 20 | import Int8Histogram from "./Int8Histogram"; 21 | import JsHistogram from "./JsHistogram"; 22 | import PackedHistogram from "./PackedHistogram"; 23 | import Recorder from "./Recorder"; 24 | import { initWebAssembly, initWebAssemblySync, WasmHistogram } from "./wasm"; 25 | import type { BuildRequest } from "./HistogramBuilder"; 26 | import { build } from "./HistogramBuilder"; 27 | 28 | export { 29 | initWebAssembly, 30 | initWebAssemblySync, 31 | Histogram, 32 | BitBucketSize, 33 | HistogramSummary, 34 | Int8Histogram, 35 | Int16Histogram, 36 | Int32Histogram, 37 | Float64Histogram, 38 | PackedHistogram, 39 | HistogramLogReader, 40 | listTags, 41 | build, 42 | BuildRequest, 43 | ByteBuffer, 44 | decodeFromCompressedBase64, 45 | encodeIntoCompressedBase64, 46 | HistogramLogWriter, 47 | Recorder, 48 | WasmHistogram, 49 | JsHistogram, 50 | }; 51 | -------------------------------------------------------------------------------- /assembly/AbstractHistogramBase.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import RecordedValuesIterator from "./RecordedValuesIterator"; 10 | import PercentileIterator from "./PercentileIterator"; 11 | 12 | export const NO_TAG = "NO TAG"; 13 | 14 | export abstract class AbstractHistogramBase { 15 | static identityBuilder: number; 16 | 17 | identity: f64; 18 | autoResize: boolean = false; 19 | 20 | highestTrackableValue: u64; 21 | lowestDiscernibleValue: u64; 22 | numberOfSignificantValueDigits: u8; 23 | 24 | bucketCount: u64; 25 | /** 26 | * Power-of-two length of linearly scaled array slots in the counts array. Long enough to hold the first sequence of 27 | * entries that must be distinguished by a single unit (determined by configured precision). 28 | */ 29 | subBucketCount: i32; 30 | countsArrayLength: i32; 31 | wordSizeInBytes: u64; 32 | 33 | startTimeStampMsec: u64 = u64.MAX_VALUE; 34 | endTimeStampMsec: u64 = 0; 35 | tag: string = NO_TAG; 36 | 37 | integerToDoubleValueConversionRatio: f64 = 1.0; 38 | 39 | percentileIterator!: PercentileIterator; 40 | recordedValuesIterator!: RecordedValuesIterator; 41 | 42 | constructor() { 43 | this.identity = 0; 44 | this.highestTrackableValue = 0; 45 | this.lowestDiscernibleValue = 0; 46 | this.numberOfSignificantValueDigits = 0; 47 | this.bucketCount = 0; 48 | this.subBucketCount = 0; 49 | this.countsArrayLength = 0; 50 | this.wordSizeInBytes = 0; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/HistogramLogWriter.spec.ts: -------------------------------------------------------------------------------- 1 | import HistogramLogWriter from "./HistogramLogWriter"; 2 | import Int32Histogram from "./Int32Histogram"; 3 | 4 | describe("Histogram Log Writer", () => { 5 | let buffer: string; 6 | let writer: HistogramLogWriter; 7 | let histogram: Int32Histogram; 8 | beforeEach(() => { 9 | buffer = ""; 10 | writer = new HistogramLogWriter((content) => { 11 | buffer += content; 12 | }); 13 | histogram = new Int32Histogram(1, Number.MAX_SAFE_INTEGER, 3); 14 | }); 15 | 16 | it("should write a line with start time, duration, max value, and a base64 encoded histogram", () => { 17 | // given 18 | histogram.recordValue(123000); 19 | // when 20 | writer.outputIntervalHistogram(histogram, 1000, 1042); 21 | // then 22 | expect(buffer).toMatch(/^1000.000,42.000,123.000,HISTFAA/); 23 | }); 24 | 25 | it("should write start time, duration and max value using 3 digits", () => { 26 | // given 27 | histogram.recordValue(123001); 28 | // when 29 | writer.outputIntervalHistogram(histogram, 1000.0120001, 1042.013001); 30 | // then 31 | expect(buffer).toMatch(/^1000.012,42.001,123.001,HISTFAA/); 32 | }); 33 | 34 | it("should write a line starting with histogram tag", () => { 35 | // given 36 | histogram.tag = "TAG"; 37 | histogram.recordValue(123000); 38 | // when 39 | writer.outputIntervalHistogram(histogram, 1000, 1042); 40 | // then 41 | expect(buffer).toContain("Tag=TAG,1000.000,42.000,123.000,HISTFAA"); 42 | }); 43 | 44 | it("should write a histogram's start time in sec using basetime", () => { 45 | // given 46 | histogram.startTimeStampMsec = 1234001; 47 | histogram.endTimeStampMsec = 1235001; 48 | writer.baseTime = 1000000; 49 | histogram.recordValue(1); 50 | // when 51 | writer.outputIntervalHistogram(histogram); 52 | // then 53 | expect(buffer).toContain("234.001"); 54 | }); 55 | 56 | it("should write start time in seconds", () => { 57 | // given 58 | // when 59 | writer.outputStartTime(1234560); 60 | // then 61 | expect(buffer).toContain("1234.560"); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /as-pect.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * A set of globs passed to the glob package that qualify typescript files for testing. 4 | */ 5 | include: ["assembly/__tests__/**/*.spec.ts"], 6 | /** 7 | * A set of globs passed to the glob package that quality files to be added to each test. 8 | */ 9 | add: ["assembly/__tests__/**/*.include.ts"], 10 | /** 11 | * All the compiler flags needed for this test suite. Make sure that a binary file is output. 12 | */ 13 | flags: { 14 | /** To output a wat file, uncomment the following line. */ 15 | // "--textFile": ["output.wat"], 16 | /** A runtime must be provided here. */ 17 | "--runtime": ["incremental"], // Acceptable values are: full, half, stub (arena), and none 18 | }, 19 | /** 20 | * A set of regexp that will disclude source files from testing. 21 | */ 22 | disclude: [/node_modules/], 23 | /** 24 | * Add your required AssemblyScript imports here. 25 | */ 26 | imports(memory, createImports, instantiateSync, binary) { 27 | let instance; // Imports can reference this 28 | const myImports = { 29 | // put your web assembly imports here, and return the module 30 | }; 31 | instance = instantiateSync(binary, createImports(myImports)); 32 | return instance; 33 | }, 34 | /** 35 | * Add a custom reporter here if you want one. The following example is in typescript. 36 | * 37 | * @example 38 | * import { TestReporter, TestGroup, TestResult, TestContext } from "as-pect"; 39 | * 40 | * export class CustomReporter extends TestReporter { 41 | * // implement each abstract method here 42 | * public abstract onStart(suite: TestContext): void; 43 | * public abstract onGroupStart(group: TestGroup): void; 44 | * public abstract onGroupFinish(group: TestGroup): void; 45 | * public abstract onTestStart(group: TestGroup, result: TestResult): void; 46 | * public abstract onTestFinish(group: TestGroup, result: TestResult): void; 47 | * public abstract onFinish(suite: TestContext): void; 48 | * } 49 | */ 50 | // reporter: new CustomReporter(), 51 | /** 52 | * Specify if the binary wasm file should be written to the file system. 53 | */ 54 | outputBinary: false, 55 | }; 56 | -------------------------------------------------------------------------------- /src/bench/histogram-json-percentile.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | import { toSummary } from "../Histogram"; 5 | initWebAssembly().then(() => { 6 | const randomInteger = (max: number = Number.MAX_SAFE_INTEGER) => 7 | Math.floor(Math.random() * max); 8 | const options = { initCount: 100 }; 9 | 10 | b.suite( 11 | "Histogram toJSON()", 12 | b.add( 13 | "JS 32B Histogram", 14 | () => { 15 | const histogram = build({ 16 | bitBucketSize: 32 17 | }); 18 | for (let index = 0; index < 1024; index++) { 19 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 20 | } 21 | return () => { 22 | toSummary(histogram); 23 | }; 24 | }, 25 | options 26 | ), 27 | 28 | b.add( 29 | "WASM 32B Histogram", 30 | () => { 31 | const histogram = build({ 32 | bitBucketSize: 32, 33 | useWebAssembly: true 34 | }); 35 | for (let index = 0; index < 1024; index++) { 36 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 37 | } 38 | return () => { 39 | toSummary(histogram); 40 | }; 41 | }, 42 | options 43 | ), 44 | b.add( 45 | "Packed Histogram", 46 | () => { 47 | const histogram = build({ 48 | bitBucketSize: "packed" 49 | }); 50 | for (let index = 0; index < 1024; index++) { 51 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 52 | } 53 | return () => { 54 | toSummary(histogram); 55 | }; 56 | }, 57 | options 58 | ), 59 | b.add( 60 | "WASM Packed Histogram", 61 | () => { 62 | const histogram = build({ 63 | bitBucketSize: "packed", 64 | useWebAssembly: true 65 | }); 66 | for (let index = 0; index < 1024; index++) { 67 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 68 | } 69 | return () => { 70 | toSummary(histogram); 71 | }; 72 | }, 73 | options 74 | ), 75 | 76 | b.complete(), 77 | b.save({ file: "json-percentile", format: "chart.html" }) 78 | ); 79 | }); 80 | -------------------------------------------------------------------------------- /src/bench/histogram-percentile.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | initWebAssembly().then(() => { 5 | const randomInteger = (max: number = Number.MAX_SAFE_INTEGER) => 6 | Math.floor(Math.random() * max); 7 | const options = { initCount: 1000 }; 8 | 9 | b.suite( 10 | "Histogram get value at percentile", 11 | b.add( 12 | "Int32Histogram", 13 | () => { 14 | const histogram = build({ 15 | bitBucketSize: 32 16 | }); 17 | for (let index = 0; index < 1024; index++) { 18 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 19 | } 20 | return () => { 21 | histogram.getValueAtPercentile(99); 22 | }; 23 | }, 24 | options 25 | ), 26 | 27 | b.add( 28 | "WASM 32B Histogram", 29 | () => { 30 | const histogram = build({ 31 | bitBucketSize: 32, 32 | useWebAssembly: true 33 | }); 34 | for (let index = 0; index < 1024; index++) { 35 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 36 | } 37 | return () => { 38 | histogram.getValueAtPercentile(99); 39 | }; 40 | }, 41 | options 42 | ), 43 | b.add( 44 | "Packed Histogram", 45 | () => { 46 | const histogram = build({ 47 | bitBucketSize: "packed" 48 | }); 49 | for (let index = 0; index < 1024; index++) { 50 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 51 | } 52 | return () => { 53 | histogram.getValueAtPercentile(99); 54 | }; 55 | }, 56 | options 57 | ), 58 | b.add( 59 | "WASM Packed Histogram", 60 | () => { 61 | const histogram = build({ 62 | bitBucketSize: "packed", 63 | useWebAssembly: true 64 | }); 65 | for (let index = 0; index < 1024; index++) { 66 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 67 | } 68 | return () => { 69 | histogram.getValueAtPercentile(99); 70 | }; 71 | }, 72 | options 73 | ), 74 | 75 | b.complete(), 76 | b.save({ file: "percentile", format: "chart.html" }) 77 | ); 78 | }); 79 | -------------------------------------------------------------------------------- /src/bench/histogram-distribution.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | initWebAssembly().then(() => { 5 | const randomInteger = (max: number = Number.MAX_SAFE_INTEGER) => 6 | Math.floor(Math.random() * max); 7 | const options = { initCount: 100 }; 8 | 9 | b.suite( 10 | "Histogram percentile distribution", 11 | b.add( 12 | "Int32Histogram", 13 | () => { 14 | const histogram = build({ 15 | bitBucketSize: 32 16 | }); 17 | for (let index = 0; index < 1024; index++) { 18 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 19 | } 20 | return () => { 21 | histogram.outputPercentileDistribution(); 22 | }; 23 | }, 24 | options 25 | ), 26 | 27 | b.add( 28 | "WASM 32B Histogram", 29 | () => { 30 | const histogram = build({ 31 | bitBucketSize: 32, 32 | useWebAssembly: true 33 | }); 34 | for (let index = 0; index < 1024; index++) { 35 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 36 | } 37 | return () => { 38 | histogram.outputPercentileDistribution(); 39 | }; 40 | }, 41 | options 42 | ), 43 | b.add( 44 | "Packed Histogram", 45 | () => { 46 | const histogram = build({ 47 | bitBucketSize: "packed" 48 | }); 49 | for (let index = 0; index < 1024; index++) { 50 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 51 | } 52 | return () => { 53 | histogram.outputPercentileDistribution(); 54 | }; 55 | }, 56 | options 57 | ), 58 | b.add( 59 | "WASM Packed Histogram", 60 | () => { 61 | const histogram = build({ 62 | bitBucketSize: "packed", 63 | useWebAssembly: true 64 | }); 65 | for (let index = 0; index < 1024; index++) { 66 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 67 | } 68 | return () => { 69 | histogram.outputPercentileDistribution(); 70 | }; 71 | }, 72 | options 73 | ), 74 | 75 | b.complete(), 76 | b.save({ file: "distribution", format: "chart.html" }) 77 | ); 78 | }); 79 | -------------------------------------------------------------------------------- /src/ByteBuffer.ts: -------------------------------------------------------------------------------- 1 | const { pow, floor } = Math; 2 | const TWO_POW_32 = pow(2, 32); 3 | 4 | /** 5 | * Mimic Java's ByteBufffer with big endian order 6 | */ 7 | class ByteBuffer { 8 | position: number; 9 | 10 | data: Uint8Array; 11 | 12 | int32ArrayForConvert: Uint32Array; 13 | int8ArrayForConvert: Uint8Array; 14 | 15 | static allocate(size = 16): ByteBuffer { 16 | return new ByteBuffer(new Uint8Array(size)); 17 | } 18 | 19 | constructor(data: Uint8Array) { 20 | this.position = 0; 21 | this.data = data; 22 | this.int32ArrayForConvert = new Uint32Array(1); 23 | this.int8ArrayForConvert = new Uint8Array(this.int32ArrayForConvert.buffer); 24 | } 25 | 26 | put(value: number) { 27 | if (this.position === this.data.length) { 28 | const oldArray = this.data; 29 | this.data = new Uint8Array(this.data.length * 2); 30 | this.data.set(oldArray); 31 | } 32 | this.data[this.position] = value; 33 | this.position++; 34 | } 35 | 36 | putInt32(value: number) { 37 | if (this.data.length - this.position < 4) { 38 | const oldArray = this.data; 39 | this.data = new Uint8Array(this.data.length * 2 + 4); 40 | this.data.set(oldArray); 41 | } 42 | this.int32ArrayForConvert[0] = value; 43 | this.data.set(this.int8ArrayForConvert.reverse(), this.position); 44 | this.position += 4; 45 | } 46 | 47 | putInt64(value: number) { 48 | this.putInt32(floor(value / TWO_POW_32)); 49 | this.putInt32(value); 50 | } 51 | 52 | putArray(array: Uint8Array) { 53 | if (this.data.length - this.position < array.byteLength) { 54 | const oldArray = this.data; 55 | this.data = new Uint8Array(this.position + array.byteLength); 56 | this.data.set(oldArray); 57 | } 58 | this.data.set(array, this.position); 59 | this.position += array.byteLength; 60 | } 61 | 62 | get(): number { 63 | const value = this.data[this.position]; 64 | this.position++; 65 | return value; 66 | } 67 | 68 | getInt32(): number { 69 | this.int8ArrayForConvert.set( 70 | this.data.slice(this.position, this.position + 4).reverse() 71 | ); 72 | const value = this.int32ArrayForConvert[0]; 73 | this.position += 4; 74 | return value; 75 | } 76 | 77 | getInt64(): number { 78 | const high = this.getInt32(); 79 | const low = this.getInt32(); 80 | return high * TWO_POW_32 + low; 81 | } 82 | 83 | resetPosition() { 84 | this.position = 0; 85 | } 86 | } 87 | 88 | export default ByteBuffer; 89 | -------------------------------------------------------------------------------- /src/HistogramBuilder.ts: -------------------------------------------------------------------------------- 1 | import Histogram, { BitBucketSize } from "./Histogram"; 2 | import { constructorFromBucketSize } from "./JsHistogramFactory"; 3 | import { WasmHistogram, webAssemblyReady, webAssemblyAvailable } from "./wasm"; 4 | 5 | export interface BuildRequest { 6 | /** 7 | * The size in bit of each count bucket 8 | * Default value is 32 9 | */ 10 | bitBucketSize?: BitBucketSize; 11 | /** 12 | * Control whether or not the histogram can auto-resize and auto-adjust it's 13 | * highestTrackableValue 14 | * Default value is true 15 | */ 16 | autoResize?: boolean; 17 | /** 18 | * The lowest value that can be discerned (distinguished from 0) by the histogram. 19 | * Must be a positive integer that is {@literal >=} 1. May be internally rounded 20 | * down to nearest power of 2. 21 | * Default value is 1 22 | */ 23 | lowestDiscernibleValue?: number; 24 | /** 25 | * The highest value to be tracked by the histogram. Must be a positive 26 | * integer that is {@literal >=} (2 * lowestDiscernibleValue). 27 | * Default value is Number.MAX_SAFE_INTEGER 28 | */ 29 | highestTrackableValue?: number; 30 | /** 31 | * The number of significant decimal digits to which the histogram will 32 | * maintain value resolution and separation. Must be a non-negative 33 | * integer between 0 and 5. 34 | * Default value is 3 35 | */ 36 | numberOfSignificantValueDigits?: 1 | 2 | 3 | 4 | 5; 37 | /** 38 | * Is WebAssembly used to speed up computations. 39 | * Default value is false 40 | */ 41 | useWebAssembly?: boolean; 42 | } 43 | 44 | export const defaultRequest: BuildRequest = { 45 | bitBucketSize: 32, 46 | autoResize: true, 47 | lowestDiscernibleValue: 1, 48 | highestTrackableValue: 2, 49 | numberOfSignificantValueDigits: 3, 50 | useWebAssembly: false, 51 | }; 52 | 53 | export const build = (request = defaultRequest): Histogram => { 54 | const parameters = Object.assign({}, defaultRequest, request); 55 | if (request.useWebAssembly && webAssemblyAvailable) { 56 | return WasmHistogram.build(parameters); 57 | } 58 | 59 | const histogramConstr = constructorFromBucketSize( 60 | parameters.bitBucketSize as BitBucketSize 61 | ); 62 | 63 | const histogram = new histogramConstr( 64 | parameters.lowestDiscernibleValue as number, 65 | parameters.highestTrackableValue as number, 66 | parameters.numberOfSignificantValueDigits as number 67 | ); 68 | histogram.autoResize = parameters.autoResize as boolean; 69 | return histogram; 70 | }; 71 | -------------------------------------------------------------------------------- /src/bench/histogram-data-access.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | initWebAssembly().then(() => { 5 | const randomInteger = () => 6 | Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); 7 | const options = { initCount: 1000 }; 8 | 9 | b.suite( 10 | "Histogram data access", 11 | b.add( 12 | "Int32Histogram", 13 | () => { 14 | const histogram = build({ bitBucketSize: 32 }); 15 | return () => { 16 | histogram.recordValue(randomInteger()); 17 | }; 18 | }, 19 | options 20 | ), 21 | b.add( 22 | "PackedHistogram", 23 | () => { 24 | const histogram = build({ bitBucketSize: "packed" }); 25 | return () => { 26 | histogram.recordValue(randomInteger()); 27 | }; 28 | }, 29 | options 30 | ), 31 | b.add( 32 | "Float64Histogram", 33 | () => { 34 | const histogram = build({ bitBucketSize: 64 }); 35 | return () => { 36 | histogram.recordValue(randomInteger()); 37 | }; 38 | }, 39 | options 40 | ), 41 | b.add( 42 | "Int32Histogram eager allocation", 43 | () => { 44 | const histogram = build({ 45 | bitBucketSize: 32, 46 | highestTrackableValue: Number.MAX_SAFE_INTEGER 47 | }); 48 | return () => { 49 | histogram.recordValue(randomInteger()); 50 | }; 51 | }, 52 | options 53 | ), 54 | b.add( 55 | "WASM Int32Histogram", 56 | () => { 57 | const histogram = build({ 58 | useWebAssembly: true 59 | }); 60 | return () => { 61 | histogram.recordValue(randomInteger()); 62 | }; 63 | }, 64 | options 65 | ), 66 | b.add( 67 | "WASM PackedHistogram", 68 | () => { 69 | const histogram = build({ 70 | useWebAssembly: true 71 | }); 72 | return () => { 73 | histogram.recordValue(randomInteger()); 74 | }; 75 | }, 76 | options 77 | ), 78 | b.add( 79 | "Float64Histogram eager allocation", 80 | () => { 81 | const histogram = build({ 82 | bitBucketSize: 64, 83 | highestTrackableValue: Number.MAX_SAFE_INTEGER 84 | }); 85 | return () => { 86 | histogram.recordValue(randomInteger()); 87 | }; 88 | }, 89 | options 90 | ), 91 | b.complete(), 92 | b.save({ file: "data-access", format: "chart.html" }) 93 | ); 94 | }); 95 | -------------------------------------------------------------------------------- /assembly/ByteBuffer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | /** 10 | * Mimic Java's ByteBufffer with big endian order 11 | */ 12 | class ByteBuffer { 13 | position: i32; 14 | 15 | data: Uint8Array; 16 | view: DataView; 17 | 18 | static allocate(size: i32 = 16): ByteBuffer { 19 | return new ByteBuffer(new Uint8Array(size)); 20 | } 21 | 22 | constructor(data: Uint8Array) { 23 | this.position = 0; 24 | this.data = data; 25 | this.view = new DataView(data.buffer); 26 | } 27 | 28 | public resize(newSize: i32): ByteBuffer { 29 | 30 | const buf = new Uint8Array(newSize); 31 | buf.set(this.data); 32 | 33 | this.data = buf; 34 | this.view = new DataView(buf.buffer); 35 | 36 | return this; 37 | } 38 | 39 | put(value: u8): void { 40 | if (this.position === this.data.length) { 41 | this.resize(this.data.length << 1); 42 | } 43 | this.view.setUint8(this.position, value); 44 | this.position++; 45 | } 46 | 47 | putInt32(value: u32): void { 48 | if (this.data.length - this.position < 4) { 49 | this.resize((this.data.length << 1) + 4); 50 | } 51 | this.view.setUint32(this.position, value, false); 52 | this.position += 4; 53 | } 54 | 55 | putInt64(value: u64): void { 56 | if (this.data.length - this.position < 8) { 57 | this.resize((this.data.length << 1) + 8); 58 | } 59 | this.view.setUint64(this.position, value, false); 60 | this.position += 8; 61 | } 62 | 63 | putArray(array: Uint8Array): void { 64 | if (this.data.length - this.position < array.byteLength) { 65 | this.resize(this.position + array.byteLength); 66 | } 67 | this.data.set(array, this.position); 68 | this.position += array.byteLength; 69 | } 70 | 71 | @inline 72 | get(): u8 { 73 | const value = unchecked(this.view.getUint8(this.position)); 74 | this.position++; 75 | return value; 76 | } 77 | 78 | @inline 79 | getInt32(): u32 { 80 | const value = unchecked(this.view.getUint32(this.position, false)); 81 | this.position += 4; 82 | return value; 83 | } 84 | 85 | @inline 86 | getInt64(): u64 { 87 | const value = unchecked(this.view.getUint64(this.position, false)); 88 | this.position += 8; 89 | return value; 90 | } 91 | 92 | resetPosition(): void { 93 | this.position = 0; 94 | } 95 | } 96 | 97 | export default ByteBuffer; 98 | -------------------------------------------------------------------------------- /src/bench/histogram-decoding.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { 4 | encodeIntoCompressedBase64, 5 | decodeFromCompressedBase64 6 | } from "../encoding"; 7 | import { initWebAssembly } from "../wasm"; 8 | initWebAssembly().then(() => { 9 | const randomInteger = (max: number = Number.MAX_SAFE_INTEGER) => 10 | Math.floor(Math.random() * max); 11 | const options = { initCount: 1000 }; 12 | 13 | b.suite( 14 | "Histogram decoding", 15 | b.add( 16 | "Int32Histogram", 17 | () => { 18 | const histogram = build(); 19 | for (let index = 0; index < 1024; index++) { 20 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 21 | } 22 | const b64 = encodeIntoCompressedBase64(histogram); 23 | return () => { 24 | decodeFromCompressedBase64(b64, 32, false).destroy(); 25 | }; 26 | }, 27 | options 28 | ), 29 | 30 | b.add( 31 | "WASM 32B Histogram", 32 | () => { 33 | const histogram = build(); 34 | for (let index = 0; index < 1024; index++) { 35 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 36 | } 37 | const b64 = encodeIntoCompressedBase64(histogram); 38 | histogram.destroy(); 39 | return () => { 40 | decodeFromCompressedBase64(b64, 32, true).destroy(); 41 | }; 42 | }, 43 | options 44 | ), 45 | b.add( 46 | "Packed Histogram", 47 | () => { 48 | const histogram = build(); 49 | for (let index = 0; index < 1024; index++) { 50 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 51 | } 52 | const b64 = encodeIntoCompressedBase64(histogram); 53 | return () => { 54 | decodeFromCompressedBase64(b64, "packed", false).destroy(); 55 | }; 56 | }, 57 | options 58 | ), 59 | b.add( 60 | "WASM Packed Histogram", 61 | () => { 62 | const histogram = build({ 63 | bitBucketSize: "packed", 64 | useWebAssembly: true 65 | }); 66 | for (let index = 0; index < 1024; index++) { 67 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 68 | } 69 | const b64 = encodeIntoCompressedBase64(histogram); 70 | histogram.destroy(); 71 | return () => { 72 | decodeFromCompressedBase64(b64, "packed", true).destroy(); 73 | }; 74 | }, 75 | options 76 | ), 77 | 78 | b.complete(), 79 | b.save({ file: "decoding", format: "chart.html" }) 80 | ); 81 | }); 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hdr-histogram-js", 3 | "version": "3.0.1", 4 | "description": "TypeScript port of HdrHistogram", 5 | "main": "dist/index.js", 6 | "browser": "dist/hdrhistogram.umd.js", 7 | "types": "dist/index.d.ts", 8 | "keywords": [ 9 | "hdr-histogram", 10 | "hdr-histogram-js", 11 | "percentiles", 12 | "monitoring", 13 | "latency", 14 | "performance" 15 | ], 16 | "engines": { 17 | "node": ">=14" 18 | }, 19 | "scripts": { 20 | "tsc": "tsc --skipLibCheck --outDir dist --declaration true --sourceMap true", 21 | "test": "jest", 22 | "build": "npm run tsc && npm run rollup", 23 | "prettier": "prettier --parser typescript -l src/**/*.ts", 24 | "prettier:fix": "prettier --write --parser typescript -l src/**/*.ts", 25 | "astest": "asp --verbose", 26 | "astest:ci": "asp --summary", 27 | "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --debug --exportRuntime", 28 | "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap -O3 --noAssert --exportRuntime", 29 | "asbuild:base64": "echo \"/** @internal */\nexport const BINARY = \\\"$(node ./base64.js build/optimized.wasm)\\\";\" > src/wasm/generated-wasm.ts", 30 | "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized && npm run asbuild:base64", 31 | "rollup": "rollup -c" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/HdrHistogram/HdrHistogramJS.git" 36 | }, 37 | "author": "Alexandre Victoor", 38 | "license": "BSD-2-Clause", 39 | "devDependencies": { 40 | "@as-pect/cli": "^6.2.4", 41 | "@babel/preset-env": "^7.14.8", 42 | "@babel/preset-typescript": "^7.14.5", 43 | "@types/benchmark": "^1.0.31", 44 | "@types/jest": "^25.2.1", 45 | "@types/node": "7.0.0", 46 | "assemblyscript": "^0.19.21", 47 | "benchmark": "^2.1.4", 48 | "benny": "^3.6.14", 49 | "fast-check": "^1.1.1", 50 | "jest": "25.1.x", 51 | "node-notifier": ">=8.0.1", 52 | "prettier": "^1.6.1", 53 | "rollup": "^2.8.2", 54 | "rollup-plugin-commonjs": "^10.1.0", 55 | "rollup-plugin-node-resolve": "^5.2.0", 56 | "rollup-plugin-terser": "^5.3.0", 57 | "rollup-plugin-typescript": "^1.0.1", 58 | "ts-jest": "25.0.x", 59 | "ts-loader": "^3.5.0", 60 | "ts-node": "^5.0.1", 61 | "typescript": "^5.6.3", 62 | "yargs": "^6.4.0" 63 | }, 64 | "dependencies": { 65 | "@assemblyscript/loader": "^0.19.21", 66 | "base64-js": "^1.2.0", 67 | "pako": "^1.0.3" 68 | }, 69 | "files": [ 70 | ".", 71 | "dist" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/formatters.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | integerFormatter, 3 | floatFormatter, 4 | keepSignificantDigits, 5 | } from "./formatters"; 6 | 7 | describe("Integer formatter", () => { 8 | it("should format integer as a string", () => { 9 | // given 10 | const formatter = integerFormatter(3); 11 | // when 12 | const result = formatter(123); 13 | // then 14 | expect(result).toBe("123"); 15 | }); 16 | 17 | it("should add padding on the left when input has a few digits", () => { 18 | // given 19 | const formatter = integerFormatter(5); 20 | // when 21 | const result = formatter(123); 22 | // then 23 | expect(result).toBe(" 123"); 24 | }); 25 | }); 26 | 27 | describe("Integer processor", () => { 28 | it("should keep value unchanged when value small enough comapred to number of value digits", () => { 29 | // given 30 | const processor = keepSignificantDigits(3); 31 | // when 32 | const result = processor(421); 33 | // then 34 | expect(result).toBe(421); 35 | }); 36 | 37 | it("should lower value when value has more digits than what is needed", () => { 38 | // given 39 | const processor = keepSignificantDigits(3); 40 | // when 41 | const result = processor(123456); 42 | // then 43 | expect(result).toBe(123000); 44 | }); 45 | }); 46 | 47 | describe("Float formatter", () => { 48 | it("should format float as a string", () => { 49 | // given 50 | const formatter = floatFormatter(5, 2); 51 | // when 52 | const result = formatter(12.34); 53 | // then 54 | expect(result).toBe("12.34"); 55 | }); 56 | 57 | it("should format float as a string with given number of fraction digits", () => { 58 | // given 59 | const formatter = floatFormatter(5, 2); 60 | // when 61 | const result = formatter(12.342); 62 | // then 63 | expect(result).toBe("12.34"); 64 | }); 65 | 66 | it("should format float as a string adding fraction digits", () => { 67 | // given 68 | const formatter = floatFormatter(5, 2); 69 | // when 70 | const result = formatter(12.3); 71 | // then 72 | expect(result).toBe("12.30"); 73 | }); 74 | 75 | it("should format the whole float input even with lots of digits", () => { 76 | // given 77 | const formatter = floatFormatter(5, 2); 78 | // when 79 | const result = formatter(12456789.34); 80 | // then 81 | expect(result).toBe("12456789.34"); 82 | }); 83 | 84 | it("should add padding on the left when not enough digits", () => { 85 | // given 86 | const formatter = floatFormatter(5, 2); 87 | // when 88 | const result = formatter(9.34); 89 | // then 90 | expect(result).toBe(" 9.34"); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /assembly/__tests__/formatters.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import { FloatFormatter, IntegerFormatter } from "../formatters"; 10 | 11 | describe("Integer formatter", () => { 12 | it("should format integer as a string", () => { 13 | // given 14 | const formatter = new IntegerFormatter(3); 15 | // when 16 | const result = formatter.format(123); 17 | // then 18 | expect(result).toBe("123"); 19 | }); 20 | 21 | it("should add padding on the left when input has a few digits", () => { 22 | // given 23 | const formatter = new IntegerFormatter(5); 24 | // when 25 | const result = formatter.format(123); 26 | // then 27 | expect(result).toBe(" 123"); 28 | }); 29 | }); 30 | 31 | describe("Float formatter", () => { 32 | it("should format float as a string", () => { 33 | // given 34 | const formatter = new FloatFormatter(5, 2); 35 | // when 36 | const result = formatter.format(12.34); 37 | // then 38 | expect(result).toBe("12.34"); 39 | }); 40 | 41 | it("should format float as a string with given number of fraction digits", () => { 42 | // given 43 | const formatter = new FloatFormatter(5, 2); 44 | // when 45 | const result = formatter.format(12.347); 46 | // then 47 | expect(result).toBe("12.35"); 48 | }); 49 | 50 | it("should format float as a string with given number of fraction digits (bis)", () => { 51 | // given 52 | const formatter = new FloatFormatter(12, 3); 53 | // when 54 | const result = formatter.format(50); 55 | // then 56 | expect(result).toBe(" 50.000"); 57 | }); 58 | 59 | it("should format float as a string adding fraction digits", () => { 60 | // given 61 | const formatter = new FloatFormatter(5, 2); 62 | // when 63 | const result = formatter.format(12.3); 64 | // then 65 | expect(result).toBe("12.30"); 66 | }); 67 | 68 | it("should format the whole float input even with lots of digits", () => { 69 | // given 70 | const formatter = new FloatFormatter(5, 2); 71 | // when 72 | const result = formatter.format(12456789.34); 73 | // then 74 | expect(result).toBe("12456789.34"); 75 | }); 76 | 77 | it("should add padding on the left when not enough digits", () => { 78 | // given 79 | const formatter = new FloatFormatter(5, 2); 80 | // when 81 | const result = formatter.format(9.34); 82 | // then 83 | expect(result).toBe(" 9.34"); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /src/bench/histogram-add.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | initWebAssembly().then(() => { 5 | const randomInteger = (max: number = Number.MAX_SAFE_INTEGER) => 6 | Math.floor(Math.random() * max); 7 | const options = { initCount: 1000 }; 8 | 9 | b.suite( 10 | "Histogram add", 11 | b.add( 12 | "Int32Histogram", 13 | () => { 14 | const histogram = build(); 15 | const histogram2 = build(); 16 | for (let index = 0; index < 1024; index++) { 17 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 18 | histogram2.recordValueWithCount(randomInteger(), randomInteger(100)); 19 | } 20 | return () => { 21 | histogram.add(histogram2); 22 | }; 23 | }, 24 | options 25 | ), 26 | 27 | b.add( 28 | "WASM 32B Histogram", 29 | () => { 30 | const histogram = build({ useWebAssembly: true }); 31 | const histogram2 = build({ useWebAssembly: true }); 32 | for (let index = 0; index < 1024; index++) { 33 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 34 | histogram2.recordValueWithCount(randomInteger(), randomInteger(100)); 35 | } 36 | return () => { 37 | histogram.add(histogram2); 38 | }; 39 | }, 40 | options 41 | ), 42 | b.add( 43 | "Packed Histogram", 44 | () => { 45 | const histogram = build({ bitBucketSize: "packed" }); 46 | const histogram2 = build({ bitBucketSize: "packed" }); 47 | for (let index = 0; index < 1024; index++) { 48 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 49 | histogram2.recordValueWithCount(randomInteger(), randomInteger(100)); 50 | } 51 | return () => { 52 | histogram.add(histogram2); 53 | }; 54 | }, 55 | options 56 | ), 57 | b.add( 58 | "WASM Packed Histogram", 59 | () => { 60 | const histogram = build({ 61 | bitBucketSize: "packed", 62 | useWebAssembly: true 63 | }); 64 | const histogram2 = build({ 65 | bitBucketSize: "packed", 66 | useWebAssembly: true 67 | }); 68 | for (let index = 0; index < 1024; index++) { 69 | histogram.recordValueWithCount(randomInteger(), randomInteger(100)); 70 | histogram2.recordValueWithCount(randomInteger(), randomInteger(100)); 71 | } 72 | return () => { 73 | histogram.add(histogram2); 74 | }; 75 | }, 76 | options 77 | ), 78 | 79 | b.complete(), 80 | b.save({ file: "add", format: "chart.html" }) 81 | ); 82 | }); 83 | -------------------------------------------------------------------------------- /assembly/__tests__/encoding.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import { decodeFromByteBuffer, encodeIntoByteBuffer } from "../encoding"; 10 | import { Histogram32, Uint32Storage } from "../Histogram"; 11 | import ByteBuffer from "../ByteBuffer"; 12 | 13 | describe("Histogram encoding", () => { 14 | it("should encode filling a byte buffer", () => { 15 | // given 16 | const histogram = new Histogram32(1, 9007199254740991, 2); 17 | histogram.recordValue(42); 18 | const buffer = ByteBuffer.allocate(); 19 | // when 20 | const encodedSize = encodeIntoByteBuffer( 21 | histogram, 22 | buffer 23 | ); 24 | // then 25 | expect(encodedSize).toBe(42); 26 | }); 27 | 28 | it("should encode / decode", () => { 29 | // given 30 | const histogram = new Histogram32(1, 9007199254740991, 2); 31 | histogram.recordValue(42); 32 | histogram.recordValue(7); 33 | histogram.recordValue(77); 34 | const buffer = ByteBuffer.allocate(); 35 | const encodedSize = encodeIntoByteBuffer( 36 | histogram, 37 | buffer 38 | ); 39 | buffer.position = 0; 40 | // when 41 | const result = decodeFromByteBuffer(buffer, 0); 42 | // then 43 | expect(result.outputPercentileDistribution()).toBe( 44 | histogram.outputPercentileDistribution() 45 | ); 46 | }); 47 | it("should encode / decode bis", () => { 48 | // given 49 | const histogram = new Histogram32(1, 9007199254740991, 2); 50 | histogram.recordValue(42); 51 | histogram.recordValue(7); 52 | histogram.recordValue(77); 53 | const data = histogram.encode(); 54 | // when 55 | const buffer = new ByteBuffer(data); 56 | const result = decodeFromByteBuffer(buffer, 0); 57 | // then 58 | expect(result.outputPercentileDistribution()).toBe( 59 | histogram.outputPercentileDistribution() 60 | ); 61 | }); 62 | xit("should encode / decode without any assemblyscript crash", () => { 63 | // given 64 | const histogram = new Histogram32(1, 9007199254740991, 3); 65 | histogram.autoResize = true; 66 | histogram.recordValue(32415482); 67 | const data = histogram.encode(); 68 | // when 69 | const buffer = new ByteBuffer(data); 70 | const result = decodeFromByteBuffer(buffer, 0); 71 | // then 72 | expect(result.outputPercentileDistribution()).toBe( 73 | histogram.outputPercentileDistribution() 74 | ); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/TypedArrayHistogram.spec.ts: -------------------------------------------------------------------------------- 1 | import Int8Histogram from "./Int8Histogram"; 2 | import Int16Histogram from "./Int16Histogram"; 3 | import Int32Histogram from "./Int32Histogram"; 4 | import Float64Histogram from "./Float64Histogram"; 5 | 6 | [Int8Histogram, Int16Histogram, Int32Histogram, Float64Histogram].forEach( 7 | (Histogram) => { 8 | describe(`${Histogram} histogram`, () => { 9 | it("should record a value", () => { 10 | // given 11 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 3); 12 | // when 13 | histogram.recordValue(123456); 14 | // then 15 | expect(histogram.getCountAtIndex(8073)).toBe(1); 16 | }); 17 | 18 | it("should compute median value in first bucket", () => { 19 | // given 20 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 3); 21 | histogram.recordValue(123456); 22 | histogram.recordValue(127); 23 | histogram.recordValue(42); 24 | // when 25 | const medianValue = histogram.getValueAtPercentile(50); 26 | // then 27 | expect(medianValue).toBe(127); 28 | }); 29 | 30 | it("should compute value outside first bucket with an error less than 1000", () => { 31 | // given 32 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 3); 33 | histogram.recordValue(123456); 34 | histogram.recordValue(122777); 35 | histogram.recordValue(127); 36 | histogram.recordValue(42); 37 | // when 38 | const percentileValue = histogram.getValueAtPercentile(99.9); 39 | // then 40 | expect(Math.abs(percentileValue - 123456)).toBeLessThan(1000); 41 | // TODO the value is 123519 > max, ask Gil if it is a bug 42 | }); 43 | 44 | it("should resize recording values above max", () => { 45 | // given 46 | const histogram = new Histogram(1, 2, 3); 47 | histogram.autoResize = true; 48 | // when 49 | histogram.recordValue(123456); 50 | histogram.recordValue(127000); 51 | histogram.recordValue(420000); 52 | // then 53 | const medianValue = histogram.getValueAtPercentile(50); 54 | expect(Math.abs(medianValue - 127000)).toBeLessThan(1000); 55 | }); 56 | 57 | it("should compute proper value at percentile even with rounding issues", () => { 58 | // given 59 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 3); 60 | histogram.recordValue(1); 61 | histogram.recordValue(2); 62 | // when & then 63 | expect(histogram.getValueAtPercentile(50.0)).toBe(1); 64 | expect(histogram.getValueAtPercentile(50.00000000000001)).toBe(1); 65 | expect(histogram.getValueAtPercentile(50.0000000000001)).toBe(2); 66 | }); 67 | }); 68 | } 69 | ); 70 | -------------------------------------------------------------------------------- /src/PackedHistogram.spec.ts: -------------------------------------------------------------------------------- 1 | import Histogram from "./PackedHistogram"; 2 | 3 | describe("Packed histogram", () => { 4 | it("should compute median value in first bucket", () => { 5 | // given 6 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 3); 7 | histogram.recordValue(123456); 8 | histogram.recordValue(127); 9 | histogram.recordValue(42); 10 | // when 11 | const medianValue = histogram.getValueAtPercentile(50); 12 | // then 13 | expect(medianValue).toBe(127); 14 | }); 15 | 16 | it("should compute same values when new or reseted", () => { 17 | // given 18 | const histogram = new Histogram(1, 2, 3); 19 | const histogram2 = new Histogram(1, 2, 3); 20 | histogram.autoResize = true; 21 | histogram2.autoResize = true; 22 | 23 | [1, 49026].forEach((v) => histogram.recordValue(v)); 24 | 25 | // when 26 | histogram.reset(); 27 | [7, 67, 42357, 805017].forEach((v) => { 28 | histogram.recordValue(v); 29 | histogram2.recordValue(v); 30 | }); 31 | // then 32 | expect(histogram.getValueAtPercentile(5)).toBe( 33 | histogram2.getValueAtPercentile(5) 34 | ); 35 | }); 36 | 37 | it("should compute value outside first bucket with an error less than 1000", () => { 38 | // given 39 | const histogram = new Histogram(1, 123456, 2); 40 | histogram.recordValue(5234); 41 | histogram.recordValue(127); 42 | histogram.recordValue(42); 43 | // when 44 | const percentileValue = histogram.getValueAtPercentile(90); 45 | // then 46 | expect(Math.abs(percentileValue - 5234)).toBeLessThan(100); 47 | }); 48 | 49 | it("should resize underlying packed array when recording an out of bound value", () => { 50 | // given 51 | const histogram = new Histogram(1, 2, 3); 52 | histogram.autoResize = true; 53 | // when 54 | histogram.recordValue(123456); 55 | // then 56 | expect(histogram.totalCount).toBe(1); 57 | }); 58 | }); 59 | 60 | describe("Histogram clearing support", () => { 61 | it("should reset data in order to reuse histogram", () => { 62 | // given 63 | const histogram = new Histogram(1, Number.MAX_SAFE_INTEGER, 2); 64 | const histogram2 = new Histogram(1, Number.MAX_SAFE_INTEGER, 2); 65 | histogram.startTimeStampMsec = 42; 66 | histogram.endTimeStampMsec = 56; 67 | histogram.tag = "blabla"; 68 | histogram.recordValue(10); 69 | histogram.recordValue(1000); 70 | histogram.recordValue(10000000); 71 | // when 72 | histogram.reset(); 73 | // then 74 | expect(histogram.totalCount).toBe(0); 75 | expect(histogram.startTimeStampMsec).toBe(0); 76 | expect(histogram.endTimeStampMsec).toBe(0); 77 | //expect(histogram.tag).toBe(NO_TAG); 78 | expect(histogram.maxValue).toBe(0); 79 | expect(histogram.minNonZeroValue).toBe(Number.MAX_SAFE_INTEGER); 80 | expect(histogram.getValueAtPercentile(99.999)).toBe(0); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /assembly/HistogramIterationValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | /** 10 | * Represents a value point iterated through in a Histogram, with associated stats. 11 | *
    12 | *
  • valueIteratedTo :
    The actual value level that was iterated to by the iterator
  • 13 | *
  • prevValueIteratedTo :
    The actual value level that was iterated from by the iterator
  • 14 | *
  • countAtValueIteratedTo :
    The count of recorded values in the histogram that 15 | * exactly match this [lowestEquivalentValue(valueIteratedTo)...highestEquivalentValue(valueIteratedTo)] value 16 | * range.
  • 17 | *
  • countAddedInThisIterationStep :
    The count of recorded values in the histogram that 18 | * were added to the totalCountToThisValue (below) as a result on this iteration step. Since multiple iteration 19 | * steps may occur with overlapping equivalent value ranges, the count may be lower than the count found at 20 | * the value (e.g. multiple linear steps or percentile levels can occur within a single equivalent value range)
  • 21 | *
  • totalCountToThisValue :
    The total count of all recorded values in the histogram at 22 | * values equal or smaller than valueIteratedTo.
  • 23 | *
  • totalValueToThisValue :
    The sum of all recorded values in the histogram at values 24 | * equal or smaller than valueIteratedTo.
  • 25 | *
  • percentile :
    The percentile of recorded values in the histogram at values equal 26 | * or smaller than valueIteratedTo.
  • 27 | *
  • percentileLevelIteratedTo :
    The percentile level that the iterator returning this 28 | * HistogramIterationValue had iterated to. Generally, percentileLevelIteratedTo will be equal to or smaller than 29 | * percentile, but the same value point can contain multiple iteration levels for some iterators. E.g. a 30 | * PercentileIterator can stop multiple times in the exact same value point (if the count at that value covers a 31 | * range of multiple percentiles in the requested percentile iteration points).
  • 32 | *
33 | */ 34 | class HistogramIterationValue { 35 | valueIteratedTo: u64; 36 | valueIteratedFrom: u64; 37 | countAtValueIteratedTo: u64; 38 | countAddedInThisIterationStep: u64; 39 | totalCountToThisValue: u64; 40 | totalValueToThisValue: u64; 41 | percentile: f64; 42 | percentileLevelIteratedTo: f64; 43 | 44 | constructor() { 45 | this.reset(); 46 | } 47 | 48 | reset(): void { 49 | this.valueIteratedTo = 0; 50 | this.valueIteratedFrom = 0; 51 | this.countAtValueIteratedTo = 0; 52 | this.countAddedInThisIterationStep = 0; 53 | this.totalCountToThisValue = 0; 54 | this.totalValueToThisValue = 0; 55 | this.percentile = 0.0; 56 | this.percentileLevelIteratedTo = 0.0; 57 | } 58 | } 59 | 60 | export default HistogramIterationValue; 61 | -------------------------------------------------------------------------------- /src/HistogramIterationValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | /** 10 | * Represents a value point iterated through in a Histogram, with associated stats. 11 | *
    12 | *
  • valueIteratedTo :
    The actual value level that was iterated to by the iterator
  • 13 | *
  • prevValueIteratedTo :
    The actual value level that was iterated from by the iterator
  • 14 | *
  • countAtValueIteratedTo :
    The count of recorded values in the histogram that 15 | * exactly match this [lowestEquivalentValue(valueIteratedTo)...highestEquivalentValue(valueIteratedTo)] value 16 | * range.
  • 17 | *
  • countAddedInThisIterationStep :
    The count of recorded values in the histogram that 18 | * were added to the totalCountToThisValue (below) as a result on this iteration step. Since multiple iteration 19 | * steps may occur with overlapping equivalent value ranges, the count may be lower than the count found at 20 | * the value (e.g. multiple linear steps or percentile levels can occur within a single equivalent value range)
  • 21 | *
  • totalCountToThisValue :
    The total count of all recorded values in the histogram at 22 | * values equal or smaller than valueIteratedTo.
  • 23 | *
  • totalValueToThisValue :
    The sum of all recorded values in the histogram at values 24 | * equal or smaller than valueIteratedTo.
  • 25 | *
  • percentile :
    The percentile of recorded values in the histogram at values equal 26 | * or smaller than valueIteratedTo.
  • 27 | *
  • percentileLevelIteratedTo :
    The percentile level that the iterator returning this 28 | * HistogramIterationValue had iterated to. Generally, percentileLevelIteratedTo will be equal to or smaller than 29 | * percentile, but the same value point can contain multiple iteration levels for some iterators. E.g. a 30 | * PercentileIterator can stop multiple times in the exact same value point (if the count at that value covers a 31 | * range of multiple percentiles in the requested percentile iteration points).
  • 32 | *
33 | */ 34 | class HistogramIterationValue { 35 | valueIteratedTo: number; 36 | valueIteratedFrom: number; 37 | countAtValueIteratedTo: number; 38 | countAddedInThisIterationStep: number; 39 | totalCountToThisValue: number; 40 | totalValueToThisValue: number; 41 | percentile: number; 42 | percentileLevelIteratedTo: number; 43 | 44 | constructor() { 45 | this.reset(); 46 | } 47 | 48 | reset() { 49 | this.valueIteratedTo = 0; 50 | this.valueIteratedFrom = 0; 51 | this.countAtValueIteratedTo = 0; 52 | this.countAddedInThisIterationStep = 0; 53 | this.totalCountToThisValue = 0; 54 | this.totalValueToThisValue = 0; 55 | this.percentile = 0.0; 56 | this.percentileLevelIteratedTo = 0.0; 57 | } 58 | } 59 | 60 | export default HistogramIterationValue; 61 | -------------------------------------------------------------------------------- /src/ZigZagEncoding.spec.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer from "./ByteBuffer"; 2 | import ZigZagEncoding from "./ZigZagEncoding"; 3 | 4 | describe("Zig Zag Encoding", () => { 5 | it("should encode int using one byte when value is less than 64", () => { 6 | // given 7 | const buffer = ByteBuffer.allocate(4); 8 | // when 9 | ZigZagEncoding.encode(buffer, 56); 10 | // then 11 | expect(buffer.data).toHaveLength(4); 12 | expect(buffer.data[0]).toBe(112); 13 | }); 14 | 15 | it("should encode int using several bytes when value is more than 64", () => { 16 | // given 17 | const buffer = ByteBuffer.allocate(4); 18 | // when 19 | ZigZagEncoding.encode(buffer, 456); 20 | // then 21 | expect(buffer.data).toHaveLength(4); 22 | expect(Array.from(buffer.data)).toEqual([144, 7, 0, 0]); 23 | }); 24 | 25 | it("should encode negative int using several bytes when value is more than 64", () => { 26 | // given 27 | const buffer = ByteBuffer.allocate(4); 28 | // when 29 | ZigZagEncoding.encode(buffer, -456); 30 | // then 31 | expect(buffer.data).toHaveLength(4); 32 | expect(Array.from(buffer.data)).toEqual([143, 7, 0, 0]); 33 | }); 34 | 35 | it("should encode large safe int greater than 2^32", () => { 36 | // given 37 | const buffer = ByteBuffer.allocate(4); 38 | // when 39 | ZigZagEncoding.encode(buffer, Math.pow(2, 50)); 40 | // then 41 | expect(buffer.data).toHaveLength(8); 42 | expect(Array.from(buffer.data)).toEqual([ 43 | 128, 44 | 128, 45 | 128, 46 | 128, 47 | 128, 48 | 128, 49 | 128, 50 | 4, 51 | ]); 52 | }); 53 | 54 | it("should decode int using one byte", () => { 55 | // given 56 | const buffer = ByteBuffer.allocate(8); 57 | ZigZagEncoding.encode(buffer, 56); 58 | buffer.resetPosition(); 59 | // when 60 | const value = ZigZagEncoding.decode(buffer); 61 | // then 62 | expect(value).toBe(56); 63 | }); 64 | 65 | it("should decode int using multiple bytes", () => { 66 | // given 67 | const buffer = ByteBuffer.allocate(8); 68 | ZigZagEncoding.encode(buffer, 70000); 69 | ZigZagEncoding.encode(buffer, 56); 70 | buffer.resetPosition(); 71 | // when 72 | const value = ZigZagEncoding.decode(buffer); 73 | // then 74 | expect(value).toBe(70000); 75 | }); 76 | 77 | it("should decode negative int using multiple bytes", () => { 78 | // given 79 | const buffer = ByteBuffer.allocate(8); 80 | ZigZagEncoding.encode(buffer, -1515); 81 | ZigZagEncoding.encode(buffer, 56); 82 | buffer.resetPosition(); 83 | // when 84 | const value = ZigZagEncoding.decode(buffer); 85 | // then 86 | expect(value).toBe(-1515); 87 | }); 88 | 89 | it("should decode large safe int greater than 2^32", () => { 90 | // given 91 | const buffer = ByteBuffer.allocate(4); 92 | ZigZagEncoding.encode(buffer, Math.pow(2, 50) + 1234); 93 | ZigZagEncoding.encode(buffer, 56); 94 | buffer.resetPosition(); 95 | // when 96 | const value = ZigZagEncoding.decode(buffer); 97 | // then 98 | expect(value).toBe(Math.pow(2, 50) + 1234); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /src/TypedArrayHistogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import JsHistogram from "./JsHistogram"; 9 | 10 | type TypedArray = ArrayLike & { 11 | readonly BYTES_PER_ELEMENT: number; 12 | [key: number]: number; 13 | fill(v: number): void; 14 | set(other: TypedArray): void; 15 | }; 16 | 17 | class TypedArrayHistogram extends JsHistogram { 18 | _counts: TypedArray; 19 | _totalCount: number; 20 | 21 | constructor( 22 | private arrayCtr: new (size: number) => TypedArray, 23 | lowestDiscernibleValue: number, 24 | highestTrackableValue: number, 25 | numberOfSignificantValueDigits: number 26 | ) { 27 | super( 28 | lowestDiscernibleValue, 29 | highestTrackableValue, 30 | numberOfSignificantValueDigits 31 | ); 32 | this._totalCount = 0; 33 | this._counts = new arrayCtr(this.countsArrayLength); 34 | } 35 | 36 | clearCounts() { 37 | this._counts.fill(0); 38 | } 39 | 40 | incrementCountAtIndex(index: number) { 41 | const currentCount = this._counts[index]; 42 | const newCount = currentCount + 1; 43 | if (newCount < 0) { 44 | throw newCount + " would overflow short integer count"; 45 | } 46 | this._counts[index] = newCount; 47 | } 48 | 49 | addToCountAtIndex(index: number, value: number) { 50 | const currentCount = this._counts[index]; 51 | const newCount = currentCount + value; 52 | if ( 53 | newCount < Number.MIN_SAFE_INTEGER || 54 | newCount > Number.MAX_SAFE_INTEGER 55 | ) { 56 | throw newCount + " would overflow integer count"; 57 | } 58 | this._counts[index] = newCount; 59 | } 60 | 61 | setCountAtIndex(index: number, value: number) { 62 | if (value < Number.MIN_SAFE_INTEGER || value > Number.MAX_SAFE_INTEGER) { 63 | throw value + " would overflow integer count"; 64 | } 65 | this._counts[index] = value; 66 | } 67 | 68 | resize(newHighestTrackableValue: number) { 69 | this.establishSize(newHighestTrackableValue); 70 | const newCounts = new this.arrayCtr(this.countsArrayLength); 71 | newCounts.set(this._counts); 72 | this._counts = newCounts; 73 | } 74 | 75 | getCountAtIndex(index: number) { 76 | return this._counts[index]; 77 | } 78 | 79 | protected _getEstimatedFootprintInBytes() { 80 | return 1024 + this._counts.BYTES_PER_ELEMENT * this._counts.length; 81 | } 82 | 83 | copyCorrectedForCoordinatedOmission( 84 | expectedIntervalBetweenValueSamples: number 85 | ) { 86 | const copy = new TypedArrayHistogram( 87 | this.arrayCtr, 88 | this.lowestDiscernibleValue, 89 | this.highestTrackableValue, 90 | this.numberOfSignificantValueDigits 91 | ); 92 | copy.addWhileCorrectingForCoordinatedOmission( 93 | this, 94 | expectedIntervalBetweenValueSamples 95 | ); 96 | return copy; 97 | } 98 | 99 | toString() { 100 | return `Histogram ${this._counts.BYTES_PER_ELEMENT * 8}b ${JSON.stringify( 101 | this, 102 | null, 103 | 2 104 | )}`; 105 | } 106 | } 107 | 108 | export default TypedArrayHistogram; 109 | -------------------------------------------------------------------------------- /src/bench/histogram-data-access-co.ts: -------------------------------------------------------------------------------- 1 | import b from "benny"; 2 | import { build } from "../index"; 3 | import { initWebAssembly } from "../wasm"; 4 | initWebAssembly().then(() => { 5 | const randomInteger = () => Math.floor(Math.random() * 1000000); 6 | const randomSmallInteger = () => Math.floor(Math.random() * 1000); 7 | const options = { initCount: 1000 }; 8 | 9 | b.suite( 10 | "Histogram data access with coordinated ommissions", 11 | b.add( 12 | "Int32Histogram", 13 | () => { 14 | const histogram = build({ bitBucketSize: 32 }); 15 | return () => { 16 | histogram.recordValueWithExpectedInterval(randomInteger(), 100000); 17 | }; 18 | }, 19 | options 20 | ), 21 | 22 | b.add( 23 | "Int32Histogram no correction needed", 24 | () => { 25 | const histogram = build({ bitBucketSize: 32 }); 26 | return () => { 27 | histogram.recordValueWithExpectedInterval( 28 | randomSmallInteger(), 29 | 100000 30 | ); 31 | }; 32 | }, 33 | options 34 | ), 35 | b.add( 36 | "PackedHistogram", 37 | () => { 38 | const histogram = build({ bitBucketSize: "packed" }); 39 | return () => { 40 | histogram.recordValueWithExpectedInterval(randomInteger(), 100000); 41 | }; 42 | }, 43 | options 44 | ), 45 | b.add( 46 | "PackedHistogram no correction needed", 47 | () => { 48 | const histogram = build({ bitBucketSize: "packed" }); 49 | return () => { 50 | histogram.recordValueWithExpectedInterval( 51 | randomSmallInteger(), 52 | 100000 53 | ); 54 | }; 55 | }, 56 | options 57 | ), 58 | 59 | b.add( 60 | "WASM Int32Histogram", 61 | () => { 62 | const histogram = build({ 63 | useWebAssembly: true 64 | }); 65 | return () => { 66 | histogram.recordValueWithExpectedInterval(randomInteger(), 100000); 67 | }; 68 | }, 69 | options 70 | ), 71 | 72 | b.add( 73 | "WASM Int32Histogram no correction needed", 74 | () => { 75 | const histogram = build({ 76 | useWebAssembly: true 77 | }); 78 | return () => { 79 | histogram.recordValueWithExpectedInterval( 80 | randomSmallInteger(), 81 | 100000 82 | ); 83 | }; 84 | }, 85 | options 86 | ), 87 | 88 | b.add( 89 | "WASM PackedHistogram", 90 | () => { 91 | const histogram = build({ 92 | useWebAssembly: true, 93 | bitBucketSize: "packed" 94 | }); 95 | return () => { 96 | histogram.recordValueWithExpectedInterval(randomInteger(), 100000); 97 | }; 98 | }, 99 | options 100 | ), 101 | 102 | b.add( 103 | "WASM PackedHistogram no correction needed", 104 | () => { 105 | const histogram = build({ 106 | useWebAssembly: true, 107 | bitBucketSize: "packed" 108 | }); 109 | return () => { 110 | histogram.recordValueWithExpectedInterval( 111 | randomSmallInteger(), 112 | 100000 113 | ); 114 | }; 115 | }, 116 | options 117 | ), 118 | b.complete(), 119 | b.save({ file: "data-access-co", format: "chart.html" }) 120 | ); 121 | }); 122 | -------------------------------------------------------------------------------- /src/HistogramLogWriter.ts: -------------------------------------------------------------------------------- 1 | import { NO_TAG } from "./Histogram"; 2 | import { encodeIntoCompressedBase64 } from "./encoding"; 3 | import { floatFormatter } from "./formatters"; 4 | import Histogram from "./Histogram"; 5 | 6 | export interface Writable { 7 | (c: string): void; 8 | } 9 | 10 | const HISTOGRAM_LOG_FORMAT_VERSION = "1.3"; 11 | const timeFormatter = floatFormatter(5, 3); 12 | 13 | class HistogramLogWriter { 14 | /** 15 | * Base time to subtract from supplied histogram start/end timestamps when 16 | * logging based on histogram timestamps. 17 | * Base time is expected to be in msec since the epoch, as histogram start/end times 18 | * are typically stamped with absolute times in msec since the epoch. 19 | */ 20 | baseTime = 0; 21 | 22 | constructor(private log: Writable) {} 23 | 24 | /** 25 | * Output an interval histogram, with the given timestamp information and the [optional] tag 26 | * associated with the histogram, using a configurable maxValueUnitRatio. (note that the 27 | * specified timestamp information will be used, and the timestamp information in the actual 28 | * histogram will be ignored). 29 | * The max value reported with the interval line will be scaled by the given maxValueUnitRatio. 30 | * @param startTimeStampSec The start timestamp to log with the interval histogram, in seconds. 31 | * @param endTimeStampSec The end timestamp to log with the interval histogram, in seconds. 32 | * @param histogram The interval histogram to log. 33 | * @param maxValueUnitRatio The ratio by which to divide the histogram's max value when reporting on it. 34 | */ 35 | outputIntervalHistogram( 36 | histogram: Histogram, 37 | startTimeStampSec = (histogram.startTimeStampMsec - this.baseTime) / 1000, 38 | endTimeStampSec = (histogram.endTimeStampMsec - this.baseTime) / 1000, 39 | maxValueUnitRatio = 1000 40 | ) { 41 | const base64 = encodeIntoCompressedBase64(histogram); 42 | const start = timeFormatter(startTimeStampSec); 43 | const duration = timeFormatter(endTimeStampSec - startTimeStampSec); 44 | const max = timeFormatter(histogram.maxValue / maxValueUnitRatio); 45 | const lineContent = `${start},${duration},${max},${base64}\n`; 46 | if (histogram.tag && histogram.tag !== NO_TAG) { 47 | this.log(`Tag=${histogram.tag},${lineContent}`); 48 | } else { 49 | this.log(lineContent); 50 | } 51 | } 52 | 53 | /** 54 | * Log a comment to the log. 55 | * Comments will be preceded with with the '#' character. 56 | * @param comment the comment string. 57 | */ 58 | outputComment(comment: string) { 59 | this.log(`#${comment}\n`); 60 | } 61 | 62 | /** 63 | * Log a start time in the log. 64 | * @param startTimeMsec time (in milliseconds) since the absolute start time (the epoch) 65 | */ 66 | outputStartTime(startTimeMsec: number) { 67 | this.outputComment( 68 | `[StartTime: ${floatFormatter( 69 | 5, 70 | 3 71 | )(startTimeMsec / 1000)} (seconds since epoch), ${new Date( 72 | startTimeMsec 73 | )}]\n` 74 | ); 75 | } 76 | 77 | /** 78 | * Output a legend line to the log. 79 | */ 80 | outputLegend() { 81 | this.log( 82 | '"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram"\n' 83 | ); 84 | } 85 | 86 | /** 87 | * Output a log format version to the log. 88 | */ 89 | outputLogFormatVersion() { 90 | this.outputComment( 91 | "[Histogram log format version " + HISTOGRAM_LOG_FORMAT_VERSION + "]" 92 | ); 93 | } 94 | } 95 | 96 | export default HistogramLogWriter; 97 | -------------------------------------------------------------------------------- /src/encoding.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import { JsHistogram } from "./JsHistogram"; 9 | import ByteBuffer from "./ByteBuffer"; 10 | import Histogram from "./Histogram"; 11 | import { WasmHistogram } from "./wasm"; 12 | 13 | // @ts-ignore 14 | import * as base64 from "base64-js"; 15 | import { inflate, deflate } from "./JsHistogram.encoding"; 16 | 17 | const V2CompressedEncodingCookieBase = 0x1c849304; 18 | const compressedEncodingCookie = V2CompressedEncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding 19 | 20 | export function decompress(data: Uint8Array): Uint8Array { 21 | const buffer = new ByteBuffer(data); 22 | const initialTargetPosition = buffer.position; 23 | 24 | const cookie = buffer.getInt32(); 25 | 26 | if ((cookie & ~0xf0) !== V2CompressedEncodingCookieBase) { 27 | throw new Error("Encoding not supported, only V2 is supported"); 28 | } 29 | 30 | const lengthOfCompressedContents = buffer.getInt32(); 31 | 32 | const uncompressedBuffer: Uint8Array = inflate( 33 | buffer.data.slice( 34 | initialTargetPosition + 8, 35 | initialTargetPosition + 8 + lengthOfCompressedContents 36 | ) 37 | ); 38 | return uncompressedBuffer; 39 | } 40 | 41 | export const decodeFromCompressedBase64 = ( 42 | base64String: string, 43 | bitBucketSize: 8 | 16 | 32 | 64 | "packed" = 32, 44 | useWebAssembly: boolean = false, 45 | minBarForHighestTrackableValue: number = 0 46 | ): Histogram => { 47 | const data = base64.toByteArray(base64String.trim()); 48 | const uncompressedData = decompress(data); 49 | if (useWebAssembly) { 50 | return WasmHistogram.decode( 51 | uncompressedData, 52 | bitBucketSize, 53 | minBarForHighestTrackableValue 54 | ); 55 | } 56 | return JsHistogram.decode( 57 | uncompressedData, 58 | bitBucketSize, 59 | minBarForHighestTrackableValue 60 | ); 61 | }; 62 | 63 | function encodeWasmIntoCompressedBase64(compressionLevel?: number): string { 64 | const compressionOptions = compressionLevel 65 | ? { level: compressionLevel } 66 | : {}; 67 | const self: WasmHistogram = this as any; 68 | 69 | const targetBuffer = ByteBuffer.allocate(); 70 | targetBuffer.putInt32(compressedEncodingCookie); 71 | 72 | const uncompressedData = self.encode(); 73 | const compressedData: Uint8Array = deflate( 74 | uncompressedData, 75 | compressionOptions 76 | ); 77 | 78 | targetBuffer.putInt32(compressedData.byteLength); 79 | targetBuffer.putArray(compressedData); 80 | 81 | return base64.fromByteArray(targetBuffer.data); 82 | } 83 | 84 | declare module "./wasm" { 85 | interface WasmHistogram { 86 | encodeIntoCompressedBase64: typeof encodeWasmIntoCompressedBase64; 87 | } 88 | } 89 | 90 | WasmHistogram.prototype.encodeIntoCompressedBase64 = encodeWasmIntoCompressedBase64; 91 | 92 | export const encodeIntoCompressedBase64 = ( 93 | histogram: Histogram, 94 | compressionLevel?: number 95 | ): string => { 96 | if (histogram instanceof WasmHistogram) { 97 | return histogram.encodeIntoCompressedBase64(compressionLevel); 98 | } 99 | if (histogram instanceof JsHistogram) { 100 | return histogram.encodeIntoCompressedBase64(compressionLevel); 101 | } 102 | throw new Error("Unsupported Histogram implementation"); 103 | }; 104 | -------------------------------------------------------------------------------- /assembly/__tests__/ZigZagEncoding.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import ByteBuffer from "../ByteBuffer"; 10 | import ZigZagEncoding from "../ZigZagEncoding"; 11 | 12 | describe("Zig Zag Encoding", () => { 13 | it("should encode int using one byte when value is less than 64", () => { 14 | // given 15 | const buffer = ByteBuffer.allocate(4); 16 | // when 17 | ZigZagEncoding.encode(buffer, 56); 18 | // then 19 | expect(buffer.data).toHaveLength(4); 20 | expect(buffer.data[0]).toBe(112); 21 | }); 22 | 23 | it("should encode int using several bytes when value is more than 64", () => { 24 | // given 25 | const buffer = ByteBuffer.allocate(4); 26 | // when 27 | ZigZagEncoding.encode(buffer, 456); 28 | // then 29 | expect(buffer.data).toHaveLength(4); 30 | expect(buffer.data[0]).toBe(144); 31 | expect(buffer.data[1]).toBe(7); 32 | expect(buffer.data[2]).toBe(0); 33 | expect(buffer.data[3]).toBe(0); 34 | }); 35 | 36 | it("should encode negative int using several bytes when value is more than 64", () => { 37 | // given 38 | const buffer = ByteBuffer.allocate(4); 39 | // when 40 | ZigZagEncoding.encode(buffer, -456); 41 | // then 42 | expect(buffer.data).toHaveLength(4); 43 | expect(buffer.data[0]).toBe(143); 44 | expect(buffer.data[1]).toBe(7); 45 | expect(buffer.data[2]).toBe(0); 46 | expect(buffer.data[3]).toBe(0); 47 | }); 48 | 49 | it("should encode large safe int greater than 2^32", () => { 50 | // given 51 | const buffer = ByteBuffer.allocate(4); 52 | // when 53 | ZigZagEncoding.encode(buffer, Math.pow(2, 50)); 54 | // then 55 | expect(buffer.data).toHaveLength(8); 56 | expect(buffer.data[0]).toBe(128); 57 | expect(buffer.data[1]).toBe(128); 58 | expect(buffer.data[2]).toBe(128); 59 | expect(buffer.data[3]).toBe(128); 60 | expect(buffer.data[4]).toBe(128); 61 | expect(buffer.data[5]).toBe(128); 62 | expect(buffer.data[6]).toBe(128); 63 | expect(buffer.data[7]).toBe(4); 64 | }); 65 | 66 | it("should decode int using one byte", () => { 67 | // given 68 | const buffer = ByteBuffer.allocate(8); 69 | ZigZagEncoding.encode(buffer, 56); 70 | buffer.resetPosition(); 71 | // when 72 | const value = ZigZagEncoding.decode(buffer); 73 | // then 74 | expect(value).toBe(56); 75 | }); 76 | 77 | it("should decode int using multiple bytes", () => { 78 | // given 79 | const buffer = ByteBuffer.allocate(8); 80 | ZigZagEncoding.encode(buffer, 70000); 81 | ZigZagEncoding.encode(buffer, 56); 82 | buffer.resetPosition(); 83 | // when 84 | const value = ZigZagEncoding.decode(buffer); 85 | // then 86 | expect(value).toBe(70000); 87 | }); 88 | 89 | it("should decode negative int using multiple bytes", () => { 90 | // given 91 | const buffer = ByteBuffer.allocate(8); 92 | ZigZagEncoding.encode(buffer, -1515); 93 | ZigZagEncoding.encode(buffer, 56); 94 | buffer.resetPosition(); 95 | // when 96 | const value = ZigZagEncoding.decode(buffer); 97 | // then 98 | expect(value).toBe(-1515); 99 | }); 100 | 101 | it("should decode large safe int greater than 2^32", () => { 102 | // given 103 | const buffer = ByteBuffer.allocate(4); 104 | ZigZagEncoding.encode(buffer, (Math.pow(2, 50) + 1234)); 105 | ZigZagEncoding.encode(buffer, 56); 106 | buffer.resetPosition(); 107 | // when 108 | const value = ZigZagEncoding.decode(buffer); 109 | // then 110 | expect(value).toBe(Math.pow(2, 50) + 1234); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /src/ByteBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer from "./ByteBuffer"; 2 | 3 | describe("ByteBuffer", () => { 4 | it("should put value moving the position", () => { 5 | // given 6 | const buffer = ByteBuffer.allocate(3); 7 | // when 8 | buffer.put(123); 9 | // then 10 | expect(buffer.data[0]).toBe(123); 11 | expect(buffer.position).toBe(1); 12 | }); 13 | 14 | it("should resize when values overflow ", () => { 15 | // given 16 | const buffer = ByteBuffer.allocate(1); 17 | buffer.put(123); 18 | // when 19 | buffer.put(42); 20 | // then 21 | expect(buffer.data[0]).toBe(123); 22 | expect(buffer.data[1]).toBe(42); 23 | }); 24 | 25 | it("should get value moving the position", () => { 26 | // given 27 | const buffer = ByteBuffer.allocate(1); 28 | buffer.put(123); 29 | buffer.resetPosition(); 30 | // when 31 | const value = buffer.get(); 32 | // then 33 | expect(value).toBe(123); 34 | expect(buffer.position).toBe(1); 35 | }); 36 | 37 | it("should put int32 value moving the position", () => { 38 | // given 39 | const buffer = ByteBuffer.allocate(8); 40 | // when 41 | buffer.putInt32(123); 42 | // then 43 | expect(buffer.data[3]).toBe(123); 44 | expect(buffer.position).toBe(4); 45 | }); 46 | 47 | it("should resize when int32 values overflow ", () => { 48 | // given 49 | const buffer = ByteBuffer.allocate(1); 50 | // when 51 | buffer.putInt32(42); 52 | // then 53 | expect(buffer.data[3]).toBe(42); 54 | expect(buffer.position).toBe(4); 55 | }); 56 | 57 | it("should get int32 value moving the position", () => { 58 | // given 59 | const buffer = ByteBuffer.allocate(1); 60 | buffer.putInt32(123); 61 | buffer.resetPosition(); 62 | // when 63 | const value = buffer.getInt32(); 64 | // then 65 | expect(value).toBe(123); 66 | expect(buffer.position).toBe(4); 67 | }); 68 | 69 | it("should put int64 value moving the position", () => { 70 | // given 71 | const buffer = ByteBuffer.allocate(8); 72 | // when 73 | buffer.putInt64(123); 74 | // then 75 | expect(buffer.data[7]).toBe(123); 76 | expect(buffer.position).toBe(8); 77 | }); 78 | 79 | it("should resize when int64 values overflow ", () => { 80 | // given 81 | const buffer = ByteBuffer.allocate(1); 82 | // when 83 | buffer.putInt64(42); 84 | // then 85 | expect(buffer.data[7]).toBe(42); 86 | expect(buffer.position).toBe(8); 87 | }); 88 | 89 | it("should get int64 value moving the position", () => { 90 | // given 91 | const buffer = ByteBuffer.allocate(1); 92 | buffer.putInt64(Number.MAX_SAFE_INTEGER); 93 | buffer.resetPosition(); 94 | // when 95 | const value = buffer.getInt64(); 96 | // then 97 | expect(value).toBe(Number.MAX_SAFE_INTEGER); 98 | expect(buffer.position).toBe(8); 99 | }); 100 | 101 | it("should copy all data when putting array", () => { 102 | // given 103 | const buffer = ByteBuffer.allocate(1024); 104 | const array = new Uint8Array([1, 2, 3, 4]); 105 | // when 106 | buffer.putArray(array); 107 | // then 108 | buffer.resetPosition(); 109 | expect(buffer.get()).toBe(1); 110 | expect(buffer.get()).toBe(2); 111 | expect(buffer.get()).toBe(3); 112 | expect(buffer.get()).toBe(4); 113 | }); 114 | 115 | it("should resize when putting array bigger than capacity", () => { 116 | // given 117 | const buffer = ByteBuffer.allocate(1024); 118 | const array = new Uint8Array([1, 2, 3, 4]); 119 | // when 120 | buffer.position = 1022; 121 | buffer.putArray(array); 122 | // then 123 | buffer.position = 1022; 124 | expect(buffer.get()).toBe(1); 125 | expect(buffer.get()).toBe(2); 126 | expect(buffer.get()).toBe(3); 127 | expect(buffer.get()).toBe(4); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /src/PercentileIterator.ts: -------------------------------------------------------------------------------- 1 | import JsHistogram from "./JsHistogram"; 2 | import JsHistogramIterator from "./JsHistogramIterator"; 3 | 4 | const { pow, floor, log2 } = Math; 5 | 6 | /** 7 | * Used for iterating through histogram values according to percentile levels. The iteration is 8 | * performed in steps that start at 0% and reduce their distance to 100% according to the 9 | * percentileTicksPerHalfDistance parameter, ultimately reaching 100% when all recorded histogram 10 | * values are exhausted. 11 | */ 12 | class PercentileIterator extends JsHistogramIterator { 13 | percentileTicksPerHalfDistance: number; 14 | percentileLevelToIterateTo: number; 15 | percentileLevelToIterateFrom: number; 16 | reachedLastRecordedValue: boolean; 17 | 18 | /** 19 | * @param histogram The histogram this iterator will operate on 20 | * @param percentileTicksPerHalfDistance The number of equal-sized iteration steps per half-distance to 100%. 21 | */ 22 | public constructor( 23 | histogram: JsHistogram, 24 | percentileTicksPerHalfDistance: number 25 | ) { 26 | super(); 27 | this.percentileTicksPerHalfDistance = 0; 28 | this.percentileLevelToIterateTo = 0; 29 | this.percentileLevelToIterateFrom = 0; 30 | this.reachedLastRecordedValue = false; 31 | this.doReset(histogram, percentileTicksPerHalfDistance); 32 | } 33 | 34 | /** 35 | * Reset iterator for re-use in a fresh iteration over the same histogram data set. 36 | * 37 | * @param percentileTicksPerHalfDistance The number of iteration steps per half-distance to 100%. 38 | */ 39 | reset(percentileTicksPerHalfDistance: number) { 40 | this.doReset(this.histogram, percentileTicksPerHalfDistance); 41 | } 42 | 43 | private doReset( 44 | histogram: JsHistogram, 45 | percentileTicksPerHalfDistance: number 46 | ) { 47 | super.resetIterator(histogram); 48 | this.percentileTicksPerHalfDistance = percentileTicksPerHalfDistance; 49 | this.percentileLevelToIterateTo = 0; 50 | this.percentileLevelToIterateFrom = 0; 51 | this.reachedLastRecordedValue = false; 52 | } 53 | 54 | public hasNext(): boolean { 55 | if (super.hasNext()) return true; 56 | if (!this.reachedLastRecordedValue && this.arrayTotalCount > 0) { 57 | this.percentileLevelToIterateTo = 100; 58 | this.reachedLastRecordedValue = true; 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | incrementIterationLevel() { 65 | this.percentileLevelToIterateFrom = this.percentileLevelToIterateTo; 66 | 67 | // The choice to maintain fixed-sized "ticks" in each half-distance to 100% [starting 68 | // from 0%], as opposed to a "tick" size that varies with each interval, was made to 69 | // make the steps easily comprehensible and readable to humans. The resulting percentile 70 | // steps are much easier to browse through in a percentile distribution output, for example. 71 | // 72 | // We calculate the number of equal-sized "ticks" that the 0-100 range will be divided 73 | // by at the current scale. The scale is detemined by the percentile level we are 74 | // iterating to. The following math determines the tick size for the current scale, 75 | // and maintain a fixed tick size for the remaining "half the distance to 100%" 76 | // [from either 0% or from the previous half-distance]. When that half-distance is 77 | // crossed, the scale changes and the tick size is effectively cut in half. 78 | 79 | // percentileTicksPerHalfDistance = 5 80 | // percentileReportingTicks = 10, 81 | 82 | const percentileReportingTicks = 83 | this.percentileTicksPerHalfDistance * 84 | pow(2, floor(log2(100 / (100 - this.percentileLevelToIterateTo))) + 1); 85 | 86 | this.percentileLevelToIterateTo += 100 / percentileReportingTicks; 87 | } 88 | 89 | reachedIterationLevel(): boolean { 90 | if (this.countAtThisValue === 0) { 91 | return false; 92 | } 93 | const currentPercentile = 94 | (100 * this.totalCountToCurrentIndex) / this.arrayTotalCount; 95 | return currentPercentile >= this.percentileLevelToIterateTo; 96 | } 97 | 98 | getPercentileIteratedTo(): number { 99 | return this.percentileLevelToIterateTo; 100 | } 101 | 102 | getPercentileIteratedFrom(): number { 103 | return this.percentileLevelToIterateFrom; 104 | } 105 | } 106 | 107 | export default PercentileIterator; 108 | -------------------------------------------------------------------------------- /assembly/__tests__/ByteBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import ByteBuffer from "../ByteBuffer"; 10 | 11 | describe("ByteBuffer", () => { 12 | it("should put value moving the position", () => { 13 | // given 14 | const buffer = ByteBuffer.allocate(3); 15 | // when 16 | buffer.put(123); 17 | // then 18 | expect(buffer.data[0]).toBe(123); 19 | expect(buffer.position).toBe(1); 20 | }); 21 | 22 | it("should resize when values overflow ", () => { 23 | // given 24 | const buffer = ByteBuffer.allocate(1); 25 | buffer.put(123); 26 | // when 27 | buffer.put(42); 28 | // then 29 | expect(buffer.data[0]).toBe(123); 30 | expect(buffer.data[1]).toBe(42); 31 | }); 32 | 33 | it("should get value moving the position", () => { 34 | // given 35 | const buffer = ByteBuffer.allocate(1); 36 | buffer.put(123); 37 | buffer.resetPosition(); 38 | // when 39 | const value = buffer.get(); 40 | // then 41 | expect(value).toBe(123); 42 | expect(buffer.position).toBe(1); 43 | }); 44 | 45 | it("should put int32 value moving the position", () => { 46 | // given 47 | const buffer = ByteBuffer.allocate(8); 48 | // when 49 | buffer.putInt32(123); 50 | // then 51 | expect(buffer.data[3]).toBe(123); 52 | expect(buffer.position).toBe(4); 53 | }); 54 | 55 | it("should resize when int32 values overflow ", () => { 56 | // given 57 | const buffer = ByteBuffer.allocate(1); 58 | // when 59 | buffer.putInt32(42); 60 | // then 61 | expect(buffer.data[3]).toBe(42); 62 | expect(buffer.position).toBe(4); 63 | }); 64 | 65 | it("should get int32 value moving the position", () => { 66 | // given 67 | const buffer = ByteBuffer.allocate(1); 68 | buffer.putInt32(123); 69 | buffer.resetPosition(); 70 | // when 71 | const value = buffer.getInt32(); 72 | // then 73 | expect(value).toBe(123); 74 | expect(buffer.position).toBe(4); 75 | }); 76 | 77 | it("should put int64 value moving the position", () => { 78 | // given 79 | const buffer = ByteBuffer.allocate(8); 80 | // when 81 | buffer.putInt64(123); 82 | // then 83 | expect(buffer.data[7]).toBe(123); 84 | expect(buffer.position).toBe(8); 85 | }); 86 | 87 | it("should resize when int64 values overflow ", () => { 88 | // given 89 | const buffer = ByteBuffer.allocate(1); 90 | // when 91 | buffer.putInt64(42); 92 | // then 93 | expect(buffer.data[7]).toBe(42); 94 | expect(buffer.position).toBe(8); 95 | }); 96 | 97 | it("should get int64 value moving the position", () => { 98 | // given 99 | const buffer = ByteBuffer.allocate(1); 100 | buffer.putInt64(u64.MAX_VALUE); 101 | buffer.resetPosition(); 102 | // when 103 | const value = buffer.getInt64(); 104 | // then 105 | expect(value).toBe(u64.MAX_VALUE); 106 | expect(buffer.position).toBe(8); 107 | }); 108 | 109 | it("should copy all data when putting array", () => { 110 | // given 111 | const buffer = ByteBuffer.allocate(1024); 112 | const array = new Uint8Array(4); 113 | for (let index = 0; index < array.length; index++) { 114 | array[index] = (index + 1); 115 | } 116 | // when 117 | buffer.putArray(array); 118 | // then 119 | buffer.resetPosition(); 120 | expect(buffer.get()).toBe(1); 121 | expect(buffer.get()).toBe(2); 122 | expect(buffer.get()).toBe(3); 123 | expect(buffer.get()).toBe(4); 124 | }); 125 | 126 | it("should resize when putting array bigger than capacity", () => { 127 | // given 128 | const buffer = ByteBuffer.allocate(1024); 129 | const array = new Uint8Array(4); 130 | for (let index = 0; index < array.length; index++) { 131 | array[index] = (index + 1); 132 | } 133 | // when 134 | buffer.position = 1022; 135 | buffer.putArray(array); 136 | // then 137 | buffer.position = 1022; 138 | expect(buffer.get()).toBe(1); 139 | expect(buffer.get()).toBe(2); 140 | expect(buffer.get()).toBe(3); 141 | expect(buffer.get()).toBe(4); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /src/PackedHistogram.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import JsHistogram from "./JsHistogram"; 9 | import { PackedArray } from "./packedarray/PackedArray"; 10 | /** 11 | *

A High Dynamic Range (HDR) Histogram that uses a packed internal representation

12 | *

13 | * {@link PackedHistogram} supports the recording and analyzing sampled data value counts across a configurable 14 | * integer value range with configurable value precision within the range. Value precision is expressed as the 15 | * number of significant digits in the value recording, and provides control over value quantization behavior 16 | * across the value range and the subsequent value resolution at any given level. 17 | *

18 | * {@link PackedHistogram} tracks value counts in a packed internal representation optimized 19 | * for typical histogram recoded values are sparse in the value range and tend to be incremented in small unit counts. 20 | * This packed representation tends to require significantly smaller amounts of stoarge when compared to unpacked 21 | * representations, but can incur additional recording cost due to resizing and repacking operations that may 22 | * occur as previously unrecorded values are encountered. 23 | *

24 | * For example, a {@link PackedHistogram} could be configured to track the counts of observed integer values between 0 and 25 | * 3,600,000,000,000 while maintaining a value precision of 3 significant digits across that range. Value quantization 26 | * within the range will thus be no larger than 1/1,000th (or 0.1%) of any value. This example Histogram could 27 | * be used to track and analyze the counts of observed response times ranging between 1 nanosecond and 1 hour 28 | * in magnitude, while maintaining a value resolution of 1 microsecond up to 1 millisecond, a resolution of 29 | * 1 millisecond (or better) up to one second, and a resolution of 1 second (or better) up to 1,000 seconds. At its 30 | * maximum tracked value (1 hour), it would still maintain a resolution of 3.6 seconds (or better). 31 | *

32 | * Auto-resizing: When constructed with no specified value range range (or when auto-resize is turned on with {@link 33 | * Histogram#setAutoResize}) a {@link PackedHistogram} will auto-resize its dynamic range to include recorded values as 34 | * they are encountered. Note that recording calls that cause auto-resizing may take longer to execute, as resizing 35 | * incurs allocation and copying of internal data structures. 36 | *

37 | */ 38 | class PackedHistogram extends JsHistogram { 39 | packedCounts: PackedArray; 40 | 41 | constructor( 42 | lowestDiscernibleValue: number, 43 | highestTrackableValue: number, 44 | numberOfSignificantValueDigits: number 45 | ) { 46 | super( 47 | lowestDiscernibleValue, 48 | highestTrackableValue, 49 | numberOfSignificantValueDigits 50 | ); 51 | this._totalCount = 0; 52 | this.packedCounts = new PackedArray(this.countsArrayLength); 53 | } 54 | 55 | clearCounts() { 56 | this.packedCounts.clear(); 57 | } 58 | 59 | incrementCountAtIndex(index: number) { 60 | this.packedCounts.increment(index); 61 | } 62 | 63 | addToCountAtIndex(index: number, value: number) { 64 | this.packedCounts.add(index, value); 65 | } 66 | 67 | setCountAtIndex(index: number, value: number) { 68 | this.packedCounts.set(index, value); 69 | } 70 | 71 | resize(newHighestTrackableValue: number) { 72 | this.establishSize(newHighestTrackableValue); 73 | this.packedCounts.setVirtualLength(this.countsArrayLength); 74 | } 75 | 76 | getCountAtIndex(index: number) { 77 | return this.packedCounts.get(index); 78 | } 79 | 80 | protected _getEstimatedFootprintInBytes() { 81 | return 192 + 8 * this.packedCounts.getPhysicalLength(); 82 | } 83 | 84 | copyCorrectedForCoordinatedOmission( 85 | expectedIntervalBetweenValueSamples: number 86 | ) { 87 | const copy = new PackedHistogram( 88 | this.lowestDiscernibleValue, 89 | this.highestTrackableValue, 90 | this.numberOfSignificantValueDigits 91 | ); 92 | copy.addWhileCorrectingForCoordinatedOmission( 93 | this, 94 | expectedIntervalBetweenValueSamples 95 | ); 96 | return copy; 97 | } 98 | 99 | toString() { 100 | return `PackedHistogram ${JSON.stringify(this, null, 2)}`; 101 | } 102 | } 103 | 104 | export default PackedHistogram; 105 | -------------------------------------------------------------------------------- /assembly/ZigZagEncoding.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import ByteBuffer from "./ByteBuffer"; 10 | 11 | /** 12 | * This class provides encoding and decoding methods for writing and reading 13 | * ZigZag-encoded LEB128-64b9B-variant (Little Endian Base 128) values to/from a 14 | * {@link ByteBuffer}. LEB128's variable length encoding provides for using a 15 | * smaller nuber of bytes for smaller values, and the use of ZigZag encoding 16 | * allows small (closer to zero) negative values to use fewer bytes. Details 17 | * on both LEB128 and ZigZag can be readily found elsewhere. 18 | * 19 | * The LEB128-64b9B-variant encoding used here diverges from the "original" 20 | * LEB128 as it extends to 64 bit values: In the original LEB128, a 64 bit 21 | * value can take up to 10 bytes in the stream, where this variant's encoding 22 | * of a 64 bit values will max out at 9 bytes. 23 | * 24 | * As such, this encoder/decoder should NOT be used for encoding or decoding 25 | * "standard" LEB128 formats (e.g. Google Protocol Buffers). 26 | */ 27 | class ZigZagEncoding { 28 | /** 29 | * Writes a 64b value to the given buffer in LEB128 ZigZag encoded format 30 | * (negative numbers not supported) 31 | * @param buffer the buffer to write to 32 | * @param value the value to write to the buffer 33 | */ 34 | static encode(buffer: ByteBuffer, value: i64): void { 35 | value = (value << 1) ^ (value >> 63); 36 | if (value >>> 7 === 0) { 37 | buffer.put(value); 38 | } else { 39 | buffer.put(((value & 0x7f) | 0x80)); 40 | if (value >>> 14 === 0) { 41 | buffer.put((value >>> 7)); 42 | } else { 43 | buffer.put(((value >>> 7) | 0x80)); 44 | if (value >>> 21 === 0) { 45 | buffer.put((value >>> 14)); 46 | } else { 47 | buffer.put(((value >>> 14) | 0x80)); 48 | if (value >>> 28 === 0) { 49 | buffer.put((value >>> 21)); 50 | } else { 51 | buffer.put(((value >>> 21) | 0x80)); 52 | if (value >>> 35 === 0) { 53 | buffer.put((value >>> 28)); 54 | } else { 55 | buffer.put(((value >>> 28) | 0x80)); 56 | if (value >>> 42 === 0) { 57 | buffer.put((value >>> 35)); 58 | } else { 59 | buffer.put(((value >>> 35) | 0x80)); 60 | if (value >>> 49 === 0) { 61 | buffer.put((value >>> 42)); 62 | } else { 63 | buffer.put(((value >>> 42) | 0x80)); 64 | if (value >>> 56 === 0) { 65 | buffer.put((value >>> 49)); 66 | } else { 67 | buffer.put(((value >>> 49) | 0x80)); 68 | buffer.put((value >>> 56)); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Read an LEB128-64b9B ZigZag encoded long value from the given buffer 81 | * (negative numbers not supported) 82 | * @param buffer the buffer to read from 83 | * @return the value read from the buffer 84 | */ 85 | static decode(buffer: ByteBuffer): i64 { 86 | let v = buffer.get(); 87 | let value: i64 = (v) & (0x7f); 88 | if ((v & 0x80) != 0) { 89 | v = buffer.get(); 90 | value |= (v & 0x7f) << 7; 91 | if ((v & 0x80) != 0) { 92 | v = buffer.get(); 93 | value |= (v & 0x7f) << 14; 94 | if ((v & 0x80) != 0) { 95 | v = buffer.get(); 96 | value |= (v & 0x7f) << 21; 97 | if ((v & 0x80) != 0) { 98 | v = buffer.get(); 99 | value |= (v & 0x7f) << 28; 100 | if ((v & 0x80) != 0) { 101 | v = buffer.get(); 102 | value |= (v & 0x7f) << 35; 103 | if ((v & 0x80) != 0) { 104 | v = buffer.get(); 105 | value |= (v & 0x7f) << 42; 106 | if ((v & 0x80) != 0) { 107 | v = buffer.get(); 108 | value |= (v & 0x7f) << 49; 109 | if ((v & 0x80) != 0) { 110 | v = buffer.get(); 111 | value |= v << 56; 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | value = (value >>> 1) ^ -(value & 1); 121 | return value; 122 | } 123 | } 124 | 125 | export default ZigZagEncoding; 126 | -------------------------------------------------------------------------------- /src/packedarray/PackedArray.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import { PackedArrayContext } from "./PackedArrayContext"; 9 | import { PackedArray } from "./PackedArray"; 10 | 11 | const { pow } = Math; 12 | 13 | describe("Packed array context", () => { 14 | it("Should initialize array", () => { 15 | const ctx = new PackedArrayContext(1024, 128); 16 | expect(ctx.isPacked).toBe(true); 17 | expect(ctx.getPopulatedShortLength()).toBeGreaterThan(0); 18 | }); 19 | }); 20 | 21 | describe("Packed array", () => { 22 | it("Should initialize array", () => { 23 | const array = new PackedArray(1024, 128); 24 | expect(array.getPhysicalLength()).toBe(128); 25 | expect(array.length()).toBe(1024); 26 | }); 27 | 28 | it("Should retrieve data stored in array", () => { 29 | // given 30 | const array = new PackedArray(1024, 16); 31 | 32 | // when 33 | array.set(16, 1); 34 | array.set(12, 42); 35 | 36 | // then 37 | expect(array.get(12)).toBe(42); 38 | expect(array.get(16)).toBe(1); 39 | }); 40 | 41 | it("Should resize array when storing data", () => { 42 | // given 43 | const array = new PackedArray(1024, 16); 44 | 45 | // when 46 | array.set(12, 361); 47 | 48 | // then 49 | const storedData = array.get(12); 50 | expect(storedData).toBe(361); 51 | }); 52 | 53 | it("Should retrieve big numbers stored in array", () => { 54 | // given 55 | const array = new PackedArray(1024, 16); 56 | 57 | // when 58 | array.set(12, Math.pow(2, 16) + 1); 59 | 60 | // then 61 | const storedData = array.get(12); 62 | expect(storedData).toBe(Math.pow(2, 16) + 1); 63 | }); 64 | 65 | it("Should copy data when resizing array", () => { 66 | const array = new PackedArray(1024); 67 | for (let value = 1; value <= 272; value++) { 68 | array.set(value, value); 69 | } 70 | 71 | expect(array.get(1)).toBe(1); 72 | expect(array.get(255)).toBe(255); 73 | expect(array.get(272)).toBe(272); 74 | }); 75 | 76 | it("Should increment data stored in array", () => { 77 | // given 78 | const array = new PackedArray(1024, 16); 79 | array.set(16, 1); 80 | 81 | // when 82 | array.add(16, 41); 83 | 84 | // then 85 | expect(array.get(16)).toBe(42); 86 | }); 87 | 88 | it("Should increment data stored in array with big numbers", () => { 89 | // given 90 | const array = new PackedArray(1024, 16); 91 | array.set(16, 42); 92 | 93 | // when 94 | array.add(16, pow(2, 33)); 95 | 96 | // then 97 | expect(array.get(16)).toBe(pow(2, 33) + 42); 98 | }); 99 | 100 | it("Should increment data stored in array with big numbers when a resize is needed", () => { 101 | // given 102 | const array = new PackedArray(10000, 16); 103 | array.set(6144, 243); 104 | array.set(60, 243); 105 | array.set(1160, 243); 106 | 107 | // when 108 | array.add(6144, 25); 109 | 110 | // then 111 | expect(array.get(6144)).toBe(268); 112 | }); 113 | 114 | it("Should increment data stored in array with big numbers", () => { 115 | // given 116 | const array = new PackedArray(1024, 16); 117 | array.set(16, 42); 118 | 119 | // when 120 | array.add(16, pow(2, 33)); 121 | 122 | // then 123 | expect(array.get(16)).toBe(pow(2, 33) + 42); 124 | }); 125 | 126 | it("Should clear data stored in array", () => { 127 | // given 128 | const array = new PackedArray(1024, 16); 129 | array.set(16, 42); 130 | 131 | // when 132 | array.clear(); 133 | 134 | // then 135 | expect(array.get(16)).toBe(0); 136 | }); 137 | 138 | it("Should resize array when virtual length change", () => { 139 | // given 140 | const array = new PackedArray(16, 16); 141 | array.set(7, 42); 142 | 143 | // when 144 | array.setVirtualLength(pow(2, 20)); 145 | array.add(pow(2, 19), 42); 146 | 147 | // then 148 | expect(array.get(7)).toBe(42); 149 | expect(array.get(pow(2, 19))).toBe(42); 150 | }); 151 | 152 | it("should handle properly big numbers", () => { 153 | // given 154 | const array = new PackedArray(45056, 16); 155 | // when 156 | array.set(32768, 1); 157 | // then 158 | expect(array.get(32768)).toBe(1); 159 | expect(array.get(0)).toBe(0); 160 | }); 161 | }); 162 | 163 | describe("Unpacked array", () => { 164 | it("Should increment data stored in array", () => { 165 | // given 166 | const array = new PackedArray(1024, pow(2, 20)); 167 | array.set(16, 1); 168 | 169 | // when 170 | array.add(16, 41); 171 | 172 | // then 173 | expect(array.get(16)).toBe(42); 174 | }); 175 | }); 176 | -------------------------------------------------------------------------------- /src/ZigZagEncoding.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import ByteBuffer from "./ByteBuffer"; 9 | 10 | const { pow, floor } = Math; 11 | 12 | const TWO_POW_7 = pow(2, 7); 13 | const TWO_POW_14 = pow(2, 14); 14 | const TWO_POW_21 = pow(2, 21); 15 | const TWO_POW_28 = pow(2, 28); 16 | const TWO_POW_35 = pow(2, 35); 17 | const TWO_POW_42 = pow(2, 42); 18 | const TWO_POW_49 = pow(2, 49); 19 | const TWO_POW_56 = pow(2, 56); 20 | 21 | /** 22 | * This class provides encoding and decoding methods for writing and reading 23 | * ZigZag-encoded LEB128-64b9B-variant (Little Endian Base 128) values to/from a 24 | * {@link ByteBuffer}. LEB128's variable length encoding provides for using a 25 | * smaller nuber of bytes for smaller values, and the use of ZigZag encoding 26 | * allows small (closer to zero) negative values to use fewer bytes. Details 27 | * on both LEB128 and ZigZag can be readily found elsewhere. 28 | * 29 | * The LEB128-64b9B-variant encoding used here diverges from the "original" 30 | * LEB128 as it extends to 64 bit values: In the original LEB128, a 64 bit 31 | * value can take up to 10 bytes in the stream, where this variant's encoding 32 | * of a 64 bit values will max out at 9 bytes. 33 | * 34 | * As such, this encoder/decoder should NOT be used for encoding or decoding 35 | * "standard" LEB128 formats (e.g. Google Protocol Buffers). 36 | */ 37 | class ZigZagEncoding { 38 | /** 39 | * Writes a long value to the given buffer in LEB128 ZigZag encoded format 40 | * (negative numbers not supported) 41 | * @param buffer the buffer to write to 42 | * @param value the value to write to the buffer 43 | */ 44 | static encode(buffer: ByteBuffer, value: number) { 45 | if (value >= 0) { 46 | value = value * 2; 47 | } else { 48 | value = -value * 2 - 1; 49 | } 50 | 51 | if (value < TWO_POW_7) { 52 | buffer.put(value); 53 | } else { 54 | buffer.put(value | 0x80); 55 | if (value < TWO_POW_14) { 56 | buffer.put(floor(value / TWO_POW_7)); 57 | } else { 58 | buffer.put(floor(value / TWO_POW_7) | 0x80); 59 | if (value < TWO_POW_21) { 60 | buffer.put(floor(value / TWO_POW_14)); 61 | } else { 62 | buffer.put(floor(value / TWO_POW_14) | 0x80); 63 | if (value < TWO_POW_28) { 64 | buffer.put(floor(value / TWO_POW_21)); 65 | } else { 66 | buffer.put(floor(value / TWO_POW_21) | 0x80); 67 | if (value < TWO_POW_35) { 68 | buffer.put(floor(value / TWO_POW_28)); 69 | } else { 70 | buffer.put(floor(value / TWO_POW_28) | 0x80); 71 | if (value < TWO_POW_42) { 72 | buffer.put(floor(value / TWO_POW_35)); 73 | } else { 74 | buffer.put(floor(value / TWO_POW_35) | 0x80); 75 | if (value < TWO_POW_49) { 76 | buffer.put(floor(value / TWO_POW_42)); 77 | } else { 78 | buffer.put(floor(value / TWO_POW_42) | 0x80); 79 | if (value < TWO_POW_56) { 80 | buffer.put(floor(value / TWO_POW_49)); 81 | } else { 82 | // should not happen 83 | buffer.put(floor(value / TWO_POW_49) + 0x80); 84 | buffer.put(floor(value / TWO_POW_56)); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Read an LEB128-64b9B ZigZag encoded long value from the given buffer 97 | * (negative numbers not supported) 98 | * @param buffer the buffer to read from 99 | * @return the value read from the buffer 100 | */ 101 | static decode(buffer: ByteBuffer): number { 102 | let v = buffer.get(); 103 | let value = v & 0x7f; 104 | if ((v & 0x80) != 0) { 105 | v = buffer.get(); 106 | value += (v & 0x7f) * TWO_POW_7; 107 | if ((v & 0x80) != 0) { 108 | v = buffer.get(); 109 | value += (v & 0x7f) * TWO_POW_14; 110 | if ((v & 0x80) != 0) { 111 | v = buffer.get(); 112 | value += (v & 0x7f) * TWO_POW_21; 113 | if ((v & 0x80) != 0) { 114 | v = buffer.get(); 115 | value += (v & 0x7f) * TWO_POW_28; 116 | if ((v & 0x80) != 0) { 117 | v = buffer.get(); 118 | value += (v & 0x7f) * TWO_POW_35; 119 | if ((v & 0x80) != 0) { 120 | v = buffer.get(); 121 | value += (v & 0x7f) * TWO_POW_42; 122 | if ((v & 0x80) != 0) { 123 | v = buffer.get(); 124 | value += (v & 0x7f) * TWO_POW_49; 125 | if ((v & 0x80) != 0) { 126 | v = buffer.get(); 127 | value += (v & 0x7f) * TWO_POW_56; 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | if (value % 2 === 0) { 137 | value = value / 2; 138 | } else { 139 | value = -(value + 1) / 2; 140 | } 141 | 142 | return value; 143 | } 144 | } 145 | 146 | export default ZigZagEncoding; 147 | -------------------------------------------------------------------------------- /src/JsHistogramIterator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import JsHistogram from "./JsHistogram"; 9 | import HistogramIterationValue from "./HistogramIterationValue"; 10 | 11 | /** 12 | * Used for iterating through histogram values. 13 | */ 14 | abstract class JsHistogramIterator /* implements Iterator */ { 15 | histogram: JsHistogram; 16 | savedHistogramTotalRawCount: number; 17 | currentIndex: number; 18 | currentValueAtIndex: number; 19 | nextValueAtIndex: number; 20 | prevValueIteratedTo: number; 21 | totalCountToPrevIndex: number; 22 | totalCountToCurrentIndex: number; 23 | totalValueToCurrentIndex: number; 24 | arrayTotalCount: number; 25 | countAtThisValue: number; 26 | 27 | private freshSubBucket: boolean; 28 | 29 | currentIterationValue: HistogramIterationValue = new HistogramIterationValue(); 30 | 31 | resetIterator(histogram: JsHistogram) { 32 | this.histogram = histogram; 33 | this.savedHistogramTotalRawCount = histogram.totalCount; 34 | this.arrayTotalCount = histogram.totalCount; 35 | this.currentIndex = 0; 36 | this.currentValueAtIndex = 0; 37 | this.nextValueAtIndex = Math.pow(2, histogram.unitMagnitude); 38 | this.prevValueIteratedTo = 0; 39 | this.totalCountToPrevIndex = 0; 40 | this.totalCountToCurrentIndex = 0; 41 | this.totalValueToCurrentIndex = 0; 42 | this.countAtThisValue = 0; 43 | this.freshSubBucket = true; 44 | this.currentIterationValue.reset(); 45 | } 46 | 47 | /** 48 | * Returns true if the iteration has more elements. (In other words, returns true if next would return an 49 | * element rather than throwing an exception.) 50 | * 51 | * @return true if the iterator has more elements. 52 | */ 53 | public hasNext(): boolean { 54 | if (this.histogram.totalCount !== this.savedHistogramTotalRawCount) { 55 | throw "Concurrent Modification Exception"; 56 | } 57 | return this.totalCountToCurrentIndex < this.arrayTotalCount; 58 | } 59 | 60 | /** 61 | * Returns the next element in the iteration. 62 | * 63 | * @return the {@link HistogramIterationValue} associated with the next element in the iteration. 64 | */ 65 | public next(): HistogramIterationValue { 66 | // Move through the sub buckets and buckets until we hit the next reporting level: 67 | while (!this.exhaustedSubBuckets()) { 68 | this.countAtThisValue = this.histogram.getCountAtIndex(this.currentIndex); 69 | if (this.freshSubBucket) { 70 | // Don't add unless we've incremented since last bucket... 71 | this.totalCountToCurrentIndex += this.countAtThisValue; 72 | this.totalValueToCurrentIndex += 73 | this.countAtThisValue * 74 | this.histogram.highestEquivalentValue(this.currentValueAtIndex); 75 | this.freshSubBucket = false; 76 | } 77 | if (this.reachedIterationLevel()) { 78 | const valueIteratedTo = this.getValueIteratedTo(); 79 | 80 | Object.assign(this.currentIterationValue, { 81 | valueIteratedTo, 82 | valueIteratedFrom: this.prevValueIteratedTo, 83 | countAtValueIteratedTo: this.countAtThisValue, 84 | countAddedInThisIterationStep: 85 | this.totalCountToCurrentIndex - this.totalCountToPrevIndex, 86 | totalCountToThisValue: this.totalCountToCurrentIndex, 87 | totalValueToThisValue: this.totalValueToCurrentIndex, 88 | percentile: 89 | (100 * this.totalCountToCurrentIndex) / this.arrayTotalCount, 90 | percentileLevelIteratedTo: this.getPercentileIteratedTo(), 91 | }); 92 | 93 | this.prevValueIteratedTo = valueIteratedTo; 94 | this.totalCountToPrevIndex = this.totalCountToCurrentIndex; 95 | this.incrementIterationLevel(); 96 | if (this.histogram.totalCount !== this.savedHistogramTotalRawCount) { 97 | throw new Error("Concurrent Modification Exception"); 98 | } 99 | return this.currentIterationValue; 100 | } 101 | this.incrementSubBucket(); 102 | } 103 | throw new Error("Index Out Of Bounds Exception"); 104 | } 105 | 106 | abstract incrementIterationLevel(): void; 107 | 108 | /** 109 | * @return true if the current position's data should be emitted by the iterator 110 | */ 111 | abstract reachedIterationLevel(): boolean; 112 | 113 | getPercentileIteratedTo(): number { 114 | return (100 * this.totalCountToCurrentIndex) / this.arrayTotalCount; 115 | } 116 | 117 | getPercentileIteratedFrom(): number { 118 | return (100 * this.totalCountToPrevIndex) / this.arrayTotalCount; 119 | } 120 | 121 | getValueIteratedTo(): number { 122 | return this.histogram.highestEquivalentValue(this.currentValueAtIndex); 123 | } 124 | 125 | private exhaustedSubBuckets(): boolean { 126 | return this.currentIndex >= this.histogram.countsArrayLength; 127 | } 128 | 129 | incrementSubBucket() { 130 | this.freshSubBucket = true; 131 | this.currentIndex++; 132 | this.currentValueAtIndex = this.histogram.valueFromIndex(this.currentIndex); 133 | this.nextValueAtIndex = this.histogram.valueFromIndex( 134 | this.currentIndex + 1 135 | ); 136 | } 137 | } 138 | 139 | export default JsHistogramIterator; 140 | -------------------------------------------------------------------------------- /test_files/tagged-Log.logV2.hlog: -------------------------------------------------------------------------------- 1 | #[Logged with jHiccup version 2.0.7-SNAPSHOT, manually edited to duplicate contents with Tag=A] 2 | #[Histogram log format version 1.2] 3 | #[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] 4 | "StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" 5 | 0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= 6 | Tag=A,0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= 7 | 1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= 8 | Tag=A,1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= 9 | 2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= 10 | Tag=A,2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= 11 | 3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= 12 | Tag=A,3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= 13 | 4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= 14 | Tag=A,4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= 15 | 5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== 16 | Tag=A,5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== 17 | 6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== 18 | Tag=A,6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== 19 | 7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= 20 | Tag=A,7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= 21 | 8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw 22 | Tag=A,8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw 23 | 9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= 24 | Tag=A,9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= 25 | 10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR 26 | Tag=A,10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR 27 | 11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== 28 | Tag=A,11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== 29 | 12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko 30 | Tag=A,12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko 31 | 13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= 32 | Tag=A,13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= 33 | 14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ 34 | Tag=A,14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ 35 | 15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== 36 | Tag=A,15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== 37 | 16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne 38 | Tag=A,16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne 39 | 17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 40 | Tag=A,17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 41 | 18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== 42 | Tag=A,18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== 43 | 19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== 44 | Tag=A,19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== 45 | 20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== 46 | Tag=A,20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== 47 | -------------------------------------------------------------------------------- /src/Histogram.fc.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import * as fc from "fast-check"; 9 | import * as hdr from "./index"; 10 | import { initWebAssembly } from "./wasm"; 11 | import Histogram, { BitBucketSize } from "./Histogram"; 12 | 13 | const runFromStryker = __dirname.includes("stryker"); 14 | 15 | const runnerOptions = { 16 | numRuns: runFromStryker ? 10 : 1000, 17 | verbose: true, 18 | }; 19 | 20 | describe("Histogram percentile computation", () => { 21 | beforeAll(initWebAssembly); 22 | 23 | const numberOfSignificantValueDigits = 3; 24 | [true, false].forEach((useWebAssembly) => 25 | [16, "packed"].forEach((bitBucketSize: BitBucketSize) => 26 | it(`Histogram ${bitBucketSize} (wasm: ${useWebAssembly}) should be accurate according to its significant figures`, async () => { 27 | await initWebAssembly(); 28 | 29 | fc.assert( 30 | fc.property(arbData(2000), (numbers) => { 31 | const histogram = hdr.build({ 32 | bitBucketSize, 33 | numberOfSignificantValueDigits, 34 | useWebAssembly, 35 | }); 36 | numbers.forEach((n) => histogram.recordValue(n)); 37 | const actual = quantile(numbers, 90); 38 | const got = histogram.getValueAtPercentile(90); 39 | const relativeError = Math.abs(1 - got / actual); 40 | const variation = Math.pow(10, -numberOfSignificantValueDigits); 41 | histogram.destroy(); 42 | return relativeError < variation; 43 | }), 44 | runnerOptions 45 | ); 46 | }) 47 | ) 48 | ); 49 | }); 50 | 51 | describe("Histogram percentile computation (packed vs classic)", () => { 52 | const numberOfSignificantValueDigits = 3; 53 | const classicHistogram = hdr.build({ 54 | numberOfSignificantValueDigits, 55 | }); 56 | const histogram = hdr.build({ 57 | numberOfSignificantValueDigits, 58 | bitBucketSize: "packed", 59 | useWebAssembly: false, 60 | }); 61 | 62 | it(`should be accurate according to its significant figures`, () => { 63 | fc.assert( 64 | fc.property(arbData(5), (numbers) => { 65 | histogram.reset(); 66 | classicHistogram.reset(); 67 | numbers.forEach((n) => histogram.recordValue(n)); 68 | numbers.forEach((n) => classicHistogram.recordValue(n)); 69 | const actual = classicHistogram.getValueAtPercentile(90); 70 | const got = histogram.getValueAtPercentile(90); 71 | return actual === got; 72 | }), 73 | runnerOptions 74 | ); 75 | }); 76 | }); 77 | 78 | describe("Histogram percentile computation with CO correction (wasm vs js)", () => { 79 | beforeAll(initWebAssembly); 80 | 81 | let jsHistogram: Histogram; 82 | let wasmHistogram: Histogram; 83 | 84 | beforeEach(() => { 85 | jsHistogram = hdr.build({ 86 | useWebAssembly: false, 87 | }); 88 | wasmHistogram = hdr.build({ 89 | useWebAssembly: true, 90 | }); 91 | }); 92 | 93 | afterEach(() => { 94 | jsHistogram.destroy(); 95 | wasmHistogram.destroy(); 96 | }); 97 | 98 | it(`should be accurate according to its significant figures`, () => { 99 | fc.assert( 100 | fc.property(arbData(1, 100 * 1000), (numbers) => { 101 | jsHistogram.reset(); 102 | wasmHistogram.reset(); 103 | numbers.forEach((n) => { 104 | jsHistogram.recordValueWithExpectedInterval(n, 1000); 105 | }); 106 | numbers.forEach((n) => { 107 | wasmHistogram.recordValueWithExpectedInterval(n, 1000); 108 | }); 109 | const js = jsHistogram.getValueAtPercentile(90); 110 | const wasm = wasmHistogram.getValueAtPercentile(90); 111 | const relativeError = Math.abs(1 - js / wasm); 112 | const variation = Math.pow(10, -3); 113 | if (relativeError >= variation) { 114 | console.log({ js, wasm }); 115 | } 116 | return relativeError < variation; 117 | }), 118 | runnerOptions 119 | ); 120 | }); 121 | }); 122 | 123 | describe("Histogram encoding/decoding", () => { 124 | beforeAll(initWebAssembly); 125 | 126 | const numberOfSignificantValueDigits = 3; 127 | [true, false].forEach((useWebAssembly) => 128 | [8, 16, 32, 64, "packed"].forEach((bitBucketSize: BitBucketSize) => { 129 | it(`Histogram ${bitBucketSize} (wasm: ${useWebAssembly}) should keep all data after an encoding/decoding roundtrip`, () => { 130 | fc.assert( 131 | fc.property(arbData(1), fc.double(50, 100), (numbers, percentile) => { 132 | const histogram = hdr.build({ 133 | bitBucketSize, 134 | numberOfSignificantValueDigits, 135 | useWebAssembly, 136 | }); 137 | numbers.forEach((n) => histogram.recordValue(n)); 138 | const encodedHistogram = hdr.encodeIntoCompressedBase64(histogram); 139 | const decodedHistogram = hdr.decodeFromCompressedBase64( 140 | encodedHistogram 141 | ); 142 | const actual = histogram.getValueAtPercentile(percentile); 143 | const got = decodedHistogram.getValueAtPercentile(percentile); 144 | histogram.destroy(); 145 | decodedHistogram.destroy(); 146 | return actual === got; 147 | }), 148 | runnerOptions 149 | ); 150 | }); 151 | }) 152 | ); 153 | }); 154 | 155 | const arbData = (size: number, max: number = Number.MAX_SAFE_INTEGER) => 156 | fc.array(fc.integer(1, max), size, size); 157 | 158 | // reference implementation 159 | const quantile = (inputData: number[], percentile: number) => { 160 | const data = [...inputData].sort((a, b) => a - b); 161 | const index = (percentile / 100) * (data.length - 1); 162 | let result: number; 163 | if (Math.floor(index) === index) { 164 | result = data[index]; 165 | } else { 166 | const i = Math.floor(index); 167 | const fraction = index - i; 168 | result = data[i] + (data[i + 1] - data[i]) * fraction; 169 | } 170 | return result; 171 | }; 172 | -------------------------------------------------------------------------------- /assembly/RecordedValuesIterator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import Histogram from "./Histogram"; 10 | import HistogramIterationValue from "./HistogramIterationValue"; 11 | 12 | /** 13 | * Used for iterating through all recorded histogram values using the finest granularity steps supported by the 14 | * underlying representation. The iteration steps through all non-zero recorded value counts, and terminates when 15 | * all recorded histogram values are exhausted. 16 | */ 17 | class RecordedValuesIterator { 18 | visitedIndex: i32; 19 | histogram!: Histogram; 20 | savedHistogramTotalRawCount: u64; 21 | currentIndex: i32; 22 | currentValueAtIndex: u64; 23 | nextValueAtIndex: u64; 24 | prevValueIteratedTo: u64; 25 | totalCountToPrevIndex: u64; 26 | totalCountToCurrentIndex: u64; 27 | totalValueToCurrentIndex: u64; 28 | arrayTotalCount: u64; 29 | countAtThisValue: u64; 30 | 31 | private freshSubBucket: boolean; 32 | 33 | currentIterationValue: HistogramIterationValue = new HistogramIterationValue(); 34 | 35 | /** 36 | * @param histogram The histogram this iterator will operate on 37 | */ 38 | constructor(histogram: Histogram) { 39 | this.doReset(histogram); 40 | } 41 | 42 | /** 43 | * Reset iterator for re-use in a fresh iteration over the same histogram data set. 44 | */ 45 | public reset(): void { 46 | this.doReset(this.histogram); 47 | } 48 | 49 | private doReset(histogram: Histogram): void { 50 | this.resetIterator(histogram); 51 | this.visitedIndex = -1; 52 | } 53 | 54 | incrementIterationLevel(): void { 55 | this.visitedIndex = this.currentIndex; 56 | } 57 | 58 | reachedIterationLevel(): bool { 59 | const currentCount = this.histogram.getCountAtIndex(this.currentIndex); 60 | return currentCount != 0 && this.visitedIndex !== this.currentIndex; 61 | } 62 | 63 | resetIterator(histogram: Histogram): void { 64 | this.histogram = histogram; 65 | this.savedHistogramTotalRawCount = histogram.totalCount; 66 | this.arrayTotalCount = histogram.totalCount; 67 | this.currentIndex = 0; 68 | this.currentValueAtIndex = 0; 69 | this.nextValueAtIndex = 1 << histogram.unitMagnitude; 70 | this.prevValueIteratedTo = 0; 71 | this.totalCountToPrevIndex = 0; 72 | this.totalCountToCurrentIndex = 0; 73 | this.totalValueToCurrentIndex = 0; 74 | this.countAtThisValue = 0; 75 | this.freshSubBucket = true; 76 | this.currentIterationValue.reset(); 77 | } 78 | 79 | /** 80 | * Returns true if the iteration has more elements. (In other words, returns true if next would return an 81 | * element rather than throwing an exception.) 82 | * 83 | * @return true if the iterator has more elements. 84 | */ 85 | public hasNext(): boolean { 86 | if (this.histogram.totalCount !== this.savedHistogramTotalRawCount) { 87 | throw "Concurrent Modification Exception"; 88 | } 89 | return this.totalCountToCurrentIndex < this.arrayTotalCount; 90 | } 91 | 92 | /** 93 | * Returns the next element in the iteration. 94 | * 95 | * @return the {@link HistogramIterationValue} associated with the next element in the iteration. 96 | */ 97 | public next(): HistogramIterationValue { 98 | // Move through the sub buckets and buckets until we hit the next reporting level: 99 | while (!this.exhaustedSubBuckets()) { 100 | this.countAtThisValue = this.histogram.getCountAtIndex(this.currentIndex); 101 | if (this.freshSubBucket) { 102 | // Don't add unless we've incremented since last bucket... 103 | this.totalCountToCurrentIndex += this.countAtThisValue; 104 | this.totalValueToCurrentIndex += 105 | this.countAtThisValue * 106 | this.histogram.highestEquivalentValue(this.currentValueAtIndex); 107 | this.freshSubBucket = false; 108 | } 109 | if (this.reachedIterationLevel()) { 110 | const valueIteratedTo = this.getValueIteratedTo(); 111 | 112 | //Object.assign(this.currentIterationValue, { 113 | this.currentIterationValue.valueIteratedTo = valueIteratedTo; 114 | this.currentIterationValue.valueIteratedFrom = this.prevValueIteratedTo; 115 | this.currentIterationValue.countAtValueIteratedTo = this.countAtThisValue; 116 | this.currentIterationValue.countAddedInThisIterationStep = 117 | this.totalCountToCurrentIndex - this.totalCountToPrevIndex; 118 | this.currentIterationValue.totalCountToThisValue = this.totalCountToCurrentIndex; 119 | this.currentIterationValue.totalValueToThisValue = this.totalValueToCurrentIndex; 120 | this.currentIterationValue.percentile = 121 | (100 * this.totalCountToCurrentIndex) / 122 | this.arrayTotalCount; 123 | this.currentIterationValue.percentileLevelIteratedTo = this.getPercentileIteratedTo(); 124 | this.prevValueIteratedTo = valueIteratedTo; 125 | this.totalCountToPrevIndex = this.totalCountToCurrentIndex; 126 | this.incrementIterationLevel(); 127 | if (this.histogram.totalCount !== this.savedHistogramTotalRawCount) { 128 | throw new Error("Concurrent Modification Exception"); 129 | } 130 | return this.currentIterationValue; 131 | } 132 | this.incrementSubBucket(); 133 | } 134 | throw new Error("Index Out Of Bounds Exception"); 135 | } 136 | 137 | getPercentileIteratedTo(): f64 { 138 | return ( 139 | (100 * this.totalCountToCurrentIndex) / 140 | this.arrayTotalCount 141 | ); 142 | } 143 | 144 | getPercentileIteratedFrom(): f64 { 145 | return ( 146 | (100 * this.totalCountToPrevIndex) / this.arrayTotalCount 147 | ); 148 | } 149 | 150 | getValueIteratedTo(): u64 { 151 | return this.histogram.highestEquivalentValue(this.currentValueAtIndex); 152 | } 153 | 154 | private exhaustedSubBuckets(): boolean { 155 | return this.currentIndex >= this.histogram.countsArrayLength; 156 | } 157 | 158 | incrementSubBucket(): void { 159 | this.freshSubBucket = true; 160 | this.currentIndex++; 161 | this.currentValueAtIndex = this.histogram.valueFromIndex(this.currentIndex); 162 | this.nextValueAtIndex = this.histogram.valueFromIndex( 163 | this.currentIndex + 1 164 | ); 165 | } 166 | } 167 | 168 | export default RecordedValuesIterator; 169 | -------------------------------------------------------------------------------- /assembly/encoding.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import ByteBuffer from "./ByteBuffer"; 10 | import Histogram from "./Histogram"; 11 | import ZigZagEncoding from "./ZigZagEncoding"; 12 | 13 | const V2EncodingCookieBase = 0x1c849303; 14 | const V2CompressedEncodingCookieBase = 0x1c849304; 15 | const V2maxWordSizeInBytes = 9; // LEB128-64b9B + ZigZag require up to 9 bytes per word 16 | const encodingCookie = V2EncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding 17 | const compressedEncodingCookie = V2CompressedEncodingCookieBase | 0x10; // LSBit of wordsize byte indicates TLZE Encoding 18 | 19 | function fillBufferFromCountsArray( 20 | self: Histogram, 21 | buffer: ByteBuffer 22 | ): void { 23 | const countsLimit = self.countsArrayIndex(self.maxValue) + 1; 24 | let srcIndex = 0; 25 | 26 | while (srcIndex < countsLimit) { 27 | // V2 encoding format uses a ZigZag LEB128-64b9B encoded long. Positive values are counts, 28 | // while negative values indicate a repeat zero counts. 29 | const count = self.getCountAtIndex(srcIndex++); 30 | if (count < 0) { 31 | throw new Error( 32 | "Cannot encode histogram containing negative counts (" + 33 | count.toString() + 34 | ") at index " + 35 | srcIndex.toString() + 36 | ", corresponding the value range [" + 37 | self.lowestEquivalentValue(self.valueFromIndex(srcIndex)).toString() + 38 | "," + 39 | self 40 | .nextNonEquivalentValue(self.valueFromIndex(srcIndex)) 41 | .toString() + 42 | ")" 43 | ); 44 | } 45 | // Count trailing 0s (which follow this count): 46 | let zerosCount = 0; 47 | if (count == 0) { 48 | zerosCount = 1; 49 | while (srcIndex < countsLimit && self.getCountAtIndex(srcIndex) == 0) { 50 | zerosCount++; 51 | srcIndex++; 52 | } 53 | } 54 | if (zerosCount > 1) { 55 | ZigZagEncoding.encode(buffer, -zerosCount); 56 | } else { 57 | ZigZagEncoding.encode(buffer, count); 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * Encode this histogram into a ByteBuffer 64 | * @param buffer The buffer to encode into 65 | * @return The number of bytes written to the buffer 66 | */ 67 | export function encodeIntoByteBuffer( 68 | self: Histogram, 69 | buffer: ByteBuffer 70 | ): i32 { 71 | const initialPosition = buffer.position; 72 | buffer.putInt32(encodingCookie); 73 | buffer.putInt32(0); // Placeholder for payload length in bytes. 74 | buffer.putInt32(1); 75 | buffer.putInt32(self.numberOfSignificantValueDigits); 76 | buffer.putInt64(self.lowestDiscernibleValue); 77 | buffer.putInt64(self.highestTrackableValue); 78 | buffer.putInt64(1); 79 | 80 | const payloadStartPosition = buffer.position; 81 | fillBufferFromCountsArray(self, buffer); 82 | 83 | const backupIndex = buffer.position; 84 | buffer.position = initialPosition + 4; 85 | buffer.putInt32(backupIndex - payloadStartPosition); // Record the payload length 86 | 87 | buffer.position = backupIndex; 88 | 89 | return backupIndex - initialPosition; 90 | } 91 | 92 | function fillCountsArrayFromSourceBuffer( 93 | self: Histogram, 94 | sourceBuffer: ByteBuffer, 95 | lengthInBytes: u32, 96 | wordSizeInBytes: u32 97 | ): i32 { 98 | if ( 99 | wordSizeInBytes != 2 && 100 | wordSizeInBytes != 4 && 101 | wordSizeInBytes != 8 && 102 | wordSizeInBytes != V2maxWordSizeInBytes 103 | ) { 104 | throw new Error( 105 | "word size must be 2, 4, 8, or V2maxWordSizeInBytes (" + 106 | V2maxWordSizeInBytes.toString() + 107 | ") bytes" 108 | ); 109 | } 110 | let dstIndex: i32 = 0; 111 | const endPosition = sourceBuffer.position + lengthInBytes; 112 | while (sourceBuffer.position < endPosition) { 113 | let zerosCount: i32 = 0; 114 | let count = ZigZagEncoding.decode(sourceBuffer); 115 | if (count < 0) { 116 | zerosCount = -count; 117 | dstIndex += zerosCount; // No need to set zeros in array. Just skip them. 118 | } else { 119 | self.setCountAtIndex(dstIndex++, count); 120 | } 121 | } 122 | return dstIndex; // this is the destination length 123 | } 124 | 125 | function getCookieBase(cookie: u32): u32 { 126 | return cookie & ~0xf0; 127 | } 128 | 129 | function getWordSizeInBytesFromCookie(cookie: u32): u32 { 130 | if ( 131 | getCookieBase(cookie) == V2EncodingCookieBase || 132 | getCookieBase(cookie) == V2CompressedEncodingCookieBase 133 | ) { 134 | return V2maxWordSizeInBytes; 135 | } 136 | const sizeByte = (cookie & 0xf0) >> 4; 137 | return sizeByte & 0xe; 138 | } 139 | 140 | export function decodeFromByteBuffer( 141 | buffer: ByteBuffer, 142 | minBarForHighestTrackableValue: u64 143 | ): Histogram { 144 | const cookie = buffer.getInt32(); 145 | 146 | let payloadLengthInBytes: u32; 147 | let numberOfSignificantValueDigits: u8; 148 | let lowestTrackableUnitValue: u64; 149 | let highestTrackableValue: u64; 150 | 151 | if (getCookieBase(cookie) === V2EncodingCookieBase) { 152 | if (getWordSizeInBytesFromCookie(cookie) != V2maxWordSizeInBytes) { 153 | throw new Error( 154 | "The buffer does not contain a Histogram (no valid cookie found)" 155 | ); 156 | } 157 | payloadLengthInBytes = buffer.getInt32(); 158 | buffer.getInt32(); // normalizingIndexOffset not used 159 | numberOfSignificantValueDigits = buffer.getInt32(); 160 | lowestTrackableUnitValue = buffer.getInt64(); 161 | highestTrackableValue = buffer.getInt64(); 162 | buffer.getInt64(); // integerToDoubleValueConversionRatio not used 163 | } else { 164 | throw new Error( 165 | "The buffer does not contain a Histogram (no valid V2 encoding cookie found)" 166 | ); 167 | } 168 | 169 | highestTrackableValue = max( 170 | highestTrackableValue, 171 | minBarForHighestTrackableValue 172 | ); 173 | 174 | const histogram: Histogram = instantiate>( 175 | lowestTrackableUnitValue, 176 | highestTrackableValue, 177 | numberOfSignificantValueDigits 178 | ); 179 | 180 | const filledLength = fillCountsArrayFromSourceBuffer( 181 | histogram, 182 | buffer, 183 | payloadLengthInBytes, 184 | V2maxWordSizeInBytes 185 | ); 186 | 187 | histogram.establishInternalTackingValues(filledLength); 188 | 189 | return histogram; 190 | } 191 | -------------------------------------------------------------------------------- /src/Recorder.spec.ts: -------------------------------------------------------------------------------- 1 | import Recorder from "./Recorder"; 2 | import Int32Histogram from "./Int32Histogram"; 3 | import PackedHistogram from "./PackedHistogram"; 4 | import JsHistogram from "./JsHistogram"; 5 | import { initWebAssembly, WasmHistogram } from "./wasm"; 6 | 7 | describe("Recorder", () => { 8 | beforeAll(initWebAssembly); 9 | 10 | it("should record value", () => { 11 | // given 12 | const recorder = new Recorder(); 13 | // when 14 | recorder.recordValue(123); 15 | // then 16 | const histogram = recorder.getIntervalHistogram(); 17 | expect(histogram.totalCount).toBe(1); 18 | }); 19 | 20 | it("should record value in a packed histogram", () => { 21 | // given 22 | const recorder = new Recorder({ 23 | numberOfSignificantValueDigits: 5, 24 | bitBucketSize: "packed", 25 | }); 26 | // when 27 | recorder.recordValue(123); 28 | // then 29 | expect(recorder.getIntervalHistogram() instanceof PackedHistogram).toBe( 30 | true 31 | ); 32 | expect(recorder.getIntervalHistogram() instanceof PackedHistogram).toBe( 33 | true 34 | ); 35 | }); 36 | 37 | it("should record value in a WASM histogram", () => { 38 | // given 39 | const recorder = new Recorder({ 40 | numberOfSignificantValueDigits: 5, 41 | bitBucketSize: "packed", 42 | useWebAssembly: true, 43 | }); 44 | try { 45 | // when 46 | recorder.recordValue(123); 47 | // then 48 | expect(recorder.getIntervalHistogram() instanceof WasmHistogram).toBe( 49 | true 50 | ); 51 | } finally { 52 | recorder.destroy(); 53 | } 54 | }); 55 | 56 | it("should record value with count", () => { 57 | // given 58 | const recorder = new Recorder(); 59 | // when 60 | recorder.recordValueWithCount(123, 3); 61 | // then 62 | const histogram = recorder.getIntervalHistogram(); 63 | expect(histogram.totalCount).toBe(3); 64 | }); 65 | 66 | it("should record value with expected interval", () => { 67 | // given 68 | const recorder = new Recorder(); 69 | // when 70 | recorder.recordValueWithExpectedInterval(223, 100); 71 | // then 72 | const histogram = recorder.getIntervalHistogram(); 73 | expect(histogram.totalCount).toBe(2); 74 | }); 75 | 76 | it("should record value in a packed histogram", () => { 77 | // given 78 | const recorder = new Recorder({ bitBucketSize: "packed" }); 79 | recorder.recordValue(42); 80 | // when 81 | const histogram = recorder.getIntervalHistogram(); 82 | // then 83 | expect(histogram instanceof PackedHistogram).toBe(true); 84 | }); 85 | 86 | it("should record value only on one interval histogram", () => { 87 | // given 88 | const recorder = new Recorder(); 89 | // when 90 | recorder.recordValue(123); 91 | const firstHistogram = recorder.getIntervalHistogram(); 92 | // then 93 | const secondHistogram = recorder.getIntervalHistogram(); 94 | expect(secondHistogram.totalCount).toBe(0); 95 | }); 96 | 97 | it("should not record value on returned interval histogram", () => { 98 | // given 99 | const recorder = new Recorder(); 100 | const firstHistogram = recorder.getIntervalHistogram(); 101 | const secondHistogram = recorder.getIntervalHistogram(); 102 | // when 103 | firstHistogram.recordValue(42); // should have 0 impact on recorder 104 | const thirdHistogram = recorder.getIntervalHistogram(); 105 | // then 106 | expect(thirdHistogram.totalCount).toBe(0); 107 | }); 108 | 109 | it("should return interval histograms with expected significant digits", () => { 110 | // given 111 | const recorder = new Recorder({ numberOfSignificantValueDigits: 4 }); 112 | const firstHistogram = recorder.getIntervalHistogram(); 113 | const secondHistogram = recorder.getIntervalHistogram(); 114 | // when 115 | const thirdHistogram = recorder.getIntervalHistogram(); 116 | // then 117 | expect((thirdHistogram as JsHistogram).numberOfSignificantValueDigits).toBe( 118 | 4 119 | ); 120 | }); 121 | 122 | it("should return recycled histograms when asking for interval histogram", () => { 123 | // given 124 | const recorder = new Recorder(); 125 | const firstHistogram = recorder.getIntervalHistogram(); 126 | // when 127 | const secondHistogram = recorder.getIntervalHistogram(firstHistogram); 128 | const thirdHistogram = recorder.getIntervalHistogram(); 129 | // then 130 | expect(thirdHistogram === firstHistogram).toBe(true); 131 | }); 132 | 133 | it("should throw an error when trying to recycle an histogram not created by the recorder", () => { 134 | // given 135 | const recorder = new Recorder(); 136 | const somehistogram = new Int32Histogram(1, 2, 3); 137 | // when & then 138 | expect(() => recorder.getIntervalHistogram(somehistogram)).toThrowError(); 139 | }); 140 | 141 | it("should reset histogram when recycling", () => { 142 | // given 143 | const recorder = new Recorder(); 144 | recorder.recordValue(42); 145 | const firstHistogram = recorder.getIntervalHistogram(); 146 | // when 147 | const secondHistogram = recorder.getIntervalHistogram(firstHistogram); 148 | const thirdHistogram = recorder.getIntervalHistogram(); 149 | // then 150 | expect(thirdHistogram.totalCount).toBe(0); 151 | }); 152 | 153 | it("should set timestamps on first interval histogram", () => { 154 | // given 155 | let currentTime = 42; 156 | let clock = () => currentTime; 157 | const recorder = new Recorder({}, clock); 158 | // when 159 | currentTime = 123; 160 | const histogram = recorder.getIntervalHistogram(); 161 | // then 162 | expect(histogram.startTimeStampMsec).toBe(42); 163 | expect(histogram.endTimeStampMsec).toBe(123); 164 | }); 165 | 166 | it("should set timestamps on any interval histogram", () => { 167 | // given 168 | let currentTime = 42; 169 | let clock = () => currentTime; 170 | const recorder = new Recorder({}, clock); 171 | currentTime = 51; 172 | const firstHistogram = recorder.getIntervalHistogram(); 173 | // when 174 | currentTime = 56; 175 | const secondHistogram = recorder.getIntervalHistogram(); 176 | // then 177 | expect(secondHistogram.startTimeStampMsec).toBe(51); 178 | expect(secondHistogram.endTimeStampMsec).toBe(56); 179 | }); 180 | 181 | it("should copy interval histogram", () => { 182 | // given 183 | let currentTime = 42; 184 | let clock = () => currentTime; 185 | const recorder = new Recorder({ numberOfSignificantValueDigits: 4 }, clock); 186 | recorder.recordValue(123); 187 | // when 188 | const histogram = new Int32Histogram(1, Number.MAX_SAFE_INTEGER, 3); 189 | currentTime = 51; 190 | recorder.getIntervalHistogramInto(histogram); 191 | // then 192 | expect(histogram.totalCount).toBe(1); 193 | expect(histogram.startTimeStampMsec).toBe(42); 194 | expect(histogram.endTimeStampMsec).toBe(51); 195 | }); 196 | 197 | it("should reset values and timestamp", () => { 198 | // given 199 | let currentTime = 42; 200 | let clock = () => currentTime; 201 | const recorder = new Recorder({ numberOfSignificantValueDigits: 4 }, clock); 202 | recorder.recordValue(123); 203 | // when 204 | currentTime = 55; 205 | recorder.reset(); 206 | const histogram = recorder.getIntervalHistogram(); 207 | // then 208 | expect(histogram.totalCount).toBe(0); 209 | expect(histogram.startTimeStampMsec).toBe(55); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /test_files/jHiccup-no-header-2.0.7S.logV2.hlog: -------------------------------------------------------------------------------- 1 | 0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= 2 | 1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= 3 | 2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= 4 | 3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= 5 | 4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= 6 | 5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== 7 | 6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== 8 | 7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= 9 | 8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw 10 | 9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= 11 | 10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR 12 | 11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== 13 | 12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko 14 | 13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= 15 | 14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ 16 | 15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== 17 | 16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne 18 | 17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 19 | 18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== 20 | 19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== 21 | 20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== 22 | 21.135,0.999,0.492,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhk1AjINDRECAj4+Hi49LKS5CS2EGo1kXa4ANExMDEwAmOQil 23 | 22.134,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBFmHhE+MRExCTEZAS4RMQERvRI1hSuMTidY3KQ4mAAXhgks 24 | 23.131,1.004,0.508,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhotHSEBASEyMg09MQUSIT6tKS2YKY8gfFj8tJmYmJgAsowkz 25 | 24.135,0.998,0.492,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEBLjkhETEpET4BISEhCR6FsqAQFY1jjBoTWPQEOJiZAC2aCUY= 26 | 25.133,1.002,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBkuHh4BITEpMSEpLiE5AS6FoAgdpQuMJk9YzMRYmAAdngk2 27 | 26.135,0.998,0.508,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUAkOKSEJKTUJOT4+IQkeIT69LYwVCnIbGI0eMZtJsTAxMwEAQvkJyg== 28 | 27.133,0.998,0.442,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBE2CQUZFTkZOSURKQkRMT6NKYwhbYxaOocY/a4xSUmwAQA4pQpb 29 | 28.131,1.002,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGtFDcHIy0jDQUdPjENFZUzjNNYHCT4uBQkzJiYADIGCcY= 30 | 29.133,1.460,968.884,HISTFAAAAJZ42pNpmSzMwMDUwgABTBDKT4GBgdnNYMcCBvsPEBE5AwMDJSUFISk2ETYuAS6PQ0xSXCzsTEw7GZnKgdCTyZLJGog1maSZZIFYGkpLMnEz8QIhOolgcTKxAiEzmGRFYxMShbEYUCAalzRBsjSjARYmTIBNjDKFSIIsIMDGAgPYWJRJE1DIxQEEaAQHF2GCNDVsAE2dFJE= 31 | 30.593,0.541,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEFCxUNBRkFMTE+Pj4ZHgGHFYwGIkJcMiIpbEwMTAAdQQhJ 32 | 31.134,0.997,0.737,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEJGAHsYexqKaIAcPPRMVKTEhoR6mJUxqfBx8LFwCTOxM0kwAfR8KqA== 33 | 32.131,1.002,0.508,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEJKCDMcHJw8jOTUfNSEZGQuUb4x9GHxkJDg2hMA4WViYmAHWrC2k= 34 | 33.133,1.000,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGXGK8QHS09PRM9BRMxBa55jBOY03REhByE3DhYADicCkc= 35 | 34.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBE1NzsfJwMVEw0pFS0hOZm4FqYKPy2FAoUJjFIsTAA/mQql 36 | 35.131,1.000,0.459,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBERMy0jPTk5LRUFJQk1GamYdUzHGO0UxIrUljBKsbEwAQBKXgqU 37 | 36.131,1.001,0.557,HISTFAAAAEd42pNpmSzMwMCgygABTBDKT4GBgdnNYMcCBvsPEBExJzcNMyU5PRUpLSkJKYWwHqYWRjslkTKNC4wKHGwMTExArUwAi/IKnA== 38 | 37.132,1.002,0.442,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEFLRsVPQkTKTkhPT4ZBTm3V4yTGD20pFoYtZqYxESYAEjICok= 39 | 38.134,1.000,0.803,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNERM7Hwk3LRslMSkZMQExDLGQL0yTGIC2pKJ1VjCwcTJpMAFufCso= 40 | 39.134,0.997,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE5Oz8DPRsFORM5FQkNKaGCA8wtjCoSfBYSTYxCLEBtTABiWgor 41 | 40.131,1.000,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBExJQUNFTElFRUZBRUZDTGfJqYKHzmhHka5ZUwSQmwANK0J+g== 42 | 41.131,1.002,0.475,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE2Hj45PiEFGSU5EQkpKREJuVmMLYwaWk8YQyYwa3CxMTABAEOgCdQ= 43 | 42.133,1.000,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBk+Lg4+ER4hMT4hIT4lLh69OAOZZ4wOr1hCpFiYABjUCSY= 44 | 43.133,1.002,0.442,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBFmLgEJMTERHjEuCRERBSERoww5rRuMendYPFRYAA3tCTM= 45 | 44.135,0.998,0.590,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBk+FT0lJTktJSUjOTE1OQGpmnOMdnorGF3WMemxCTIBAEAhCnU= 46 | 45.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWMS0DIyMFOSsNPTEFMSGNA4x+LxidfOp0VjBKcLAAAECLCv4= 47 | 46.131,1.004,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEuMS0VEyMlLSkzGQUJOSkJj6RnjE56WxjNWpik2JgAO34KfQ== 48 | 47.135,0.996,0.475,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUgk2GR0ZOQkSAR4aLS0KKTyNtDqOWxjVGu2fMGlJMTEwANsIJvA== 49 | 48.131,1.950,1803.551,HISTFAAAAKF42pNpmSzMwMD0mQECmCCUnwIDA7ObwY4FDPYfICKsTExMLCysLCxsbEwMTAIsDHIsWTwsbNsZmcqZKpncmayZLIFYnUmWSRoMIbQkEy8TNxQjkwgWJxMrGDJDaews/KIMKBCNSytBZCYqYGHCBNjEiBckoJAFBNhYYADBwipIhkIC0lwcQIBGcHARJqigBkwKCQgICSAIFA75IlwAeB8ZpQ== 50 | 50.081,0.050,0.393,HISTFAAAADl42pNpmSzMwMAgxgABTBDKT4GBgdnNYMcCBvsPEBE2BiYWNiYWZiYGJiZmJg4OLiYuFiYWAMWGBSM= 51 | 50.131,1.001,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2Lj4VAQkuJT45KTkOKSExI68eRgeDvB2MfcxxckwAJD8JyA== 52 | 51.132,0.999,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2NgUFGSkNAQEeJSkuKSmxhAojhZADjKuYiyS4WAAlWgm/ 53 | 52.131,1.002,0.557,HISTFAAAAER42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPUBkWPjEFGSMZKQMJJSEhPgkJiyodjZIHjB+YSvh4mBiYWJkAVc8KVw== 54 | 53.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2HjklJR0VPSUDHTUxJSkJs02MuxhtrLxKHjH6cbEAADjeCuw= 55 | 54.131,1.003,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkNPzMLIw0NLQ0pFTERCTGLT4wpQSVbGFcwynExAQA/uwsC 56 | 55.134,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPUBkWFTUjCy01BQ0VFRUJGSkJjRamiqA5jHmXGIV4ACoyCmo= 57 | 56.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUhk1FzsrAQElFQ0xCQkJOTEDnE6ObxwrGDsYuJjUODiYASN8KbA== 58 | 57.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPMBk5FT0JAzUNKTklKQ0FMaGUJ4wJFjcYk+4wqnAwMAEAQooK6Q== 59 | 58.131,1.002,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEuCRMNJwMlIzUtLR0ZMREZv6IHjFYGdUXLGE14WAA4OwsG 60 | 59.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBklExUdIwcdFRUlOTMZPhWXB4wBTssYsy4xKnGwAQA8bAry 61 | 60.131,1.000,0.524,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmIRcjPR0bFR0lDSk5KQkZpXlMXkF5qxh3MMqIcDIBADy8CoE= 62 | 61.131,1.000,26.083,HISTFAAAAF542pNpmSzMwMAQyAABTBDKT4GBgdnNYMcCBvsPMBkFHSMrCzEZLSUFCSkJOTmTf4xRQW2MYT8Y5diYdjIylTNVMrkzWTJZA7EmkzQYykJpSSZeJm4ghpAQFgATDg85 63 | -------------------------------------------------------------------------------- /src/HistogramLogReader.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import { NO_TAG } from "./Histogram"; 9 | import { decodeFromCompressedBase64 } from "./encoding"; 10 | import Histogram, { BitBucketSize } from "./Histogram"; 11 | 12 | const TAG_PREFIX = "Tag="; 13 | const TAG_PREFIX_LENGTH = "Tag=".length; 14 | 15 | /** 16 | * A histogram log reader. 17 | *

18 | * Histogram logs are used to capture full fidelity, per-time-interval 19 | * histograms of a recorded value. 20 | *

21 | * For example, a histogram log can be used to capture high fidelity 22 | * reaction-time logs for some measured system or subsystem component. 23 | * Such a log would capture a full reaction time histogram for each 24 | * logged interval, and could be used to later reconstruct a full 25 | * HdrHistogram of the measured reaction time behavior for any arbitrary 26 | * time range within the log, by adding [only] the relevant interval 27 | * histograms. 28 | *

Histogram log format:

29 | * A histogram log file consists of text lines. Lines beginning with 30 | * the "#" character are optional and treated as comments. Lines 31 | * containing the legend (starting with "Timestamp") are also optional 32 | * and ignored in parsing the histogram log. All other lines must 33 | * be valid interval description lines. Text fields are delimited by 34 | * commas, spaces. 35 | *

36 | * A valid interval description line contains an optional Tag=tagString 37 | * text field, followed by an interval description. 38 | *

39 | * A valid interval description must contain exactly four text fields: 40 | *

    41 | *
  • StartTimestamp: The first field must contain a number parse-able as a Double value, 42 | * representing the start timestamp of the interval in seconds.
  • 43 | *
  • intervalLength: The second field must contain a number parse-able as a Double value, 44 | * representing the length of the interval in seconds.
  • 45 | *
  • Interval_Max: The third field must contain a number parse-able as a Double value, 46 | * which generally represents the maximum value of the interval histogram.
  • 47 | *
  • Interval_Compressed_Histogram: The fourth field must contain a text field 48 | * parse-able as a Base64 text representation of a compressed HdrHistogram.
  • 49 | *
50 | * The log file may contain an optional indication of a starting time. Starting time 51 | * is indicated using a special comments starting with "#[StartTime: " and followed 52 | * by a number parse-able as a double, representing the start time (in seconds) 53 | * that may be added to timestamps in the file to determine an absolute 54 | * timestamp (e.g. since the epoch) for each interval. 55 | */ 56 | class HistogramLogReader { 57 | startTimeSec: number; 58 | baseTimeSec: number; 59 | 60 | lines: string[]; 61 | currentLineIndex: number; 62 | bitBucketSize: BitBucketSize; 63 | useWebAssembly: boolean; 64 | 65 | constructor( 66 | logContent: string, 67 | bitBucketSize: BitBucketSize = 32, 68 | useWebAssembly: boolean = false 69 | ) { 70 | this.lines = splitLines(logContent); 71 | this.currentLineIndex = 0; 72 | this.bitBucketSize = bitBucketSize; 73 | this.useWebAssembly = useWebAssembly; 74 | } 75 | 76 | /** 77 | * Read the next interval histogram from the log. Returns a Histogram object if 78 | * an interval line was found, or null if not. 79 | *

Upon encountering any unexpected format errors in reading the next interval 80 | * from the file, this method will return a null. 81 | * @return a DecodedInterval, or a null if no appropriate interval found 82 | */ 83 | public nextIntervalHistogram( 84 | rangeStartTimeSec = 0, 85 | rangeEndTimeSec = Number.MAX_VALUE 86 | ): Histogram | null { 87 | while (this.currentLineIndex < this.lines.length) { 88 | const currentLine = this.lines[this.currentLineIndex]; 89 | this.currentLineIndex++; 90 | if (currentLine.startsWith("#[StartTime:")) { 91 | this.parseStartTimeFromLine(currentLine); 92 | } else if (currentLine.startsWith("#[BaseTime:")) { 93 | this.parseBaseTimeFromLine(currentLine); 94 | } else if ( 95 | currentLine.startsWith("#") || 96 | currentLine.startsWith('"StartTimestamp"') 97 | ) { 98 | // skip legend & meta data for now 99 | } else if (currentLine.includes(",")) { 100 | const tokens = currentLine.split(","); 101 | const [firstToken] = tokens; 102 | let tag: string; 103 | if (firstToken.startsWith(TAG_PREFIX)) { 104 | tag = firstToken.substring(TAG_PREFIX_LENGTH); 105 | tokens.shift(); 106 | } else { 107 | tag = NO_TAG; 108 | } 109 | 110 | const [ 111 | rawLogTimeStampInSec, 112 | rawIntervalLengthSec, 113 | , 114 | base64Histogram, 115 | ] = tokens; 116 | const logTimeStampInSec = Number.parseFloat(rawLogTimeStampInSec); 117 | 118 | if (!this.baseTimeSec) { 119 | // No explicit base time noted. Deduce from 1st observed time (compared to start time): 120 | if (logTimeStampInSec < this.startTimeSec - 365 * 24 * 3600.0) { 121 | // Criteria Note: if log timestamp is more than a year in the past (compared to 122 | // StartTime), we assume that timestamps in the log are not absolute 123 | this.baseTimeSec = this.startTimeSec; 124 | } else { 125 | // Timestamps are absolute 126 | this.baseTimeSec = 0.0; 127 | } 128 | } 129 | 130 | if (rangeEndTimeSec < logTimeStampInSec) { 131 | return null; 132 | } 133 | if (logTimeStampInSec < rangeStartTimeSec) { 134 | continue; 135 | } 136 | const histogram = decodeFromCompressedBase64( 137 | base64Histogram, 138 | this.bitBucketSize, 139 | this.useWebAssembly 140 | ); 141 | histogram.startTimeStampMsec = 142 | (this.baseTimeSec + logTimeStampInSec) * 1000; 143 | const intervalLengthSec = Number.parseFloat(rawIntervalLengthSec); 144 | histogram.endTimeStampMsec = 145 | (this.baseTimeSec + logTimeStampInSec + intervalLengthSec) * 1000; 146 | 147 | histogram.tag = tag; 148 | 149 | return histogram; 150 | } 151 | } 152 | return null; 153 | } 154 | 155 | private parseStartTimeFromLine(line: string) { 156 | this.startTimeSec = Number.parseFloat(line.split(" ")[1]); 157 | } 158 | 159 | private parseBaseTimeFromLine(line: string) { 160 | this.baseTimeSec = Number.parseFloat(line.split(" ")[1]); 161 | } 162 | } 163 | 164 | const splitLines = (logContent: string) => logContent.split(/\r\n|\r|\n/g); 165 | 166 | const shouldIncludeNoTag = (lines: string[]) => 167 | lines.find( 168 | (line) => 169 | !line.startsWith("#") && 170 | !line.startsWith('"') && 171 | !line.startsWith(TAG_PREFIX) && 172 | line.includes(",") 173 | ); 174 | 175 | export const listTags = (content: string) => { 176 | const lines = splitLines(content); 177 | const tags = lines 178 | .filter((line) => line.includes(",") && line.startsWith(TAG_PREFIX)) 179 | .map((line) => line.substring(TAG_PREFIX_LENGTH, line.indexOf(","))); 180 | const tagsWithoutDuplicates = new Set(tags); 181 | const result = Array.from(tagsWithoutDuplicates); 182 | if (shouldIncludeNoTag(lines)) { 183 | result.unshift("NO TAG"); 184 | } 185 | return result; 186 | }; 187 | 188 | export default HistogramLogReader; 189 | -------------------------------------------------------------------------------- /assembly/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a AssemblyScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | 9 | import Histogram from "./Histogram"; 10 | import { 11 | Uint8Storage, 12 | Uint16Storage, 13 | Uint32Storage, 14 | Uint64Storage, 15 | } from "./Histogram"; 16 | import { decodeFromByteBuffer } from "./encoding"; 17 | import ByteBuffer from "./ByteBuffer"; 18 | import { PackedArray } from "./packedarray/PackedArray"; 19 | 20 | export const UINT8ARRAY_ID = idof(); 21 | 22 | class HistogramAdapter { 23 | private _histogram: Histogram; 24 | constructor( 25 | lowestDiscernibleValue: f64, 26 | highestTrackableValue: f64, 27 | numberOfSignificantValueDigits: f64, 28 | autoResize: boolean, 29 | histogram: Histogram | null = null 30 | ) { 31 | if (histogram) { 32 | this._histogram = histogram; 33 | } else { 34 | this._histogram = new Histogram( 35 | lowestDiscernibleValue, 36 | highestTrackableValue, 37 | numberOfSignificantValueDigits 38 | ); 39 | this._histogram.autoResize = autoResize; 40 | } 41 | } 42 | 43 | public get autoResize(): boolean { 44 | return this._histogram.autoResize; 45 | } 46 | 47 | public set autoResize(resize: boolean) { 48 | this._histogram.autoResize = resize; 49 | } 50 | 51 | public get highestTrackableValue(): f64 { 52 | return this._histogram.highestTrackableValue; 53 | } 54 | 55 | public set highestTrackableValue(value: f64) { 56 | this._histogram.highestTrackableValue = value; 57 | } 58 | 59 | public get startTimeStampMsec(): f64 { 60 | return this._histogram.startTimeStampMsec; 61 | } 62 | 63 | public set startTimeStampMsec(value: f64) { 64 | this._histogram.startTimeStampMsec = value; 65 | } 66 | 67 | public get endTimeStampMsec(): f64 { 68 | return this._histogram.endTimeStampMsec; 69 | } 70 | 71 | public set endTimeStampMsec(value: f64) { 72 | this._histogram.endTimeStampMsec = value; 73 | } 74 | 75 | public get minNonZeroValue(): f64 { 76 | return this._histogram.minNonZeroValue; 77 | } 78 | 79 | public get maxValue(): f64 { 80 | return this._histogram.maxValue; 81 | } 82 | 83 | public get totalCount(): f64 { 84 | return this._histogram.totalCount; 85 | } 86 | 87 | public get stdDeviation(): f64 { 88 | return this._histogram.getStdDeviation(); 89 | } 90 | 91 | public get mean(): f64 { 92 | return this._histogram.getMean(); 93 | } 94 | 95 | public get estimatedFootprintInBytes(): f64 { 96 | return ( 97 | (offsetof>() + 98 | this._histogram.estimatedFootprintInBytes) 99 | ); 100 | } 101 | 102 | recordValue(value: f64): void { 103 | this._histogram.recordSingleValue(value); 104 | } 105 | 106 | recordValueWithCount(value: f64, count: f64): void { 107 | this._histogram.recordCountAtValue(count, value); 108 | } 109 | 110 | recordValueWithExpectedInterval( 111 | value: f64, 112 | expectedIntervalBetweenValueSamples: f64 113 | ): void { 114 | this._histogram.recordSingleValueWithExpectedInterval( 115 | value, 116 | expectedIntervalBetweenValueSamples 117 | ); 118 | } 119 | 120 | getValueAtPercentile(percentile: f64): f64 { 121 | return this._histogram.getValueAtPercentile(percentile); 122 | } 123 | 124 | outputPercentileDistribution( 125 | percentileTicksPerHalfDistance: f64, 126 | outputValueUnitScalingRatio: f64 127 | ): string { 128 | return this._histogram.outputPercentileDistribution( 129 | percentileTicksPerHalfDistance, 130 | outputValueUnitScalingRatio 131 | ); 132 | } 133 | 134 | copyCorrectedForCoordinatedOmission( 135 | expectedIntervalBetweenValueSamples: f64 136 | ): HistogramAdapter { 137 | const copy = this._histogram.copyCorrectedForCoordinatedOmission( 138 | expectedIntervalBetweenValueSamples 139 | ); 140 | 141 | return new HistogramAdapter(0, 0, 0, false, copy); 142 | } 143 | 144 | addHistogram8(otherHistogram: Histogram8): void { 145 | this._histogram.add(otherHistogram._histogram); 146 | } 147 | addHistogram16(otherHistogram: Histogram16): void { 148 | this._histogram.add(otherHistogram._histogram); 149 | } 150 | addHistogram32(otherHistogram: Histogram32): void { 151 | this._histogram.add(otherHistogram._histogram); 152 | } 153 | addHistogram64(otherHistogram: Histogram64): void { 154 | this._histogram.add(otherHistogram._histogram); 155 | } 156 | addPackedHistogram(otherHistogram: PackedHistogram): void { 157 | this._histogram.add(otherHistogram._histogram); 158 | } 159 | 160 | subtractHistogram8(otherHistogram: Histogram8): void { 161 | this._histogram.subtract(otherHistogram._histogram); 162 | } 163 | subtractHistogram16(otherHistogram: Histogram16): void { 164 | this._histogram.subtract(otherHistogram._histogram); 165 | } 166 | subtractHistogram32(otherHistogram: Histogram32): void { 167 | this._histogram.subtract(otherHistogram._histogram); 168 | } 169 | subtractHistogram64(otherHistogram: Histogram64): void { 170 | this._histogram.subtract(otherHistogram._histogram); 171 | } 172 | subtractPackedHistogram(otherHistogram: PackedHistogram): void { 173 | this._histogram.subtract(otherHistogram._histogram); 174 | } 175 | 176 | encode(): Uint8Array { 177 | return this._histogram.encode(); 178 | } 179 | 180 | reset(): void { 181 | this._histogram.reset(); 182 | } 183 | } 184 | 185 | export class Histogram8 extends HistogramAdapter {} 186 | export class Histogram16 extends HistogramAdapter {} 187 | export class Histogram32 extends HistogramAdapter {} 188 | export class Histogram64 extends HistogramAdapter {} 189 | export class PackedHistogram extends HistogramAdapter {} 190 | 191 | function decodeHistogram( 192 | data: Uint8Array, 193 | minBarForHighestTrackableValue: f64 194 | ): HistogramAdapter { 195 | const buffer = new ByteBuffer(data); 196 | const histogram = decodeFromByteBuffer( 197 | buffer, 198 | minBarForHighestTrackableValue 199 | ); 200 | return new HistogramAdapter(0, 0, 0, false, histogram); 201 | } 202 | 203 | export function decodeHistogram8( 204 | data: Uint8Array, 205 | minBarForHighestTrackableValue: f64 206 | ): HistogramAdapter { 207 | return decodeHistogram( 208 | data, 209 | minBarForHighestTrackableValue 210 | ); 211 | } 212 | export function decodeHistogram16( 213 | data: Uint8Array, 214 | minBarForHighestTrackableValue: f64 215 | ): HistogramAdapter { 216 | return decodeHistogram( 217 | data, 218 | minBarForHighestTrackableValue 219 | ); 220 | } 221 | export function decodeHistogram32( 222 | data: Uint8Array, 223 | minBarForHighestTrackableValue: f64 224 | ): HistogramAdapter { 225 | return decodeHistogram( 226 | data, 227 | minBarForHighestTrackableValue 228 | ); 229 | } 230 | export function decodeHistogram64( 231 | data: Uint8Array, 232 | minBarForHighestTrackableValue: f64 233 | ): HistogramAdapter { 234 | return decodeHistogram( 235 | data, 236 | minBarForHighestTrackableValue 237 | ); 238 | } 239 | export function decodePackedHistogram( 240 | data: Uint8Array, 241 | minBarForHighestTrackableValue: f64 242 | ): HistogramAdapter { 243 | return decodeHistogram( 244 | data, 245 | minBarForHighestTrackableValue 246 | ); 247 | } 248 | -------------------------------------------------------------------------------- /test_files/jHiccup-2.0.7S.logV2.hlog: -------------------------------------------------------------------------------- 1 | #[Logged with jHiccup version 2.0.7-SNAPSHOT] 2 | #[Histogram log format version 1.2] 3 | #[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] 4 | "StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" 5 | 0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= 6 | 1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= 7 | 2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= 8 | 3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= 9 | 4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= 10 | 5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== 11 | 6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== 12 | 7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= 13 | 8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw 14 | 9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= 15 | 10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR 16 | 11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== 17 | 12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko 18 | 13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= 19 | 14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ 20 | 15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== 21 | 16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne 22 | 17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 23 | 18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== 24 | 19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== 25 | 20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== 26 | 21.135,0.999,0.492,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhk1AjINDRECAj4+Hi49LKS5CS2EGo1kXa4ANExMDEwAmOQil 27 | 22.134,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBFmHhE+MRExCTEZAS4RMQERvRI1hSuMTidY3KQ4mAAXhgks 28 | 23.131,1.004,0.508,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhotHSEBASEyMg09MQUSIT6tKS2YKY8gfFj8tJmYmJgAsowkz 29 | 24.135,0.998,0.492,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEBLjkhETEpET4BISEhCR6FsqAQFY1jjBoTWPQEOJiZAC2aCUY= 30 | 25.133,1.002,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBkuHh4BITEpMSEpLiE5AS6FoAgdpQuMJk9YzMRYmAAdngk2 31 | 26.135,0.998,0.508,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUAkOKSEJKTUJOT4+IQkeIT69LYwVCnIbGI0eMZtJsTAxMwEAQvkJyg== 32 | 27.133,0.998,0.442,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBE2CQUZFTkZOSURKQkRMT6NKYwhbYxaOocY/a4xSUmwAQA4pQpb 33 | 28.131,1.002,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGtFDcHIy0jDQUdPjENFZUzjNNYHCT4uBQkzJiYADIGCcY= 34 | 29.133,1.460,968.884,HISTFAAAAJZ42pNpmSzMwMDUwgABTBDKT4GBgdnNYMcCBvsPEBE5AwMDJSUFISk2ETYuAS6PQ0xSXCzsTEw7GZnKgdCTyZLJGog1maSZZIFYGkpLMnEz8QIhOolgcTKxAiEzmGRFYxMShbEYUCAalzRBsjSjARYmTIBNjDKFSIIsIMDGAgPYWJRJE1DIxQEEaAQHF2GCNDVsAE2dFJE= 35 | 30.593,0.541,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEFCxUNBRkFMTE+Pj4ZHgGHFYwGIkJcMiIpbEwMTAAdQQhJ 36 | 31.134,0.997,0.737,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEJGAHsYexqKaIAcPPRMVKTEhoR6mJUxqfBx8LFwCTOxM0kwAfR8KqA== 37 | 32.131,1.002,0.508,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEJKCDMcHJw8jOTUfNSEZGQuUb4x9GHxkJDg2hMA4WViYmAHWrC2k= 38 | 33.133,1.000,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGXGK8QHS09PRM9BRMxBa55jBOY03REhByE3DhYADicCkc= 39 | 34.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBE1NzsfJwMVEw0pFS0hOZm4FqYKPy2FAoUJjFIsTAA/mQql 40 | 35.131,1.000,0.459,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBERMy0jPTk5LRUFJQk1GamYdUzHGO0UxIrUljBKsbEwAQBKXgqU 41 | 36.131,1.001,0.557,HISTFAAAAEd42pNpmSzMwMCgygABTBDKT4GBgdnNYMcCBvsPEBExJzcNMyU5PRUpLSkJKYWwHqYWRjslkTKNC4wKHGwMTExArUwAi/IKnA== 42 | 37.132,1.002,0.442,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEFLRsVPQkTKTkhPT4ZBTm3V4yTGD20pFoYtZqYxESYAEjICok= 43 | 38.134,1.000,0.803,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNERM7Hwk3LRslMSkZMQExDLGQL0yTGIC2pKJ1VjCwcTJpMAFufCso= 44 | 39.134,0.997,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE5Oz8DPRsFORM5FQkNKaGCA8wtjCoSfBYSTYxCLEBtTABiWgor 45 | 40.131,1.000,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBExJQUNFTElFRUZBRUZDTGfJqYKHzmhHka5ZUwSQmwANK0J+g== 46 | 41.131,1.002,0.475,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE2Hj45PiEFGSU5EQkpKREJuVmMLYwaWk8YQyYwa3CxMTABAEOgCdQ= 47 | 42.133,1.000,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBk+Lg4+ER4hMT4hIT4lLh69OAOZZ4wOr1hCpFiYABjUCSY= 48 | 43.133,1.002,0.442,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBFmLgEJMTERHjEuCRERBSERoww5rRuMendYPFRYAA3tCTM= 49 | 44.135,0.998,0.590,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBk+FT0lJTktJSUjOTE1OQGpmnOMdnorGF3WMemxCTIBAEAhCnU= 50 | 45.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWMS0DIyMFOSsNPTEFMSGNA4x+LxidfOp0VjBKcLAAAECLCv4= 51 | 46.131,1.004,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEuMS0VEyMlLSkzGQUJOSkJj6RnjE56WxjNWpik2JgAO34KfQ== 52 | 47.135,0.996,0.475,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUgk2GR0ZOQkSAR4aLS0KKTyNtDqOWxjVGu2fMGlJMTEwANsIJvA== 53 | 48.131,1.950,1803.551,HISTFAAAAKF42pNpmSzMwMD0mQECmCCUnwIDA7ObwY4FDPYfICKsTExMLCysLCxsbEwMTAIsDHIsWTwsbNsZmcqZKpncmayZLIFYnUmWSRoMIbQkEy8TNxQjkwgWJxMrGDJDaews/KIMKBCNSytBZCYqYGHCBNjEiBckoJAFBNhYYADBwipIhkIC0lwcQIBGcHARJqigBkwKCQgICSAIFA75IlwAeB8ZpQ== 54 | 50.081,0.050,0.393,HISTFAAAADl42pNpmSzMwMAgxgABTBDKT4GBgdnNYMcCBvsPEBE2BiYWNiYWZiYGJiZmJg4OLiYuFiYWAMWGBSM= 55 | 50.131,1.001,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2Lj4VAQkuJT45KTkOKSExI68eRgeDvB2MfcxxckwAJD8JyA== 56 | 51.132,0.999,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2NgUFGSkNAQEeJSkuKSmxhAojhZADjKuYiyS4WAAlWgm/ 57 | 52.131,1.002,0.557,HISTFAAAAER42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPUBkWPjEFGSMZKQMJJSEhPgkJiyodjZIHjB+YSvh4mBiYWJkAVc8KVw== 58 | 53.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2HjklJR0VPSUDHTUxJSkJs02MuxhtrLxKHjH6cbEAADjeCuw= 59 | 54.131,1.003,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkNPzMLIw0NLQ0pFTERCTGLT4wpQSVbGFcwynExAQA/uwsC 60 | 55.134,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPUBkWFTUjCy01BQ0VFRUJGSkJjRamiqA5jHmXGIV4ACoyCmo= 61 | 56.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUhk1FzsrAQElFQ0xCQkJOTEDnE6ObxwrGDsYuJjUODiYASN8KbA== 62 | 57.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPMBk5FT0JAzUNKTklKQ0FMaGUJ4wJFjcYk+4wqnAwMAEAQooK6Q== 63 | 58.131,1.002,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEuCRMNJwMlIzUtLR0ZMREZv6IHjFYGdUXLGE14WAA4OwsG 64 | 59.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBklExUdIwcdFRUlOTMZPhWXB4wBTssYsy4xKnGwAQA8bAry 65 | 60.131,1.000,0.524,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmIRcjPR0bFR0lDSk5KQkZpXlMXkF5qxh3MMqIcDIBADy8CoE= 66 | 61.131,1.000,26.083,HISTFAAAAF542pNpmSzMwMAQyAABTBDKT4GBgdnNYMcCBvsPMBkFHSMrCzEZLSUFCSkJOTmTf4xRQW2MYT8Y5diYdjIylTNVMrkzWTJZA7EmkzQYykJpSSZeJm4ghpAQFgATDg85 67 | -------------------------------------------------------------------------------- /test_files/jHiccup-with-basetime-2.0.7S.logV2.hlog: -------------------------------------------------------------------------------- 1 | #[Logged with jHiccup version 2.0.7-SNAPSHOT] 2 | #[Histogram log format version 1.2] 3 | #[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] 4 | #[BaseTime: 1441812123.123 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] 5 | "StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" 6 | 0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= 7 | 1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= 8 | 2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= 9 | 3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= 10 | 4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= 11 | 5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== 12 | 6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== 13 | 7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= 14 | 8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw 15 | 9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= 16 | 10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR 17 | 11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== 18 | 12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko 19 | 13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= 20 | 14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ 21 | 15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== 22 | 16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne 23 | 17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 24 | 18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== 25 | 19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== 26 | 20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== 27 | 21.135,0.999,0.492,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhk1AjINDRECAj4+Hi49LKS5CS2EGo1kXa4ANExMDEwAmOQil 28 | 22.134,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBFmHhE+MRExCTEZAS4RMQERvRI1hSuMTidY3KQ4mAAXhgks 29 | 23.131,1.004,0.508,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhotHSEBASEyMg09MQUSIT6tKS2YKY8gfFj8tJmYmJgAsowkz 30 | 24.135,0.998,0.492,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEBLjkhETEpET4BISEhCR6FsqAQFY1jjBoTWPQEOJiZAC2aCUY= 31 | 25.133,1.002,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBkuHh4BITEpMSEpLiE5AS6FoAgdpQuMJk9YzMRYmAAdngk2 32 | 26.135,0.998,0.508,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUAkOKSEJKTUJOT4+IQkeIT69LYwVCnIbGI0eMZtJsTAxMwEAQvkJyg== 33 | 27.133,0.998,0.442,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBE2CQUZFTkZOSURKQkRMT6NKYwhbYxaOocY/a4xSUmwAQA4pQpb 34 | 28.131,1.002,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGtFDcHIy0jDQUdPjENFZUzjNNYHCT4uBQkzJiYADIGCcY= 35 | 29.133,1.460,968.884,HISTFAAAAJZ42pNpmSzMwMDUwgABTBDKT4GBgdnNYMcCBvsPEBE5AwMDJSUFISk2ETYuAS6PQ0xSXCzsTEw7GZnKgdCTyZLJGog1maSZZIFYGkpLMnEz8QIhOolgcTKxAiEzmGRFYxMShbEYUCAalzRBsjSjARYmTIBNjDKFSIIsIMDGAgPYWJRJE1DIxQEEaAQHF2GCNDVsAE2dFJE= 36 | 30.593,0.541,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEFCxUNBRkFMTE+Pj4ZHgGHFYwGIkJcMiIpbEwMTAAdQQhJ 37 | 31.134,0.997,0.737,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEJGAHsYexqKaIAcPPRMVKTEhoR6mJUxqfBx8LFwCTOxM0kwAfR8KqA== 38 | 32.131,1.002,0.508,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEJKCDMcHJw8jOTUfNSEZGQuUb4x9GHxkJDg2hMA4WViYmAHWrC2k= 39 | 33.133,1.000,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGXGK8QHS09PRM9BRMxBa55jBOY03REhByE3DhYADicCkc= 40 | 34.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBE1NzsfJwMVEw0pFS0hOZm4FqYKPy2FAoUJjFIsTAA/mQql 41 | 35.131,1.000,0.459,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBERMy0jPTk5LRUFJQk1GamYdUzHGO0UxIrUljBKsbEwAQBKXgqU 42 | 36.131,1.001,0.557,HISTFAAAAEd42pNpmSzMwMCgygABTBDKT4GBgdnNYMcCBvsPEBExJzcNMyU5PRUpLSkJKYWwHqYWRjslkTKNC4wKHGwMTExArUwAi/IKnA== 43 | 37.132,1.002,0.442,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEFLRsVPQkTKTkhPT4ZBTm3V4yTGD20pFoYtZqYxESYAEjICok= 44 | 38.134,1.000,0.803,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNERM7Hwk3LRslMSkZMQExDLGQL0yTGIC2pKJ1VjCwcTJpMAFufCso= 45 | 39.134,0.997,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE5Oz8DPRsFORM5FQkNKaGCA8wtjCoSfBYSTYxCLEBtTABiWgor 46 | 40.131,1.000,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBExJQUNFTElFRUZBRUZDTGfJqYKHzmhHka5ZUwSQmwANK0J+g== 47 | 41.131,1.002,0.475,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE2Hj45PiEFGSU5EQkpKREJuVmMLYwaWk8YQyYwa3CxMTABAEOgCdQ= 48 | 42.133,1.000,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBk+Lg4+ER4hMT4hIT4lLh69OAOZZ4wOr1hCpFiYABjUCSY= 49 | 43.133,1.002,0.442,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBFmLgEJMTERHjEuCRERBSERoww5rRuMendYPFRYAA3tCTM= 50 | 44.135,0.998,0.590,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBk+FT0lJTktJSUjOTE1OQGpmnOMdnorGF3WMemxCTIBAEAhCnU= 51 | 45.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWMS0DIyMFOSsNPTEFMSGNA4x+LxidfOp0VjBKcLAAAECLCv4= 52 | 46.131,1.004,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEuMS0VEyMlLSkzGQUJOSkJj6RnjE56WxjNWpik2JgAO34KfQ== 53 | 47.135,0.996,0.475,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUgk2GR0ZOQkSAR4aLS0KKTyNtDqOWxjVGu2fMGlJMTEwANsIJvA== 54 | 48.131,1.950,1803.551,HISTFAAAAKF42pNpmSzMwMD0mQECmCCUnwIDA7ObwY4FDPYfICKsTExMLCysLCxsbEwMTAIsDHIsWTwsbNsZmcqZKpncmayZLIFYnUmWSRoMIbQkEy8TNxQjkwgWJxMrGDJDaews/KIMKBCNSytBZCYqYGHCBNjEiBckoJAFBNhYYADBwipIhkIC0lwcQIBGcHARJqigBkwKCQgICSAIFA75IlwAeB8ZpQ== 55 | 50.081,0.050,0.393,HISTFAAAADl42pNpmSzMwMAgxgABTBDKT4GBgdnNYMcCBvsPEBE2BiYWNiYWZiYGJiZmJg4OLiYuFiYWAMWGBSM= 56 | 50.131,1.001,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2Lj4VAQkuJT45KTkOKSExI68eRgeDvB2MfcxxckwAJD8JyA== 57 | 51.132,0.999,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2NgUFGSkNAQEeJSkuKSmxhAojhZADjKuYiyS4WAAlWgm/ 58 | 52.131,1.002,0.557,HISTFAAAAER42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPUBkWPjEFGSMZKQMJJSEhPgkJiyodjZIHjB+YSvh4mBiYWJkAVc8KVw== 59 | 53.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2HjklJR0VPSUDHTUxJSkJs02MuxhtrLxKHjH6cbEAADjeCuw= 60 | 54.131,1.003,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkNPzMLIw0NLQ0pFTERCTGLT4wpQSVbGFcwynExAQA/uwsC 61 | 55.134,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPUBkWFTUjCy01BQ0VFRUJGSkJjRamiqA5jHmXGIV4ACoyCmo= 62 | 56.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUhk1FzsrAQElFQ0xCQkJOTEDnE6ObxwrGDsYuJjUODiYASN8KbA== 63 | 57.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPMBk5FT0JAzUNKTklKQ0FMaGUJ4wJFjcYk+4wqnAwMAEAQooK6Q== 64 | 58.131,1.002,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEuCRMNJwMlIzUtLR0ZMREZv6IHjFYGdUXLGE14WAA4OwsG 65 | 59.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBklExUdIwcdFRUlOTMZPhWXB4wBTssYsy4xKnGwAQA8bAry 66 | 60.131,1.000,0.524,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmIRcjPR0bFR0lDSk5KQkZpXlMXkF5qxh3MMqIcDIBADy8CoE= 67 | 61.131,1.000,26.083,HISTFAAAAF542pNpmSzMwMAQyAABTBDKT4GBgdnNYMcCBvsPMBkFHSMrCzEZLSUFCSkJOTmTf4xRQW2MYT8Y5diYdjIylTNVMrkzWTJZA7EmkzQYykJpSSZeJm4ghpAQFgATDg85 68 | -------------------------------------------------------------------------------- /src/packedarray/PackedArray.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a TypeScript port of the original Java version, which was written by 3 | * Gil Tene as described in 4 | * https://github.com/HdrHistogram/HdrHistogram 5 | * and released to the public domain, as explained at 6 | * http://creativecommons.org/publicdomain/zero/1.0/ 7 | */ 8 | import { 9 | PackedArrayContext, 10 | MINIMUM_INITIAL_PACKED_ARRAY_CAPACITY 11 | } from "./PackedArrayContext"; 12 | 13 | const NUMBER_OF_SETS = 8; 14 | const { pow, floor } = Math; 15 | 16 | /** 17 | * A Packed array of signed 64 bit values, and supports {@link #get get()}, {@link #set set()}, 18 | * {@link #add add()} and {@link #increment increment()} operations on the logical contents of the array. 19 | * 20 | * An {@link PackedLongArray} Uses {@link PackedArrayContext} to track 21 | * the array's logical contents. Contexts may be switched when a context requires resizing 22 | * to complete logical array operations (get, set, add, increment). Contexts are 23 | * established and used within critical sections in order to facilitate concurrent 24 | * implementors. 25 | * 26 | */ 27 | export class PackedArray { 28 | private arrayContext: PackedArrayContext; 29 | 30 | constructor( 31 | virtualLength: number, 32 | initialPhysicalLength: number = MINIMUM_INITIAL_PACKED_ARRAY_CAPACITY 33 | ) { 34 | this.arrayContext = new PackedArrayContext( 35 | virtualLength, 36 | initialPhysicalLength 37 | ); 38 | } 39 | 40 | public setVirtualLength(newVirtualArrayLength: number) { 41 | if (newVirtualArrayLength < this.length()) { 42 | throw new Error( 43 | "Cannot set virtual length, as requested length " + 44 | newVirtualArrayLength + 45 | " is smaller than the current virtual length " + 46 | this.length() 47 | ); 48 | } 49 | const currentArrayContext = this.arrayContext; 50 | if ( 51 | currentArrayContext.isPacked && 52 | currentArrayContext.determineTopLevelShiftForVirtualLength( 53 | newVirtualArrayLength 54 | ) == currentArrayContext.getTopLevelShift() 55 | ) { 56 | // No changes to the array context contents is needed. Just change the virtual length. 57 | currentArrayContext.setVirtualLength(newVirtualArrayLength); 58 | return; 59 | } 60 | this.arrayContext = currentArrayContext.copyAndIncreaseSize( 61 | this.getPhysicalLength(), 62 | newVirtualArrayLength 63 | ); 64 | } 65 | 66 | /** 67 | * Get value at virtual index in the array 68 | * @param index the virtual array index 69 | * @return the array value at the virtual index given 70 | */ 71 | get(index: number) { 72 | let value = 0; 73 | for (let byteNum = 0; byteNum < NUMBER_OF_SETS; byteNum++) { 74 | let byteValueAtPackedIndex = 0; 75 | 76 | // Deal with unpacked context: 77 | if (!this.arrayContext.isPacked) { 78 | return this.arrayContext.getAtUnpackedIndex(index); 79 | } 80 | // Context is packed: 81 | const packedIndex = this.arrayContext.getPackedIndex( 82 | byteNum, 83 | index, 84 | false 85 | ); 86 | if (packedIndex < 0) { 87 | return value; 88 | } 89 | byteValueAtPackedIndex = 90 | this.arrayContext.getAtByteIndex(packedIndex) * pow(2, byteNum << 3); 91 | value += byteValueAtPackedIndex; 92 | } 93 | return value; 94 | } 95 | 96 | /** 97 | * Increment value at a virrual index in the array 98 | * @param index virtual index of value to increment 99 | */ 100 | public increment(index: number) { 101 | this.add(index, 1); 102 | } 103 | 104 | private safeGetPackedIndexgetPackedIndex( 105 | setNumber: number, 106 | virtualIndex: number 107 | ) { 108 | //do { 109 | //try { 110 | return this.arrayContext.getPackedIndex(setNumber, virtualIndex, true); 111 | /*} catch (ex) { 112 | if (ex instanceof ResizeError) { 113 | this.arrayContext.resizeArray(ex.newSize); 114 | } else { 115 | throw ex; 116 | } 117 | }*/ 118 | //} while (true); 119 | } 120 | 121 | /** 122 | * Add to a value at a virtual index in the array 123 | * @param index the virtual index of the value to be added to 124 | * @param value the value to add 125 | */ 126 | public add(index: number, value: number) { 127 | let remainingValueToAdd = value; 128 | 129 | for ( 130 | let byteNum = 0, byteShift = 0; 131 | byteNum < NUMBER_OF_SETS; 132 | byteNum++, byteShift += 8 133 | ) { 134 | // Deal with unpacked context: 135 | if (!this.arrayContext.isPacked) { 136 | this.arrayContext.addAndGetAtUnpackedIndex(index, value); 137 | return; 138 | } 139 | // Context is packed: 140 | const packedIndex = this.safeGetPackedIndexgetPackedIndex(byteNum, index); 141 | 142 | const byteToAdd = remainingValueToAdd & 0xff; 143 | 144 | const afterAddByteValue = this.arrayContext.addAtByteIndex( 145 | packedIndex, 146 | byteToAdd 147 | ); 148 | 149 | // Reduce remaining value to add by amount just added: 150 | remainingValueToAdd -= byteToAdd; 151 | 152 | remainingValueToAdd = remainingValueToAdd / pow(2, 8); 153 | // Account for carry: 154 | remainingValueToAdd += floor(afterAddByteValue / pow(2, 8)); 155 | 156 | if (remainingValueToAdd == 0) { 157 | return; // nothing to add to higher magnitudes 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * Set the value at a virtual index in the array 164 | * @param index the virtual index of the value to set 165 | * @param value the value to set 166 | */ 167 | set(index: number, value: number) { 168 | let bytesAlreadySet = 0; 169 | let valueForNextLevels = value; 170 | for (let byteNum = 0; byteNum < NUMBER_OF_SETS; byteNum++) { 171 | // Establish context within: critical section 172 | 173 | // Deal with unpacked context: 174 | if (!this.arrayContext.isPacked) { 175 | this.arrayContext.setAtUnpackedIndex(index, value); 176 | return; 177 | } 178 | // Context is packed: 179 | if (valueForNextLevels == 0) { 180 | // Special-case zeros to avoid inflating packed array for no reason 181 | const packedIndex = this.arrayContext.getPackedIndex( 182 | byteNum, 183 | index, 184 | false 185 | ); 186 | if (packedIndex < 0) { 187 | return; // no need to create entries for zero values if they don't already exist 188 | } 189 | } 190 | // Make sure byte is populated: 191 | const packedIndex = this.arrayContext.getPackedIndex( 192 | byteNum, 193 | index, 194 | true 195 | ); 196 | 197 | // Determine value to write, and prepare for next levels 198 | const byteToWrite = valueForNextLevels & 0xff; 199 | valueForNextLevels = floor(valueForNextLevels / pow(2, 8)); 200 | 201 | if (byteNum < bytesAlreadySet) { 202 | // We want to avoid writing to the same byte twice when not doing so for the 203 | // entire 64 bit value atomically, as doing so opens a race with e.g. concurrent 204 | // adders. So dobn't actually write the byte if has been written before. 205 | continue; 206 | } 207 | this.arrayContext.setAtByteIndex(packedIndex, byteToWrite); 208 | bytesAlreadySet++; 209 | } 210 | } 211 | 212 | /** 213 | * Get the current physical length (in longs) of the array's backing storage 214 | * @return the current physical length (in longs) of the array's current backing storage 215 | */ 216 | getPhysicalLength() { 217 | return this.arrayContext.physicalLength; 218 | } 219 | 220 | /** 221 | * Get the (virtual) length of the array 222 | * @return the (virtual) length of the array 223 | */ 224 | length() { 225 | return this.arrayContext.getVirtualLength(); 226 | } 227 | 228 | /** 229 | * Clear the array contents 230 | */ 231 | public clear() { 232 | this.arrayContext.clear(); 233 | } 234 | 235 | public toString() { 236 | let output = "PackedArray:\n"; 237 | output += this.arrayContext.toString(); 238 | return output; 239 | } 240 | } 241 | --------------------------------------------------------------------------------