152 lines
7 KiB
JavaScript
152 lines
7 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.listTags = void 0;
|
||
|
/*
|
||
|
* This is a TypeScript port of the original Java version, which was written by
|
||
|
* Gil Tene as described in
|
||
|
* https://github.com/HdrHistogram/HdrHistogram
|
||
|
* and released to the public domain, as explained at
|
||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||
|
*/
|
||
|
const Histogram_1 = require("./Histogram");
|
||
|
const encoding_1 = require("./encoding");
|
||
|
const TAG_PREFIX = "Tag=";
|
||
|
const TAG_PREFIX_LENGTH = "Tag=".length;
|
||
|
/**
|
||
|
* A histogram log reader.
|
||
|
* <p>
|
||
|
* Histogram logs are used to capture full fidelity, per-time-interval
|
||
|
* histograms of a recorded value.
|
||
|
* <p>
|
||
|
* For example, a histogram log can be used to capture high fidelity
|
||
|
* reaction-time logs for some measured system or subsystem component.
|
||
|
* Such a log would capture a full reaction time histogram for each
|
||
|
* logged interval, and could be used to later reconstruct a full
|
||
|
* HdrHistogram of the measured reaction time behavior for any arbitrary
|
||
|
* time range within the log, by adding [only] the relevant interval
|
||
|
* histograms.
|
||
|
* <h3>Histogram log format:</h3>
|
||
|
* A histogram log file consists of text lines. Lines beginning with
|
||
|
* the "#" character are optional and treated as comments. Lines
|
||
|
* containing the legend (starting with "Timestamp") are also optional
|
||
|
* and ignored in parsing the histogram log. All other lines must
|
||
|
* be valid interval description lines. Text fields are delimited by
|
||
|
* commas, spaces.
|
||
|
* <p>
|
||
|
* A valid interval description line contains an optional Tag=tagString
|
||
|
* text field, followed by an interval description.
|
||
|
* <p>
|
||
|
* A valid interval description must contain exactly four text fields:
|
||
|
* <ul>
|
||
|
* <li>StartTimestamp: The first field must contain a number parse-able as a Double value,
|
||
|
* representing the start timestamp of the interval in seconds.</li>
|
||
|
* <li>intervalLength: The second field must contain a number parse-able as a Double value,
|
||
|
* representing the length of the interval in seconds.</li>
|
||
|
* <li>Interval_Max: The third field must contain a number parse-able as a Double value,
|
||
|
* which generally represents the maximum value of the interval histogram.</li>
|
||
|
* <li>Interval_Compressed_Histogram: The fourth field must contain a text field
|
||
|
* parse-able as a Base64 text representation of a compressed HdrHistogram.</li>
|
||
|
* </ul>
|
||
|
* The log file may contain an optional indication of a starting time. Starting time
|
||
|
* is indicated using a special comments starting with "#[StartTime: " and followed
|
||
|
* by a number parse-able as a double, representing the start time (in seconds)
|
||
|
* that may be added to timestamps in the file to determine an absolute
|
||
|
* timestamp (e.g. since the epoch) for each interval.
|
||
|
*/
|
||
|
class HistogramLogReader {
|
||
|
constructor(logContent, bitBucketSize = 32, useWebAssembly = false) {
|
||
|
this.lines = splitLines(logContent);
|
||
|
this.currentLineIndex = 0;
|
||
|
this.bitBucketSize = bitBucketSize;
|
||
|
this.useWebAssembly = useWebAssembly;
|
||
|
}
|
||
|
/**
|
||
|
* Read the next interval histogram from the log. Returns a Histogram object if
|
||
|
* an interval line was found, or null if not.
|
||
|
* <p>Upon encountering any unexpected format errors in reading the next interval
|
||
|
* from the file, this method will return a null.
|
||
|
* @return a DecodedInterval, or a null if no appropriate interval found
|
||
|
*/
|
||
|
nextIntervalHistogram(rangeStartTimeSec = 0, rangeEndTimeSec = Number.MAX_VALUE) {
|
||
|
while (this.currentLineIndex < this.lines.length) {
|
||
|
const currentLine = this.lines[this.currentLineIndex];
|
||
|
this.currentLineIndex++;
|
||
|
if (currentLine.startsWith("#[StartTime:")) {
|
||
|
this.parseStartTimeFromLine(currentLine);
|
||
|
}
|
||
|
else if (currentLine.startsWith("#[BaseTime:")) {
|
||
|
this.parseBaseTimeFromLine(currentLine);
|
||
|
}
|
||
|
else if (currentLine.startsWith("#") ||
|
||
|
currentLine.startsWith('"StartTimestamp"')) {
|
||
|
// skip legend & meta data for now
|
||
|
}
|
||
|
else if (currentLine.includes(",")) {
|
||
|
const tokens = currentLine.split(",");
|
||
|
const [firstToken] = tokens;
|
||
|
let tag;
|
||
|
if (firstToken.startsWith(TAG_PREFIX)) {
|
||
|
tag = firstToken.substring(TAG_PREFIX_LENGTH);
|
||
|
tokens.shift();
|
||
|
}
|
||
|
else {
|
||
|
tag = Histogram_1.NO_TAG;
|
||
|
}
|
||
|
const [rawLogTimeStampInSec, rawIntervalLengthSec, , base64Histogram,] = tokens;
|
||
|
const logTimeStampInSec = Number.parseFloat(rawLogTimeStampInSec);
|
||
|
if (!this.baseTimeSec) {
|
||
|
// No explicit base time noted. Deduce from 1st observed time (compared to start time):
|
||
|
if (logTimeStampInSec < this.startTimeSec - 365 * 24 * 3600.0) {
|
||
|
// Criteria Note: if log timestamp is more than a year in the past (compared to
|
||
|
// StartTime), we assume that timestamps in the log are not absolute
|
||
|
this.baseTimeSec = this.startTimeSec;
|
||
|
}
|
||
|
else {
|
||
|
// Timestamps are absolute
|
||
|
this.baseTimeSec = 0.0;
|
||
|
}
|
||
|
}
|
||
|
if (rangeEndTimeSec < logTimeStampInSec) {
|
||
|
return null;
|
||
|
}
|
||
|
if (logTimeStampInSec < rangeStartTimeSec) {
|
||
|
continue;
|
||
|
}
|
||
|
const histogram = encoding_1.decodeFromCompressedBase64(base64Histogram, this.bitBucketSize, this.useWebAssembly);
|
||
|
histogram.startTimeStampMsec =
|
||
|
(this.baseTimeSec + logTimeStampInSec) * 1000;
|
||
|
const intervalLengthSec = Number.parseFloat(rawIntervalLengthSec);
|
||
|
histogram.endTimeStampMsec =
|
||
|
(this.baseTimeSec + logTimeStampInSec + intervalLengthSec) * 1000;
|
||
|
histogram.tag = tag;
|
||
|
return histogram;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
parseStartTimeFromLine(line) {
|
||
|
this.startTimeSec = Number.parseFloat(line.split(" ")[1]);
|
||
|
}
|
||
|
parseBaseTimeFromLine(line) {
|
||
|
this.baseTimeSec = Number.parseFloat(line.split(" ")[1]);
|
||
|
}
|
||
|
}
|
||
|
const splitLines = (logContent) => logContent.split(/\r\n|\r|\n/g);
|
||
|
const shouldIncludeNoTag = (lines) => lines.find((line) => !line.startsWith("#") &&
|
||
|
!line.startsWith('"') &&
|
||
|
!line.startsWith(TAG_PREFIX) &&
|
||
|
line.includes(","));
|
||
|
exports.listTags = (content) => {
|
||
|
const lines = splitLines(content);
|
||
|
const tags = lines
|
||
|
.filter((line) => line.includes(",") && line.startsWith(TAG_PREFIX))
|
||
|
.map((line) => line.substring(TAG_PREFIX_LENGTH, line.indexOf(",")));
|
||
|
const tagsWithoutDuplicates = new Set(tags);
|
||
|
const result = Array.from(tagsWithoutDuplicates);
|
||
|
if (shouldIncludeNoTag(lines)) {
|
||
|
result.unshift("NO TAG");
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
exports.default = HistogramLogReader;
|
||
|
//# sourceMappingURL=HistogramLogReader.js.map
|