Skip to content

fix: fallback for detailed memory #1696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 26, 2024
Merged
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
15 changes: 8 additions & 7 deletions src/components/MemoryViewer/MemoryViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {HoverPopup} from '../HoverPopup/HoverPopup';
import type {FormatProgressViewerValues} from '../ProgressViewer/ProgressViewer';
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';

import {getMemorySegments} from './utils';
import {calculateAllocatedMemory, getMemorySegments} from './utils';

import './MemoryViewer.scss';

Expand Down Expand Up @@ -53,22 +53,23 @@ export function MemoryViewer({
warningThreshold = 60,
dangerThreshold = 80,
}: MemoryProgressViewerProps) {
const value = stats.AnonRss;
const memoryUsage = stats.AnonRss ?? calculateAllocatedMemory(stats);

const capacity = stats.HardLimit;

const theme = useTheme();
let fillWidth =
Math.round((parseFloat(String(value)) / parseFloat(String(capacity))) * 100) || 0;
Math.round((parseFloat(String(memoryUsage)) / parseFloat(String(capacity))) * 100) || 0;
fillWidth = fillWidth > 100 ? 100 : fillWidth;
let valueText: number | string | undefined = value,
let valueText: number | string | undefined = memoryUsage,
capacityText: number | string | undefined = capacity,
divider = '/';
if (percents) {
valueText = fillWidth + '%';
capacityText = '';
divider = '';
} else if (formatValues) {
[valueText, capacityText] = formatValues(Number(value), Number(capacity));
[valueText, capacityText] = formatValues(Number(memoryUsage), Number(capacity));
}

const renderContent = () => {
Expand All @@ -80,13 +81,13 @@ export function MemoryViewer({
};

const calculateMemoryShare = (segmentSize: number) => {
if (!value) {
if (!memoryUsage) {
return 0;
}
return (segmentSize / parseFloat(String(capacity))) * 100;
};

const memorySegments = getMemorySegments(stats);
const memorySegments = getMemorySegments(stats, Number(memoryUsage));

const status = calculateProgressStatus({
fillWidth,
Expand Down
30 changes: 16 additions & 14 deletions src/components/MemoryViewer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import {isNumeric} from '../../utils/utils';

import i18n from './i18n';

function getMaybeNumber(value: string | number | undefined): number | undefined {
export function calculateAllocatedMemory(stats: TMemoryStats) {
const allocatedMemory = getMaybeNumber(stats.AllocatedMemory) || 0;
const allocatorCaches = getMaybeNumber(stats.AllocatorCachesMemory) || 0;
return String(allocatedMemory + allocatorCaches);
}

export function getMaybeNumber(value: string | number | undefined): number | undefined {
return isNumeric(value) ? parseFloat(String(value)) : undefined;
}

Expand All @@ -15,7 +21,7 @@ interface MemorySegment {
isInfo?: boolean;
}

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

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

if (totalMemory) {
const otherMemory = Math.max(0, totalMemory - sumNonInfoSegments);

segments.push({
label: i18n('text_other'),
key: 'Other',
value: otherMemory,
isInfo: false,
});
}
segments.push({
label: i18n('text_other'),
key: 'Other',
value: otherMemory,
isInfo: false,
});

segments.push(
{
Expand All @@ -74,7 +76,7 @@ export function getMemorySegments(stats: TMemoryStats): MemorySegment[] {
{
label: i18n('text_usage'),
key: 'Usage',
value: getMaybeNumber(stats.AnonRss),
value: memoryUsage,
isInfo: true,
},
{
Expand Down
3 changes: 2 additions & 1 deletion src/types/api/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ export interface TNodesGroup {
}

export interface TMemoryStats {
AnonRss: string;
AnonRss?: string;
ExternalConsumption?: string;
AllocatorCachesMemory?: string;
AllocatedMemory?: string;

SharedCacheConsumption?: string;
SharedCacheLimit?: string;
Expand Down
125 changes: 125 additions & 0 deletions tests/suites/memoryViewer/MemoryViewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type {Locator, Page} from '@playwright/test';

export class MemoryViewer {
readonly container: Locator;
readonly progressContainer: Locator;
readonly allocatorSegment: Locator;
readonly otherSegment: Locator;
readonly text: Locator;
readonly popup: Locator;
readonly definitionList: Locator;

constructor(page: Page) {
const paginatedTable = page.locator('.ydb-paginated-table__table');
this.container = paginatedTable.locator('tbody tr').nth(0).locator('.memory-viewer');
this.progressContainer = this.container.locator('.memory-viewer__progress-container');
this.allocatorSegment = this.progressContainer.locator(
'.memory-viewer__segment_type_AllocatorCachesMemory',
);
this.otherSegment = this.progressContainer.locator('.memory-viewer__segment_type_Other');
this.text = this.progressContainer.locator('.memory-viewer__text');

// Popup elements
this.popup = page.locator('.g-popup.g-popup_open');
this.definitionList = this.popup.locator('.g-definition-list');
}

async getStatus() {
const classList = await this.container.getAttribute('class');
if (classList?.includes('memory-viewer_status_good')) {
return 'good';
}
return 'unknown';
}

async getTheme() {
const classList = await this.container.getAttribute('class');
if (classList?.includes('memory-viewer_theme_system')) {
return 'system';
}
return 'unknown';
}

async getAllocatorSegmentWidth() {
return this.allocatorSegment.evaluate((el) => el.style.width);
}

async getOtherSegmentWidth() {
return this.otherSegment.evaluate((el) => el.style.width);
}

async getOtherSegmentOffset() {
return this.otherSegment.evaluate((el) => el.style.left);
}

async getText() {
return this.text.innerText();
}

async getItemValue(name: string) {
const item = this.getDefinitionItem(name);
return item.locator('.g-definition-list__definition').innerText();
}

async getProgressValue(name: string) {
const item = this.getDefinitionItem(name);
const progressViewer = item.locator('.progress-viewer');
const line = progressViewer.locator('.progress-viewer__line');
const text = progressViewer.locator('.progress-viewer__text');

return {
width: await line.evaluate((el) => el.style.width),
text: await text.innerText(),
status: await this.getProgressStatus(progressViewer),
};
}

async getSharedCacheInfo() {
return this.getProgressValue('Shared Cache');
}

async getQueryExecutionInfo() {
return this.getProgressValue('Query Execution');
}

async getMemTableInfo() {
return this.getProgressValue('MemTable');
}

async getAllocatorCachesInfo() {
return this.getItemValue('Allocator Caches');
}

async getOtherInfo() {
return this.getItemValue('Other');
}

async getUsageInfo() {
return this.getItemValue('Usage');
}

async getSoftLimitInfo() {
return this.getItemValue('Soft Limit');
}

async getHardLimitInfo() {
return this.getItemValue('Hard Limit');
}

async hover() {
await this.container.hover();
}

// Private methods
private getDefinitionItem(name: string) {
return this.definitionList.locator('.g-definition-list__item').filter({hasText: name});
}

private async getProgressStatus(progressViewer: Locator) {
const classList = await progressViewer.getAttribute('class');
if (classList?.includes('progress-viewer_status_good')) {
return 'good';
}
return 'unknown';
}
}
74 changes: 74 additions & 0 deletions tests/suites/memoryViewer/memoryViewer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {expect, test} from '@playwright/test';

import {NodesPage} from '../nodes/NodesPage';

import {MemoryViewer} from './MemoryViewer';

test.describe('Memory Viewer Widget', () => {
test.beforeEach(async ({page}) => {
const nodesPage = new NodesPage(page);
await nodesPage.goto();

// Get the first row's memory viewer
const paginatedTable = await page.locator('.ydb-paginated-table__table');
await paginatedTable.waitFor();
});

test('Memory viewer is visible and has correct status', async ({page}) => {
const memoryViewer = new MemoryViewer(page);

await expect(memoryViewer.container).toBeVisible();
expect(await memoryViewer.getStatus()).toBe('good');
expect(await memoryViewer.getTheme()).toBe('system');
});

test('Memory viewer shows correct base metrics', async ({page}) => {
const memoryViewer = new MemoryViewer(page);

const allocatorWidth = await memoryViewer.getAllocatorSegmentWidth();
const otherWidth = await memoryViewer.getOtherSegmentWidth();
const otherOffset = await memoryViewer.getOtherSegmentOffset();

expect(allocatorWidth).toBeTruthy();
expect(otherWidth).toBeTruthy();
expect(otherOffset).toBeTruthy();

const text = await memoryViewer.getText();
expect(text).toMatch(/[\d.]+ \/ [\d.]+\s*GB/);
});

test('Memory viewer popup shows on hover with all metrics', async ({page}) => {
const memoryViewer = new MemoryViewer(page);

await memoryViewer.hover();

// Check progress viewer metrics
const sharedCache = await memoryViewer.getSharedCacheInfo();
expect(sharedCache.status).toBe('good');
expect(sharedCache.text).toMatch(/\d+ \/ [\d.]+\s*GB/);

const queryExecution = await memoryViewer.getQueryExecutionInfo();
expect(queryExecution.status).toBe('good');
expect(queryExecution.text).toMatch(/\d+ \/ [\d.]+\s*GB/);

const memTable = await memoryViewer.getMemTableInfo();
expect(memTable.status).toBe('good');
expect(memTable.text).toMatch(/\d+ \/ \d+\s*GB/);

// Check simple metrics
const allocatorCaches = await memoryViewer.getAllocatorCachesInfo();
expect(allocatorCaches).toMatch(/[\d.]+\s*GB/);

const other = await memoryViewer.getOtherInfo();
expect(other).toMatch(/[\d.]+\s*GB/);

const usage = await memoryViewer.getUsageInfo();
expect(usage).toMatch(/[\d.]+\s*GB/);

const softLimit = await memoryViewer.getSoftLimitInfo();
expect(softLimit).toMatch(/[\d.]+\s*GB/);

const hardLimit = await memoryViewer.getHardLimitInfo();
expect(hardLimit).toMatch(/[\d.]+\s*GB/);
});
});
Loading