Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
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