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

Show used resources #1097

Merged
merged 16 commits into from
Oct 1, 2024
Merged
2 changes: 2 additions & 0 deletions src/components/app-overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DefaultAppAlias } from '../component/default-app-alias';
import { DNSAliases } from '../component/dns-aliases';
import { EnvironmentsSummary } from '../environments-summary';
import { JobsList } from '../jobs-list';
import { UsedResources } from '../resources';

const LATEST_JOBS_LIMIT = 5;

Expand Down Expand Up @@ -50,6 +51,7 @@ export function AppOverview({ appName }: { appName: string }) {
<div className="grid grid--gap-medium grid--overview-columns">
<ApplicationCost appName={appName} />
<FutureApplicationCost appName={appName} />
<UsedResources appName={appName} />
</div>

{appAlias && <DefaultAppAlias appName={appName} appAlias={appAlias} />}
Expand Down
2 changes: 1 addition & 1 deletion src/components/replica-list/replica-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const ReplicaName: FunctionComponent<{
displayName={'Job Manager'}
replicaName={replica.name}
description={
'Job Manager creates, gets, deletes singe jobs and batch jobs with Job API'
'Job Manager creates, gets, deletes single jobs and batch jobs with Job API'
}
replicaUrlFunc={replicaUrlFunc}
/>
Expand Down
177 changes: 177 additions & 0 deletions src/components/resources/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { Icon, Table, Tooltip, Typography } from '@equinor/eds-core-react';
import { info_circle, library_books } from '@equinor/eds-icons';
import * as PropTypes from 'prop-types';
import { type FunctionComponent, useState } from 'react';
import { externalUrls } from '../../externalUrls';
import {
type GetResourcesApiResponse,
useGetResourcesQuery,
} from '../../store/radix-api';
import { formatDateTimeYear } from '../../utils/datetime';
import AsyncResource from '../async-resource/async-resource';
import { ScrimPopup } from '../scrim-popup';

import './style.css';

function getPeriod({ from, to }: GetResourcesApiResponse): string {
return `${formatDateTimeYear(new Date(from))} - ${formatDateTimeYear(
new Date(to)
)}`;
}

export interface UsedResourcesProps {
appName: string;
}

export const UsedResources: FunctionComponent<UsedResourcesProps> = ({
appName,
}) => {
const { data: resources, ...state } = useGetResourcesQuery(
{ appName },
{ skip: !appName }
);
const [visibleScrim, setVisibleScrim] = useState(false);
return (
<div className="grid grid--gap-medium">
<div className="grid grid--gap-medium grid--auto-columns">
<Typography variant="h6"> Used resources</Typography>
<Typography
link
href={externalUrls.resourcesDocs}
rel="noopener noreferrer"
>
<Tooltip title="Read more in the documentation">
<Icon data={library_books} />
</Tooltip>
</Typography>
</div>
<AsyncResource asyncState={state}>
{resources ? (
<div className="resources-section grid grid--gap-medium">
<div className="grid grid--gap-small">
<Typography variant="overline">Period</Typography>
<Typography group="input" variant="text">
{getPeriod(resources)}
</Typography>
</div>

<div className="grid grid--gap-small grid--auto-columns">
<div>
<Typography>
CPU{' '}
<strong>
min {resources?.cpu?.min ?? '-'}, max{' '}
{resources?.cpu?.max ?? '-'}, avg{' '}
{resources?.cpu?.average ?? '-'}
</strong>
</Typography>
<Typography>
Memory{' '}
<strong>
min {resources?.memory?.min ?? '-'}, max{' '}
{resources?.memory?.max ?? '-'}, avg{' '}
{resources?.memory?.average ?? '-'}
</strong>
</Typography>
</div>
<Icon
style={{ cursor: 'pointer' }}
data={info_circle}
className={'icon-justify-end'}
onClick={() => setVisibleScrim(true)}
/>
<ScrimPopup
className={'resources__scrim'}
title={'Used resources'}
open={visibleScrim}
isDismissable
onClose={() => setVisibleScrim(false)}
>
<div className={'resources__scrim-content'}>
<Table className={'resources-content'}>
<Table.Head>
<Table.Row>
<Table.Cell />
<Table.Cell>Min</Table.Cell>
<Table.Cell>Max</Table.Cell>
<Table.Cell>Average</Table.Cell>
</Table.Row>
</Table.Head>
<Table.Body>
<Table.Row>
<Table.Cell>
CPU (millicores , rounded){' '}
<Typography
link
href={externalUrls.kubernetesResourcesCpuUnits}
rel="noopener noreferrer"
target="_blank"
>
<Icon data={library_books} />
</Typography>
</Table.Cell>
<Table.Cell>{resources?.cpu?.min ?? '-'}</Table.Cell>
<Table.Cell>{resources?.cpu?.max ?? '-'}</Table.Cell>
<Table.Cell>
{resources?.cpu?.average ?? '-'}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>CPU (millicores, actual)</Table.Cell>
<Table.Cell>
{resources?.cpu?.minActual ?? '-'}
</Table.Cell>
<Table.Cell>
{resources?.cpu?.maxActual ?? '-'}
</Table.Cell>
<Table.Cell>
{resources?.cpu?.avgActual ?? '-'}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Memory (MB, rounded){' '}
<Typography
link
href={externalUrls.kubernetesResourcesMemoryUnits}
rel="noopener noreferrer"
target="_blank"
>
<Icon data={library_books} />
</Typography>
</Table.Cell>
<Table.Cell>{resources?.memory?.min ?? '-'}</Table.Cell>
<Table.Cell>{resources?.memory?.max ?? '-'}</Table.Cell>
<Table.Cell>
{resources?.memory?.average ?? '-'}
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>Memory ( MB , actual)</Table.Cell>
<Table.Cell>
{resources?.memory?.minActual ?? '-'}
</Table.Cell>
<Table.Cell>
{resources?.memory?.maxActual ?? '-'}
</Table.Cell>
<Table.Cell>
{resources?.memory?.avgActual ?? '-'}
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
</div>
</ScrimPopup>
</div>
</div>
) : (
<Typography variant="caption">No data</Typography>
)}
</AsyncResource>
</div>
);
};

UsedResources.propTypes = {
appName: PropTypes.string.isRequired,
};
26 changes: 26 additions & 0 deletions src/components/resources/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.resources-section > div {
grid-auto-rows: min-content;
}

@media (min-width: 30rem) {
.resources-section {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
}

.icon-justify-end {
justify-self: end;
}

.resources__scrim .resources__scrim-content {
margin: var(--eds_spacing_medium);
margin-top: initial;
align-content: center;
min-width: 500px;
}

.resources-content {
padding: var(--eds_spacing_medium);
padding-top: 0;
overflow: auto;
}
3 changes: 3 additions & 0 deletions src/externalUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const externalUrls = {
externalDNSGuide: 'https://www.radix.equinor.com/guides/external-alias/',
workloadIdentityGuide: 'https://www.radix.equinor.com/guides/workload-identity/',
uptimeDocs: 'https://radix.equinor.com/docs/topic-uptime/',
resourcesDocs: 'https://radix.equinor.com/guides/resource-request/',
kubernetesResourcesCpuUnits: 'https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-cpu',
kubernetesResourcesMemoryUnits: 'https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-memory',
radixPlatformWebConsole: `https://console.${clusterBases.radixPlatformWebConsole}/`,
radixPlatform2WebConsole: `https://console.${clusterBases.radixPlatform2WebConsole}/`,
playgroundWebConsole: `https://console.${clusterBases.playgroundWebConsole}/`,
Expand Down
61 changes: 61 additions & 0 deletions src/store/radix-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,22 @@ const injectedRtkApi = api.injectEndpoints({
},
}),
}),
getResources: build.query<GetResourcesApiResponse, GetResourcesApiArg>({
query: (queryArg) => ({
url: `/applications/${queryArg.appName}/resources`,
headers: {
'Impersonate-User': queryArg['Impersonate-User'],
'Impersonate-Group': queryArg['Impersonate-Group'],
},
params: {
environment: queryArg.environment,
component: queryArg.component,
duration: queryArg.duration,
since: queryArg.since,
ignorezero: queryArg.ignorezero,
},
}),
}),
restartApplication: build.mutation<
RestartApplicationApiResponse,
RestartApplicationApiArg
Expand Down Expand Up @@ -2205,6 +2221,26 @@ export type ResetManuallyScaledComponentsInApplicationApiArg = {
/** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */
'Impersonate-Group'?: string;
};
export type GetResourcesApiResponse =
/** status 200 Successful trigger pipeline */ UsedResources;
export type GetResourcesApiArg = {
/** Name of the application */
appName: string;
/** Name of the application environment */
environment?: string;
/** Name of the application component in an environment */
component?: string;
/** Duration of the period, default is 30d (30 days). Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks */
duration?: string;
/** End time-point of the period in the past, default is now. Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks */
since?: string;
/** Ignore metrics with zero value if true, default is false */
ignorezero?: string;
/** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */
'Impersonate-User'?: string;
/** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */
'Impersonate-Group'?: string;
};
export type RestartApplicationApiResponse = unknown;
export type RestartApplicationApiArg = {
/** Name of application */
Expand Down Expand Up @@ -3346,6 +3382,30 @@ export type RegenerateDeployKeyAndSecretData = {
/** SharedSecret of the shared secret */
sharedSecret?: string;
};
export type UsedResource = {
/** Average resource used */
average?: string;
/** AvgActual actual precise resource used */
avgActual?: number;
/** Max resource used */
max?: string;
/** MaxActual actual precise resource used */
maxActual?: number;
/** Min resource used */
min?: string;
/** MinActual actual precise resource used */
minActual?: number;
};
export type UsedResources = {
cpu?: UsedResource;
/** From timestamp */
from: string;
memory?: UsedResource;
/** To timestamp */
to: string;
/** Warning messages */
warnings?: string[];
};
export const {
useShowApplicationsQuery,
useRegisterApplicationMutation,
Expand Down Expand Up @@ -3431,6 +3491,7 @@ export const {
useUpdatePrivateImageHubsSecretValueMutation,
useRegenerateDeployKeyMutation,
useResetManuallyScaledComponentsInApplicationMutation,
useGetResourcesQuery,
useRestartApplicationMutation,
useStartApplicationMutation,
useStopApplicationMutation,
Expand Down