diff --git a/web/app/js/components/ErrorBanner.jsx b/web/app/js/components/ErrorBanner.jsx
index 11791e52b..5568393ab 100644
--- a/web/app/js/components/ErrorBanner.jsx
+++ b/web/app/js/components/ErrorBanner.jsx
@@ -50,7 +50,6 @@ class ErrorMessage extends React.Component {
Error: {status} {statusText}
{ !error ? null : Message: {error}
}
URL: {url}
-
Dismiss X
diff --git a/web/app/js/components/TapEventTable.jsx b/web/app/js/components/TapEventTable.jsx
index b8cfc0d27..3ff77b4ad 100644
--- a/web/app/js/components/TapEventTable.jsx
+++ b/web/app/js/components/TapEventTable.jsx
@@ -1,6 +1,6 @@
import _ from 'lodash';
import BaseTable from './BaseTable.jsx';
-import { formatLatency } from './util/Utils.js';
+import { formatLatencySec } from './util/Utils.js';
import React from 'react';
import { Col, Icon, Row } from 'antd';
@@ -157,9 +157,8 @@ let tapColumns = filterOptions => [
}
];
-let formatTapLatency = str => {
- let millis = parseFloat(str.replace("s", "")) * 1000;
- return formatLatency(millis);
+const formatTapLatency = str => {
+ return formatLatencySec(str.replace("s", ""));
};
// hide verbose information
diff --git a/web/app/js/components/util/ApiHelpers.jsx b/web/app/js/components/util/ApiHelpers.jsx
index 8986b77a9..076d34f38 100644
--- a/web/app/js/components/util/ApiHelpers.jsx
+++ b/web/app/js/components/util/ApiHelpers.jsx
@@ -5,7 +5,9 @@ import React from 'react';
import 'whatwg-fetch';
const checkFetchOk = resp => {
- if (resp.ok) { return resp; }
+ if (resp.ok) {
+ return resp;
+ }
return resp.json().then(error => {
throw {
@@ -15,7 +17,6 @@ const checkFetchOk = resp => {
error: error.error
};
});
-
};
// makeCancelable from @istarkov
@@ -43,7 +44,7 @@ const makeCancelable = (promise, onSuccess) => {
};
};
-export const apiErrorPropType = PropTypes.shape({
+export const apiErrorPropType = PropTypes.shape({
status: PropTypes.number,
url: PropTypes.string,
statusText: PropTypes.string,
@@ -115,7 +116,6 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
});
};
-
// prefix all links in the app with `pathPrefix`
class PrefixedLink extends React.Component {
static defaultProps = {
diff --git a/web/app/js/components/util/Utils.js b/web/app/js/components/util/Utils.js
index 308b252c5..a5c739206 100644
--- a/web/app/js/components/util/Utils.js
+++ b/web/app/js/components/util/Utils.js
@@ -11,23 +11,37 @@ export const rowGutter = 3 * baseWidth;
* Number formatters
*/
const successRateFormatter = d3.format(".2%");
-const latencySecFormatter = d3.format(".3f");
const latencyFormatter = d3.format(",");
-export const formatLatency = m => {
+export const formatLatencyMs = m => {
if (_.isNil(m)) {
return "---";
- } else if (m < 1000) {
- return `${latencyFormatter(m)} ms`;
} else {
- return `${latencySecFormatter(m / 1000)} s`;
+ return `${formatLatencySec(m / 1000)}`;
+ }
+};
+
+const niceLatency = l => latencyFormatter(Math.round(l));
+
+export const formatLatencySec = latency => {
+ let s = parseFloat(latency);
+ if (_.isNil(s)) {
+ return "---";
+ } else if (s === parseFloat(0.0)) {
+ return "0 s";
+ } else if (s < 0.001) {
+ return `${niceLatency(s * 1000 * 1000)} µs`;
+ } else if (s < 1.0) {
+ return `${niceLatency(s * 1000)} ms`;
+ } else {
+ return `${niceLatency(s)} s`;
}
};
export const metricToFormatter = {
"REQUEST_RATE": m => _.isNil(m) ? "---" : styleNum(m, " RPS", true),
"SUCCESS_RATE": m => _.isNil(m) ? "---" : successRateFormatter(m),
- "LATENCY": formatLatency,
+ "LATENCY": formatLatencyMs,
"UNTRUNCATED": m => styleNum(m, "", false)
};
diff --git a/web/app/js/components/util/withREST.jsx b/web/app/js/components/util/withREST.jsx
index 843a08d6c..a49f962a6 100644
--- a/web/app/js/components/util/withREST.jsx
+++ b/web/app/js/components/util/withREST.jsx
@@ -121,4 +121,4 @@ const withREST = (WrappedComponent, componentPromises, options={}) => {
return withContext(RESTWrapper);
};
-export default withREST;
\ No newline at end of file
+export default withREST;
diff --git a/web/app/test/UtilsTest.js b/web/app/test/UtilsTest.js
index c46d1a6c7..35cc356dc 100644
--- a/web/app/test/UtilsTest.js
+++ b/web/app/test/UtilsTest.js
@@ -1,5 +1,10 @@
import { expect } from 'chai';
-import { metricToFormatter, styleNum, toClassName } from '../js/components/util/Utils.js';
+import {
+ formatLatencySec,
+ metricToFormatter,
+ styleNum,
+ toClassName
+} from '../js/components/util/Utils.js';
// introduce some binary floating point rounding errors, like ya do
function float(num) {
@@ -74,9 +79,9 @@ describe('Utils', () => {
});
it('formats latency greater than 1s as s', () => {
- expect(metricToFormatter["LATENCY"](1000)).to.equal('1.000 s');
- expect(metricToFormatter["LATENCY"](9999)).to.equal('9.999 s');
- expect(metricToFormatter["LATENCY"](99999)).to.equal('99.999 s');
+ expect(metricToFormatter["LATENCY"](1000)).to.equal('1 s');
+ expect(metricToFormatter["LATENCY"](9999)).to.equal('10 s');
+ expect(metricToFormatter["LATENCY"](99999)).to.equal('100 s');
});
it('formats success rate', () => {
@@ -86,6 +91,16 @@ describe('Utils', () => {
expect(metricToFormatter["SUCCESS_RATE"](0.9999)).to.equal('99.99%');
expect(metricToFormatter["SUCCESS_RATE"](4)).to.equal('400.00%');
});
+
+ it('formats latencies expressed as seconds into a more appropriate display unit', () => {
+ expect(formatLatencySec("0.002837700")).to.equal("3 ms");
+ expect(formatLatencySec("0.000")).to.equal("0 s");
+ expect(formatLatencySec("0.000000797")).to.equal("1 µs");
+ expect(formatLatencySec("0.000231910")).to.equal("232 µs");
+ expect(formatLatencySec("0.000988600")).to.equal("989 µs");
+ expect(formatLatencySec("0.005598200")).to.equal("6 ms");
+ expect(formatLatencySec("3.029409200")).to.equal("3 s");
+ });
});
describe('toClassName', () => {