Skip to content

Commit 05094d7

Browse files
fix: fallback for detailed memory (#1696)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent db8ec37 commit 05094d7

File tree

5 files changed

+225
-22
lines changed

5 files changed

+225
-22
lines changed

src/components/MemoryViewer/MemoryViewer.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {HoverPopup} from '../HoverPopup/HoverPopup';
1010
import type {FormatProgressViewerValues} from '../ProgressViewer/ProgressViewer';
1111
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
1212

13-
import {getMemorySegments} from './utils';
13+
import {calculateAllocatedMemory, getMemorySegments} from './utils';
1414

1515
import './MemoryViewer.scss';
1616

@@ -53,22 +53,23 @@ export function MemoryViewer({
5353
warningThreshold = 60,
5454
dangerThreshold = 80,
5555
}: MemoryProgressViewerProps) {
56-
const value = stats.AnonRss;
56+
const memoryUsage = stats.AnonRss ?? calculateAllocatedMemory(stats);
57+
5758
const capacity = stats.HardLimit;
5859

5960
const theme = useTheme();
6061
let fillWidth =
61-
Math.round((parseFloat(String(value)) / parseFloat(String(capacity))) * 100) || 0;
62+
Math.round((parseFloat(String(memoryUsage)) / parseFloat(String(capacity))) * 100) || 0;
6263
fillWidth = fillWidth > 100 ? 100 : fillWidth;
63-
let valueText: number | string | undefined = value,
64+
let valueText: number | string | undefined = memoryUsage,
6465
capacityText: number | string | undefined = capacity,
6566
divider = '/';
6667
if (percents) {
6768
valueText = fillWidth + '%';
6869
capacityText = '';
6970
divider = '';
7071
} else if (formatValues) {
71-
[valueText, capacityText] = formatValues(Number(value), Number(capacity));
72+
[valueText, capacityText] = formatValues(Number(memoryUsage), Number(capacity));
7273
}
7374

7475
const renderContent = () => {
@@ -80,13 +81,13 @@ export function MemoryViewer({
8081
};
8182

8283
const calculateMemoryShare = (segmentSize: number) => {
83-
if (!value) {
84+
if (!memoryUsage) {
8485
return 0;
8586
}
8687
return (segmentSize / parseFloat(String(capacity))) * 100;
8788
};
8889

89-
const memorySegments = getMemorySegments(stats);
90+
const memorySegments = getMemorySegments(stats, Number(memoryUsage));
9091

9192
const status = calculateProgressStatus({
9293
fillWidth,

src/components/MemoryViewer/utils.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import {isNumeric} from '../../utils/utils';
33

44
import i18n from './i18n';
55

6-
function getMaybeNumber(value: string | number | undefined): number | undefined {
6+
export function calculateAllocatedMemory(stats: TMemoryStats) {
7+
const allocatedMemory = getMaybeNumber(stats.AllocatedMemory) || 0;
8+
const allocatorCaches = getMaybeNumber(stats.AllocatorCachesMemory) || 0;
9+
return String(allocatedMemory + allocatorCaches);
10+
}
11+
12+
export function getMaybeNumber(value: string | number | undefined): number | undefined {
713
return isNumeric(value) ? parseFloat(String(value)) : undefined;
814
}
915

@@ -15,7 +21,7 @@ interface MemorySegment {
1521
isInfo?: boolean;
1622
}
1723

18-
export function getMemorySegments(stats: TMemoryStats): MemorySegment[] {
24+
export function getMemorySegments(stats: TMemoryStats, memoryUsage: number): MemorySegment[] {
1925
const segments = [
2026
{
2127
label: i18n('text_shared-cache'),
@@ -51,18 +57,14 @@ export function getMemorySegments(stats: TMemoryStats): MemorySegment[] {
5157
) as MemorySegment[];
5258
const sumNonInfoSegments = nonInfoSegments.reduce((acc, segment) => acc + segment.value, 0);
5359

54-
const totalMemory = getMaybeNumber(stats.AnonRss);
60+
const otherMemory = Math.max(0, memoryUsage - sumNonInfoSegments);
5561

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-
}
62+
segments.push({
63+
label: i18n('text_other'),
64+
key: 'Other',
65+
value: otherMemory,
66+
isInfo: false,
67+
});
6668

6769
segments.push(
6870
{
@@ -74,7 +76,7 @@ export function getMemorySegments(stats: TMemoryStats): MemorySegment[] {
7476
{
7577
label: i18n('text_usage'),
7678
key: 'Usage',
77-
value: getMaybeNumber(stats.AnonRss),
79+
value: memoryUsage,
7880
isInfo: true,
7981
},
8082
{

src/types/api/nodes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ export interface TNodesGroup {
7171
}
7272

7373
export interface TMemoryStats {
74-
AnonRss: string;
74+
AnonRss?: string;
7575
ExternalConsumption?: string;
7676
AllocatorCachesMemory?: string;
77+
AllocatedMemory?: string;
7778

7879
SharedCacheConsumption?: string;
7980
SharedCacheLimit?: string;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type {Locator, Page} from '@playwright/test';
2+
3+
export class MemoryViewer {
4+
readonly container: Locator;
5+
readonly progressContainer: Locator;
6+
readonly allocatorSegment: Locator;
7+
readonly otherSegment: Locator;
8+
readonly text: Locator;
9+
readonly popup: Locator;
10+
readonly definitionList: Locator;
11+
12+
constructor(page: Page) {
13+
const paginatedTable = page.locator('.ydb-paginated-table__table');
14+
this.container = paginatedTable.locator('tbody tr').nth(0).locator('.memory-viewer');
15+
this.progressContainer = this.container.locator('.memory-viewer__progress-container');
16+
this.allocatorSegment = this.progressContainer.locator(
17+
'.memory-viewer__segment_type_AllocatorCachesMemory',
18+
);
19+
this.otherSegment = this.progressContainer.locator('.memory-viewer__segment_type_Other');
20+
this.text = this.progressContainer.locator('.memory-viewer__text');
21+
22+
// Popup elements
23+
this.popup = page.locator('.g-popup.g-popup_open');
24+
this.definitionList = this.popup.locator('.g-definition-list');
25+
}
26+
27+
async getStatus() {
28+
const classList = await this.container.getAttribute('class');
29+
if (classList?.includes('memory-viewer_status_good')) {
30+
return 'good';
31+
}
32+
return 'unknown';
33+
}
34+
35+
async getTheme() {
36+
const classList = await this.container.getAttribute('class');
37+
if (classList?.includes('memory-viewer_theme_system')) {
38+
return 'system';
39+
}
40+
return 'unknown';
41+
}
42+
43+
async getAllocatorSegmentWidth() {
44+
return this.allocatorSegment.evaluate((el) => el.style.width);
45+
}
46+
47+
async getOtherSegmentWidth() {
48+
return this.otherSegment.evaluate((el) => el.style.width);
49+
}
50+
51+
async getOtherSegmentOffset() {
52+
return this.otherSegment.evaluate((el) => el.style.left);
53+
}
54+
55+
async getText() {
56+
return this.text.innerText();
57+
}
58+
59+
async getItemValue(name: string) {
60+
const item = this.getDefinitionItem(name);
61+
return item.locator('.g-definition-list__definition').innerText();
62+
}
63+
64+
async getProgressValue(name: string) {
65+
const item = this.getDefinitionItem(name);
66+
const progressViewer = item.locator('.progress-viewer');
67+
const line = progressViewer.locator('.progress-viewer__line');
68+
const text = progressViewer.locator('.progress-viewer__text');
69+
70+
return {
71+
width: await line.evaluate((el) => el.style.width),
72+
text: await text.innerText(),
73+
status: await this.getProgressStatus(progressViewer),
74+
};
75+
}
76+
77+
async getSharedCacheInfo() {
78+
return this.getProgressValue('Shared Cache');
79+
}
80+
81+
async getQueryExecutionInfo() {
82+
return this.getProgressValue('Query Execution');
83+
}
84+
85+
async getMemTableInfo() {
86+
return this.getProgressValue('MemTable');
87+
}
88+
89+
async getAllocatorCachesInfo() {
90+
return this.getItemValue('Allocator Caches');
91+
}
92+
93+
async getOtherInfo() {
94+
return this.getItemValue('Other');
95+
}
96+
97+
async getUsageInfo() {
98+
return this.getItemValue('Usage');
99+
}
100+
101+
async getSoftLimitInfo() {
102+
return this.getItemValue('Soft Limit');
103+
}
104+
105+
async getHardLimitInfo() {
106+
return this.getItemValue('Hard Limit');
107+
}
108+
109+
async hover() {
110+
await this.container.hover();
111+
}
112+
113+
// Private methods
114+
private getDefinitionItem(name: string) {
115+
return this.definitionList.locator('.g-definition-list__item').filter({hasText: name});
116+
}
117+
118+
private async getProgressStatus(progressViewer: Locator) {
119+
const classList = await progressViewer.getAttribute('class');
120+
if (classList?.includes('progress-viewer_status_good')) {
121+
return 'good';
122+
}
123+
return 'unknown';
124+
}
125+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {NodesPage} from '../nodes/NodesPage';
4+
5+
import {MemoryViewer} from './MemoryViewer';
6+
7+
test.describe('Memory Viewer Widget', () => {
8+
test.beforeEach(async ({page}) => {
9+
const nodesPage = new NodesPage(page);
10+
await nodesPage.goto();
11+
12+
// Get the first row's memory viewer
13+
const paginatedTable = await page.locator('.ydb-paginated-table__table');
14+
await paginatedTable.waitFor();
15+
});
16+
17+
test('Memory viewer is visible and has correct status', async ({page}) => {
18+
const memoryViewer = new MemoryViewer(page);
19+
20+
await expect(memoryViewer.container).toBeVisible();
21+
expect(await memoryViewer.getStatus()).toBe('good');
22+
expect(await memoryViewer.getTheme()).toBe('system');
23+
});
24+
25+
test('Memory viewer shows correct base metrics', async ({page}) => {
26+
const memoryViewer = new MemoryViewer(page);
27+
28+
const allocatorWidth = await memoryViewer.getAllocatorSegmentWidth();
29+
const otherWidth = await memoryViewer.getOtherSegmentWidth();
30+
const otherOffset = await memoryViewer.getOtherSegmentOffset();
31+
32+
expect(allocatorWidth).toBeTruthy();
33+
expect(otherWidth).toBeTruthy();
34+
expect(otherOffset).toBeTruthy();
35+
36+
const text = await memoryViewer.getText();
37+
expect(text).toMatch(/[\d.]+ \/ [\d.]+\s*GB/);
38+
});
39+
40+
test('Memory viewer popup shows on hover with all metrics', async ({page}) => {
41+
const memoryViewer = new MemoryViewer(page);
42+
43+
await memoryViewer.hover();
44+
45+
// Check progress viewer metrics
46+
const sharedCache = await memoryViewer.getSharedCacheInfo();
47+
expect(sharedCache.status).toBe('good');
48+
expect(sharedCache.text).toMatch(/\d+ \/ [\d.]+\s*GB/);
49+
50+
const queryExecution = await memoryViewer.getQueryExecutionInfo();
51+
expect(queryExecution.status).toBe('good');
52+
expect(queryExecution.text).toMatch(/\d+ \/ [\d.]+\s*GB/);
53+
54+
const memTable = await memoryViewer.getMemTableInfo();
55+
expect(memTable.status).toBe('good');
56+
expect(memTable.text).toMatch(/\d+ \/ \d+\s*GB/);
57+
58+
// Check simple metrics
59+
const allocatorCaches = await memoryViewer.getAllocatorCachesInfo();
60+
expect(allocatorCaches).toMatch(/[\d.]+\s*GB/);
61+
62+
const other = await memoryViewer.getOtherInfo();
63+
expect(other).toMatch(/[\d.]+\s*GB/);
64+
65+
const usage = await memoryViewer.getUsageInfo();
66+
expect(usage).toMatch(/[\d.]+\s*GB/);
67+
68+
const softLimit = await memoryViewer.getSoftLimitInfo();
69+
expect(softLimit).toMatch(/[\d.]+\s*GB/);
70+
71+
const hardLimit = await memoryViewer.getHardLimitInfo();
72+
expect(hardLimit).toMatch(/[\d.]+\s*GB/);
73+
});
74+
});

0 commit comments

Comments
 (0)