Skip to content
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

NAS-130848 / 25.04 / Add apps stats #10620

Merged
merged 9 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
25 changes: 12 additions & 13 deletions src/app/interfaces/api/api-event-directory.interface.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DockerStatusResponse } from 'app/enums/docker-config.interface';
import { FailoverStatus } from 'app/enums/failover-status.enum';
import { Alert } from 'app/interfaces/alert.interface';
import { App, ChartStatisticsUpdate } from 'app/interfaces/app.interface';
import { App, AppStats } from 'app/interfaces/app.interface';
import { ContainerImage } from 'app/interfaces/container-image.interface';
import { DirectoryServicesState } from 'app/interfaces/directory-services-state.interface';
import { Disk } from 'app/interfaces/disk.interface';
Expand All @@ -20,24 +20,23 @@ import { ZfsSnapshot } from 'app/interfaces/zfs-snapshot.interface';

export interface ApiEventDirectory {
'alert.list': { response: Alert };
'chart.release.query': { response: App };
'app.image.query': { response: ContainerImage };
'app.query': { response: App };
'chart.release.statistics': { response: ChartStatisticsUpdate[] };
'app.stats': { response: AppStats[] };
'core.get_jobs': { response: Job };
'directoryservices.status': { response: DirectoryServicesState };
'failover.status': { response: { status: FailoverStatus } };
'disk.query': { response: Disk };
'docker.state': { response: DockerStatusResponse };
'failover.disabled.reasons': { response: FailoverDisabledReasonEvent };
'failover.status': { response: { status: FailoverStatus } };
'group.query': { response: Group };
'pool.query': { response: Pool };
'reporting.realtime': { response: ReportingRealtimeUpdate };
'service.query': { response: Service };
'smart.test.progress': { response: SmartTestProgressUpdate };
'truecommand.config': { response: TrueCommandConfig };
'user.query': { response: User };
'vm.query': { response: VirtualMachine };
'zfs.snapshot.query': { response: ZfsSnapshot };
'zfs.pool.scan': { response: PoolScan };
'user.query': { response: User };
'disk.query': { response: Disk };
'pool.query': { response: Pool };
'group.query': { response: Group };
'app.image.query': { response: ContainerImage };
'reporting.realtime': { response: ReportingRealtimeUpdate };
'smart.test.progress': { response: SmartTestProgressUpdate };
'docker.state': { response: DockerStatusResponse };
'zfs.snapshot.query': { response: ZfsSnapshot };
}
44 changes: 34 additions & 10 deletions src/app/interfaces/app.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,44 @@ export interface App {
version_details?: ChartSchema;
}

export interface ChartStatisticsUpdate {
id: string;
stats: ChartReleaseStats;
}

export interface ChartReleaseStats {
cpu: number;
export interface AppStats {
app_name: string;
/**
* Percentage of cpu used by an app
*/
cpu_usage: number;
/**
* Current memory(in bytes) used by an app
*/
memory: number;
network: {
incoming: number;
outgoing: number;
networks: AppNetworkStats[];
blkio: {
/**
* Blkio read bytes
*/
read: number;
/**
* Blkio write bytes
*/
write: number;
};
}

interface AppNetworkStats {
/**
* Name of the interface use by the app
*/
interface_name: string;
/**
* Received bytes/s by an interface
*/
rx_bytes: number;
/**
* Transmitted bytes/s by an interface
*/
tx_bytes: number;
}

export interface AppCreate {
values: Record<string, ChartFormValue>;
app_name: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import {
AppResourcesCardComponent,
} from 'app/pages/apps/components/app-detail-view/app-resources-card/app-resources-card.component';
import { AppsStatsService } from 'app/pages/apps/store/apps-stats.service';
import { AppsStore } from 'app/pages/apps/store/apps-store.service';
import { DockerStore } from 'app/pages/apps/store/docker.store';
import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.service';
Expand Down Expand Up @@ -88,6 +89,7 @@ describe('AppDetailViewComponent', () => {
mockProvider(DockerStore, {
selectedPool$: of('pool'),
}),
mockProvider(AppsStatsService),
],
params: { appId: 'webdav', catalog: 'TRUENAS', train: 'community' },
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { AppsFilterStore } from 'app/pages/apps/store/apps-filter-store.service';
import { AppsStatsService } from 'app/pages/apps/store/apps-stats.service';
import { AppsStore } from 'app/pages/apps/store/apps-store.service';
import { DockerStore } from 'app/pages/apps/store/docker.store';
import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.service';
Expand All @@ -14,6 +15,7 @@ import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.se
InstalledAppsStore,
DockerStore,
AppsStore,
AppsStatsService,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CustomAppButtonComponent,
} from 'app/pages/apps/components/available-apps/custom-app-button/custom-app-button.component';
import { AppsFilterStore } from 'app/pages/apps/store/apps-filter-store.service';
import { AppsStatsService } from 'app/pages/apps/store/apps-stats.service';
import { AppsStore } from 'app/pages/apps/store/apps-store.service';
import { DockerStore } from 'app/pages/apps/store/docker.store';
import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.service';
Expand Down Expand Up @@ -67,6 +68,7 @@ describe('Finding app', () => {
searchQuery$: of('webdav'),
}),
mockAuth(),
mockProvider(AppsStatsService),
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@
(click)="statusPressed(); $event.stopPropagation()"
></ix-app-state-cell>
</div>
<div class="cell cell-cpu" [matTooltip]="'Percentage of total core utilization' | translate">
@if (stats()?.cpu_usage) {
<span>{{ stats().cpu_usage.toFixed(0) }}%</span>
} @else {
{{ 'N/A' | translate }}
}
</div>
<div class="cell cell-ram" [matTooltip]="'Memory usage of app' | translate">
@if (stats()?.memory) {
<span>{{ stats().memory | ixFileSize }}</span>
} @else {
{{ 'N/A' | translate }}
}
</div>
<div class="cell cell-network" [matTooltip]="'Incoming / Outgoing network traffic' | translate">
@if (stats()?.networks) {
<span>
{{ stats().networks[0].rx_bytes | ixNetworkSpeed }}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented in incorrect place. App widgets can be fixed later, but network stats need to be fixed here now.

-
{{ stats().networks[0].tx_bytes | ixNetworkSpeed }}
</span>
} @else {
{{ 'N/A' | translate }}
}
</div>
<div class="cell cell-update">
<ix-app-update-cell [app]="app()" [showIcon]="true"></ix-app-update-cell>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { MockComponents } from 'ng-mocks';
import { ImgFallbackModule } from 'ngx-img-fallback';
import { MiB } from 'app/constants/bytes.constant';
import { mockAuth } from 'app/core/testing/utils/mock-auth.utils';
import { AppState } from 'app/enums/app-state.enum';
import { App } from 'app/interfaces/app.interface';
Expand All @@ -22,6 +23,21 @@ describe('AppRowComponent', () => {
metadata: { icon: 'https://image/' },
} as App;

const stats = {
app_name: app.name,
cpu_usage: 90,
memory: 80 * MiB,
networks: [{
interface_name: 'eth0',
rx_bytes: 256,
tx_bytes: 512,
}],
blkio: {
read: 1024,
write: 2048,
},
};

const createComponent = createComponentFactory({
component: AppRowComponent,
imports: [
Expand All @@ -42,6 +58,7 @@ describe('AppRowComponent', () => {
props: {
app,
selected: false,
stats,
},
});

Expand All @@ -64,11 +81,10 @@ describe('AppRowComponent', () => {
expect(updateCell.hasUpdate).toBeFalsy();
});

// TODO: https://ixsystems.atlassian.net/browse/NAS-130471
it.skip('shows app usages statistics', () => {
expect(spectator.query('.cell-cpu')).toHaveText('50%');
expect(spectator.query('.cell-ram')).toHaveText('19.07 MiB');
expect(spectator.query('.cell-network')).toHaveText('50 Mb/s - 55.5 Mb/s');
it('shows app usages stats', () => {
expect(spectator.query('.cell-cpu')).toHaveText('90%');
expect(spectator.query('.cell-ram')).toHaveText('80 MiB');
expect(spectator.query('.cell-network')).toHaveText('256 b/s - 512 b/s');
});

describe('actions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import { appImagePlaceholder } from 'app/constants/catalog.constants';
import { AppState } from 'app/enums/app-state.enum';
import { Role } from 'app/enums/role.enum';
import { App, AppStartQueryParams } from 'app/interfaces/app.interface';
import { App, AppStartQueryParams, AppStats } from 'app/interfaces/app.interface';
import { Job } from 'app/interfaces/job.interface';

@Component({
Expand All @@ -16,6 +16,7 @@ import { Job } from 'app/interfaces/job.interface';
})
export class AppRowComponent {
readonly app = input.required<App>();
readonly stats = input.required<AppStats>();
readonly selected = input.required<boolean>();
readonly job = input<Job<void, AppStartQueryParams>>();

Expand All @@ -27,10 +28,7 @@ export class AppRowComponent {
protected readonly imagePlaceholder = appImagePlaceholder;
protected readonly requiredRoles = [Role.AppsWrite];

readonly hasUpdates = computed(() => {
return this.app().upgrade_available;
});

readonly hasUpdates = computed(() => this.app().upgrade_available);
readonly isAppStopped = computed(() => this.app().state === AppState.Stopped);

readonly inProgress = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ <h2>{{ 'Applications' | translate }}</h2>
>
{{ 'Status' | translate }}
</div>
<!-- TODO: https://ixsystems.atlassian.net/browse/NAS-130471 -->
<!-- <div>{{ 'CPU' | translate }}</div> -->
<!-- <div>{{ 'RAM' | translate }}</div> -->
<!-- <div>{{ 'Network' | translate }}</div> -->
<div>{{ 'CPU' | translate }}</div>
<div>{{ 'RAM' | translate }}</div>
<div>{{ 'Network' | translate }}</div>
<div
class="app-update-header"
[matColumnDef]="sortableField.Updates"
Expand Down Expand Up @@ -183,6 +182,7 @@ <h2>{{ 'Applications' | translate }}</h2>
<ix-app-row
tabindex="0"
[app]="app"
[stats]="getAppStats(app.name) | async"
[class.selected]="selectedApp.id === app.id"
[selected]="selection.isSelected(app.id)"
[job]="appJobs.get(app.name)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@

ix-app-row,
.app-header-row {
// TODO: https://ixsystems.atlassian.net/browse/NAS-130471
//grid-template-columns: 45px minmax(160px, auto) minmax(100px, 120px) 50px 55px 85px 85px 40px;
grid-template-columns: 45px minmax(160px, auto) minmax(100px, 120px) 85px 40px;
grid-template-columns: 45px minmax(160px, auto) minmax(100px, 120px) 50px 55px 85px 85px 40px;

@media (max-width: $breakpoint-tablet) {
grid-template-columns: 45px auto 0 0 0 0 0 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { AppSettingsButtonComponent } from 'app/pages/apps/components/installed-
import { DockerStatusComponent } from 'app/pages/apps/components/installed-apps/docker-status/docker-status.component';
import { InstalledAppsComponent } from 'app/pages/apps/components/installed-apps/installed-apps.component';
import { ApplicationsService } from 'app/pages/apps/services/applications.service';
import { AppsStatsService } from 'app/pages/apps/store/apps-stats.service';
import { AppsStore } from 'app/pages/apps/store/apps-store.service';
import { DockerStore } from 'app/pages/apps/store/docker.store';
import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.service';
Expand Down Expand Up @@ -101,6 +102,7 @@ describe('InstalledAppsComponent', () => {
},
mockWebSocket([]),
mockAuth(),
mockProvider(AppsStatsService),
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import {
combineLatest, filter,
Observable,
} from 'rxjs';
import { AppState } from 'app/enums/app-state.enum';
import { EmptyType } from 'app/enums/empty-type.enum';
import { Role } from 'app/enums/role.enum';
import { WINDOW } from 'app/helpers/window.helper';
import { helptextApps } from 'app/helptext/apps/apps';
import { App, AppStartQueryParams } from 'app/interfaces/app.interface';
import { App, AppStartQueryParams, AppStats } from 'app/interfaces/app.interface';
import { CoreBulkResponse } from 'app/interfaces/core-bulk.interface';
import { EmptyConfig } from 'app/interfaces/empty-config.interface';
import { Job } from 'app/interfaces/job.interface';
Expand All @@ -37,6 +38,7 @@ import { SnackbarService } from 'app/modules/snackbar/services/snackbar.service'
import { AppBulkUpgradeComponent } from 'app/pages/apps/components/installed-apps/app-bulk-upgrade/app-bulk-upgrade.component';
import { installedAppsElements } from 'app/pages/apps/components/installed-apps/installed-apps.elements';
import { ApplicationsService } from 'app/pages/apps/services/applications.service';
import { AppsStatsService } from 'app/pages/apps/store/apps-stats.service';
import { DockerStore } from 'app/pages/apps/store/docker.store';
import { InstalledAppsStore } from 'app/pages/apps/store/installed-apps-store.service';
import { ErrorHandlerService } from 'app/services/error-handler.service';
Expand Down Expand Up @@ -158,6 +160,7 @@ export class InstalledAppsComponent implements OnInit, AfterViewInit {
private errorHandler: ErrorHandlerService,
private store$: Store<AppsState>,
private location: Location,
private appsStats: AppsStatsService,
@Inject(WINDOW) private window: Window,
) {
this.router.events
Expand Down Expand Up @@ -459,4 +462,8 @@ export class InstalledAppsComponent implements OnInit, AfterViewInit {
this.cdr.markForCheck();
});
}

getAppStats(name: string): Observable<AppStats> {
return this.appsStats.getStatsForApp(name);
}
}
4 changes: 3 additions & 1 deletion src/app/pages/apps/services/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { AppExtraCategory } from 'app/enums/app-extra-category.enum';
import { AppState } from 'app/enums/app-state.enum';
import { JobState } from 'app/enums/job-state.enum';
import { ApiEvent } from 'app/interfaces/api-message.interface';
import { App, AppStartQueryParams, AppUpgradeParams } from 'app/interfaces/app.interface';
import {
App, AppStartQueryParams, AppUpgradeParams,
} from 'app/interfaces/app.interface';
import { AppUpgradeSummary } from 'app/interfaces/application.interface';
import { AppsFiltersValues } from 'app/interfaces/apps-filters-values.interface';
import { AvailableApp } from 'app/interfaces/available-app.interface';
Expand Down
Loading
Loading