Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions components/src/model/throughput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,26 @@ const THROUGHPUT_TESTS: UnitTestCase[] = [
format: { unit: 'bits/sec' },
expected: '1 Kib/s',
},
// Additional rate units
{ value: 42, format: { unit: 'tps' }, expected: '42 tps' },
{ value: 1.5, format: { unit: 'trc/s' }, expected: '1.5 trc/s' },
{ value: 10, format: { unit: 'trx/s' }, expected: '10 trx/s' },
{ value: 3, format: { unit: 'e/s' }, expected: '3 e/s' },
{ value: 7, format: { unit: 'op/s' }, expected: '7 op/s' },
{ value: 7, format: { unit: 'ops/s' }, expected: '7 ops/s' },
{ value: 100, format: { unit: 'msg/s' }, expected: '100 msg/s' },
{ value: 2, format: { unit: 'errors/s' }, expected: '2 errors/s' },
{ value: 5, format: { unit: 'calls/s' }, expected: '5 calls/s' },
{ value: 9, format: { unit: 'qps' }, expected: '9 qps' },
{ value: 1, format: { unit: 'drop/s' }, expected: '1 drop/s' },
{ value: 1, format: { unit: 'reject/s' }, expected: '1 reject/s' },
{ value: 4, format: { unit: 'requests/s' }, expected: '4 requests/s' },
{ value: 8, format: { unit: 'flows/s' }, expected: '8 flows/s' },
{ value: 2, format: { unit: 'fail/sec' }, expected: '2 fail/sec' },
{ value: 1, format: { unit: 'to/s' }, expected: '1 to/s' },
{ value: 6, format: { unit: 'count:tps' }, expected: '6 tps' },
{ value: 6, format: { unit: 'count:traces/s' }, expected: '6 traces/s' },
{ value: 6, format: { unit: 'count:msg/s' }, expected: '6 msg/s' },
];

describe('formatValue', () => {
Expand All @@ -93,18 +113,11 @@ describe('formatValue', () => {

it('should get identical formatters from cache', () => {
const { countCacheItems, getKeys } = getFormatterStats();
expect(countCacheItems('throughput')).toBe(10);
expect(getKeys('throughput')).toStrictEqual([
'decimal|true|compact|3|counts/sec|en-US',
'decimal|true|false|ops/sec|en-US',
'decimal|true|4|false|requests/sec|en-US',
'decimal|true|compact|3|true|reads/sec|en-US',
'decimal|true|compact|4|true|writes/sec|en-US',
'decimal|true|compact|3|events/sec|en-US',
'decimal|true|false|messages/sec|en-US',
'decimal|true|4|false|records/sec|en-US',
'decimal|true|compact|3|true|rows/sec|en-US',
'decimal|true|compact|3|ops/sec|en-US',
]);
// Cache keys include unit id; extra rate units add entries after the core set.
expect(countCacheItems('throughput')).toBeGreaterThanOrEqual(10);
const keys = getKeys('throughput');
expect(keys).toContain('decimal|true|false|ops/sec|en-US');
expect(keys).toContain('decimal|true|false|tps|en-US');
expect(keys).toContain('decimal|true|false|count:tps|en-US');
});
});
66 changes: 64 additions & 2 deletions components/src/model/throughput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,32 @@ type ThroughputUnit =
| 'records/sec'
| 'requests/sec'
| 'rows/sec'
| 'writes/sec';
| 'writes/sec'
// Additional rate units (unit id = display suffix)
| 'tps'
| 'trc/s'
| 'trx/s'
| 'e/s'
| 'op/s'
| 'ops/s'
| 'msg/s'
| 'msg/sec'
| 'errors/s'
| 'calls/s'
| 'qps'
| 'drop/s'
| 'reject/s'
| 'requests/s'
| 'flows/s'
| 'fail/sec'
| 'to/s'
| 'c/s'
| 'gc/s'
| 'tk/s'
| 'cxn/s'
| 'count:tps'
| 'count:traces/s'
| 'count:msg/s';
export type ThroughputFormatOptions = {
unit?: ThroughputUnit;
decimalPlaces?: number;
Expand Down Expand Up @@ -101,8 +126,44 @@ export const THROUGHPUT_UNIT_CONFIG: Readonly<Record<ThroughputUnit, UnitConfig>
group: THROUGHPUT_GROUP,
label: 'Writes/sec',
},

tps: { group: THROUGHPUT_GROUP, label: 'Transactions/sec (tps)' },
'trc/s': { group: THROUGHPUT_GROUP, label: 'Traces/sec' },
'trx/s': { group: THROUGHPUT_GROUP, label: 'Transactions/sec (trx/s)' },
'e/s': { group: THROUGHPUT_GROUP, label: 'Events/sec (e/s)' },
'op/s': { group: THROUGHPUT_GROUP, label: 'Ops/sec (op/s)' },
'ops/s': { group: THROUGHPUT_GROUP, label: 'Ops/sec (ops/s)' },
'msg/s': { group: THROUGHPUT_GROUP, label: 'Messages/sec (msg/s)' },
'msg/sec': { group: THROUGHPUT_GROUP, label: 'Messages/sec (msg/sec)' },
'errors/s': { group: THROUGHPUT_GROUP, label: 'Errors/sec' },
'calls/s': { group: THROUGHPUT_GROUP, label: 'Calls/sec' },
qps: { group: THROUGHPUT_GROUP, label: 'Queries/sec (qps)' },
'drop/s': { group: THROUGHPUT_GROUP, label: 'Drops/sec' },
'reject/s': { group: THROUGHPUT_GROUP, label: 'Rejects/sec' },
'requests/s': { group: THROUGHPUT_GROUP, label: 'Requests/sec (requests/s)' },
'flows/s': { group: THROUGHPUT_GROUP, label: 'Flows/sec' },
'fail/sec': { group: THROUGHPUT_GROUP, label: 'Failures/sec' },
'to/s': { group: THROUGHPUT_GROUP, label: 'Timeouts/sec' },
'c/s': { group: THROUGHPUT_GROUP, label: 'Contentions/sec' },
'gc/s': { group: THROUGHPUT_GROUP, label: 'GC/sec' },
'tk/s': { group: THROUGHPUT_GROUP, label: 'Tokens/sec' },
'cxn/s': { group: THROUGHPUT_GROUP, label: 'Connections/sec' },
'count:tps': { group: THROUGHPUT_GROUP, label: 'Count tps' },
'count:traces/s': { group: THROUGHPUT_GROUP, label: 'Count traces/sec' },
'count:msg/s': { group: THROUGHPUT_GROUP, label: 'Count msg/sec' },
};

/** Display suffix for axis/stat (strip leading `count:` when present). */
function throughputSuffix(unit: ThroughputUnit | undefined): string {
if (!unit) {
return '';
}
if (unit.startsWith('count:')) {
return unit.slice('count:'.length);
}
return unit;
}

export function formatThroughput(value: number, { unit, shortValues, decimalPlaces }: ThroughputFormatOptions): string {
// special case for data throughput
if (unit === 'bits/sec') {
Expand Down Expand Up @@ -153,5 +214,6 @@ export function formatThroughput(value: number, { unit, shortValues, decimalPlac
unit,
];

return `${getFormatterFromCache(key, 'throughput', formatterOptions, 'en-US')(value)} ${unit}`;
const suffix = throughputSuffix(unit);
return `${getFormatterFromCache(key, 'throughput', formatterOptions, 'en-US')(value)} ${suffix}`;
}
21 changes: 21 additions & 0 deletions components/src/model/time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,27 @@ const TIME_TESTS: UnitTestCase[] = [
format: { unit: 'years' },
expected: '100 years',
},
// dtdhms — value is seconds; fixed D d HH:MM:SS (no month/year scale)
{
value: 0,
format: { unit: 'dtdhms' },
expected: '00:00:00',
},
{
value: 3661,
format: { unit: 'dtdhms' },
expected: '01:01:01',
},
{
value: 86400 + 3723, // 1d 01:02:03
format: { unit: 'dtdhms' },
expected: '1 d 01:02:03',
},
{
value: 14093232, // network uptime style
format: { unit: 'dtdhms' },
expected: '163 d 02:47:12',
},
];
describe('formatValue', () => {
it.each(TIME_TESTS)('returns $expected when $value formatted as $format', (args: UnitTestCase) => {
Expand Down
37 changes: 36 additions & 1 deletion components/src/model/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ type TimeUnits =
| 'days'
| 'weeks'
| 'months'
| 'years';
| 'years'
/** Duration in seconds → `D d HH:MM:SS` (no month/year auto-scale). */
| 'dtdhms';
export type TimeFormatOptions = {
unit?: TimeUnits;
decimalPlaces?: number;
Expand Down Expand Up @@ -77,6 +79,10 @@ export const TIME_UNIT_CONFIG: Readonly<Record<TimeUnits, UnitConfig>> = {
group: TIME_GROUP,
label: 'Years',
},
dtdhms: {
group: TIME_GROUP,
label: 'Duration (d HH:MM:SS)',
},
};

// Mapping of time units to what Intl.NumberFormat formatter expects
Expand Down Expand Up @@ -152,7 +158,36 @@ function isMonthOrYear(unit: TimeUnits): boolean {
return unit === 'months' || unit === 'years';
}

/**
* Format a duration given in **seconds** as `D d HH:MM:SS`
* (or `HH:MM:SS` when under one day). Does not auto-scale to months/years.
*/
export function formatDtdhms(totalSeconds: number): string {
if (!Number.isFinite(totalSeconds)) {
return String(totalSeconds);
}
const sign = totalSeconds < 0 ? '-' : '';
let s = Math.floor(Math.abs(totalSeconds));
const days = Math.floor(s / 86400);
s %= 86400;
const hours = Math.floor(s / 3600);
s %= 3600;
const minutes = Math.floor(s / 60);
const secs = s % 60;
const hh = String(hours).padStart(2, '0');
const mm = String(minutes).padStart(2, '0');
const ss = String(secs).padStart(2, '0');
if (days > 0) {
return `${sign}${days} d ${hh}:${mm}:${ss}`;
}
return `${sign}${hh}:${mm}:${ss}`;
}

export function formatTime(value: number, { unit, decimalPlaces }: TimeFormatOptions): string {
if (unit === 'dtdhms') {
return formatDtdhms(value);
}

if (value === 0) return '0s';

const results = getValueAndKindForNaturalNumbers(value, unit ?? 'seconds');
Expand Down
9 changes: 7 additions & 2 deletions cue/common/format.cue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ package common
}

#timeFormat: {
unit: "nanoseconds" | "microseconds" | "milliseconds" | "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years"
// dtdhms: duration in seconds as "D d HH:MM:SS" (Grafana dtdhms parity; no month/year auto-scale)
unit: "nanoseconds" | "microseconds" | "milliseconds" | "seconds" | "minutes" | "hours" | "days" | "weeks" | "months" | "years" | "dtdhms"
decimalPlaces?: number
}

Expand Down Expand Up @@ -59,7 +60,11 @@ package common
}

#throughputFormat: {
unit: "bits/sec" | "decbits/sec" | "bytes/sec" | "decbytes/sec" | "counts/sec" | "events/sec" | "messages/sec" | "ops/sec" | "packets/sec" | "reads/sec" | "records/sec" | "requests/sec" | "rows/sec" | "writes/sec"
unit: "bits/sec" | "decbits/sec" | "bytes/sec" | "decbytes/sec" | "counts/sec" | "events/sec" | "messages/sec" | "ops/sec" | "packets/sec" | "reads/sec" | "records/sec" | "requests/sec" | "rows/sec" | "writes/sec" |
// Additional rate units (id = display suffix; Grafana/custom panel aliases)
"tps" | "trc/s" | "trx/s" | "e/s" | "op/s" | "ops/s" | "msg/s" | "msg/sec" | "errors/s" | "calls/s" | "qps" |
"drop/s" | "reject/s" | "requests/s" | "flows/s" | "fail/sec" | "to/s" | "c/s" | "gc/s" | "tk/s" | "cxn/s" |
"count:tps" | "count:traces/s" | "count:msg/s"
decimalPlaces?: number
shortValues?: bool
}
Expand Down
27 changes: 27 additions & 0 deletions cue/common/migrate/mapping.cue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ package migrate
d: "days"
dtdurations: "seconds"
dtdurationms: "milliseconds"
// Grafana dtdhms: fixed D d HH:MM:SS (do not map to seconds — different display)
dtdhms: "dtdhms"
dateTimeAsIso: "datetime-iso"
// percent units
percent: "percent"
Expand Down Expand Up @@ -52,6 +54,31 @@ package migrate
ops: "ops/sec"
pps: "packets/sec"
wps: "writes/sec"
// Additional Grafana / custom rate unit aliases → Perses throughput ids
tps: "tps"
"trc/s": "trc/s"
"trx/s": "trx/s"
"e/s": "e/s"
"op/s": "op/s"
"ops/s": "ops/s"
"msg/s": "msg/s"
"msg/sec": "msg/sec"
"errors/s": "errors/s"
"calls/s": "calls/s"
qps: "qps"
"drop/s": "drop/s"
"reject/s": "reject/s"
"requests/s": "requests/s"
"flows/s": "flows/s"
"fail/sec": "fail/sec"
"to/s": "to/s"
"c/s": "c/s"
"gc/s": "gc/s"
"tk/s": "tk/s"
"cxn/s": "cxn/s"
"count:tps": "count:tps"
"count:traces/s": "count:traces/s"
"count:msg/s": "count:msg/s"
}
// mapping table for the calculation attribute (key = grafana unit, value = perses equivalent)
calc: {
Expand Down