diff --git a/lib/index.html b/lib/index.html
index 2fa8fab..5807dfc 100644
--- a/lib/index.html
+++ b/lib/index.html
@@ -580,7 +580,7 @@
Available libs
https://k6.io/docs/javascript-api/jslib/utils |
k6-summary |
- 0.0.1, 0.0.2, 0.0.3 |
+ 0.0.1, 0.0.2, 0.0.3, 0.0.4 |
https://github.com/grafana/k6-jslib-summary |
jsonpath |
diff --git a/lib/k6-summary/0.0.4/index.js b/lib/k6-summary/0.0.4/index.js
new file mode 100644
index 0000000..78a0cf7
--- /dev/null
+++ b/lib/k6-summary/0.0.4/index.js
@@ -0,0 +1,506 @@
+var forEach = function (obj, callback) {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (callback(key, obj[key])) {
+ break;
+ }
+ }
+ }
+};
+
+var palette = {
+ bold: 1,
+ faint: 2,
+ red: 31,
+ green: 32,
+ cyan: 36,
+ //TODO: add others?
+};
+
+var groupPrefix = '█';
+var detailsPrefix = '↳';
+var succMark = '✓';
+var failMark = '✗';
+var defaultOptions = {
+ indent: ' ',
+ enableColors: true,
+ summaryTimeUnit: null,
+ summaryTrendStats: null,
+};
+
+// strWidth tries to return the actual width the string will take up on the
+// screen, without any terminal formatting, unicode ligatures, etc.
+function strWidth(s) {
+ // TODO: determine if NFC or NFKD are not more appropriate? or just give up? https://hsivonen.fi/string-length/
+ var data = s.normalize('NFKC'); // This used to be NFKD in Go, but this should be better
+ var inEscSeq = false;
+ var inLongEscSeq = false;
+ var width = 0;
+ for (var char of data) {
+ if (char.done) {
+ break;
+ }
+
+ // Skip over ANSI escape codes.
+ if (char == '\x1b') {
+ inEscSeq = true;
+ continue;
+ }
+ if (inEscSeq && char == '[') {
+ inLongEscSeq = true;
+ continue;
+ }
+ if (
+ inEscSeq &&
+ inLongEscSeq &&
+ char.charCodeAt(0) >= 0x40 &&
+ char.charCodeAt(0) <= 0x7e
+ ) {
+ inEscSeq = false;
+ inLongEscSeq = false;
+ continue;
+ }
+ if (
+ inEscSeq &&
+ !inLongEscSeq &&
+ char.charCodeAt(0) >= 0x40 &&
+ char.charCodeAt(0) <= 0x5f
+ ) {
+ inEscSeq = false;
+ continue;
+ }
+
+ if (!inEscSeq && !inLongEscSeq) {
+ width++;
+ }
+ }
+ return width;
+}
+
+function summarizeCheck(indent, check, decorate) {
+ if (check.fails == 0) {
+ return decorate(indent + succMark + ' ' + check.name, palette.green);
+ }
+
+ var succPercent = Math.floor(
+ (100 * check.passes) / (check.passes + check.fails),
+ );
+ return decorate(
+ indent +
+ failMark +
+ ' ' +
+ check.name +
+ '\n' +
+ indent +
+ ' ' +
+ detailsPrefix +
+ ' ' +
+ succPercent +
+ '% — ' +
+ succMark +
+ ' ' +
+ check.passes +
+ ' / ' +
+ failMark +
+ ' ' +
+ check.fails,
+ palette.red,
+ );
+}
+
+function summarizeGroup(indent, group, decorate) {
+ var result = [];
+ if (group.name != '') {
+ result.push(indent + groupPrefix + ' ' + group.name + '\n');
+ indent = indent + ' ';
+ }
+
+ for (var i = 0; i < group.checks.length; i++) {
+ result.push(summarizeCheck(indent, group.checks[i], decorate));
+ }
+ if (group.checks.length > 0) {
+ result.push('');
+ }
+ for (var i = 0; i < group.groups.length; i++) {
+ Array.prototype.push.apply(
+ result,
+ summarizeGroup(indent, group.groups[i], decorate),
+ );
+ }
+
+ return result;
+}
+
+function displayNameForMetric(name) {
+ var subMetricPos = name.indexOf('{');
+ if (subMetricPos >= 0) {
+ return '{ ' + name.substring(subMetricPos + 1, name.length - 1) + ' }';
+ }
+ return name;
+}
+
+function indentForMetric(name) {
+ if (name.indexOf('{') >= 0) {
+ return ' ';
+ }
+ return '';
+}
+
+function humanizeBytes(bytes) {
+ var units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+ var base = 1000;
+ if (bytes < 10) {
+ return bytes + ' B';
+ }
+
+ var e = Math.floor(Math.log(bytes) / Math.log(base));
+ var suffix = units[e | 0];
+ var val = Math.floor((bytes / Math.pow(base, e)) * 10 + 0.5) / 10;
+ return val.toFixed(val < 10 ? 1 : 0) + ' ' + suffix;
+}
+
+var unitMap = {
+ s: { unit: 's', coef: 0.001 },
+ ms: { unit: 'ms', coef: 1 },
+ us: { unit: 'µs', coef: 1000 },
+};
+
+function toFixedNoTrailingZeros(val, prec) {
+ // TODO: figure out something better?
+ return parseFloat(val.toFixed(prec)).toString();
+}
+
+function toFixedNoTrailingZerosTrunc(val, prec) {
+ var mult = Math.pow(10, prec);
+ return toFixedNoTrailingZeros(Math.trunc(mult * val) / mult, prec);
+}
+
+function humanizeGenericDuration(dur) {
+ if (dur === 0) {
+ return '0s';
+ }
+
+ if (dur < 0.001) {
+ // smaller than a microsecond, print nanoseconds
+ return Math.trunc(dur * 1000000) + 'ns';
+ }
+ if (dur < 1) {
+ // smaller than a millisecond, print microseconds
+ return toFixedNoTrailingZerosTrunc(dur * 1000, 2) + 'µs';
+ }
+ if (dur < 1000) {
+ // duration is smaller than a second
+ return toFixedNoTrailingZerosTrunc(dur, 2) + 'ms';
+ }
+
+ var result =
+ toFixedNoTrailingZerosTrunc((dur % 60000) / 1000, dur > 60000 ? 0 : 2) +
+ 's';
+ var rem = Math.trunc(dur / 60000);
+ if (rem < 1) {
+ // less than a minute
+ return result;
+ }
+ result = (rem % 60) + 'm' + result;
+ rem = Math.trunc(rem / 60);
+ if (rem < 1) {
+ // less than an hour
+ return result;
+ }
+ return rem + 'h' + result;
+}
+
+function humanizeDuration(dur, timeUnit) {
+ if (timeUnit !== '' && unitMap.hasOwnProperty(timeUnit)) {
+ return (dur * unitMap[timeUnit].coef).toFixed(2) + unitMap[timeUnit].unit;
+ }
+
+ return humanizeGenericDuration(dur);
+}
+
+function humanizeValue(val, metric, timeUnit) {
+ if (metric.type == 'rate') {
+ // Truncate instead of round when decreasing precision to 2 decimal places
+ return (Math.trunc(val * 100 * 100) / 100).toFixed(2) + '%';
+ }
+
+ switch (metric.contains) {
+ case 'data':
+ return humanizeBytes(val);
+ case 'time':
+ return humanizeDuration(val, timeUnit);
+ default:
+ return toFixedNoTrailingZeros(val, 6);
+ }
+}
+
+function nonTrendMetricValueForSum(metric, timeUnit) {
+ switch (metric.type) {
+ case 'counter':
+ return [
+ humanizeValue(metric.values.count, metric, timeUnit),
+ humanizeValue(metric.values.rate, metric, timeUnit) + '/s',
+ ];
+ case 'gauge':
+ return [
+ humanizeValue(metric.values.value, metric, timeUnit),
+ 'min=' + humanizeValue(metric.values.min, metric, timeUnit),
+ 'max=' + humanizeValue(metric.values.max, metric, timeUnit),
+ ];
+ case 'rate':
+ return [
+ humanizeValue(metric.values.rate, metric, timeUnit),
+ succMark + ' ' + metric.values.passes,
+ failMark + ' ' + metric.values.fails,
+ ];
+ default:
+ return ['[no data]'];
+ }
+}
+
+function summarizeMetrics(options, data, decorate) {
+ var indent = options.indent + ' ';
+ var result = [];
+
+ var names = [];
+ var nameLenMax = 0;
+
+ var nonTrendValues = {};
+ var nonTrendValueMaxLen = 0;
+ var nonTrendExtras = {};
+ var nonTrendExtraMaxLens = [0, 0];
+
+ var trendCols = {};
+ var numTrendColumns = options.summaryTrendStats.length;
+ var trendColMaxLens = new Array(numTrendColumns).fill(0);
+ forEach(data.metrics, function (name, metric) {
+ names.push(name);
+ // When calculating widths for metrics, account for the indentation on submetrics.
+ var displayName = indentForMetric(name) + displayNameForMetric(name);
+ var displayNameWidth = strWidth(displayName);
+ if (displayNameWidth > nameLenMax) {
+ nameLenMax = displayNameWidth;
+ }
+
+ if (metric.type == 'trend') {
+ var cols = [];
+ for (var i = 0; i < numTrendColumns; i++) {
+ var tc = options.summaryTrendStats[i];
+ var value = metric.values[tc];
+ if (tc === 'count') {
+ value = value.toString();
+ } else {
+ value = humanizeValue(value, metric, options.summaryTimeUnit);
+ }
+ var valLen = strWidth(value);
+ if (valLen > trendColMaxLens[i]) {
+ trendColMaxLens[i] = valLen;
+ }
+ cols[i] = value;
+ }
+ trendCols[name] = cols;
+ return;
+ }
+ var values = nonTrendMetricValueForSum(metric, options.summaryTimeUnit);
+ nonTrendValues[name] = values[0];
+ var valueLen = strWidth(values[0]);
+ if (valueLen > nonTrendValueMaxLen) {
+ nonTrendValueMaxLen = valueLen;
+ }
+ nonTrendExtras[name] = values.slice(1);
+ for (var i = 1; i < values.length; i++) {
+ var extraLen = strWidth(values[i]);
+ if (extraLen > nonTrendExtraMaxLens[i - 1]) {
+ nonTrendExtraMaxLens[i - 1] = extraLen;
+ }
+ }
+ });
+
+ // sort all metrics but keep sub metrics grouped with their parent metrics
+ names.sort(function (metric1, metric2) {
+ var parent1 = metric1.split('{', 1)[0];
+ var parent2 = metric2.split('{', 1)[0];
+ var result = parent1.localeCompare(parent2);
+ if (result !== 0) {
+ return result;
+ }
+ var sub1 = metric1.substring(parent1.length);
+ var sub2 = metric2.substring(parent2.length);
+ return sub1.localeCompare(sub2);
+ });
+
+ var getData = function (name) {
+ if (trendCols.hasOwnProperty(name)) {
+ var cols = trendCols[name];
+ var tmpCols = new Array(numTrendColumns);
+ for (var i = 0; i < cols.length; i++) {
+ tmpCols[i] =
+ options.summaryTrendStats[i] +
+ '=' +
+ decorate(cols[i], palette.cyan) +
+ ' '.repeat(trendColMaxLens[i] - strWidth(cols[i]));
+ }
+ return tmpCols.join(' ');
+ }
+
+ var value = nonTrendValues[name];
+ var fmtData =
+ decorate(value, palette.cyan) +
+ ' '.repeat(nonTrendValueMaxLen - strWidth(value));
+
+ var extras = nonTrendExtras[name];
+ if (extras.length == 1) {
+ fmtData =
+ fmtData + ' ' + decorate(extras[0], palette.cyan, palette.faint);
+ } else if (extras.length > 1) {
+ var parts = new Array(extras.length);
+ for (var i = 0; i < extras.length; i++) {
+ parts[i] =
+ decorate(extras[i], palette.cyan, palette.faint) +
+ ' '.repeat(nonTrendExtraMaxLens[i] - strWidth(extras[i]));
+ }
+ fmtData = fmtData + ' ' + parts.join(' ');
+ }
+
+ return fmtData;
+ };
+
+ for (var name of names) {
+ var metric = data.metrics[name];
+ var mark = ' ';
+ var markColor = function (text) {
+ return text;
+ }; // noop
+
+ if (metric.thresholds) {
+ mark = succMark;
+ markColor = function (text) {
+ return decorate(text, palette.green);
+ };
+ forEach(metric.thresholds, function (name, threshold) {
+ if (!threshold.ok) {
+ mark = failMark;
+ markColor = function (text) {
+ return decorate(text, palette.red);
+ };
+ return true; // break
+ }
+ });
+ }
+ var fmtIndent = indentForMetric(name);
+ var fmtName = displayNameForMetric(name);
+ fmtName =
+ fmtName +
+ decorate(
+ '.'.repeat(nameLenMax - strWidth(fmtName) - strWidth(fmtIndent) + 3) +
+ ':',
+ palette.faint,
+ );
+
+ result.push(
+ indent +
+ fmtIndent +
+ markColor(mark) +
+ ' ' +
+ fmtName +
+ ' ' +
+ getData(name),
+ );
+ }
+
+ return result;
+}
+
+function generateTextSummary(data, options) {
+ var mergedOpts = Object.assign({}, defaultOptions, data.options, options);
+ var lines = [];
+
+ // TODO: move all of these functions into an object with methods?
+ var decorate = function (text) {
+ return text;
+ };
+ if (mergedOpts.enableColors) {
+ decorate = function (text, color /*, ...rest*/) {
+ var result = '\x1b[' + color;
+ for (var i = 2; i < arguments.length; i++) {
+ result += ';' + arguments[i];
+ }
+ return result + 'm' + text + '\x1b[0m';
+ };
+ }
+
+ Array.prototype.push.apply(
+ lines,
+ summarizeGroup(mergedOpts.indent + ' ', data.root_group, decorate),
+ );
+
+ Array.prototype.push.apply(
+ lines,
+ summarizeMetrics(mergedOpts, data, decorate),
+ );
+
+ return lines.join('\n');
+}
+
+exports.humanizeValue = humanizeValue;
+exports.textSummary = generateTextSummary;
+
+const replacements = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''',
+ '"': '"',
+};
+
+function escapeHTML(str) {
+ return str.replace(/[&<>'"]/g, (char) => replacements[char]);
+}
+
+function generateJUnitXML(data, options) {
+ let failures = 0;
+ let cases = [];
+
+ Object.entries(data.metrics).forEach(([metricName, metric]) => {
+ if (!metric.thresholds) {
+ return;
+ }
+ Object.entries(metric.thresholds).forEach(([thresholdName, threshold]) => {
+ if (threshold.ok) {
+ cases.push(
+ ``,
+ );
+ } else {
+ failures++;
+ const failureMessage =
+ `">';
+
+ cases.push(
+ `
+
+
+ ${cases.join('\n')}
+
+ `;
+}
+
+exports.jUnit = generateJUnitXML;
diff --git a/supported.json b/supported.json
index 685a2ad..dd96284 100644
--- a/supported.json
+++ b/supported.json
@@ -4,7 +4,7 @@
"docs-url": "https://k6.io/docs/javascript-api/jslib/utils"
},
"k6-summary": {
- "versions": ["0.0.1", "0.0.2", "0.0.3"],
+ "versions": ["0.0.1", "0.0.2", "0.0.3", "0.0.4"],
"docs-url": "https://github.com/grafana/k6-jslib-summary"
},
"jsonpath": {