Skip to content

Commit 6103a2a

Browse files
authored
Merge 638fa69 into 7e234a5
2 parents 7e234a5 + 638fa69 commit 6103a2a

File tree

13 files changed

+435
-38
lines changed

13 files changed

+435
-38
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
@import '../../styles/mixins.scss';
2+
3+
$memory-type-colors: (
4+
'AllocatorCachesMemory': var(--g-color-base-utility-medium-hover),
5+
'SharedCacheConsumption': var(--g-color-base-info-medium-hover),
6+
'MemTableConsumption': var(--g-color-base-warning-medium-hover),
7+
'QueryExecutionConsumption': var(--g-color-base-positive-medium-hover),
8+
'Other': var(--g-color-base-neutral-light-hover),
9+
);
10+
11+
@mixin memory-type-color($type) {
12+
background-color: map-get($memory-type-colors, $type);
13+
}
14+
15+
.memory-viewer {
16+
$block: &;
17+
18+
position: relative;
19+
z-index: 0;
20+
21+
min-width: 150px;
22+
padding: 0 var(--g-spacing-1);
23+
24+
&__progress-container {
25+
position: relative;
26+
27+
overflow: hidden;
28+
29+
height: 23px;
30+
31+
border-radius: 2px;
32+
background: var(--g-color-base-generic);
33+
}
34+
35+
&__container {
36+
display: flex;
37+
38+
padding: 2px 0;
39+
}
40+
41+
&__legend {
42+
position: absolute;
43+
bottom: 2px;
44+
45+
width: 20px;
46+
height: 20px;
47+
48+
border-radius: 2px;
49+
50+
@each $type, $color in $memory-type-colors {
51+
&_type_#{$type} {
52+
@include memory-type-color($type);
53+
}
54+
}
55+
}
56+
57+
&__segment {
58+
position: absolute;
59+
60+
height: 100%;
61+
62+
@each $type, $color in $memory-type-colors {
63+
&_type_#{$type} {
64+
@include memory-type-color($type);
65+
}
66+
}
67+
}
68+
69+
&__name {
70+
padding-left: 28px;
71+
}
72+
73+
&_theme_dark {
74+
color: var(--g-color-text-light-primary);
75+
76+
#{$block}__segment {
77+
opacity: 0.75;
78+
}
79+
}
80+
81+
&_status {
82+
&_good {
83+
#{$block}__progress-container {
84+
background-color: var(--g-color-base-positive-light);
85+
}
86+
}
87+
&_warning {
88+
#{$block}__progress-container {
89+
background-color: var(--g-color-base-yellow-light);
90+
}
91+
}
92+
&_danger {
93+
#{$block}__progress-container {
94+
background-color: var(--g-color-base-danger-light);
95+
}
96+
}
97+
}
98+
99+
&_size {
100+
height: 20px;
101+
@include body-2-typography();
102+
103+
#{$block}__progress-container {
104+
height: inherit;
105+
}
106+
}
107+
108+
&__text {
109+
display: flex;
110+
justify-content: center;
111+
align-items: center;
112+
}
113+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {DefinitionList, useTheme} from '@gravity-ui/uikit';
2+
3+
import type {TMemoryStats} from '../../types/api/nodes';
4+
import {cn} from '../../utils/cn';
5+
import {calculateProgressStatus} from '../../utils/progress';
6+
import {isNumeric} from '../../utils/utils';
7+
import {HoverPopup} from '../HoverPopup/HoverPopup';
8+
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
9+
10+
import {getMemorySegments} from './utils';
11+
12+
import './MemoryViewer.scss';
13+
14+
const b = cn('memory-viewer');
15+
16+
type FormatProgressViewerValues = (
17+
value?: number,
18+
capacity?: number,
19+
precision?: number,
20+
) => (string | number | undefined)[];
21+
22+
export interface MemoryProgressViewerProps {
23+
stats: TMemoryStats;
24+
className?: string;
25+
warningThreshold?: number;
26+
value?: number | string;
27+
capacity?: number | string;
28+
formatValues: FormatProgressViewerValues;
29+
percents?: boolean;
30+
dangerThreshold?: number;
31+
}
32+
33+
const MEMORY_PRECISION = 2;
34+
35+
export function MemoryViewer({
36+
stats,
37+
value,
38+
capacity,
39+
percents,
40+
formatValues,
41+
className,
42+
warningThreshold = 60,
43+
dangerThreshold = 80,
44+
}: MemoryProgressViewerProps) {
45+
const theme = useTheme();
46+
let fillWidth =
47+
Math.round((parseFloat(String(value)) / parseFloat(String(capacity))) * 100) || 0;
48+
fillWidth = fillWidth > 100 ? 100 : fillWidth;
49+
let valueText: number | string | undefined = value,
50+
capacityText: number | string | undefined = capacity,
51+
divider = '/';
52+
if (percents) {
53+
valueText = fillWidth + '%';
54+
capacityText = '';
55+
divider = '';
56+
} else if (formatValues) {
57+
[valueText, capacityText] = formatValues(Number(value), Number(capacity), MEMORY_PRECISION);
58+
}
59+
60+
const renderContent = () => {
61+
if (isNumeric(capacity)) {
62+
return `${valueText} ${divider} ${capacityText}`;
63+
}
64+
65+
return valueText;
66+
};
67+
68+
const calculateMemoryShare = (segmentSize: number) => {
69+
if (!value) {
70+
return 0;
71+
}
72+
return (segmentSize / parseFloat(String(capacity))) * 100;
73+
};
74+
75+
const memorySegments = getMemorySegments(stats);
76+
77+
const totalUsedMemory =
78+
memorySegments
79+
.filter(({isInfo}) => !isInfo)
80+
.reduce((acc, segment) => acc + calculateMemoryShare(segment.value), 0) /
81+
parseFloat(String(capacity));
82+
83+
const status = calculateProgressStatus({
84+
fillWidth: totalUsedMemory,
85+
warningThreshold,
86+
dangerThreshold,
87+
colorizeProgress: true,
88+
});
89+
90+
let currentPosition = 0;
91+
92+
return (
93+
<HoverPopup
94+
popupContent={
95+
<DefinitionList responsive>
96+
{memorySegments.map(
97+
({label, value: segmentSize, capacity: segmentCapacity, key}) => (
98+
<DefinitionList.Item
99+
key={label}
100+
name={
101+
<div className={b('container')}>
102+
<div className={b('legend', {type: key})}></div>
103+
<div className={b('name')}>{label}</div>
104+
</div>
105+
}
106+
>
107+
{segmentCapacity ? (
108+
<ProgressViewer
109+
value={segmentSize}
110+
capacity={segmentCapacity}
111+
formatValues={(val, size) =>
112+
formatValues(val, size, MEMORY_PRECISION)
113+
}
114+
colorizeProgress
115+
/>
116+
) : (
117+
formatValues(segmentSize, undefined, MEMORY_PRECISION)[0]
118+
)}
119+
</DefinitionList.Item>
120+
),
121+
)}
122+
</DefinitionList>
123+
}
124+
>
125+
<div className={b({theme, status}, className)}>
126+
<div className={b('progress-container')}>
127+
{memorySegments
128+
.filter(({isInfo}) => !isInfo)
129+
.map((segment) => {
130+
const position = currentPosition;
131+
currentPosition += calculateMemoryShare(segment.value);
132+
133+
return (
134+
<div
135+
key={segment.key}
136+
className={b('segment', {type: segment.key})}
137+
style={{
138+
width: `${calculateMemoryShare(segment.value).toFixed(2)}%`,
139+
left: `${position}%`,
140+
}}
141+
/>
142+
);
143+
})}
144+
<div className={b('text')}>{renderContent()}</div>
145+
</div>
146+
</div>
147+
</HoverPopup>
148+
);
149+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"text_memory-detailed": "Memory Detailed",
3+
"text_external-consumption": "External Consumption",
4+
"text_allocator-caches": "Allocator Caches",
5+
"text_shared-cache": "Shared Cache",
6+
"text_memtable": "MemTable",
7+
"text_query-execution": "Query Execution",
8+
"text_soft-limit": "Soft Limit",
9+
"text_hard-limit": "Hard Limit",
10+
"text_other": "Other",
11+
"memory-detailed": "Memory Detailed"
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-memory-viewer';
6+
7+
export default registerKeysets(COMPONENT, {en});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type {TMemoryStats} from '../../types/api/nodes';
2+
import {isNumeric} from '../../utils/utils';
3+
4+
import i18n from './i18n';
5+
6+
function getMaybeNumber(value: string | number | undefined): number | undefined {
7+
return isNumeric(value) ? parseFloat(String(value)) : undefined;
8+
}
9+
10+
interface MemorySegment {
11+
label: string;
12+
key: string;
13+
value: number;
14+
capacity?: number;
15+
isInfo?: boolean;
16+
}
17+
18+
export function getMemorySegments(stats: TMemoryStats): MemorySegment[] {
19+
const segments = [
20+
{
21+
label: i18n('text_shared-cache'),
22+
key: 'SharedCacheConsumption',
23+
value: getMaybeNumber(stats.SharedCacheConsumption),
24+
capacity: getMaybeNumber(stats.SharedCacheLimit),
25+
isInfo: false,
26+
},
27+
{
28+
label: i18n('text_query-execution'),
29+
key: 'QueryExecutionConsumption',
30+
value: getMaybeNumber(stats.QueryExecutionConsumption),
31+
capacity: getMaybeNumber(stats.QueryExecutionLimit),
32+
isInfo: false,
33+
},
34+
{
35+
label: i18n('text_memtable'),
36+
key: 'MemTableConsumption',
37+
value: getMaybeNumber(stats.MemTableConsumption),
38+
capacity: getMaybeNumber(stats.MemTableLimit),
39+
isInfo: false,
40+
},
41+
{
42+
label: i18n('text_allocator-caches'),
43+
key: 'AllocatorCachesMemory',
44+
value: getMaybeNumber(stats.AllocatorCachesMemory),
45+
isInfo: false,
46+
},
47+
];
48+
49+
const nonInfoSegments = segments.filter(
50+
(segment) => segment.value !== undefined,
51+
) as MemorySegment[];
52+
const sumNonInfoSegments = nonInfoSegments.reduce((acc, segment) => acc + segment.value, 0);
53+
54+
const totalMemory = getMaybeNumber(stats.AnonRss);
55+
56+
if (totalMemory) {
57+
const otherMemory = Math.max(0, totalMemory - sumNonInfoSegments);
58+
59+
segments.push({
60+
label: i18n('text_other'),
61+
key: 'Other',
62+
value: otherMemory,
63+
isInfo: false,
64+
});
65+
}
66+
67+
segments.push(
68+
{
69+
label: i18n('text_external-consumption'),
70+
key: 'ExternalConsumption',
71+
value: getMaybeNumber(stats.ExternalConsumption),
72+
isInfo: true,
73+
},
74+
{
75+
label: i18n('text_soft-limit'),
76+
key: 'SoftLimit',
77+
value: getMaybeNumber(stats.SoftLimit),
78+
isInfo: true,
79+
},
80+
{
81+
label: i18n('text_hard-limit'),
82+
key: 'HardLimit',
83+
value: getMaybeNumber(stats.HardLimit),
84+
isInfo: true,
85+
},
86+
);
87+
88+
return segments.filter((segment) => segment.value !== undefined) as MemorySegment[];
89+
}

0 commit comments

Comments
 (0)