Use GiB on traffic charts. Mention UTC as time for calculation (#24983)

* Use GiB on traffic charts. Mention UTC as time for calculation

* Add changelog

* Adding further tests for unit conversion.

* Rename `ram_size` to `binary_size` because it also used for network traffic.

---------

Co-authored-by: Dennis Oelkers <dennis@graylog.com>
Co-authored-by: Linus Pahl <linus.pahl@graylog.com>
This commit is contained in:
Maksym Yadlovskyi
2026-03-03 11:46:58 +01:00
committed by GitHub
parent c33959ea1d
commit 464412a278
8 changed files with 228 additions and 9 deletions

View File

@@ -0,0 +1,5 @@
type = "f"
message = "Use GiB units for defining traffic limits in the chart. Add a label indicating that limits are calculated in UTC."
issues = ["24982"]
pulls = ["24983"]

View File

@@ -63,7 +63,7 @@ const TrafficGraph = ({ width, traffic, trafficLimit = undefined }: Props) => {
y: yValues,
...getHoverTemplateSettings({
convertedValues: yValues,
unit: FieldUnit.fromJSON({ abbrev: 'b', unit_type: 'size' }),
unit: FieldUnit.fromJSON({ abbrev: 'b', unit_type: 'binary_size' }),
}),
},
],
@@ -122,14 +122,14 @@ const TrafficGraph = ({ width, traffic, trafficLimit = undefined }: Props) => {
const notZoomedLayout = useMemo<GeneratedLayout>(
() => ({
rangemode: 'tozero',
...(getFormatSettingsByData('size', valuesToGetFormatSettings) as GeneratedLayout),
...(getFormatSettingsByData('binary_size', valuesToGetFormatSettings) as GeneratedLayout),
}),
[valuesToGetFormatSettings],
);
const zoomedLayout = useMemo(
() => ({
rangemode: 'tozero',
...(getFormatSettingsByData('size', yValues) as GeneratedLayout),
...(getFormatSettingsByData('binary_size', yValues) as GeneratedLayout),
}),
[yValues],
);
@@ -143,7 +143,7 @@ const TrafficGraph = ({ width, traffic, trafficLimit = undefined }: Props) => {
xaxis: {
type: 'date',
title: {
text: 'Time',
text: 'Time shown in UTC',
},
},
hovermode: 'x',

View File

@@ -89,7 +89,7 @@ const TrafficGraphWithDaySelect = ({ traffic, trafficLimit = undefined, title =
const unixTraffic = useMemo(() => (traffic ? formatTrafficData(traffic) : null), [traffic]);
const formattedTotalTraffic = useMemo(() => {
const prettified = getPrettifiedValue(bytesOut, { abbrev: 'b', unitType: 'size' });
const prettified = getPrettifiedValue(bytesOut, { abbrev: 'b', unitType: 'binary_size' });
return formatValueWithUnitLabel(prettified?.value, prettified.unit.abbrev);
}, [bytesOut]);

View File

@@ -24,7 +24,7 @@ import Popover from 'components/common/Popover';
import { HoverForHelp, ModalButtonToolbar } from 'components/common';
import { Alert, Button, Input } from 'components/bootstrap';
import type { Unit } from 'views/components/visualizations/utils/unitConverters';
import { mappedUnitsFromJSON as units } from 'views/components/visualizations/utils/unitConverters';
import { mappedUnitsFromJSONForAggregation as units } from 'views/components/visualizations/utils/unitConverters';
import type { FieldUnitsFormValues } from 'views/types';
import type FieldUnit from 'views/logic/aggregationbuilder/FieldUnit';
import getUnitTextLabel from 'views/components/visualizations/utils/getUnitTextLabel';

View File

@@ -71,6 +71,22 @@ describe('Unit converter functions', () => {
});
});
it('for binary_size should convert bigger unit to bytes', () => {
const result = convertValueToBaseUnit(1, { abbrev: 'KiB', unitType: 'binary_size' });
expect(result).toEqual({
value: 1024,
unit: {
type: 'base',
abbrev: 'b',
name: 'byte',
unitType: 'binary_size',
useInPrettier: true,
conversion: undefined,
},
});
});
it('for percent should convert bigger unit to decimal percent', () => {
const result = convertValueToBaseUnit(50, { abbrev: '%', unitType: 'percent' });
@@ -201,6 +217,75 @@ describe('Unit converter functions', () => {
});
});
it('for binary_size should convert smaller unit (KiB) to bigger (GiB)', () => {
const result = convertValueToUnit(
1048576,
{ abbrev: 'KiB', unitType: 'binary_size' },
{ abbrev: 'GiB', unitType: 'binary_size' },
);
expect(result).toEqual({
value: 1,
unit: {
type: 'derived',
abbrev: 'GiB',
name: 'gibibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1073741824,
action: 'MULTIPLY',
},
},
});
});
it('for binary_size should convert bytes to GiB', () => {
const result = convertValueToUnit(
3221225472,
{ abbrev: 'b', unitType: 'binary_size' },
{ abbrev: 'GiB', unitType: 'binary_size' },
);
expect(result).toEqual({
value: 3,
unit: {
type: 'derived',
abbrev: 'GiB',
name: 'gibibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1073741824,
action: 'MULTIPLY',
},
},
});
});
it('for binary_size should convert bytes to MiB', () => {
const result = convertValueToUnit(
1572864,
{ abbrev: 'b', unitType: 'binary_size' },
{ abbrev: 'MiB', unitType: 'binary_size' },
);
expect(result).toEqual({
value: 1.5,
unit: {
type: 'derived',
abbrev: 'MiB',
name: 'mebibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1048576,
action: 'MULTIPLY',
},
},
});
});
it('return nulls when some params where missed', () => {
const result1 = convertValueToUnit(50, { abbrev: 'Gb', unitType: 'size' }, undefined);
const result2 = convertValueToUnit(50, undefined, { abbrev: 'Gb', unitType: 'size' });
@@ -301,6 +386,82 @@ describe('Unit converter functions', () => {
});
});
it('for binary_size should convert smaller then 1 value to the value with lower unit', () => {
const result = getPrettifiedValue(0.5, { abbrev: 'GiB', unitType: 'binary_size' });
expect(result).toEqual({
value: 512,
unit: {
type: 'derived',
abbrev: 'MiB',
name: 'mebibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1048576,
action: 'MULTIPLY',
},
},
});
});
it('for binary_size should convert bigger then 1 value to the value with higher unit', () => {
const result = getPrettifiedValue(2048, { abbrev: 'MiB', unitType: 'binary_size' });
expect(result).toEqual({
value: 2,
unit: {
type: 'derived',
abbrev: 'GiB',
name: 'gibibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1073741824,
action: 'MULTIPLY',
},
},
});
});
it('for binary_size should prettify larger bytes to GiB', () => {
const result = getPrettifiedValue(3221225472, { abbrev: 'b', unitType: 'binary_size' });
expect(result).toEqual({
value: 3,
unit: {
type: 'derived',
abbrev: 'GiB',
name: 'gibibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1073741824,
action: 'MULTIPLY',
},
},
});
});
it('for binary_size should prettify larger bytes to MiB', () => {
const result = getPrettifiedValue(1572864, { abbrev: 'b', unitType: 'binary_size' });
expect(result).toEqual({
value: 1.5,
unit: {
type: 'derived',
abbrev: 'MiB',
name: 'mebibyte',
unitType: 'binary_size',
useInPrettier: true,
conversion: {
value: 1048576,
action: 'MULTIPLY',
},
},
});
});
it('for percent should always convert to integer % unit', () => {
const result1 = getPrettifiedValue(100, { abbrev: 'd%', unitType: 'percent' });
const result2 = getPrettifiedValue(10000, { abbrev: '%', unitType: 'percent' });

View File

@@ -117,6 +117,8 @@ export const getFormatSettingsByData = (unitTypeKey: FieldUnitType | DefaultAxis
};
case 'size':
return getFormatSettingsWithCustomTickVals(values, 'size');
case 'binary_size':
return getFormatSettingsWithCustomTickVals(values, 'binary_size');
case 'time':
return getFormatSettingsWithCustomTickVals(values, 'time');
default:
@@ -311,7 +313,11 @@ export const getHoverTemplateSettings = ({
unit: FieldUnit;
name?: string;
}): { text: Array<string>; hovertemplate: string; meta: string } | {} => {
if (unit?.unitType === 'time' || unit?.unitType === 'size') {
if (
unit?.unitType === 'time' ||
unit?.unitType === 'size' ||
unit?.unitType === 'binary_size'
) {
return {
text: getHoverTexts({ convertedValues, unit }),
hovertemplate: `%{text}<br>${name ? '<extra>%{meta}</extra>' : '<extra></extra>'}`,

View File

@@ -28,6 +28,45 @@ import supportedUnits from '../../../../../../graylog2-server/src/main/resources
type UnitConversionAction = 'MULTIPLY' | 'DIVIDE';
const binarySize = [
{
'type': 'base',
'abbrev': 'b',
'name': 'byte',
'unit_type': 'binary_size',
},
{
'type': 'derived',
'abbrev': 'KiB',
'name': 'kibibyte',
'unit_type': 'binary_size',
'conversion': {
'value': 1024,
'action': 'MULTIPLY',
},
},
{
'type': 'derived',
'abbrev': 'MiB',
'name': 'mebibyte',
'unit_type': 'binary_size',
'conversion': {
'value': 1048576,
'action': 'MULTIPLY',
},
},
{
'type': 'derived',
'abbrev': 'GiB',
'name': 'gibibyte',
'unit_type': 'binary_size',
'conversion': {
'value': 1073741824,
'action': 'MULTIPLY',
},
},
];
const sourceUnits = supportedUnits.units as FieldUnitTypesJson;
export type UnitJson = {
type: 'base' | 'derived';
@@ -70,13 +109,21 @@ const unitFromJson = (unitJson: UnitJson): Unit => ({
conversion: unitJson.conversion,
useInPrettier: isUnitUsableInPrettier(unitJson),
});
export const mappedUnitsFromJSON: FieldUnitTypes = <FieldUnitTypes>(
export const mappedUnitsFromJSONForAggregation: FieldUnitTypes = <FieldUnitTypes>(
mapValues(
sourceUnits,
(unitsJson: Array<UnitJson>): Array<Unit> => unitsJson.map((unitJson: UnitJson): Unit => unitFromJson(unitJson)),
)
);
export const mappedUnitsFromJSON: FieldUnitTypes = <FieldUnitTypes>(
mapValues(
{ ...sourceUnits, binary_size: binarySize },
(unitsJson: Array<UnitJson>): Array<Unit> => unitsJson.map((unitJson: UnitJson): Unit => unitFromJson(unitJson)),
)
);
export const _getBaseUnit = (units: FieldUnitTypes, unitType: FieldUnitType): Unit =>
units[unitType].find(({ type }) => type === 'base');

View File

@@ -569,7 +569,7 @@ export interface WidgetCreator {
icon: React.ComponentType<{}>;
}
export type FieldUnitType = 'size' | 'time' | 'percent';
export type FieldUnitType = 'size' | 'time' | 'percent' | 'binary_size';
export type DefaultAxisKey = typeof DEFAULT_AXIS_KEY;