Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
b40e3db7
Commit
b40e3db7
authored
Dec 01, 2020
by
quzha
Browse files
Merge branch 'master' of github.com:Microsoft/nni into dev-retiarii
parents
efa4e31c
95f731e4
Changes
226
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
1018 additions
and
174 deletions
+1018
-174
ts/nni_manager/rest_server/test/mockedExperimentManager.ts
ts/nni_manager/rest_server/test/mockedExperimentManager.ts
+44
-0
ts/nni_manager/rest_server/test/mockedNNIManager.ts
ts/nni_manager/rest_server/test/mockedNNIManager.ts
+8
-3
ts/nni_manager/rest_server/test/restserver.test.ts
ts/nni_manager/rest_server/test/restserver.test.ts
+14
-1
ts/nni_manager/training_service/kubernetes/adl/adlApiClient.ts
...i_manager/training_service/kubernetes/adl/adlApiClient.ts
+56
-0
ts/nni_manager/training_service/kubernetes/adl/adlConfig.ts
ts/nni_manager/training_service/kubernetes/adl/adlConfig.ts
+93
-0
ts/nni_manager/training_service/kubernetes/adl/adlJobInfoCollector.ts
...er/training_service/kubernetes/adl/adlJobInfoCollector.ts
+94
-0
ts/nni_manager/training_service/kubernetes/adl/adlJobRestServer.ts
...nager/training_service/kubernetes/adl/adlJobRestServer.ts
+22
-0
ts/nni_manager/training_service/kubernetes/adl/adlTrainingService.ts
...ger/training_service/kubernetes/adl/adlTrainingService.ts
+342
-0
ts/nni_manager/training_service/kubernetes/kubernetesApiClient.ts
...anager/training_service/kubernetes/kubernetesApiClient.ts
+91
-3
ts/nni_manager/training_service/kubernetes/kubernetesData.ts
ts/nni_manager/training_service/kubernetes/kubernetesData.ts
+2
-0
ts/nni_manager/training_service/kubernetes/kubernetesJobInfoCollector.ts
...training_service/kubernetes/kubernetesJobInfoCollector.ts
+2
-7
ts/nni_manager/training_service/kubernetes/kubernetesTrainingService.ts
.../training_service/kubernetes/kubernetesTrainingService.ts
+7
-1
ts/nni_manager/training_service/test/adlTrainingService.test.ts
..._manager/training_service/test/adlTrainingService.test.ts
+138
-0
ts/webui/src/App.scss
ts/webui/src/App.scss
+2
-0
ts/webui/src/App.tsx
ts/webui/src/App.tsx
+3
-2
ts/webui/src/components/NavCon.tsx
ts/webui/src/components/NavCon.tsx
+12
-39
ts/webui/src/components/Overview.tsx
ts/webui/src/components/Overview.tsx
+43
-56
ts/webui/src/components/modals/ExperimentSummaryPanel.tsx
ts/webui/src/components/modals/ExperimentSummaryPanel.tsx
+26
-35
ts/webui/src/components/modals/LogPanel.tsx
ts/webui/src/components/modals/LogPanel.tsx
+16
-16
ts/webui/src/components/overview/Accuracy.tsx
ts/webui/src/components/overview/Accuracy.tsx
+3
-11
No files found.
ts/nni_manager/rest_server/test/mockedExperimentManager.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
{
ExperimentManager
}
from
'
../../common/experimentManager
'
;
import
{
Provider
}
from
'
typescript-ioc
'
;
export
const
testExperimentManagerProvider
:
Provider
=
{
get
:
():
ExperimentManager
=>
{
return
new
mockedeExperimentManager
();
}
};
export
class
mockedeExperimentManager
extends
ExperimentManager
{
public
getExperimentsInfo
():
Promise
<
JSON
>
{
const
expInfo
=
JSON
.
parse
(
JSON
.
stringify
({
"
test
"
:
{
"
port
"
:
8080
,
"
startTime
"
:
1605246730756
,
"
endTime
"
:
"
N/A
"
,
"
status
"
:
"
RUNNING
"
,
"
platform
"
:
"
local
"
,
"
experimentName
"
:
"
testExp
"
,
"
tag
"
:
[],
"
pid
"
:
11111
,
"
webuiUrl
"
:
[],
"
logDir
"
:
null
}
}));
return
new
Promise
<
JSON
>
((
resolve
,
reject
)
=>
{
resolve
(
expInfo
);
});
}
public
setExperimentPath
(
newPath
:
string
):
void
{
return
}
public
setExperimentInfo
(
experimentId
:
string
,
key
:
string
,
value
:
any
):
void
{
return
}
public
stop
():
Promise
<
void
>
{
return
new
Promise
<
void
>
(()
=>
{});
}
}
ts/nni_manager/rest_server/test/mockedNNIManager.ts
View file @
b40e3db7
...
...
@@ -101,7 +101,7 @@ export class MockedNNIManager extends Manager {
public
getTrialJob
(
trialJobId
:
string
):
Promise
<
TrialJobInfo
>
{
const
deferred
:
Deferred
<
TrialJobInfo
>
=
new
Deferred
<
TrialJobInfo
>
();
const
jobInfo
:
TrialJobInfo
=
{
i
d
:
'
1234
'
,
trialJobI
d
:
'
1234
'
,
status
:
'
SUCCEEDED
'
,
startTime
:
Date
.
now
(),
endTime
:
Date
.
now
()
...
...
@@ -110,6 +110,11 @@ export class MockedNNIManager extends Manager {
return
deferred
.
promise
;
}
public
getTrialJobMessage
(
trialJobId
:
string
):
string
|
undefined
{
return
"
TEST-MESSAGE
"
}
public
stopExperiment
():
Promise
<
void
>
{
throw
new
MethodNotImplementedError
();
}
...
...
@@ -152,7 +157,7 @@ export class MockedNNIManager extends Manager {
}
public
listTrialJobs
(
status
?:
TrialJobStatus
):
Promise
<
TrialJobInfo
[]
>
{
const
job1
:
TrialJobInfo
=
{
i
d
:
'
1234
'
,
trialJobI
d
:
'
1234
'
,
status
:
'
SUCCEEDED
'
,
startTime
:
Date
.
now
(),
endTime
:
Date
.
now
(),
...
...
@@ -166,7 +171,7 @@ export class MockedNNIManager extends Manager {
}]
};
const
job2
:
TrialJobInfo
=
{
i
d
:
'
3456
'
,
trialJobI
d
:
'
3456
'
,
status
:
'
FAILED
'
,
startTime
:
Date
.
now
(),
endTime
:
Date
.
now
(),
...
...
ts/nni_manager/rest_server/test/restserver.test.ts
View file @
b40e3db7
...
...
@@ -10,12 +10,14 @@ import { Container } from 'typescript-ioc';
import
*
as
component
from
'
../../common/component
'
;
import
{
DataStore
}
from
'
../../common/datastore
'
;
import
{
ExperimentProfile
,
Manager
}
from
'
../../common/manager
'
;
import
{
ExperimentManager
}
from
'
../../common/experimentManager
'
import
{
TrainingService
}
from
'
../../common/trainingService
'
;
import
{
cleanupUnitTest
,
prepareUnitTest
}
from
'
../../common/utils
'
;
import
{
MockedDataStore
}
from
'
../../core/test/mockedDatastore
'
;
import
{
MockedTrainingService
}
from
'
../../core/test/mockedTrainingService
'
;
import
{
NNIRestServer
}
from
'
../nniRestServer
'
;
import
{
testManagerProvider
}
from
'
./mockedNNIManager
'
;
import
{
testExperimentManagerProvider
}
from
'
./mockedExperimentManager
'
;
describe
(
'
Unit test for rest server
'
,
()
=>
{
...
...
@@ -26,6 +28,7 @@ describe('Unit test for rest server', () => {
Container
.
bind
(
Manager
).
provider
(
testManagerProvider
);
Container
.
bind
(
DataStore
).
to
(
MockedDataStore
);
Container
.
bind
(
TrainingService
).
to
(
MockedTrainingService
);
Container
.
bind
(
ExperimentManager
).
provider
(
testExperimentManagerProvider
)
const
restServer
:
NNIRestServer
=
component
.
get
(
NNIRestServer
);
restServer
.
start
().
then
(()
=>
{
ROOT_URL
=
`
${
restServer
.
endPoint
}
/api/v1/nni`
;
...
...
@@ -57,7 +60,7 @@ describe('Unit test for rest server', () => {
assert
.
fail
(
err
.
message
);
}
else
{
expect
(
res
.
statusCode
).
to
.
equal
(
200
);
expect
(
JSON
.
parse
(
body
).
i
d
).
to
.
equal
(
'
1234
'
);
expect
(
JSON
.
parse
(
body
).
trialJobI
d
).
to
.
equal
(
'
1234
'
);
}
done
();
});
...
...
@@ -84,6 +87,16 @@ describe('Unit test for rest server', () => {
});
});
it
(
'
Test GET experiments-info
'
,
(
done
:
Mocha
.
Done
)
=>
{
request
.
get
(
`
${
ROOT_URL
}
/experiments-info`
,
(
err
:
Error
,
res
:
request
.
Response
)
=>
{
expect
(
res
.
statusCode
).
to
.
equal
(
200
);
if
(
err
)
{
assert
.
fail
(
err
.
message
);
}
done
();
});
});
it
(
'
Test change concurrent-trial-jobs
'
,
(
done
:
Mocha
.
Done
)
=>
{
request
.
get
(
`
${
ROOT_URL
}
/experiment`
,
(
err
:
Error
,
res
:
request
.
Response
,
body
:
any
)
=>
{
if
(
err
)
{
...
...
ts/nni_manager/training_service/kubernetes/adl/adlApiClient.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
*
as
fs
from
'
fs
'
;
import
{
GeneralK8sClient
,
KubernetesCRDClient
}
from
'
../kubernetesApiClient
'
;
/**
* Adl ClientV1
*/
class
AdlClientV1
extends
KubernetesCRDClient
{
/**
* constructor, to initialize adl CRD definition
*/
public
constructor
()
{
super
();
this
.
crdSchema
=
JSON
.
parse
(
fs
.
readFileSync
(
'
./config/adl/adaptdl-crd-v1.json
'
,
'
utf8
'
));
this
.
client
.
addCustomResourceDefinition
(
this
.
crdSchema
);
}
protected
get
operator
():
any
{
return
this
.
client
.
apis
[
'
adaptdl.petuum.com
'
].
v1
.
namespaces
(
'
default
'
).
adaptdljobs
;
}
public
get
containerName
():
string
{
return
'
main
'
;
}
public
async
getKubernetesPods
(
jobName
:
string
):
Promise
<
any
>
{
let
result
:
Promise
<
any
>
;
const
response
=
await
this
.
client
.
api
.
v1
.
namespaces
(
'
default
'
).
pods
.
get
({
qs
:
{
labelSelector
:
`adaptdl/job=
${
jobName
}
`
}
});
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
response
.
body
);
}
else
{
result
=
Promise
.
reject
(
`AdlClient getKubernetesPods failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
}
/**
* Adl Client
*/
class
AdlClientFactory
{
/**
* Factory method to generate operator client
*/
public
static
createClient
():
KubernetesCRDClient
{
return
new
AdlClientV1
();
}
}
export
{
AdlClientFactory
,
GeneralK8sClient
};
export
{
AdlClientV1
}
ts/nni_manager/training_service/kubernetes/adl/adlConfig.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
{
KubernetesTrialConfig
}
from
"
../kubernetesConfig
"
;
/**
* Checkpoint Config
*/
export
class
CheckpointConfig
{
public
readonly
storageClass
:
string
;
public
readonly
storageSize
:
string
;
constructor
(
storageClass
:
string
,
storageSize
:
string
)
{
this
.
storageClass
=
storageClass
;
this
.
storageSize
=
storageSize
;
}
}
/**
* imagePullSecret Config
*/
export
class
ImagePullSecretConfig
{
public
readonly
name
:
string
;
constructor
(
name
:
string
)
{
this
.
name
=
name
}
}
/**
* NFS Config
*/
export
class
NFSConfig
{
public
readonly
server
:
string
;
public
readonly
path
:
string
;
public
readonly
containerMountPath
:
string
;
constructor
(
server
:
string
,
path
:
string
,
containerMountPath
:
string
)
{
this
.
server
=
server
;
this
.
path
=
path
;
this
.
containerMountPath
=
containerMountPath
;
}
}
/**
* Trial job configuration for Adl
*/
export
class
AdlTrialConfig
extends
KubernetesTrialConfig
{
public
readonly
command
:
string
;
public
readonly
gpuNum
:
number
;
public
readonly
image
:
string
;
public
readonly
imagePullSecrets
?:
ImagePullSecretConfig
[];
public
readonly
nfs
?:
NFSConfig
;
public
readonly
checkpoint
?:
CheckpointConfig
;
public
readonly
cpuNum
?:
number
;
public
readonly
memorySize
?:
string
;
public
readonly
adaptive
?:
boolean
;
// adaptive == preemptible
constructor
(
codeDir
:
string
,
command
:
string
,
gpuNum
:
number
,
image
:
string
,
imagePullSecrets
?:
ImagePullSecretConfig
[],
nfs
?:
NFSConfig
,
checkpoint
?:
CheckpointConfig
,
cpuNum
?:
number
,
memorySize
?:
string
,
adaptive
?:
boolean
)
{
super
(
codeDir
);
this
.
command
=
command
;
this
.
gpuNum
=
gpuNum
;
this
.
image
=
image
;
this
.
imagePullSecrets
=
imagePullSecrets
;
this
.
nfs
=
nfs
;
this
.
checkpoint
=
checkpoint
;
this
.
cpuNum
=
cpuNum
;
this
.
memorySize
=
memorySize
;
this
.
adaptive
=
adaptive
;
}
}
export
type
AdlJobStatus
=
"
Pending
"
|
"
Running
"
|
"
Starting
"
|
"
Stopping
"
|
"
Failed
"
|
"
Succeeded
"
;
ts/nni_manager/training_service/kubernetes/adl/adlJobInfoCollector.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
{
AdlClientV1
}
from
'
./adlApiClient
'
;
import
{
KubernetesTrialJobDetail
}
from
'
../kubernetesData
'
;
import
{
KubernetesJobInfoCollector
}
from
'
../kubernetesJobInfoCollector
'
;
import
{
AdlJobStatus
}
from
'
./adlConfig
'
;
/**
* Collector Adl jobs info from Kubernetes cluster, and update adl job status locally
*/
export
class
AdlJobInfoCollector
extends
KubernetesJobInfoCollector
{
constructor
(
jobMap
:
Map
<
string
,
KubernetesTrialJobDetail
>
)
{
super
(
jobMap
);
}
protected
async
retrieveSingleTrialJobInfo
(
kubernetesCRDClient
:
AdlClientV1
|
undefined
,
kubernetesTrialJob
:
KubernetesTrialJobDetail
):
Promise
<
void
>
{
if
(
!
this
.
statusesNeedToCheck
.
includes
(
kubernetesTrialJob
.
status
))
{
return
Promise
.
resolve
();
}
if
(
kubernetesCRDClient
===
undefined
)
{
return
Promise
.
reject
(
'
kubernetesCRDClient is undefined
'
);
}
let
kubernetesJobInfo
:
any
;
let
kubernetesPodsInfo
:
any
;
try
{
kubernetesJobInfo
=
await
kubernetesCRDClient
.
getKubernetesJob
(
kubernetesTrialJob
.
kubernetesJobName
);
kubernetesPodsInfo
=
await
kubernetesCRDClient
.
getKubernetesPods
(
kubernetesTrialJob
.
kubernetesJobName
);
}
catch
(
error
)
{
// Notice: it maynot be a 'real' error since cancel trial job can also cause getKubernetesJob failed.
this
.
log
.
error
(
`Get job
${
kubernetesTrialJob
.
kubernetesJobName
}
info failed, error is
${
error
}
`
);
//This is not treat as a error status
return
Promise
.
resolve
();
}
/* eslint-disable require-atomic-updates */
if
(
kubernetesJobInfo
.
status
)
{
const
phase
:
AdlJobStatus
=
<
AdlJobStatus
>
kubernetesJobInfo
.
status
.
phase
switch
(
phase
)
{
case
'
Pending
'
:
case
'
Starting
'
:
kubernetesTrialJob
.
status
=
'
WAITING
'
;
if
(
kubernetesPodsInfo
.
items
.
length
>
0
){
if
(
kubernetesPodsInfo
.
items
[
0
].
status
.
containerStatuses
!=
undefined
)
{
const
currState
:
any
=
kubernetesPodsInfo
.
items
[
0
].
status
.
containerStatuses
[
0
].
state
if
(
currState
.
waiting
!=
undefined
)
{
const
msg
:
string
=
currState
.
waiting
.
reason
if
(
msg
==
"
ImagePullBackOff
"
||
msg
==
"
ErrImagePull
"
)
{
kubernetesTrialJob
.
status
=
'
FAILED
'
;
}
}
}
kubernetesTrialJob
.
message
=
kubernetesPodsInfo
.
items
.
map
((
pod
:
any
)
=>
JSON
.
stringify
(
pod
.
status
.
containerStatuses
))
.
join
(
'
\n
'
);
}
kubernetesTrialJob
.
startTime
=
Date
.
parse
(
<
string
>
kubernetesJobInfo
.
metadata
.
creationTimestamp
);
break
;
case
'
Running
'
:
case
'
Stopping
'
:
kubernetesTrialJob
.
status
=
'
RUNNING
'
;
kubernetesTrialJob
.
message
=
`Use 'nnictl log trial --trial_id
${
kubernetesTrialJob
.
id
}
' to check the log stream.`
;
if
(
kubernetesTrialJob
.
startTime
===
undefined
)
{
kubernetesTrialJob
.
startTime
=
Date
.
parse
(
<
string
>
kubernetesJobInfo
.
metadata
.
creationTimestamp
);
}
break
;
case
'
Failed
'
:
kubernetesTrialJob
.
status
=
'
FAILED
'
;
kubernetesTrialJob
.
message
=
kubernetesJobInfo
.
status
.
message
;
if
(
kubernetesPodsInfo
.
items
.
length
>
0
)
{
kubernetesTrialJob
.
message
+=
"
;
"
;
kubernetesTrialJob
.
message
+=
`Use 'nnictl log trial --trial_id
${
kubernetesTrialJob
.
id
}
' for the path of the collected logs.`
;
}
// undefined => NaN as endTime here
kubernetesTrialJob
.
endTime
=
Date
.
parse
(
<
string
>
kubernetesJobInfo
.
status
.
completionTimestamp
);
break
;
case
'
Succeeded
'
:
kubernetesTrialJob
.
status
=
'
SUCCEEDED
'
;
kubernetesTrialJob
.
endTime
=
Date
.
parse
(
<
string
>
kubernetesJobInfo
.
status
.
completionTimestamp
);
kubernetesTrialJob
.
message
=
`Succeeded at
${
kubernetesJobInfo
.
status
.
completionTimestamp
}
`
break
;
default
:
}
}
/* eslint-enable require-atomic-updates */
return
Promise
.
resolve
();
}
}
ts/nni_manager/training_service/kubernetes/adl/adlJobRestServer.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
*
as
component
from
'
../../../common/component
'
;
import
{
KubernetesJobRestServer
}
from
'
../kubernetesJobRestServer
'
;
import
{
AdlTrainingService
}
from
'
./adlTrainingService
'
;
/**
* Adl Training service Rest server, provides rest API to support adl job metrics update
*
*/
@
component
.
Singleton
export
class
AdlJobRestServer
extends
KubernetesJobRestServer
{
/**
* constructor to provide NNIRestServer's own rest property, e.g. port
*/
constructor
()
{
super
(
component
.
get
(
AdlTrainingService
));
}
}
ts/nni_manager/training_service/kubernetes/adl/adlTrainingService.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
*
as
fs
from
'
fs
'
;
import
*
as
component
from
'
../../../common/component
'
;
import
{
String
}
from
'
typescript-string-operations
'
;
import
{
getExperimentId
}
from
'
../../../common/experimentStartupInfo
'
;
import
{
NNIManagerIpConfig
,
TrialJobApplicationForm
,
TrialJobDetail
,
TrialJobStatus
}
from
'
../../../common/trainingService
'
;
import
{
delay
,
generateParamFileName
,
getVersion
,
uniqueString
}
from
'
../../../common/utils
'
;
import
{
TrialConfigMetadataKey
}
from
'
../../common/trialConfigMetadataKey
'
;
import
{
KubernetesTrialJobDetail
}
from
'
../kubernetesData
'
;
import
{
KubernetesTrainingService
}
from
'
../kubernetesTrainingService
'
;
import
{
AdlClientFactory
}
from
'
./adlApiClient
'
import
{
AdlJobInfoCollector
}
from
'
./adlJobInfoCollector
'
;
import
{
AdlJobRestServer
}
from
'
./adlJobRestServer
'
;
import
{
AdlTrialConfig
}
from
'
./adlConfig
'
/**
* Training Service implementation for Adl
*/
@
component
.
Singleton
class
AdlTrainingService
extends
KubernetesTrainingService
implements
KubernetesTrainingService
{
private
adlTrialConfig
?:
AdlTrialConfig
;
private
readonly
adlJobInfoCollector
:
AdlJobInfoCollector
;
private
configmapTemplateStr
:
string
;
private
jobTemplateStr
:
string
;
private
pvcTemplateStr
:
string
;
private
tensorboardPvcTemplate
:
any
;
private
tensorboardDeploymentTemplate
:
any
;
//TODO: change the logic here when we want to support multiple tensorboard
private
tensorboardName
:
string
=
"
adaptdl-tensorboard-
"
+
getExperimentId
().
toLowerCase
();
constructor
()
{
super
();
this
.
adlJobInfoCollector
=
new
AdlJobInfoCollector
(
this
.
trialJobsMap
);
this
.
experimentId
=
getExperimentId
();
this
.
kubernetesCRDClient
=
AdlClientFactory
.
createClient
();
this
.
configmapTemplateStr
=
fs
.
readFileSync
(
'
./config/adl/adaptdl-nni-configmap-template.json
'
,
'
utf8
'
);
this
.
jobTemplateStr
=
fs
.
readFileSync
(
'
./config/adl/adaptdljob-template.json
'
,
'
utf8
'
);
this
.
pvcTemplateStr
=
fs
.
readFileSync
(
'
./config/adl/adaptdl-pvc-template.json
'
,
'
utf8
'
);
this
.
tensorboardPvcTemplate
=
JSON
.
parse
(
fs
.
readFileSync
(
'
./config/adl/adaptdl-tensorboard-pvc-template.json
'
,
'
utf8
'
));
this
.
tensorboardDeploymentTemplate
=
JSON
.
parse
(
fs
.
readFileSync
(
'
./config/adl/adaptdl-tensorboard-deployment-template.json
'
,
'
utf8
'
));
this
.
log
.
info
(
'
Construct Adl training service.
'
);
}
public
async
run
():
Promise
<
void
>
{
this
.
log
.
info
(
this
.
tensorboardName
);
this
.
log
.
info
(
'
Start tensorboard deployment.
'
);
await
this
.
launchTensorboard
()
this
.
log
.
info
(
'
Run Adl training service.
'
);
this
.
kubernetesJobRestServer
=
component
.
get
(
AdlJobRestServer
);
if
(
this
.
kubernetesJobRestServer
===
undefined
)
{
throw
new
Error
(
'
kubernetesJobRestServer not initialized!
'
);
}
await
this
.
kubernetesJobRestServer
.
start
();
this
.
kubernetesJobRestServer
.
setEnableVersionCheck
=
this
.
versionCheck
;
this
.
log
.
info
(
`Adl Training service rest server listening on:
${
this
.
kubernetesJobRestServer
.
endPoint
}
`
);
while
(
!
this
.
stopping
)
{
// collect metrics for Adl jobs by interacting with Kubernetes API server
await
delay
(
3000
);
await
this
.
adlJobInfoCollector
.
retrieveTrialStatus
(
this
.
kubernetesCRDClient
);
if
(
this
.
kubernetesJobRestServer
.
getErrorMessage
!==
undefined
)
{
throw
new
Error
(
this
.
kubernetesJobRestServer
.
getErrorMessage
);
}
}
this
.
log
.
info
(
'
Adl training service exit.
'
);
}
private
async
launchTensorboard
():
Promise
<
void
>
{
// Start the tensorboard at the beginning of the experiment.
if
(
this
.
adlTrialConfig
===
undefined
)
{
throw
new
Error
(
'
Adl trial config is undefined
'
);
}
// Create tensorboard deployment
this
.
tensorboardDeploymentTemplate
.
metadata
.
name
=
this
.
tensorboardName
this
.
tensorboardDeploymentTemplate
.
metadata
.
labels
.
expId
=
this
.
experimentId
this
.
tensorboardDeploymentTemplate
.
spec
.
selector
.
matchLabels
.
app
=
this
.
tensorboardName
this
.
tensorboardDeploymentTemplate
.
spec
.
template
.
metadata
.
labels
.
app
=
this
.
tensorboardName
this
.
tensorboardDeploymentTemplate
.
spec
.
template
.
spec
.
volumes
[
0
]
.
persistentVolumeClaim
.
claimName
=
this
.
tensorboardName
const
deploymentUid
:
string
=
await
this
.
genericK8sClient
.
createDeployment
(
this
.
tensorboardDeploymentTemplate
);
// Create pvc
this
.
tensorboardPvcTemplate
.
metadata
.
name
=
this
.
tensorboardName
;
this
.
tensorboardPvcTemplate
.
metadata
.
ownerReferences
[
0
].
name
=
this
.
tensorboardName
;
this
.
tensorboardPvcTemplate
.
metadata
.
ownerReferences
[
0
].
uid
=
deploymentUid
if
(
this
.
adlTrialConfig
.
checkpoint
!=
undefined
)
{
this
.
tensorboardPvcTemplate
.
spec
.
resources
.
requests
.
storage
=
this
.
adlTrialConfig
.
checkpoint
.
storageSize
;
this
.
tensorboardPvcTemplate
.
spec
.
storageClassName
=
this
.
adlTrialConfig
.
checkpoint
.
storageClass
;
}
else
{
this
.
tensorboardPvcTemplate
.
spec
.
resources
.
requests
.
storage
=
"
1Gi
"
this
.
tensorboardPvcTemplate
.
spec
.
storageClassName
=
await
this
.
genericK8sClient
.
getStorageClass
();
}
await
this
.
genericK8sClient
.
createPersistentVolumeClaim
(
this
.
tensorboardPvcTemplate
);
return
Promise
.
resolve
()
}
public
async
submitTrialJob
(
form
:
TrialJobApplicationForm
):
Promise
<
TrialJobDetail
>
{
if
(
this
.
kubernetesCRDClient
===
undefined
)
{
throw
new
Error
(
'
Adl job operator client is undefined
'
);
}
if
(
this
.
adlTrialConfig
===
undefined
)
{
throw
new
Error
(
'
Adl trial config is undefined
'
);
}
if
(
this
.
kubernetesRestServerPort
===
undefined
)
{
const
restServer
:
AdlJobRestServer
=
component
.
get
(
AdlJobRestServer
);
this
.
kubernetesRestServerPort
=
restServer
.
clusterRestServerPort
;
}
const
trialJobId
:
string
=
uniqueString
(
5
);
const
adlJobName
:
string
=
`nni-exp-
${
this
.
experimentId
}
-trial-
${
trialJobId
}
`
.
toLowerCase
();
const
initStatus
:
TrialJobStatus
=
'
WAITING
'
;
const
codeDir
=
this
.
adlTrialConfig
.
codeDir
;
const
outputDir
=
"
output
"
const
trialJobDetail
:
KubernetesTrialJobDetail
=
new
KubernetesTrialJobDetail
(
trialJobId
,
initStatus
,
Date
.
now
(),
codeDir
,
form
,
adlJobName
,
outputDir
);
// Create adljob
const
job
:
any
=
JSON
.
parse
(
this
.
jobTemplateStr
);
job
.
metadata
.
name
=
adlJobName
job
.
metadata
.
labels
.
app
=
this
.
NNI_KUBERNETES_TRIAL_LABEL
job
.
metadata
.
labels
.
expId
=
this
.
experimentId
job
.
metadata
.
labels
.
trialId
=
trialJobId
if
(
this
.
adlTrialConfig
.
adaptive
!==
undefined
){
job
.
spec
.
preemptible
=
this
.
adlTrialConfig
.
adaptive
}
job
.
spec
.
template
.
spec
.
containers
[
0
]
.
image
=
this
.
adlTrialConfig
.
image
;
job
.
spec
.
template
.
spec
.
volumes
[
0
]
.
persistentVolumeClaim
.
claimName
=
adlJobName
job
.
spec
.
template
.
spec
.
volumes
[
1
]
.
persistentVolumeClaim
.
claimName
=
this
.
tensorboardName
job
.
spec
.
template
.
spec
.
volumes
[
2
]
.
configMap
.
name
=
adlJobName
// Handle Pod Resource
let
cpu
:
number
=
1
;
let
memory
:
string
=
"
1Gi
"
;
if
(
this
.
adlTrialConfig
.
cpuNum
!==
undefined
)
{
cpu
=
this
.
adlTrialConfig
.
cpuNum
;
}
if
(
this
.
adlTrialConfig
.
memorySize
!==
undefined
)
{
memory
=
this
.
adlTrialConfig
.
memorySize
;
}
job
.
spec
.
template
.
spec
.
containers
[
0
]
.
resources
.
requests
.
memory
=
memory
;
job
.
spec
.
template
.
spec
.
containers
[
0
]
.
resources
.
requests
.
cpu
=
cpu
;
job
.
spec
.
template
.
spec
.
containers
[
0
]
.
resources
.
limits
[
"
nvidia.com/gpu
"
]
=
this
.
adlTrialConfig
.
gpuNum
;
// Handle imagePullSecrets
if
(
this
.
adlTrialConfig
.
imagePullSecrets
!==
undefined
)
{
job
.
spec
.
template
.
spec
.
imagePullSecrets
=
job
.
spec
.
template
.
spec
.
imagePullSecrets
.
concat
(
this
.
adlTrialConfig
.
imagePullSecrets
);
}
// Handle NFS
if
(
this
.
adlTrialConfig
.
nfs
!==
undefined
)
{
job
.
spec
.
template
.
spec
.
volumes
.
push
({
"
name
"
:
"
nfs
"
,
"
nfs
"
:
{
"
server
"
:
this
.
adlTrialConfig
.
nfs
.
server
,
"
path
"
:
this
.
adlTrialConfig
.
nfs
.
path
,
"
readOnly
"
:
false
}
});
job
.
spec
.
template
.
spec
.
containers
[
0
].
volumeMounts
.
push
({
"
name
"
:
"
nfs
"
,
"
mountPath
"
:
this
.
adlTrialConfig
.
nfs
.
containerMountPath
});
}
await
this
.
kubernetesCRDClient
.
createKubernetesJob
(
job
);
const
k8sadlJob
:
any
=
await
this
.
kubernetesCRDClient
.
getKubernetesJob
(
adlJobName
);
// Create pvc
const
pvc
:
any
=
JSON
.
parse
(
this
.
pvcTemplateStr
);
pvc
.
metadata
.
name
=
adlJobName
;
pvc
.
metadata
.
ownerReferences
[
0
].
name
=
adlJobName
;
pvc
.
metadata
.
ownerReferences
[
0
].
uid
=
k8sadlJob
.
metadata
.
uid
;
if
(
this
.
adlTrialConfig
.
checkpoint
!=
undefined
)
{
pvc
.
spec
.
resources
.
requests
.
storage
=
this
.
adlTrialConfig
.
checkpoint
.
storageSize
;
pvc
.
spec
.
storageClassName
=
this
.
adlTrialConfig
.
checkpoint
.
storageClass
;
}
else
{
pvc
.
spec
.
resources
.
requests
.
storage
=
"
1Gi
"
pvc
.
spec
.
storageClassName
=
await
this
.
genericK8sClient
.
getStorageClass
();
}
await
this
.
genericK8sClient
.
createPersistentVolumeClaim
(
pvc
);
// prepare the runscript and convert it to configmap and mount it
const
configmap
:
any
=
JSON
.
parse
(
this
.
configmapTemplateStr
);
configmap
.
metadata
.
name
=
adlJobName
;
configmap
.
metadata
.
ownerReferences
[
0
].
name
=
adlJobName
;
configmap
.
metadata
.
ownerReferences
[
0
].
uid
=
k8sadlJob
.
metadata
.
uid
;
configmap
.
data
[
"
run.sh
"
]
=
await
this
.
prepareRunScript
(
trialJobId
,
form
,
codeDir
,
outputDir
)
const
cleanupScriptTemplate
:
string
=
`#!/bin/bash
ps aux | grep "python3 -m nni_trial_tool.trial_keeper" | awk '{print $2}' | xargs kill -2
while true;
do
proc=
\`
ps aux | grep "python3 -m nni_trial_tool.trial_keeper" | awk '{print $2}' | grep "" -c
\`
if (( $proc == 1 )); then
exit 0
else
echo "waiting"
fi
sleep 1
done
`
;
configmap
.
data
[
"
cleanup.sh
"
]
=
cleanupScriptTemplate
await
this
.
genericK8sClient
.
createConfigMap
(
configmap
)
// Set trial job detail until create Adl job successfully
this
.
trialJobsMap
.
set
(
trialJobId
,
trialJobDetail
);
return
Promise
.
resolve
(
trialJobDetail
);
}
private
async
prepareRunScript
(
jobId
:
string
,
form
:
TrialJobApplicationForm
,
codeDir
:
string
,
outputDir
:
string
):
Promise
<
string
>
{
if
(
this
.
adlTrialConfig
===
undefined
)
{
throw
new
Error
(
'
Adl trial config is undefined
'
);
}
if
(
this
.
kubernetesRestServerPort
===
undefined
)
{
throw
new
Error
(
'
Adl rest server port is undefined
'
);
}
if
(
this
.
nniManagerIpConfig
===
undefined
)
{
throw
new
Error
(
'
Adl nniManager ip config is undefined
'
);
}
const
expId
:
string
=
this
.
experimentId
;
const
seqId
:
string
=
form
.
sequenceId
.
toString
();
const
command
:
string
=
this
.
adlTrialConfig
.
command
;
const
hyperParameters
:
string
=
form
.
hyperParameters
.
value
;
const
hyperParametersFile
:
string
=
generateParamFileName
(
form
.
hyperParameters
);
const
nniManagerPort
:
string
=
this
.
kubernetesRestServerPort
.
toString
();
const
nniManagerIp
:
string
=
this
.
nniManagerIpConfig
.
nniManagerIp
;
let
nniManagerVersion
:
string
=
''
;
if
(
this
.
versionCheck
)
{
nniManagerVersion
=
await
getVersion
();
}
let
nvidiaScript
:
string
=
''
;
if
(
this
.
adlTrialConfig
.
gpuNum
==
0
)
{
nvidiaScript
=
'
export CUDA_VISIBLE_DEVICES=
'
;
}
const
runScriptTemplate
:
string
=
`#!/bin/bash
export NNI_PLATFORM=adl
export MULTI_PHASE=false
export NNI_SYS_DIR={0}
export NNI_CODE_DIR={0}
export NNI_OUTPUT_DIR={1}
export NNI_TRIAL_JOB_ID={2}
export NNI_EXP_ID={3}
export NNI_TRIAL_SEQ_ID={4}
mkdir -p $NNI_OUTPUT_DIR
{5}
echo '{6}' > $NNI_CODE_DIR/{7}
python3 -m nni_trial_tool.trial_keeper --trial_command '{8}' \
--nnimanager_ip {9} --nnimanager_port {10} \
--nni_manager_version '{11}' --log_collection '{12}'
`
;
const
runScript
=
String
.
Format
(
runScriptTemplate
,
codeDir
,
outputDir
,
jobId
,
expId
,
seqId
,
nvidiaScript
,
hyperParameters
,
hyperParametersFile
,
command
,
nniManagerIp
,
nniManagerPort
,
nniManagerVersion
,
this
.
logCollection
);
return
Promise
.
resolve
(
runScript
);
}
public
async
setClusterMetadata
(
key
:
string
,
value
:
string
):
Promise
<
void
>
{
this
.
log
.
info
(
'
SetCluster
'
+
key
+
'
,
'
+
value
);
switch
(
key
)
{
case
TrialConfigMetadataKey
.
NNI_MANAGER_IP
:
this
.
nniManagerIpConfig
=
<
NNIManagerIpConfig
>
JSON
.
parse
(
value
);
break
;
case
TrialConfigMetadataKey
.
TRIAL_CONFIG
:
this
.
adlTrialConfig
=
<
AdlTrialConfig
>
JSON
.
parse
(
value
);
break
;
case
TrialConfigMetadataKey
.
VERSION_CHECK
:
this
.
versionCheck
=
(
value
===
'
true
'
||
value
===
'
True
'
);
break
;
case
TrialConfigMetadataKey
.
LOG_COLLECTION
:
this
.
logCollection
=
value
;
break
;
default
:
}
return
Promise
.
resolve
();
}
public
getClusterMetadata
(
key
:
string
):
Promise
<
string
>
{
let
result
:
string
;
switch
(
key
)
{
case
TrialConfigMetadataKey
.
TRIAL_CONFIG
:
if
(
this
.
adlTrialConfig
===
undefined
)
{
return
Promise
.
reject
(
`
${
key
}
is not set yet`
);
}
result
=
JSON
.
stringify
(
this
.
adlTrialConfig
);
break
;
case
TrialConfigMetadataKey
.
NNI_MANAGER_IP
:
if
(
this
.
nniManagerIpConfig
===
undefined
)
{
return
Promise
.
reject
(
`
${
key
}
is not set yet`
);
}
result
=
JSON
.
stringify
(
this
.
nniManagerIpConfig
);
break
;
default
:
return
Promise
.
reject
(
`
${
key
}
not set`
);
}
return
Promise
.
resolve
(
result
);
}
}
export
{
AdlTrainingService
};
ts/nni_manager/training_service/kubernetes/kubernetesApiClient.ts
View file @
b40e3db7
...
...
@@ -19,6 +19,94 @@ class GeneralK8sClient {
this
.
client
.
loadSpec
();
}
private
matchStorageClass
(
response
:
any
):
string
{
const
adlSupportedProvisioners
:
RegExp
[]
=
[
new
RegExp
(
"
microk8s.io/hostpath
"
),
new
RegExp
(
"
.*cephfs.csi.ceph.com
"
),
new
RegExp
(
"
.*azure.*
"
),
new
RegExp
(
"
\\
b
"
+
"
efs
"
+
"
\\
b
"
)
]
const
templateLen
=
adlSupportedProvisioners
.
length
,
responseLen
=
response
.
items
.
length
let
i
=
0
,
j
=
0
;
for
(;
i
<
responseLen
;
i
++
)
{
const
provisioner
:
string
=
response
.
items
[
i
].
provisioner
for
(;
j
<
templateLen
;
j
++
)
{
if
(
provisioner
.
match
(
adlSupportedProvisioners
[
j
]))
{
return
response
.
items
[
i
].
metadata
.
name
;
}
}
}
return
"
Not Found!
"
;
}
public
async
getStorageClass
():
Promise
<
string
>
{
let
result
:
Promise
<
string
>
;
const
response
:
any
=
await
this
.
client
.
apis
[
"
storage.k8s.io
"
].
v1beta1
.
storageclasses
.
get
()
const
storageClassType
:
string
=
this
.
matchStorageClass
(
response
.
body
)
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
if
(
storageClassType
!=
"
Not Found!
"
)
{
result
=
Promise
.
resolve
(
storageClassType
);
}
else
{
result
=
Promise
.
reject
(
"
No StorageClasses are supported!
"
)
}
}
else
{
result
=
Promise
.
reject
(
`List storageclasses failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
public
async
createDeployment
(
deploymentManifest
:
any
):
Promise
<
string
>
{
let
result
:
Promise
<
string
>
;
const
response
:
any
=
await
this
.
client
.
apis
.
apps
.
v1
.
namespaces
(
'
default
'
).
deployments
.
post
({
body
:
deploymentManifest
})
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
response
.
body
.
metadata
.
uid
);
}
else
{
result
=
Promise
.
reject
(
`Create deployment failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
public
async
deleteDeployment
(
deploymentName
:
string
):
Promise
<
boolean
>
{
let
result
:
Promise
<
boolean
>
;
// TODO: change this hard coded deployment name after demo
const
response
:
any
=
await
this
.
client
.
apis
.
apps
.
v1
.
namespaces
(
'
default
'
)
.
deployment
(
deploymentName
).
delete
();
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
true
);
}
else
{
result
=
Promise
.
reject
(
`Delete deployment failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
public
async
createConfigMap
(
configMapManifest
:
any
):
Promise
<
boolean
>
{
let
result
:
Promise
<
boolean
>
;
const
response
:
any
=
await
this
.
client
.
api
.
v1
.
namespaces
(
'
default
'
)
.
configmaps
.
post
({
body
:
configMapManifest
});
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
true
);
}
else
{
result
=
Promise
.
reject
(
`Create configMap failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
public
async
createPersistentVolumeClaim
(
pvcManifest
:
any
):
Promise
<
boolean
>
{
let
result
:
Promise
<
boolean
>
;
const
response
:
any
=
await
this
.
client
.
api
.
v1
.
namespaces
(
'
default
'
)
.
persistentvolumeclaims
.
post
({
body
:
pvcManifest
});
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
true
);
}
else
{
result
=
Promise
.
reject
(
`Create pvc failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
}
public
async
createSecret
(
secretManifest
:
any
):
Promise
<
boolean
>
{
let
result
:
Promise
<
boolean
>
;
const
response
:
any
=
await
this
.
client
.
api
.
v1
.
namespaces
(
'
default
'
).
secrets
...
...
@@ -77,7 +165,7 @@ abstract class KubernetesCRDClient {
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
true
);
}
else
{
result
=
Promise
.
reject
(
`
C
reate
k
ubernetes
j
ob failed, statusCode is
${
response
.
statusCode
}
`
);
result
=
Promise
.
reject
(
`
KubernetesApiClient c
reate
K
ubernetes
J
ob failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
...
...
@@ -91,7 +179,7 @@ abstract class KubernetesCRDClient {
if
(
response
.
statusCode
&&
(
response
.
statusCode
>=
200
&&
response
.
statusCode
<=
299
))
{
result
=
Promise
.
resolve
(
response
.
body
);
}
else
{
result
=
Promise
.
reject
(
`Kube
flowOperatorClient get tfj
ob
s
failed, statusCode is
${
response
.
statusCode
}
`
);
result
=
Promise
.
reject
(
`Kube
rnetesApiClient getKubernetesJ
ob failed, statusCode is
${
response
.
statusCode
}
`
);
}
return
result
;
...
...
@@ -115,7 +203,7 @@ abstract class KubernetesCRDClient {
result
=
Promise
.
resolve
(
true
);
}
else
{
result
=
Promise
.
reject
(
`Kube
flowOperator
Client, delete labels
${
matchQuery
}
get wrong statusCode
${
deleteResult
.
statusCode
}
`
);
`Kube
rnetesApi
Client, delete labels
${
matchQuery
}
get wrong statusCode
${
deleteResult
.
statusCode
}
`
);
}
}
catch
(
err
)
{
result
=
Promise
.
reject
(
err
);
...
...
ts/nni_manager/training_service/kubernetes/kubernetesData.ts
View file @
b40e3db7
...
...
@@ -11,6 +11,7 @@ import { TrialJobApplicationForm, TrialJobDetail, TrialJobStatus } from '../../
export
class
KubernetesTrialJobDetail
implements
TrialJobDetail
{
public
id
:
string
;
public
status
:
TrialJobStatus
;
public
message
?:
string
;
public
submitTime
:
number
;
public
startTime
?:
number
;
public
endTime
?:
number
;
...
...
@@ -26,6 +27,7 @@ export class KubernetesTrialJobDetail implements TrialJobDetail {
kubernetesJobName
:
string
,
url
:
string
)
{
this
.
id
=
id
;
this
.
status
=
status
;
this
.
message
=
'
Pending for creating the trial job.
'
;
this
.
submitTime
=
submitTime
;
this
.
workingDirectory
=
workingDirectory
;
this
.
form
=
form
;
...
...
ts/nni_manager/training_service/kubernetes/kubernetesJobInfoCollector.ts
View file @
b40e3db7
...
...
@@ -23,21 +23,16 @@ export class KubernetesJobInfoCollector {
this
.
statusesNeedToCheck
=
[
'
RUNNING
'
,
'
WAITING
'
];
}
public
async
retrieveTrialStatus
(
kubernetesCRDClient
:
KubernetesCRDClient
|
undefined
):
Promise
<
void
>
{
public
async
retrieveTrialStatus
(
kubernetesCRDClient
:
KubernetesCRDClient
|
undefined
):
Promise
<
void
[]
>
{
assert
(
kubernetesCRDClient
!==
undefined
);
const
updateKubernetesTrialJobs
:
Promise
<
void
>
[]
=
[];
for
(
const
[
trialJobId
,
kubernetesTrialJob
]
of
this
.
trialJobsMap
)
{
if
(
kubernetesTrialJob
===
undefined
)
{
throw
new
NNIError
(
NNIErrorNames
.
NOT_FOUND
,
`trial job id
${
trialJobId
}
not found`
);
}
// Since Kubeflow needs some delay to schedule jobs, we provide 20 seconds buffer time to check kubeflow job's status
if
(
Date
.
now
()
-
kubernetesTrialJob
.
submitTime
<
20
*
1000
)
{
return
Promise
.
resolve
();
}
updateKubernetesTrialJobs
.
push
(
this
.
retrieveSingleTrialJobInfo
(
kubernetesCRDClient
,
kubernetesTrialJob
));
}
await
Promise
.
all
(
updateKubernetesTrialJobs
);
return
Promise
.
all
(
updateKubernetesTrialJobs
);
}
protected
async
retrieveSingleTrialJobInfo
(
_kubernetesCRDClient
:
KubernetesCRDClient
|
undefined
,
...
...
ts/nni_manager/training_service/kubernetes/kubernetesTrainingService.ts
View file @
b40e3db7
...
...
@@ -209,6 +209,13 @@ abstract class KubernetesTrainingService {
return
Promise
.
reject
(
error
);
}
try
{
await
this
.
genericK8sClient
.
deleteDeployment
(
"
adaptdl-tensorboard-
"
+
getExperimentId
().
toLowerCase
())
this
.
log
.
info
(
'
tensorboard deployment deleted
'
)
}
catch
(
error
)
{
this
.
log
.
error
(
`tensorboard deployment deletion failed:
${
error
.
message
}
`
)
}
return
Promise
.
resolve
();
}
...
...
@@ -377,6 +384,5 @@ abstract class KubernetesTrainingService {
}
return
Promise
.
resolve
(
folderUriInAzure
);
}
}
export
{
KubernetesTrainingService
};
ts/nni_manager/training_service/test/adlTrainingService.test.ts
0 → 100644
View file @
b40e3db7
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
'
use strict
'
;
import
*
as
chai
from
'
chai
'
;
import
*
as
chaiAsPromised
from
'
chai-as-promised
'
;
import
*
as
fs
from
'
fs
'
;
import
*
as
tmp
from
'
tmp
'
;
import
*
as
component
from
'
../../common/component
'
;
import
{
TrialJobApplicationForm
,
TrialJobDetail
,
TrainingService
}
from
'
../../common/trainingService
'
;
import
{
cleanupUnitTest
,
prepareUnitTest
}
from
'
../../common/utils
'
;
import
{
TrialConfigMetadataKey
}
from
'
../common/trialConfigMetadataKey
'
;
import
{
AdlTrainingService
}
from
'
../kubernetes/adl/adlTrainingService
'
;
const
localCodeDir
:
string
=
tmp
.
dirSync
().
name
describe
(
'
Unit Test for AdlTrainingService
'
,
()
=>
{
let
skip
:
boolean
=
false
;
try
{
const
testKubeflowConfig
=
fs
.
readFileSync
(
'
/home/vsts/.kube/config
'
,
'
utf8
'
);
}
catch
(
err
)
{
console
.
log
(
'
Please have kubernetes cluster to enable its training service unit test.
'
);
skip
=
true
;
}
let
testAdlTrialConfig
:
any
=
JSON
.
stringify
({
"
command
"
:
"
python3 /root/apps/nni_linear_regression/main.py
"
,
"
codeDir
"
:
"
.
"
,
"
gpuNum
"
:
0
,
"
image
"
:
"
test.image:latest
"
,
"
imagePullSecrets
"
:
[
{
"
name
"
:
"
stagingsecrets
"
}
],
"
nfs
"
:
{
"
server
"
:
"
172.20.188.236
"
,
"
path
"
:
"
/exports
"
,
"
containerMountPath
"
:
"
/nfs
"
},
"
memorySize
"
:
"
1Gi
"
,
"
cpuNum
"
:
1
});
let
testAdlTrialConfig2
:
any
=
JSON
.
stringify
({
"
command
"
:
"
python3 /root/apps/nni_linear_regression/main.py
"
,
"
codeDir
"
:
"
.
"
,
"
gpuNum
"
:
0
,
"
image
"
:
"
test.image:latest
"
,
"
imagePullSecrets
"
:
[
{
"
name
"
:
"
stagingsecrets
"
}
],
"
adaptive
"
:
true
,
"
checkpoint
"
:
{
"
storageClass
"
:
"
aws-efs
"
,
"
storageSize
"
:
"
1Gi
"
},
"
nfs
"
:
{
"
server
"
:
"
172.20.188.236
"
,
"
path
"
:
"
/exports
"
,
"
containerMountPath
"
:
"
/nfs
"
}
});
let
testNniManagerIp
:
any
=
JSON
.
stringify
({
"
nniManagerIp
"
:
"
0.0.0.0
"
});
let
adlTrainingService
:
AdlTrainingService
;
console
.
log
(
tmp
.
dirSync
().
name
);
before
(()
=>
{
chai
.
should
();
chai
.
use
(
chaiAsPromised
);
prepareUnitTest
();
});
after
(()
=>
{
cleanupUnitTest
();
});
beforeEach
(()
=>
{
if
(
skip
)
{
return
;
}
adlTrainingService
=
component
.
get
(
AdlTrainingService
);
adlTrainingService
.
run
()
});
afterEach
(()
=>
{
if
(
skip
)
{
return
;
}
adlTrainingService
.
cleanUp
();
});
it
(
'
Set and get cluster metadata
'
,
async
()
=>
{
if
(
skip
)
{
return
;
}
await
adlTrainingService
.
setClusterMetadata
(
TrialConfigMetadataKey
.
TRIAL_CONFIG
,
testAdlTrialConfig2
);
await
adlTrainingService
.
setClusterMetadata
(
TrialConfigMetadataKey
.
NNI_MANAGER_IP
,
testNniManagerIp
);
let
data
:
string
=
await
adlTrainingService
.
getClusterMetadata
(
TrialConfigMetadataKey
.
TRIAL_CONFIG
);
chai
.
expect
(
data
).
to
.
be
.
equals
(
testAdlTrialConfig2
);
});
it
(
'
Submit job
'
,
async
()
=>
{
if
(
skip
)
{
return
;
}
// job without given checkpoint, with resource config
await
adlTrainingService
.
setClusterMetadata
(
TrialConfigMetadataKey
.
TRIAL_CONFIG
,
testAdlTrialConfig
);
let
form
:
TrialJobApplicationForm
=
{
sequenceId
:
0
,
hyperParameters
:
{
value
:
'
mock hyperparameters
'
,
index
:
0
}
};
let
jobDetail
:
TrialJobDetail
=
await
adlTrainingService
.
submitTrialJob
(
form
);
chai
.
expect
(
jobDetail
.
status
).
to
.
be
.
equals
(
'
WAITING
'
);
await
adlTrainingService
.
cancelTrialJob
(
jobDetail
.
id
);
chai
.
expect
(
jobDetail
.
status
).
to
.
be
.
equals
(
'
USER_CANCELED
'
);
// job with given checkpoint
await
adlTrainingService
.
setClusterMetadata
(
TrialConfigMetadataKey
.
TRIAL_CONFIG
,
testAdlTrialConfig2
);
form
=
{
sequenceId
:
0
,
hyperParameters
:
{
value
:
'
mock hyperparameters
'
,
index
:
0
}
};
jobDetail
=
await
adlTrainingService
.
submitTrialJob
(
form
);
chai
.
expect
(
jobDetail
.
status
).
to
.
be
.
equals
(
'
WAITING
'
);
await
adlTrainingService
.
cancelTrialJob
(
jobDetail
.
id
);
chai
.
expect
(
jobDetail
.
status
).
to
.
be
.
equals
(
'
USER_CANCELED
'
);
}).
timeout
(
3000000
);
});
ts/webui/src/App.scss
View file @
b40e3db7
...
...
@@ -29,6 +29,8 @@
width
:
87%
;
margin
:
0
auto
;
min-width
:
1200px
;
/* nav bar: 56 + marginTop: 18 */
margin-top
:
74px
;
margin-bottom
:
30px
;
}
...
...
ts/webui/src/App.tsx
View file @
b40e3db7
...
...
@@ -4,8 +4,9 @@ import { COLUMN } from './static/const';
import
{
EXPERIMENT
,
TRIALS
}
from
'
./static/datamodel
'
;
import
NavCon
from
'
./components/NavCon
'
;
import
MessageInfo
from
'
./components/modals/MessageInfo
'
;
import
{
TrialConfigButton
}
from
'
./components/
public-child/config/TrialConfigButton
'
;
import
{
SlideNavBtns
}
from
'
./components/
slideNav/SlideNavBtns
'
;
import
'
./App.scss
'
;
import
'
./static/style/common.scss
'
;
interface
AppState
{
interval
:
number
;
...
...
@@ -164,7 +165,7 @@ class App extends React.Component<{}, AppState> {
updateOverviewPage
:
this
.
updateOverviewPage
}
}
>
<
TrialConfigButton
/>
<
SlideNavBtns
/>
</
AppContext
.
Provider
>
{
/* if api has error field, show error message */
}
{
errorList
.
map
(
...
...
ts/webui/src/components/NavCon.tsx
View file @
b40e3db7
...
...
@@ -10,9 +10,8 @@ import {
IStackTokens
,
IStackStyles
}
from
'
@fluentui/react
'
;
import
LogPanel
from
'
./modals/LogPanel
'
;
import
ExperimentPanel
from
'
./modals/ExperimentPanel
'
;
import
{
downLoadIcon
,
infoIconAbout
,
timeIcon
,
disableUpdates
,
requency
,
closeTimer
}
from
'
./buttons/Icon
'
;
import
ExperimentSummaryPanel
from
'
./modals/ExperimentSummaryPanel
'
;
import
{
infoIconAbout
,
timeIcon
,
disableUpdates
,
requency
,
closeTimer
}
from
'
./buttons/Icon
'
;
import
{
OVERVIEWTABS
,
DETAILTABS
,
NNILOGO
}
from
'
./stateless-component/NNItabs
'
;
import
{
EXPERIMENT
}
from
'
../static/datamodel
'
;
import
'
../static/style/nav/nav.scss
'
;
...
...
@@ -36,7 +35,6 @@ interface NavState {
menuVisible
:
boolean
;
navBarVisible
:
boolean
;
isdisabledFresh
:
boolean
;
isvisibleLogDrawer
:
boolean
;
isvisibleExperimentDrawer
:
boolean
;
refreshText
:
string
;
refreshFrequency
:
number
|
string
;
...
...
@@ -55,7 +53,6 @@ class NavCon extends React.Component<NavProps, NavState> {
menuVisible
:
false
,
navBarVisible
:
false
,
isdisabledFresh
:
false
,
isvisibleLogDrawer
:
false
,
// download button (nnimanager·dispatcher) click -> drawer
isvisibleExperimentDrawer
:
false
,
refreshText
:
'
Auto refresh
'
,
refreshFrequency
:
10
...
...
@@ -67,16 +64,6 @@ class NavCon extends React.Component<NavProps, NavState> {
this
.
setState
({
isvisibleExperimentDrawer
:
true
});
};
// to see & download dispatcher | nnimanager log
showDispatcherLog
=
():
void
=>
{
this
.
setState
({
isvisibleLogDrawer
:
true
});
};
// close log drawer (nnimanager.dispatcher)
closeLogDrawer
=
():
void
=>
{
this
.
setState
({
isvisibleLogDrawer
:
false
});
};
// close download experiment parameters drawer
closeExpDrawer
=
():
void
=>
{
this
.
setState
({
isvisibleExperimentDrawer
:
false
});
...
...
@@ -121,7 +108,7 @@ class NavCon extends React.Component<NavProps, NavState> {
}
render
():
React
.
ReactNode
{
const
{
isvisibleLogDrawer
,
isvisibleExperimentDrawer
,
version
,
refreshText
,
refreshFrequency
}
=
this
.
state
;
const
{
isvisibleExperimentDrawer
,
version
,
refreshText
,
refreshFrequency
}
=
this
.
state
;
const
aboutProps
:
IContextualMenuProps
=
{
items
:
[
{
...
...
@@ -168,38 +155,24 @@ class NavCon extends React.Component<NavProps, NavState> {
/>
<
div
className
=
'nav-refresh-num'
>
{
refreshFrequency
}
</
div
>
</
div
>
<
CommandBarButton
iconProps
=
{
downLoadIcon
}
text
=
'Download'
menuProps
=
{
this
.
menuProps
}
/>
<
CommandBarButton
iconProps
=
{
{
iconName
:
'
ShowResults
'
}
}
text
=
'Experiment summary'
onClick
=
{
this
.
showExpcontent
}
/>
<
CommandBarButton
iconProps
=
{
infoIconAbout
}
text
=
'About'
menuProps
=
{
aboutProps
}
/>
</
Stack
>
</
StackItem
>
{
/* the drawer for dispatcher & nnimanager log message */
}
{
isvisibleLogDrawer
&&
<
LogPanel
closeDrawer
=
{
this
.
closeLogDrawer
}
/>
}
{
isvisibleExperimentDrawer
&&
(
<
ExperimentPanel
closeExpDrawer
=
{
this
.
closeExpDrawer
}
experimentProfile
=
{
EXPERIMENT
.
profile
}
/>
<
ExperimentSummaryPanel
closeExpDrawer
=
{
this
.
closeExpDrawer
}
experimentProfile
=
{
EXPERIMENT
.
profile
}
/>
)
}
</
Stack
>
);
}
// view and download experiment [log & experiment result]
private
menuProps
:
IContextualMenuProps
=
{
items
:
[
{
key
:
'
experiment
'
,
text
:
'
Experiment summary
'
,
iconProps
:
{
iconName
:
'
ShowResults
'
},
onClick
:
this
.
showExpcontent
},
{
key
:
'
logfiles
'
,
text
:
'
Log files
'
,
iconProps
:
{
iconName
:
'
FilePDB
'
},
onClick
:
this
.
showDispatcherLog
}
],
directionalHintFixed
:
true
};
private
refreshProps
:
IContextualMenuProps
=
{
items
:
[
{
...
...
ts/webui/src/components/Overview.tsx
View file @
b40e3db7
...
...
@@ -13,8 +13,9 @@ import { TrialCount } from './overview/count/TrialCount';
import
{
Command1
}
from
'
./overview/command/Command1
'
;
import
{
Command2
}
from
'
./overview/command/Command2
'
;
import
{
TitleContext
}
from
'
./overview/TitleContext
'
;
import
{
itemStyle
1
,
itemStyleSucceed
,
itemStyle2
,
entriesOption
}
from
'
./overview/overviewConst
'
;
import
{
itemStyle
Succeed
,
entriesOption
}
from
'
./overview/overviewConst
'
;
import
'
../static/style/overview/overview.scss
'
;
import
'
../static/style/overview/topTrial.scss
'
;
import
'
../static/style/logPath.scss
'
;
interface
OverviewState
{
...
...
@@ -89,42 +90,40 @@ class Overview extends React.Component<{}, OverviewState> {
</
BestMetricContext
.
Provider
>
</
div
>
{
/* duration & trial numbers */
}
<
div
className
=
'overviewProgress'
>
<
div
className
=
'duration'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Duration
'
,
icon
:
'
Timer
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
<
ExpDurationContext
.
Provider
value
=
{
{
maxExecDuration
,
execDuration
,
updateOverviewPage
,
maxDurationUnit
,
changeMaxDurationUnit
}
}
>
<
ExpDuration
/>
</
ExpDurationContext
.
Provider
>
</
div
>
<
div
className
=
'trialCount'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Trial numbers
'
,
icon
:
'
NumberSymbol
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
<
ExpDurationContext
.
Provider
value
=
{
{
maxExecDuration
,
execDuration
,
updateOverviewPage
,
maxDurationUnit
,
changeMaxDurationUnit
}
}
>
<
TrialCount
/>
</
ExpDurationContext
.
Provider
>
</
div
>
<
div
className
=
'duration'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Duration
'
,
icon
:
'
Timer
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
<
ExpDurationContext
.
Provider
value
=
{
{
maxExecDuration
,
execDuration
,
updateOverviewPage
,
maxDurationUnit
,
changeMaxDurationUnit
}
}
>
<
ExpDuration
/>
</
ExpDurationContext
.
Provider
>
</
div
>
<
div
className
=
'trialCount'
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Trial numbers
'
,
icon
:
'
NumberSymbol
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
<
ExpDurationContext
.
Provider
value
=
{
{
maxExecDuration
,
execDuration
,
updateOverviewPage
,
maxDurationUnit
,
changeMaxDurationUnit
}
}
>
<
TrialCount
/>
</
ExpDurationContext
.
Provider
>
</
div
>
{
/* table */
}
<
div
className
=
'overview
Table
'
>
<
div
className
=
'overview
BestMetric
'
>
<
Stack
horizontal
>
<
div
style
=
{
itemStyleSucceed
}
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Top trials
'
,
icon
:
'
BulletedList
'
}
}
>
...
...
@@ -167,7 +166,13 @@ class Overview extends React.Component<{}, OverviewState> {
</
Stack
>
</
div
>
</
Stack
>
<
SuccessTable
trialIds
=
{
bestTrials
.
map
(
trial
=>
trial
.
info
.
id
)
}
/>
<
div
className
=
'overviewChart'
>
<
Accuracy
accuracyData
=
{
accuracyGraphData
}
accNodata
=
{
noDataMessage
}
/>
<
SuccessTable
trialIds
=
{
bestTrials
.
map
(
trial
=>
trial
.
info
.
trialJobId
)
}
updateOverviewPage
=
{
updateOverviewPage
}
/>
</
div
>
</
div
>
<
div
className
=
'overviewCommand1'
>
<
Command1
/>
...
...
@@ -175,24 +180,6 @@ class Overview extends React.Component<{}, OverviewState> {
<
div
className
=
'overviewCommand2'
>
<
Command2
/>
</
div
>
<
div
className
=
'overviewChart'
>
<
Stack
horizontal
>
<
div
style
=
{
itemStyle1
}
>
<
TitleContext
.
Provider
value
=
{
{
text
:
'
Trial metric chart
'
,
icon
:
'
HomeGroup
'
}
}
>
<
Title
/>
</
TitleContext
.
Provider
>
</
div
>
<
div
style
=
{
itemStyle2
}
>
<
Stack
className
=
'maxmin'
horizontal
>
<
div
className
=
'circle'
/>
<
div
>
{
`Top
${
this
.
context
.
metricGraphMode
}
imal trials`
}
</
div
>
</
Stack
>
</
div
>
</
Stack
>
<
Accuracy
accuracyData
=
{
accuracyGraphData
}
accNodata
=
{
noDataMessage
}
height
=
{
380
}
/>
</
div
>
</
div
>
</
div
>
);
...
...
@@ -219,8 +206,8 @@ class Overview extends React.Component<{}, OverviewState> {
return
{
// support max show 0.0000000
grid
:
{
left
:
6
7
,
right
:
40
x
:
6
0
,
y
:
40
},
tooltip
:
{
trigger
:
'
item
'
...
...
ts/webui/src/components/modals/ExperimentPanel.tsx
→
ts/webui/src/components/modals/Experiment
Summary
Panel.tsx
View file @
b40e3db7
import
*
as
React
from
'
react
'
;
import
{
downFile
}
from
'
../../static/function
'
;
import
{
Stack
,
PrimaryButton
,
DefaultButton
,
Panel
,
StackItem
,
Pivot
,
PivotItem
}
from
'
@fluentui/react
'
;
import
{
Stack
,
PrimaryButton
,
DefaultButton
,
Panel
,
StackItem
}
from
'
@fluentui/react
'
;
import
{
DRAWEROPTION
}
from
'
../../static/const
'
;
import
{
EXPERIMENT
,
TRIALS
}
from
'
../../static/datamodel
'
;
import
{
caclMonacoEditorHeight
}
from
'
../../static/function
'
;
import
MonacoEditor
from
'
react-monaco-editor
'
;
import
'
../../static/style/logDrawer.scss
'
;
...
...
@@ -16,7 +17,7 @@ interface ExpDrawerState {
expDrawerHeight
:
number
;
}
class
Experiment
Drawer
extends
React
.
Component
<
ExpDrawerProps
,
ExpDrawerState
>
{
class
Experiment
SummaryPanel
extends
React
.
Component
<
ExpDrawerProps
,
ExpDrawerState
>
{
public
_isExperimentMount
!
:
boolean
;
private
refreshId
!
:
number
|
undefined
;
...
...
@@ -88,41 +89,31 @@ class ExperimentDrawer extends React.Component<ExpDrawerProps, ExpDrawerState> {
render
():
React
.
ReactNode
{
const
{
closeExpDrawer
}
=
this
.
props
;
const
{
experiment
,
expDrawerHeight
}
=
this
.
state
;
const
monacoEditorHeight
=
caclMonacoEditorHeight
(
expDrawerHeight
);
return
(
<
Stack
className
=
'logDrawer'
>
<
Panel
isOpen
=
{
true
}
hasCloseButton
=
{
false
}
isLightDismiss
=
{
true
}
onLightDismissClick
=
{
closeExpDrawer
}
styles
=
{
{
root
:
{
height
:
expDrawerHeight
,
paddingTop
:
15
}
}
}
>
<
Pivot
style
=
{
{
minHeight
:
190
}
}
className
=
'log-tab-body'
>
<
PivotItem
headerText
=
'Experiment parameters'
>
<
div
className
=
'just-for-log'
>
<
MonacoEditor
width
=
'100%'
// 92 + marginTop[16]
height
=
{
expDrawerHeight
-
108
}
language
=
'json'
value
=
{
experiment
}
options
=
{
DRAWEROPTION
}
/>
</
div
>
<
Stack
horizontal
className
=
'buttons'
>
<
StackItem
grow
=
{
50
}
className
=
'download'
>
<
PrimaryButton
text
=
'Download'
onClick
=
{
this
.
downExperimentParameters
}
/>
</
StackItem
>
<
StackItem
grow
=
{
50
}
className
=
'close'
>
<
DefaultButton
text
=
'Close'
onClick
=
{
closeExpDrawer
}
/>
</
StackItem
>
</
Stack
>
</
PivotItem
>
</
Pivot
>
</
Panel
>
</
Stack
>
<
Panel
isOpen
=
{
true
}
hasCloseButton
=
{
false
}
isLightDismiss
=
{
true
}
onLightDismissClick
=
{
closeExpDrawer
}
>
<
div
className
=
'panel'
>
<
div
className
=
'panelName'
>
Experiment summary
</
div
>
<
MonacoEditor
width
=
'100%'
height
=
{
monacoEditorHeight
}
language
=
'json'
value
=
{
experiment
}
options
=
{
DRAWEROPTION
}
/>
<
Stack
horizontal
className
=
'buttons'
>
<
StackItem
grow
=
{
50
}
className
=
'download'
>
<
PrimaryButton
text
=
'Download'
onClick
=
{
this
.
downExperimentParameters
}
/>
</
StackItem
>
<
StackItem
grow
=
{
50
}
className
=
'close'
>
<
DefaultButton
text
=
'Close'
onClick
=
{
closeExpDrawer
}
/>
</
StackItem
>
</
Stack
>
</
div
>
</
Panel
>
);
}
}
export
default
Experiment
Drawer
;
export
default
Experiment
SummaryPanel
;
ts/webui/src/components/modals/LogPanel.tsx
View file @
b40e3db7
...
...
@@ -29,7 +29,7 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
nniManagerLogStr
:
null
,
dispatcherLogStr
:
null
,
isLoading
:
true
,
logDrawerHeight
:
window
.
innerHeight
-
48
logDrawerHeight
:
window
.
innerHeight
};
}
...
...
@@ -64,7 +64,7 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
);
setLogDrawerHeight
=
():
void
=>
{
this
.
setState
(()
=>
({
logDrawerHeight
:
window
.
innerHeight
-
48
}));
this
.
setState
(()
=>
({
logDrawerHeight
:
window
.
innerHeight
}));
};
async
componentDidMount
():
Promise
<
void
>
{
...
...
@@ -80,7 +80,8 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
render
():
React
.
ReactNode
{
const
{
closeDrawer
,
activeTab
}
=
this
.
props
;
const
{
nniManagerLogStr
,
dispatcherLogStr
,
isLoading
,
logDrawerHeight
}
=
this
.
state
;
// tab[height: 56] + tab[margin-bottom: 20] + button[32] + button[margin-top: 45, -bottom: 7] + fluent-panel own paddingBottom[20] + title-border[2]
const
monacoHeight
=
logDrawerHeight
-
182
;
return
(
<
Stack
>
<
Panel
...
...
@@ -90,15 +91,13 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
isLightDismiss
=
{
true
}
onLightDismissClick
=
{
closeDrawer
}
>
<
div
className
=
'log-tab-body'
>
<
Pivot
selectedKey
=
{
activeTab
}
style
=
{
{
minHeight
:
190
,
paddingTop
:
'
16px
'
}
}
>
{
/* <PivotItem headerText={this.dispatcherHTML()} key="dispatcher" onLinkClick> */
}
<
PivotItem
headerText
=
'Dispatcher log'
key
=
'dispatcher'
>
<
Pivot
selectedKey
=
{
activeTab
}
style
=
{
{
minHeight
:
190
}
}
>
<
PivotItem
headerText
=
'Dispatcher log'
key
=
'dispatcher'
>
<
div
className
=
'panel logMargin'
>
<
MonacoHTML
content
=
{
dispatcherLogStr
||
'
Loading...
'
}
loading
=
{
isLoading
}
// paddingTop[16] + tab[44] + button[32]
height
=
{
logDrawerHeight
-
92
}
height
=
{
monacoHeight
}
/>
<
Stack
horizontal
className
=
'buttons'
>
<
StackItem
grow
=
{
12
}
className
=
'download'
>
...
...
@@ -108,13 +107,14 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
<
DefaultButton
text
=
'Close'
onClick
=
{
closeDrawer
}
/>
</
StackItem
>
</
Stack
>
</
PivotItem
>
<
PivotItem
headerText
=
'NNIManager log'
key
=
'nnimanager'
>
{
/* <TabPane tab="NNImanager Log" key="nnimanager"> */
}
</
div
>
</
PivotItem
>
<
PivotItem
headerText
=
'NNIManager log'
key
=
'nnimanager'
>
<
div
className
=
'panel logMargin'
>
<
MonacoHTML
content
=
{
nniManagerLogStr
||
'
Loading...
'
}
loading
=
{
isLoading
}
height
=
{
logDrawer
Height
-
92
}
height
=
{
monaco
Height
}
/>
<
Stack
horizontal
className
=
'buttons'
>
<
StackItem
grow
=
{
12
}
className
=
'download'
>
...
...
@@ -124,9 +124,9 @@ class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
<
DefaultButton
text
=
'Close'
onClick
=
{
closeDrawer
}
/>
</
StackItem
>
</
Stack
>
</
P
iv
otItem
>
</
Pivot
>
</
d
iv
>
</
d
iv
>
</
Pivot
Item
>
</
P
iv
ot
>
</
Panel
>
</
Stack
>
);
...
...
ts/webui/src/components/overview/Accuracy.tsx
View file @
b40e3db7
...
...
@@ -11,7 +11,6 @@ import 'echarts/lib/component/title';
interface
AccuracyProps
{
accuracyData
:
object
;
accNodata
:
string
;
height
:
number
;
}
class
Accuracy
extends
React
.
Component
<
AccuracyProps
,
{}
>
{
...
...
@@ -20,17 +19,10 @@ class Accuracy extends React.Component<AccuracyProps, {}> {
}
render
():
React
.
ReactNode
{
const
{
accNodata
,
accuracyData
,
height
}
=
this
.
props
;
const
{
accNodata
,
accuracyData
}
=
this
.
props
;
return
(
<
div
style
=
{
{
position
:
'
relative
'
}
}
>
<
ReactEcharts
option
=
{
accuracyData
}
style
=
{
{
height
:
height
,
margin
:
'
0 auto
'
}
}
theme
=
'my_theme'
/>
<
div
className
=
'defaultMetricContainer'
>
<
ReactEcharts
option
=
{
accuracyData
}
theme
=
'my_theme'
/>
<
div
className
=
'showMess'
>
{
accNodata
}
</
div
>
</
div
>
);
...
...
Prev
1
…
6
7
8
9
10
11
12
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment