Skip to content

Commit

Permalink
improves duration formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
petethepig committed Jan 19, 2021
1 parent c9423b8 commit c9b75d5
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 32 deletions.
22 changes: 22 additions & 0 deletions webapp/__tests__/format.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {DurationFormater} from '../javascript/util/format';


describe('DurationFormater', () => {
it('correctly formats duration', () => {
const df = new DurationFormater(40);
expect(df.format(0.00001)).toBe('< 0.01 seconds');
expect(df.format(1)).toBe('1 second');
expect(df.format(20)).toBe('20 seconds');
expect(df.format(20.123)).toBe('20.12 seconds');
expect(df.format(80)).toBe('80 seconds');
});

it('correctly formats duration', () => {
const df = new DurationFormater(80);
expect(df.format(60)).toBe('1 minute');
expect(df.format(1)).toBe('0.02 minutes');
expect(df.format(20)).toBe('0.33 minutes');
expect(df.format(20.123)).toBe('0.34 minutes');
expect(df.format(80)).toBe('1.33 minutes');
});
});
15 changes: 10 additions & 5 deletions webapp/javascript/components/FlameGraphRenderer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import MaxNodesSelector from "./MaxNodesSelector";
import clsx from "clsx";

import {colorBasedOnPackageName, colorGreyscale} from '../util/color';
import {numberWithCommas, shortNumber, formatPercent, formatDuration, formatDurationLong} from '../util/format';
import {numberWithCommas, shortNumber, formatPercent, DurationFormater} from '../util/format';
import {bindActionCreators} from "redux";

import { buildRenderURL } from "../util/update_requests";
Expand Down Expand Up @@ -268,6 +268,7 @@ class FlameGraphRenderer extends React.Component {
this.ctx.textBaseline = 'middle';
this.ctx.font = '400 12px system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';

const df = new DurationFormater(this.numTicks / this.sampleRate);
// i = level
for (let i = 0; i < levels.length - this.topLevel; i++) {
const level = levels[this.topLevel + i];
Expand Down Expand Up @@ -332,7 +333,7 @@ class FlameGraphRenderer extends React.Component {

if (!collapsed && sw >= LABEL_THRESHOLD) {
const percent = formatPercent(ratio);
const name = `${names[level[j + 3]]} (${percent}, ${formatDuration(numBarTicks, sampleRate)})`;
const name = `${names[level[j + 3]]} (${percent}, ${df.format(numBarTicks / sampleRate)})`;

this.ctx.save();
this.ctx.clip();
Expand Down Expand Up @@ -367,6 +368,8 @@ class FlameGraphRenderer extends React.Component {
tooltipEl.children[0].innerText = tooltipTitle;
const tooltipWidth = tooltipEl.clientWidth;

const df = new DurationFormater(this.numTicks / this.sampleRate);

this.setState({
highlightStyle: {
display: 'block',
Expand All @@ -381,7 +384,7 @@ class FlameGraphRenderer extends React.Component {
top: (this.canvas.offsetTop + e.nativeEvent.offsetY + 12) + 'px',
},
tooltipTitle: tooltipTitle,
tooltipSubtitle: `${percent}, ${numberWithCommas(numBarTicks)} samples, ${formatDuration(numBarTicks, this.sampleRate)}`,
tooltipSubtitle: `${percent}, ${numberWithCommas(numBarTicks)} samples, ${df.format(numBarTicks / this.sampleRate)}`,
});
}

Expand Down Expand Up @@ -458,6 +461,8 @@ class FlameGraphRenderer extends React.Component {
sorted = table.sort((a, b) => m * (a[sortBy] - b[sortBy]));
}

const df = new DurationFormater(this.numTicks / this.sampleRate);

return sorted.map((x) => {
const pn = this.getPackageNameFromStackTrace(spyName, x.name);
const color = colorBasedOnPackageName(pn, 1);
Expand All @@ -474,14 +479,14 @@ class FlameGraphRenderer extends React.Component {
&nbsp;
<span>{ shortNumber(x.self) }</span>
&nbsp; */}
<span title={formatDurationLong(x.self, sampleRate)}>{ formatDuration(x.self, sampleRate) }</span>
<span title={df.format(x.self / sampleRate)}>{ df.format(x.self / sampleRate) }</span>
</td>
<td style={ backgroundImageStyle(x.total, numTicks, color) }>
{/* <span>{ formatPercent(x.total / numTicks) }</span>
&nbsp;
<span>{ shortNumber(x.total) }</span>
&nbsp; */}
<span title={formatDuration(x.total, sampleRate)}>{ formatDuration(x.total, sampleRate) }</span>
<span title={df.format(x.total / sampleRate)}>{ df.format(x.total / sampleRate) }</span>
</td>
</tr>;
});
Expand Down
60 changes: 33 additions & 27 deletions webapp/javascript/util/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ export function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

import humanizeDuration from "humanize-duration";

const suffixes = [
"K",
"M",
"G",
"T",
]
];

export function shortNumber(x) {
let suffix = '';
Expand All @@ -27,29 +25,37 @@ export function formatPercent(ratio) {
return percent+'%';
}

const shortEnglishHumanizer = humanizeDuration.humanizer({
language: "shortEn",
languages: {
shortEn: {
y: () => "y",
mo: () => "mo",
w: () => "w",
d: () => "d",
h: () => "h",
m: () => "m",
s: () => "s",
ms: () => "ms",
},
},
});


export function formatDuration(v, sampleRate) {
const rounded = v * (1 / sampleRate) * 1000;
return humanizeDuration(rounded, { largest: 1, maxDecimalPoints: 2 });
}
const durations = [
[60, "minute"],
[60, "hour"],
[24, "day"],
[30, "month"],
[12, "year"],
];

// this is a class and not a function because we can save some time by
// precalculating divider and suffix and not doing it on each iteration
export class DurationFormater {
constructor(maxDur) {
this.divider = 1;
this.suffix = 'second';
for(var i = 0; i < durations.length; i++) {
if (maxDur >= durations[i][0]) {
this.divider *= durations[i][0];
maxDur /= durations[i][0];
this.suffix = durations[i][1];
} else {
break;
}
}
}

export function formatDurationLong(v, sampleRate) {
const rounded = v * (1 / sampleRate) * 1000;
return humanizeDuration(rounded, { maxDecimalPoints: 2 });
format(seconds) {
let number = seconds / this.divider;
number = Math.round(number * 100)/100;
if (number < 0.01) {
number = '< 0.01';
}
return `${number} ${this.suffix}` + (number == 1 ? '' : 's');
}
}

0 comments on commit c9b75d5

Please sign in to comment.