1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-10 08:15:25 +02:00

feat(gpu): rework docker GPU for UI performance [EE-4918] (#8518)

This commit is contained in:
Ali 2023-03-03 14:47:10 +13:00 committed by GitHub
parent 769c8372fb
commit fd916bc8a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 692 additions and 285 deletions

View file

@ -1,4 +1,3 @@
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { type EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
import { type TagId } from '@/portainer/tags/types';
@ -18,7 +17,6 @@ interface CreateLocalDockerEnvironment {
socketPath?: string;
publicUrl?: string;
meta?: EnvironmentMetadata;
gpus?: Gpu[];
}
export async function createLocalDockerEnvironment({
@ -26,7 +24,6 @@ export async function createLocalDockerEnvironment({
socketPath = '',
publicUrl = '',
meta = { tagIds: [] },
gpus = [],
}: CreateLocalDockerEnvironment) {
const url = prefixPath(socketPath);
@ -37,7 +34,6 @@ export async function createLocalDockerEnvironment({
url,
publicUrl,
meta,
gpus,
}
);
@ -113,7 +109,6 @@ export interface EnvironmentOptions {
azure?: AzureSettings;
tls?: TLSSettings;
isEdgeDevice?: boolean;
gpus?: Gpu[];
pollFrequency?: number;
edge?: EdgeSettings;
tunnelServerAddr?: string;
@ -145,7 +140,6 @@ export interface CreateAgentEnvironmentValues {
name: string;
environmentUrl: string;
meta: EnvironmentMetadata;
gpus: Gpu[];
}
export function createAgentEnvironment({
@ -173,7 +167,6 @@ interface CreateEdgeAgentEnvironment {
tunnelServerAddr?: string;
meta?: EnvironmentMetadata;
pollFrequency: number;
gpus?: Gpu[];
isEdgeDevice?: boolean;
edge: EdgeSettings;
}
@ -182,7 +175,6 @@ export function createEdgeAgentEnvironment({
name,
portainerUrl,
meta = { tagIds: [] },
gpus = [],
isEdgeDevice,
pollFrequency,
edge,
@ -196,7 +188,6 @@ export function createEdgeAgentEnvironment({
skipVerify: true,
skipClientVerify: true,
},
gpus,
isEdgeDevice,
pollFrequency,
edge,
@ -226,7 +217,6 @@ async function createEnvironment(
TagIds: arrayToJson(tagIds),
CheckinInterval: options.pollFrequency,
IsEdgeDevice: options.isEdgeDevice,
Gpus: arrayToJson(options.gpus),
};
const { tls, azure } = options;

View file

@ -1,11 +1,18 @@
import { useQuery } from 'react-query';
import { getEndpoint } from '@/react/portainer/environments/environment.service';
import { EnvironmentId } from '@/react/portainer/environments/types';
import {
Environment,
EnvironmentId,
} from '@/react/portainer/environments/types';
import { withError } from '@/react-tools/react-query';
export function useEnvironment(id?: EnvironmentId) {
export function useEnvironment<T = Environment | null>(
id?: EnvironmentId,
select?: (environment: Environment | null) => T
) {
return useQuery(['environments', id], () => (id ? getEndpoint(id) : null), {
select,
...withError('Failed loading environment'),
staleTime: 50,
enabled: !!id,

View file

@ -131,6 +131,7 @@ export type Environment = {
TagIds: TagId[];
GroupId: EnvironmentGroupId;
DeploymentOptions: DeploymentOptions | null;
EnableGPUManagement: boolean;
EdgeID?: string;
EdgeKey: string;
EdgeCheckinInterval?: number;

View file

@ -3,7 +3,6 @@ import { useReducer } from 'react';
import { Plug2 } from 'lucide-react';
import { useCreateRemoteEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
import { Hardware } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/Hardware';
import { notifySuccess } from '@/portainer/services/notifications';
import {
Environment,
@ -13,6 +12,7 @@ import {
import { LoadingButton } from '@@/buttons/LoadingButton';
import { FormControl } from '@@/form-components/FormControl';
import { Input } from '@@/form-components/Input';
import { InsightsBox } from '@@/InsightsBox';
import { NameField } from '../../shared/NameField';
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
@ -23,9 +23,10 @@ import { TLSFieldset } from './TLSFieldset';
interface Props {
onCreate(environment: Environment): void;
isDockerStandalone?: boolean;
}
export function APIForm({ onCreate }: Props) {
export function APIForm({ onCreate, isDockerStandalone }: Props) {
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
const initialValues: FormValues = {
url: '',
@ -35,7 +36,6 @@ export function APIForm({ onCreate }: Props) {
groupId: 1,
tagIds: [],
},
gpus: [],
};
const mutation = useCreateRemoteEnvironmentMutation(
@ -73,7 +73,33 @@ export function APIForm({ onCreate }: Props) {
<TLSFieldset />
<MoreSettingsSection>
<Hardware />
{isDockerStandalone && (
<InsightsBox
content={
<>
<p>
From 2.18 on, the set-up of available GPUs for a Docker
Standalone environment has been shifted from Add
environment and Environment details to Host -&gt; Setup,
so as to align with other settings.
</p>
<p>
A toggle has been introduced for enabling/disabling
management of GPU settings in the Portainer UI - to
alleviate the performance impact of showing those
settings.
</p>
<p>
The UI has been updated to clarify that GPU settings
support is only for Docker Standalone (and not Docker
Swarm, which was never supported in the UI).
</p>
</>
}
header="GPU settings update"
insightCloseId="gpu-settings-update-closed"
/>
)}
</MoreSettingsSection>
<div className="form-group">
@ -104,7 +130,6 @@ export function APIForm({ onCreate }: Props) {
options: {
tls,
meta: values.meta,
gpus: values.gpus,
},
},
{

View file

@ -1,7 +1,5 @@
import { boolean, object, SchemaOf, string } from 'yup';
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
import { useNameValidation } from '../../shared/NameField';
@ -16,6 +14,5 @@ export function useValidation(): SchemaOf<FormValues> {
skipVerify: boolean(),
meta: metadataValidation(),
...certsValidation(),
gpus: gpusListValidation(),
});
}

View file

@ -4,12 +4,13 @@ import { APIForm } from './APIForm';
interface Props {
onCreate(environment: Environment): void;
isDockerStandalone?: boolean;
}
export function APITab({ onCreate }: Props) {
export function APITab({ onCreate, isDockerStandalone }: Props) {
return (
<div className="mt-5">
<APIForm onCreate={onCreate} />
<APIForm onCreate={onCreate} isDockerStandalone={isDockerStandalone} />
</div>
);
}

View file

@ -1,4 +1,3 @@
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
export interface FormValues {
@ -10,5 +9,4 @@ export interface FormValues {
certFile?: File;
keyFile?: File;
meta: EnvironmentMetadata;
gpus?: Gpu[];
}

View file

@ -3,7 +3,6 @@ import { useReducer } from 'react';
import { Plug2 } from 'lucide-react';
import { useCreateLocalDockerEnvironmentMutation } from '@/react/portainer/environments/queries/useCreateEnvironmentMutation';
import { Hardware } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/Hardware';
import { notifySuccess } from '@/portainer/services/notifications';
import { Environment } from '@/react/portainer/environments/types';
@ -11,6 +10,7 @@ import { LoadingButton } from '@@/buttons/LoadingButton';
import { FormControl } from '@@/form-components/FormControl';
import { Input } from '@@/form-components/Input';
import { SwitchField } from '@@/form-components/SwitchField';
import { InsightsBox } from '@@/InsightsBox';
import { NameField } from '../../shared/NameField';
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
@ -20,16 +20,16 @@ import { FormValues } from './types';
interface Props {
onCreate(environment: Environment): void;
isDockerStandalone?: boolean;
}
export function SocketForm({ onCreate }: Props) {
export function SocketForm({ onCreate, isDockerStandalone }: Props) {
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
const initialValues: FormValues = {
name: '',
socketPath: '',
overridePath: false,
meta: { groupId: 1, tagIds: [] },
gpus: [],
};
const mutation = useCreateLocalDockerEnvironmentMutation();
@ -50,7 +50,33 @@ export function SocketForm({ onCreate }: Props) {
<OverrideSocketFieldset />
<MoreSettingsSection>
<Hardware />
{isDockerStandalone && (
<InsightsBox
content={
<>
<p>
From 2.18 on, the set-up of available GPUs for a Docker
Standalone environment has been shifted from Add
environment and Environment details to Host -&gt; Setup,
so as to align with other settings.
</p>
<p>
A toggle has been introduced for enabling/disabling
management of GPU settings in the Portainer UI - to
alleviate the performance impact of showing those
settings.
</p>
<p>
The UI has been updated to clarify that GPU settings
support is only for Docker Standalone (and not Docker
Swarm, which was never supported in the UI).
</p>
</>
}
header="GPU settings update"
insightCloseId="gpu-settings-update-closed"
/>
)}
</MoreSettingsSection>
<div className="form-group">
@ -76,7 +102,6 @@ export function SocketForm({ onCreate }: Props) {
{
name: values.name,
socketPath: values.overridePath ? values.socketPath : '',
gpus: values.gpus,
meta: values.meta,
},
{

View file

@ -1,7 +1,5 @@
import { boolean, object, SchemaOf, string } from 'yup';
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
import { useNameValidation } from '../../shared/NameField';
@ -21,6 +19,5 @@ export function useValidation(): SchemaOf<FormValues> {
)
: schema
),
gpus: gpusListValidation(),
});
}

View file

@ -6,15 +6,19 @@ import { SocketForm } from './SocketForm';
interface Props {
onCreate(environment: Environment): void;
isDockerStandalone?: boolean;
}
export function SocketTab({ onCreate }: Props) {
export function SocketTab({ onCreate, isDockerStandalone }: Props) {
return (
<>
<DeploymentScripts />
<div className="mt-5">
<SocketForm onCreate={onCreate} />
<SocketForm
onCreate={onCreate}
isDockerStandalone={isDockerStandalone}
/>
</div>
</>
);

View file

@ -1,4 +1,3 @@
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
export interface FormValues {
@ -6,5 +5,4 @@ export interface FormValues {
socketPath: string;
overridePath: boolean;
meta: EnvironmentMetadata;
gpus: Gpu[];
}

View file

@ -109,12 +109,14 @@ export function WizardDocker({ onCreate, isDockerStandalone }: Props) {
return (
<APITab
onCreate={(environment) => onCreate(environment, 'dockerApi')}
isDockerStandalone={isDockerStandalone}
/>
);
case 'socket':
return (
<SocketTab
onCreate={(environment) => onCreate(environment, 'localEndpoint')}
isDockerStandalone={isDockerStandalone}
/>
);
case 'edgeAgentStandard':

View file

@ -11,14 +11,12 @@ import { LoadingButton } from '@@/buttons/LoadingButton';
import { NameField } from '../NameField';
import { MoreSettingsSection } from '../MoreSettingsSection';
import { Hardware } from '../Hardware/Hardware';
import { EnvironmentUrlField } from './EnvironmentUrlField';
import { useValidation } from './AgentForm.validation';
interface Props {
onCreate(environment: Environment): void;
showGpus?: boolean;
}
const initialValues: CreateAgentEnvironmentValues = {
@ -28,10 +26,9 @@ const initialValues: CreateAgentEnvironmentValues = {
groupId: 1,
tagIds: [],
},
gpus: [],
};
export function AgentForm({ onCreate, showGpus = false }: Props) {
export function AgentForm({ onCreate }: Props) {
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
const mutation = useCreateAgentEnvironmentMutation();
@ -50,7 +47,7 @@ export function AgentForm({ onCreate, showGpus = false }: Props) {
<NameField />
<EnvironmentUrlField />
<MoreSettingsSection>{showGpus && <Hardware />}</MoreSettingsSection>
<MoreSettingsSection />
<div className="form-group">
<div className="col-sm-12">

View file

@ -1,6 +1,5 @@
import { object, SchemaOf, string } from 'yup';
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { CreateAgentEnvironmentValues } from '@/react/portainer/environments/environment.service/create';
import { metadataValidation } from '../MetadataFieldset/validation';
@ -11,7 +10,6 @@ export function useValidation(): SchemaOf<CreateAgentEnvironmentValues> {
name: useNameValidation(),
environmentUrl: environmentValidation(),
meta: metadataValidation(),
gpus: gpusListValidation(),
});
}

View file

@ -17,7 +17,6 @@ import { FormSection } from '@@/form-components/FormSection';
import { LoadingButton } from '@@/buttons/LoadingButton';
import { MoreSettingsSection } from '../../MoreSettingsSection';
import { Hardware } from '../../Hardware/Hardware';
import { EdgeAgentFieldset } from './EdgeAgentFieldset';
import { useValidationSchema } from './EdgeAgentForm.validation';
@ -26,16 +25,10 @@ import { FormValues } from './types';
interface Props {
onCreate(environment: Environment): void;
readonly: boolean;
showGpus?: boolean;
asyncMode: boolean;
}
export function EdgeAgentForm({
onCreate,
readonly,
asyncMode,
showGpus = false,
}: Props) {
export function EdgeAgentForm({ onCreate, readonly, asyncMode }: Props) {
const settingsQuery = useSettings();
const createMutation = useCreateEdgeAgentEnvironmentMutation();
@ -76,7 +69,6 @@ export function EdgeAgentForm({
/>
)}
</FormSection>
{showGpus && <Hardware />}
</MoreSettingsSection>
{!readonly && (
@ -133,6 +125,5 @@ export function buildInitialValues(settings: Settings): FormValues {
PingInterval: EDGE_ASYNC_INTERVAL_USE_DEFAULT,
SnapshotInterval: EDGE_ASYNC_INTERVAL_USE_DEFAULT,
},
gpus: [],
};
}

View file

@ -4,7 +4,6 @@ import {
edgeAsyncIntervalsValidation,
EdgeAsyncIntervalsValues,
} from '@/react/edge/components/EdgeAsyncIntervalsForm';
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { validation as urlValidation } from '@/react/portainer/common/PortainerTunnelAddrField';
import { validation as addressValidation } from '@/react/portainer/common/PortainerUrlField';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
@ -23,7 +22,6 @@ export function useValidationSchema(asyncMode: boolean): SchemaOf<FormValues> {
tunnelServerAddr: asyncMode ? string() : addressValidation(),
pollFrequency: number().required(),
meta: metadataValidation(),
gpus: gpusListValidation(),
edge: isBE
? edgeAsyncIntervalsValidation()
: (null as unknown as SchemaOf<EdgeAsyncIntervalsValues>),

View file

@ -1,5 +1,4 @@
import { EdgeAsyncIntervalsValues } from '@/react/edge/components/EdgeAsyncIntervalsForm';
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { EnvironmentMetadata } from '@/react/portainer/environments/environment.service/create';
export interface FormValues {
@ -9,7 +8,6 @@ export interface FormValues {
tunnelServerAddr?: string;
pollFrequency: number;
meta: EnvironmentMetadata;
gpus: Gpu[];
edge: EdgeAsyncIntervalsValues;
}

View file

@ -15,7 +15,6 @@ interface Props {
onCreate: (environment: Environment) => void;
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
isNomadTokenVisible?: boolean;
showGpus?: boolean;
asyncMode?: boolean;
}
@ -23,7 +22,6 @@ export function EdgeAgentTab({
onCreate,
commands,
isNomadTokenVisible,
showGpus = false,
asyncMode = false,
}: Props) {
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
@ -35,7 +33,6 @@ export function EdgeAgentTab({
onCreate={handleCreate}
readonly={!!edgeInfo}
key={formKey}
showGpus={showGpus}
asyncMode={asyncMode}
/>

View file

@ -1,72 +0,0 @@
import { array, object, string } from 'yup';
import { r2a } from '@/react-tools/react2angular';
import { withControlledInput } from '@/react-tools/withControlledInput';
import { InputList } from '@@/form-components/InputList';
import { ItemProps } from '@@/form-components/InputList/InputList';
import { InputGroup } from '@@/form-components/InputGroup';
export interface Gpu {
value: string;
name: string;
}
interface Props {
value: Gpu[];
onChange(value: Gpu[]): void;
}
function Item({ item, onChange }: ItemProps<Gpu>) {
return (
<div className="flex flex-grow gap-2">
<InputGroup size="small" className="flex-grow">
<InputGroup.Addon>GPU Name</InputGroup.Addon>
<InputGroup.Input
placeholder="my-gpu"
value={item.name}
onChange={(e) => {
onChange({ ...item, name: e.target.value });
}}
/>
</InputGroup>
<InputGroup size="small" className="flex-grow">
<InputGroup.Addon>Index or UUID</InputGroup.Addon>
<InputGroup.Input
placeholder="0 or GPU-6e2c7185-c3d3-ae22-da43-bc5267b89061"
value={item.value}
onChange={(e) => {
onChange({ ...item, value: e.target.value });
}}
/>
</InputGroup>
</div>
);
}
export function GpusList({ value, onChange }: Props) {
return (
<InputList<Gpu>
label="GPU"
value={value}
onChange={onChange}
itemBuilder={() => ({ value: '', name: '' })}
addLabel="add"
item={Item}
/>
);
}
export function gpusListValidation() {
const gpuShape = object().shape({
name: string().required(),
value: string().required(),
});
return array().of(gpuShape).default([]);
}
export const GpusListAngular = r2a(withControlledInput(GpusList), [
'value',
'onChange',
]);

View file

@ -1,22 +0,0 @@
import { useField } from 'formik';
import {
Gpu,
GpusList,
} from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
import { FormSection } from '@@/form-components/FormSection';
export function Hardware() {
const [field, , helpers] = useField('gpus');
function onChange(value: Gpu[]) {
helpers.setValue(value);
}
return (
<FormSection title="Hardware acceleration">
<GpusList value={field.value} onChange={onChange} />
</FormSection>
);
}