Commit 4bbffd17 authored by suiguoxin's avatar suiguoxin
Browse files

Merge branch 'master' of git://github.com/microsoft/nni

parents d92c69f4 4592ba41
...@@ -2,8 +2,8 @@ Introduction to NNI Training Services ...@@ -2,8 +2,8 @@ Introduction to NNI Training Services
===================================== =====================================
.. toctree:: .. toctree::
Local<LocalMode> Local<./TrainingService/LocalMode>
Remote<RemoteMachineMode> Remote<./TrainingService/RemoteMachineMode>
OpenPAI<PaiMode> OpenPAI<./TrainingService/PaiMode>
Kubeflow<KubeflowMode> Kubeflow<./TrainingService/KubeflowMode>
FrameworkController<FrameworkControllerMode> FrameworkController<./TrainingService/FrameworkControllerMode>
\ No newline at end of file \ No newline at end of file
...@@ -13,6 +13,6 @@ For details, please refer to the following tutorials: ...@@ -13,6 +13,6 @@ For details, please refer to the following tutorials:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
Builtin Tuners<BuiltinTuner> Builtin Tuners <builtin_tuner>
Customized Tuners<CustomizeTuner> Customized Tuners <Tuner/CustomizeTuner>
Customized Advisor<CustomizeAdvisor> Customized Advisor <Tuner/CustomizeAdvisor>
\ No newline at end of file
...@@ -5,12 +5,13 @@ Tutorials ...@@ -5,12 +5,13 @@ Tutorials
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
Installation Installation <Tutorial/Installation>
Write Trial<Trials> Write Trial <TrialExample/Trials>
Tuners<tuners> Tuners <tuners>
Assessors<assessors> Assessors <assessors>
WebUI WebUI <Tutorial/WebUI>
Training Platform<training_services> Training Platform <training_services>
How to use docker<HowToUseDocker> How to use docker <Tutorial/HowToUseDocker>
advanced advanced
Debug HowTo<HowToDebug> Debug HowTo <Tutorial/HowToDebug>
\ No newline at end of file NNI on Windows <Tutorial/NniOnWindows>
\ No newline at end of file
...@@ -130,8 +130,6 @@ def main(): ...@@ -130,8 +130,6 @@ def main():
if args.advisor_class_name: if args.advisor_class_name:
# advisor is enabled and starts to run # advisor is enabled and starts to run
if args.multi_phase:
raise AssertionError('multi_phase has not been supported in advisor')
if args.advisor_class_name in AdvisorModuleName: if args.advisor_class_name in AdvisorModuleName:
dispatcher = create_builtin_class_instance( dispatcher = create_builtin_class_instance(
args.advisor_class_name, args.advisor_class_name,
......
...@@ -31,7 +31,8 @@ import ConfigSpace.hyperparameters as CSH ...@@ -31,7 +31,8 @@ import ConfigSpace.hyperparameters as CSH
from nni.protocol import CommandType, send from nni.protocol import CommandType, send
from nni.msg_dispatcher_base import MsgDispatcherBase from nni.msg_dispatcher_base import MsgDispatcherBase
from nni.utils import OptimizeMode, extract_scalar_reward, randint_to_quniform from nni.utils import OptimizeMode, MetricType, extract_scalar_reward, randint_to_quniform
from nni.common import multi_phase_enabled
from .config_generator import CG_BOHB from .config_generator import CG_BOHB
...@@ -79,7 +80,7 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=- ...@@ -79,7 +80,7 @@ def create_bracket_parameter_id(brackets_id, brackets_curr_decay, increased_id=-
return params_id return params_id
class Bracket(): class Bracket(object):
""" """
A bracket in BOHB, all the information of a bracket is managed by A bracket in BOHB, all the information of a bracket is managed by
an instance of this class. an instance of this class.
...@@ -329,11 +330,12 @@ class BOHB(MsgDispatcherBase): ...@@ -329,11 +330,12 @@ class BOHB(MsgDispatcherBase):
# config generator # config generator
self.cg = None self.cg = None
def load_checkpoint(self): # record the latest parameter_id of the trial job trial_job_id.
pass # if there is no running parameter_id, self.job_id_para_id_map[trial_job_id] == None
# new trial job is added to this dict and finished trial job is removed from it.
def save_checkpoint(self): self.job_id_para_id_map = dict()
pass # record the unsatisfied parameter request from trial jobs
self.unsatisfied_jobs = []
def handle_initialize(self, data): def handle_initialize(self, data):
"""Initialize Tuner, including creating Bayesian optimization-based parametric models """Initialize Tuner, including creating Bayesian optimization-based parametric models
...@@ -399,7 +401,7 @@ class BOHB(MsgDispatcherBase): ...@@ -399,7 +401,7 @@ class BOHB(MsgDispatcherBase):
for _ in range(self.credit): for _ in range(self.credit):
self._request_one_trial_job() self._request_one_trial_job()
def _request_one_trial_job(self): def _get_one_trial_job(self):
"""get one trial job, i.e., one hyperparameter configuration. """get one trial job, i.e., one hyperparameter configuration.
If this function is called, Command will be sent by BOHB: If this function is called, Command will be sent by BOHB:
...@@ -423,7 +425,7 @@ class BOHB(MsgDispatcherBase): ...@@ -423,7 +425,7 @@ class BOHB(MsgDispatcherBase):
'parameters': '' 'parameters': ''
} }
send(CommandType.NoMoreTrialJobs, json_tricks.dumps(ret)) send(CommandType.NoMoreTrialJobs, json_tricks.dumps(ret))
return return None
assert self.generated_hyper_configs assert self.generated_hyper_configs
params = self.generated_hyper_configs.pop() params = self.generated_hyper_configs.pop()
ret = { ret = {
...@@ -432,8 +434,29 @@ class BOHB(MsgDispatcherBase): ...@@ -432,8 +434,29 @@ class BOHB(MsgDispatcherBase):
'parameters': params[1] 'parameters': params[1]
} }
self.parameters[params[0]] = params[1] self.parameters[params[0]] = params[1]
send(CommandType.NewTrialJob, json_tricks.dumps(ret)) return ret
self.credit -= 1
def _request_one_trial_job(self):
"""get one trial job, i.e., one hyperparameter configuration.
If this function is called, Command will be sent by BOHB:
a. If there is a parameter need to run, will return "NewTrialJob" with a dict:
{
'parameter_id': id of new hyperparameter
'parameter_source': 'algorithm'
'parameters': value of new hyperparameter
}
b. If BOHB don't have parameter waiting, will return "NoMoreTrialJobs" with
{
'parameter_id': '-1_0_0',
'parameter_source': 'algorithm',
'parameters': ''
}
"""
ret = self._get_one_trial_job()
if ret is not None:
send(CommandType.NewTrialJob, json_tricks.dumps(ret))
self.credit -= 1
def handle_update_search_space(self, data): def handle_update_search_space(self, data):
"""change json format to ConfigSpace format dict<dict> -> configspace """change json format to ConfigSpace format dict<dict> -> configspace
...@@ -502,23 +525,38 @@ class BOHB(MsgDispatcherBase): ...@@ -502,23 +525,38 @@ class BOHB(MsgDispatcherBase):
hyper_params: the hyperparameters (a string) generated and returned by tuner hyper_params: the hyperparameters (a string) generated and returned by tuner
""" """
logger.debug('Tuner handle trial end, result is %s', data) logger.debug('Tuner handle trial end, result is %s', data)
hyper_params = json_tricks.loads(data['hyper_params']) hyper_params = json_tricks.loads(data['hyper_params'])
s, i, _ = hyper_params['parameter_id'].split('_') self._handle_trial_end(hyper_params['parameter_id'])
if data['trial_job_id'] in self.job_id_para_id_map:
del self.job_id_para_id_map[data['trial_job_id']]
def _send_new_trial(self):
while self.unsatisfied_jobs:
ret = self._get_one_trial_job()
if ret is None:
break
one_unsatisfied = self.unsatisfied_jobs.pop(0)
ret['trial_job_id'] = one_unsatisfied['trial_job_id']
ret['parameter_index'] = one_unsatisfied['parameter_index']
# update parameter_id in self.job_id_para_id_map
self.job_id_para_id_map[ret['trial_job_id']] = ret['parameter_id']
send(CommandType.SendTrialJobParameter, json_tricks.dumps(ret))
for _ in range(self.credit):
self._request_one_trial_job()
def _handle_trial_end(self, parameter_id):
s, i, _ = parameter_id.split('_')
hyper_configs = self.brackets[int(s)].inform_trial_end(int(i)) hyper_configs = self.brackets[int(s)].inform_trial_end(int(i))
if hyper_configs is not None: if hyper_configs is not None:
logger.debug( logger.debug(
'bracket %s next round %s, hyper_configs: %s', s, i, hyper_configs) 'bracket %s next round %s, hyper_configs: %s', s, i, hyper_configs)
self.generated_hyper_configs = self.generated_hyper_configs + hyper_configs self.generated_hyper_configs = self.generated_hyper_configs + hyper_configs
for _ in range(self.credit):
self._request_one_trial_job()
# Finish this bracket and generate a new bracket # Finish this bracket and generate a new bracket
elif self.brackets[int(s)].no_more_trial: elif self.brackets[int(s)].no_more_trial:
self.curr_s -= 1 self.curr_s -= 1
self.generate_new_bracket() self.generate_new_bracket()
for _ in range(self.credit): self._send_new_trial()
self._request_one_trial_job()
def handle_report_metric_data(self, data): def handle_report_metric_data(self, data):
"""reveice the metric data and update Bayesian optimization with final result """reveice the metric data and update Bayesian optimization with final result
...@@ -535,36 +573,58 @@ class BOHB(MsgDispatcherBase): ...@@ -535,36 +573,58 @@ class BOHB(MsgDispatcherBase):
""" """
logger.debug('handle report metric data = %s', data) logger.debug('handle report metric data = %s', data)
assert 'value' in data if data['type'] == MetricType.REQUEST_PARAMETER:
value = extract_scalar_reward(data['value']) assert multi_phase_enabled()
if self.optimize_mode is OptimizeMode.Maximize: assert data['trial_job_id'] is not None
reward = -value assert data['parameter_index'] is not None
else: assert data['trial_job_id'] in self.job_id_para_id_map
reward = value self._handle_trial_end(self.job_id_para_id_map[data['trial_job_id']])
assert 'parameter_id' in data ret = self._get_one_trial_job()
s, i, _ = data['parameter_id'].split('_') if ret is None:
self.unsatisfied_jobs.append({'trial_job_id': data['trial_job_id'], 'parameter_index': data['parameter_index']})
logger.debug('bracket id = %s, metrics value = %s, type = %s', s, value, data['type']) else:
s = int(s) ret['trial_job_id'] = data['trial_job_id']
ret['parameter_index'] = data['parameter_index']
assert 'type' in data # update parameter_id in self.job_id_para_id_map
if data['type'] == 'FINAL': self.job_id_para_id_map[data['trial_job_id']] = ret['parameter_id']
# and PERIODICAL metric are independent, thus, not comparable. send(CommandType.SendTrialJobParameter, json_tricks.dumps(ret))
assert 'sequence' in data
self.brackets[s].set_config_perf(
int(i), data['parameter_id'], sys.maxsize, value)
self.completed_hyper_configs.append(data)
_parameters = self.parameters[data['parameter_id']]
_parameters.pop(_KEY)
# update BO with loss, max_s budget, hyperparameters
self.cg.new_result(loss=reward, budget=data['sequence'], parameters=_parameters, update_model=True)
elif data['type'] == 'PERIODICAL':
self.brackets[s].set_config_perf(
int(i), data['parameter_id'], data['sequence'], value)
else: else:
raise ValueError( assert 'value' in data
'Data type not supported: {}'.format(data['type'])) value = extract_scalar_reward(data['value'])
if self.optimize_mode is OptimizeMode.Maximize:
reward = -value
else:
reward = value
assert 'parameter_id' in data
s, i, _ = data['parameter_id'].split('_')
logger.debug('bracket id = %s, metrics value = %s, type = %s', s, value, data['type'])
s = int(s)
# add <trial_job_id, parameter_id> to self.job_id_para_id_map here,
# because when the first parameter_id is created, trial_job_id is not known yet.
if data['trial_job_id'] in self.job_id_para_id_map:
assert self.job_id_para_id_map[data['trial_job_id']] == data['parameter_id']
else:
self.job_id_para_id_map[data['trial_job_id']] = data['parameter_id']
assert 'type' in data
if data['type'] == MetricType.FINAL:
# and PERIODICAL metric are independent, thus, not comparable.
assert 'sequence' in data
self.brackets[s].set_config_perf(
int(i), data['parameter_id'], sys.maxsize, value)
self.completed_hyper_configs.append(data)
_parameters = self.parameters[data['parameter_id']]
_parameters.pop(_KEY)
# update BO with loss, max_s budget, hyperparameters
self.cg.new_result(loss=reward, budget=data['sequence'], parameters=_parameters, update_model=True)
elif data['type'] == MetricType.PERIODICAL:
self.brackets[s].set_config_perf(
int(i), data['parameter_id'], data['sequence'], value)
else:
raise ValueError(
'Data type not supported: {}'.format(data['type']))
def handle_add_customized_trial(self, data): def handle_add_customized_trial(self, data):
pass pass
......
...@@ -30,8 +30,8 @@ import json_tricks ...@@ -30,8 +30,8 @@ import json_tricks
from nni.protocol import CommandType, send from nni.protocol import CommandType, send
from nni.msg_dispatcher_base import MsgDispatcherBase from nni.msg_dispatcher_base import MsgDispatcherBase
from nni.common import init_logger from nni.common import init_logger, multi_phase_enabled
from nni.utils import NodeType, OptimizeMode, extract_scalar_reward, randint_to_quniform from nni.utils import NodeType, OptimizeMode, MetricType, extract_scalar_reward, randint_to_quniform
import nni.parameter_expressions as parameter_expressions import nni.parameter_expressions as parameter_expressions
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -277,7 +277,7 @@ class Hyperband(MsgDispatcherBase): ...@@ -277,7 +277,7 @@ class Hyperband(MsgDispatcherBase):
optimize_mode: str optimize_mode: str
optimize mode, 'maximize' or 'minimize' optimize mode, 'maximize' or 'minimize'
""" """
def __init__(self, R, eta=3, optimize_mode='maximize'): def __init__(self, R=60, eta=3, optimize_mode='maximize'):
"""B = (s_max + 1)R""" """B = (s_max + 1)R"""
super(Hyperband, self).__init__() super(Hyperband, self).__init__()
self.R = R # pylint: disable=invalid-name self.R = R # pylint: disable=invalid-name
...@@ -296,11 +296,10 @@ class Hyperband(MsgDispatcherBase): ...@@ -296,11 +296,10 @@ class Hyperband(MsgDispatcherBase):
# In this case, tuner increases self.credit to issue a trial config sometime later. # In this case, tuner increases self.credit to issue a trial config sometime later.
self.credit = 0 self.credit = 0
def load_checkpoint(self): # record the latest parameter_id of the trial job trial_job_id.
pass # if there is no running parameter_id, self.job_id_para_id_map[trial_job_id] == None
# new trial job is added to this dict and finished trial job is removed from it.
def save_checkpoint(self): self.job_id_para_id_map = dict()
pass
def handle_initialize(self, data): def handle_initialize(self, data):
"""data is search space """data is search space
...@@ -321,9 +320,10 @@ class Hyperband(MsgDispatcherBase): ...@@ -321,9 +320,10 @@ class Hyperband(MsgDispatcherBase):
number of trial jobs number of trial jobs
""" """
for _ in range(data): for _ in range(data):
self._request_one_trial_job() ret = self._get_one_trial_job()
send(CommandType.NewTrialJob, json_tricks.dumps(ret))
def _request_one_trial_job(self): def _get_one_trial_job(self):
"""get one trial job, i.e., one hyperparameter configuration.""" """get one trial job, i.e., one hyperparameter configuration."""
if not self.generated_hyper_configs: if not self.generated_hyper_configs:
if self.curr_s < 0: if self.curr_s < 0:
...@@ -346,7 +346,8 @@ class Hyperband(MsgDispatcherBase): ...@@ -346,7 +346,8 @@ class Hyperband(MsgDispatcherBase):
'parameter_source': 'algorithm', 'parameter_source': 'algorithm',
'parameters': params[1] 'parameters': params[1]
} }
send(CommandType.NewTrialJob, json_tricks.dumps(ret)) return ret
def handle_update_search_space(self, data): def handle_update_search_space(self, data):
"""data: JSON object, which is search space """data: JSON object, which is search space
...@@ -360,6 +361,18 @@ class Hyperband(MsgDispatcherBase): ...@@ -360,6 +361,18 @@ class Hyperband(MsgDispatcherBase):
randint_to_quniform(self.searchspace_json) randint_to_quniform(self.searchspace_json)
self.random_state = np.random.RandomState() self.random_state = np.random.RandomState()
def _handle_trial_end(self, parameter_id):
"""
Parameters
----------
parameter_id: parameter id of the finished config
"""
bracket_id, i, _ = parameter_id.split('_')
hyper_configs = self.brackets[int(bracket_id)].inform_trial_end(int(i))
if hyper_configs is not None:
_logger.debug('bracket %s next round %s, hyper_configs: %s', bracket_id, i, hyper_configs)
self.generated_hyper_configs = self.generated_hyper_configs + hyper_configs
def handle_trial_end(self, data): def handle_trial_end(self, data):
""" """
Parameters Parameters
...@@ -371,22 +384,9 @@ class Hyperband(MsgDispatcherBase): ...@@ -371,22 +384,9 @@ class Hyperband(MsgDispatcherBase):
hyper_params: the hyperparameters (a string) generated and returned by tuner hyper_params: the hyperparameters (a string) generated and returned by tuner
""" """
hyper_params = json_tricks.loads(data['hyper_params']) hyper_params = json_tricks.loads(data['hyper_params'])
bracket_id, i, _ = hyper_params['parameter_id'].split('_') self._handle_trial_end(hyper_params['parameter_id'])
hyper_configs = self.brackets[int(bracket_id)].inform_trial_end(int(i)) if data['trial_job_id'] in self.job_id_para_id_map:
if hyper_configs is not None: del self.job_id_para_id_map[data['trial_job_id']]
_logger.debug('bracket %s next round %s, hyper_configs: %s', bracket_id, i, hyper_configs)
self.generated_hyper_configs = self.generated_hyper_configs + hyper_configs
for _ in range(self.credit):
if not self.generated_hyper_configs:
break
params = self.generated_hyper_configs.pop()
ret = {
'parameter_id': params[0],
'parameter_source': 'algorithm',
'parameters': params[1]
}
send(CommandType.NewTrialJob, json_tricks.dumps(ret))
self.credit -= 1
def handle_report_metric_data(self, data): def handle_report_metric_data(self, data):
""" """
...@@ -400,18 +400,40 @@ class Hyperband(MsgDispatcherBase): ...@@ -400,18 +400,40 @@ class Hyperband(MsgDispatcherBase):
ValueError ValueError
Data type not supported Data type not supported
""" """
value = extract_scalar_reward(data['value']) if data['type'] == MetricType.REQUEST_PARAMETER:
bracket_id, i, _ = data['parameter_id'].split('_') assert multi_phase_enabled()
bracket_id = int(bracket_id) assert data['trial_job_id'] is not None
if data['type'] == 'FINAL': assert data['parameter_index'] is not None
# sys.maxsize indicates this value is from FINAL metric data, because data['sequence'] from FINAL metric assert data['trial_job_id'] in self.job_id_para_id_map
# and PERIODICAL metric are independent, thus, not comparable. self._handle_trial_end(self.job_id_para_id_map[data['trial_job_id']])
self.brackets[bracket_id].set_config_perf(int(i), data['parameter_id'], sys.maxsize, value) ret = self._get_one_trial_job()
self.completed_hyper_configs.append(data) if data['trial_job_id'] is not None:
elif data['type'] == 'PERIODICAL': ret['trial_job_id'] = data['trial_job_id']
self.brackets[bracket_id].set_config_perf(int(i), data['parameter_id'], data['sequence'], value) if data['parameter_index'] is not None:
ret['parameter_index'] = data['parameter_index']
self.job_id_para_id_map[data['trial_job_id']] = ret['parameter_id']
send(CommandType.SendTrialJobParameter, json_tricks.dumps(ret))
else: else:
raise ValueError('Data type not supported: {}'.format(data['type'])) value = extract_scalar_reward(data['value'])
bracket_id, i, _ = data['parameter_id'].split('_')
bracket_id = int(bracket_id)
# add <trial_job_id, parameter_id> to self.job_id_para_id_map here,
# because when the first parameter_id is created, trial_job_id is not known yet.
if data['trial_job_id'] in self.job_id_para_id_map:
assert self.job_id_para_id_map[data['trial_job_id']] == data['parameter_id']
else:
self.job_id_para_id_map[data['trial_job_id']] = data['parameter_id']
if data['type'] == MetricType.FINAL:
# sys.maxsize indicates this value is from FINAL metric data, because data['sequence'] from FINAL metric
# and PERIODICAL metric are independent, thus, not comparable.
self.brackets[bracket_id].set_config_perf(int(i), data['parameter_id'], sys.maxsize, value)
self.completed_hyper_configs.append(data)
elif data['type'] == MetricType.PERIODICAL:
self.brackets[bracket_id].set_config_perf(int(i), data['parameter_id'], data['sequence'], value)
else:
raise ValueError('Data type not supported: {}'.format(data['type']))
def handle_add_customized_trial(self, data): def handle_add_customized_trial(self, data):
pass pass
......
...@@ -27,6 +27,7 @@ from .msg_dispatcher_base import MsgDispatcherBase ...@@ -27,6 +27,7 @@ from .msg_dispatcher_base import MsgDispatcherBase
from .assessor import AssessResult from .assessor import AssessResult
from .common import multi_thread_enabled, multi_phase_enabled from .common import multi_thread_enabled, multi_phase_enabled
from .env_vars import dispatcher_env_vars from .env_vars import dispatcher_env_vars
from .utils import MetricType
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -133,12 +134,12 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -133,12 +134,12 @@ class MsgDispatcher(MsgDispatcherBase):
- 'value': metric value reported by nni.report_final_result() - 'value': metric value reported by nni.report_final_result()
- 'type': report type, support {'FINAL', 'PERIODICAL'} - 'type': report type, support {'FINAL', 'PERIODICAL'}
""" """
if data['type'] == 'FINAL': if data['type'] == MetricType.FINAL:
self._handle_final_metric_data(data) self._handle_final_metric_data(data)
elif data['type'] == 'PERIODICAL': elif data['type'] == MetricType.PERIODICAL:
if self.assessor is not None: if self.assessor is not None:
self._handle_intermediate_metric_data(data) self._handle_intermediate_metric_data(data)
elif data['type'] == 'REQUEST_PARAMETER': elif data['type'] == MetricType.REQUEST_PARAMETER:
assert multi_phase_enabled() assert multi_phase_enabled()
assert data['trial_job_id'] is not None assert data['trial_job_id'] is not None
assert data['parameter_index'] is not None assert data['parameter_index'] is not None
...@@ -183,7 +184,7 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -183,7 +184,7 @@ class MsgDispatcher(MsgDispatcherBase):
def _handle_intermediate_metric_data(self, data): def _handle_intermediate_metric_data(self, data):
"""Call assessor to process intermediate results """Call assessor to process intermediate results
""" """
if data['type'] != 'PERIODICAL': if data['type'] != MetricType.PERIODICAL:
return return
if self.assessor is None: if self.assessor is None:
return return
...@@ -224,7 +225,7 @@ class MsgDispatcher(MsgDispatcherBase): ...@@ -224,7 +225,7 @@ class MsgDispatcher(MsgDispatcherBase):
trial is early stopped. trial is early stopped.
""" """
_logger.debug('Early stop notify tuner data: [%s]', data) _logger.debug('Early stop notify tuner data: [%s]', data)
data['type'] = 'FINAL' data['type'] = MetricType.FINAL
if multi_thread_enabled(): if multi_thread_enabled():
self._handle_final_metric_data(data) self._handle_final_metric_data(data)
else: else:
......
...@@ -51,6 +51,14 @@ class NodeType: ...@@ -51,6 +51,14 @@ class NodeType:
NAME = '_name' NAME = '_name'
class MetricType:
"""The types of metric data
"""
FINAL = 'FINAL'
PERIODICAL = 'PERIODICAL'
REQUEST_PARAMETER = 'REQUEST_PARAMETER'
def split_index(params): def split_index(params):
""" """
Delete index infromation from params Delete index infromation from params
......
.nni{ .nni{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #212121; color: #212121;
font-size: 14px; font-size: 14px;
background: #f2f2f2; background: #f2f2f2;
......
import * as React from 'react';
import axios from 'axios';
import { downFile } from '../../static/function';
import { Drawer, Tabs, Row, Col, Button } from 'antd';
import { MANAGER_IP, DRAWEROPTION } from '../../static/const';
import MonacoEditor from 'react-monaco-editor';
const { TabPane } = Tabs;
import '../../static/style/logDrawer.scss';
interface ExpDrawerProps {
isVisble: boolean;
closeExpDrawer: () => void;
}
interface ExpDrawerState {
experiment: string;
}
class ExperimentDrawer extends React.Component<ExpDrawerProps, ExpDrawerState> {
public _isCompareMount: boolean;
constructor(props: ExpDrawerProps) {
super(props);
this.state = {
experiment: ''
};
}
getExperimentContent = () => {
axios
.all([
axios.get(`${MANAGER_IP}/experiment`),
axios.get(`${MANAGER_IP}/trial-jobs`),
axios.get(`${MANAGER_IP}/metric-data`)
])
.then(axios.spread((res, res1, res2) => {
if (res.status === 200 && res1.status === 200 && res2.status === 200) {
if (res.data.params.searchSpace) {
res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace);
}
let trialMessagesArr = res1.data;
const interResultList = res2.data;
Object.keys(trialMessagesArr).map(item => {
// transform hyperparameters as object to show elegantly
trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters);
const trialId = trialMessagesArr[item].id;
// add intermediate result message
trialMessagesArr[item].intermediate = [];
Object.keys(interResultList).map(key => {
const interId = interResultList[key].trialJobId;
if (trialId === interId) {
trialMessagesArr[item].intermediate.push(interResultList[key]);
}
});
});
const result = {
experimentParameters: res.data,
trialMessage: trialMessagesArr
};
if (this._isCompareMount === true) {
this.setState(() => ({ experiment: JSON.stringify(result, null, 4) }));
}
}
}));
}
downExperimentParameters = () => {
const { experiment } = this.state;
downFile(experiment, 'experiment.json');
}
componentDidMount() {
this._isCompareMount = true;
this.getExperimentContent();
}
componentWillReceiveProps(nextProps: ExpDrawerProps) {
const { isVisble } = nextProps;
if (isVisble === true) {
this.getExperimentContent();
}
}
componentWillUnmount() {
this._isCompareMount = false;
}
render() {
const { isVisble, closeExpDrawer } = this.props;
const { experiment } = this.state;
const heights: number = window.innerHeight - 48;
return (
<Row className="logDrawer">
<Drawer
// title="Log Message"
placement="right"
closable={false}
destroyOnClose={true}
onClose={closeExpDrawer}
visible={isVisble}
width="54%"
height={heights}
>
<div className="card-container log-tab-body" style={{ height: heights }}>
<Tabs type="card">
<TabPane tab="Experiment Parameters" key="Experiment">
<div className="just-for-log">
<MonacoEditor
width="100%"
height={heights * 0.9}
language="json"
value={experiment}
options={DRAWEROPTION}
/>
</div>
<Row className="buttons">
<Col span={12}>
<Button
type="primary"
onClick={this.downExperimentParameters}
>
Download
</Button>
</Col>
<Col span={12} className="close">
<Button
type="default"
onClick={closeExpDrawer}
>
Close
</Button>
</Col>
</Row>
</TabPane>
</Tabs>
</div>
</Drawer>
</Row>
);
}
}
export default ExperimentDrawer;
import * as React from 'react';
import axios from 'axios';
import { Drawer, Tabs, Row, Col, Button, Icon } from 'antd';
import { DOWNLOAD_IP } from '../../static/const';
import { downFile } from '../../static/function';
const { TabPane } = Tabs;
import MonacoHTML from '../public-child/MonacoEditor';
import '../../static/style/logDrawer.scss';
interface LogDrawerProps {
isVisble: boolean;
closeDrawer: () => void;
activeTab?: string;
}
interface LogDrawerState {
nniManagerLogStr: string;
dispatcherLogStr: string;
isLoading: boolean;
isLoadispatcher: boolean;
}
class LogDrawer extends React.Component<LogDrawerProps, LogDrawerState> {
public _isLogDrawer: boolean;
constructor(props: LogDrawerProps) {
super(props);
this.state = {
nniManagerLogStr: 'nnimanager',
dispatcherLogStr: 'dispatcher',
isLoading: false,
isLoadispatcher: false
};
}
getNNImanagerLogmessage = () => {
if (this._isLogDrawer === true) {
this.setState({ isLoading: true }, () => {
axios(`${DOWNLOAD_IP}/nnimanager.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
setTimeout(() => { this.setNNImanager(res.data); }, 300);
}
});
});
}
}
setDispatcher = (value: string) => {
if (this._isLogDrawer === true) {
this.setState(() => ({ isLoadispatcher: false, dispatcherLogStr: value }));
}
}
setNNImanager = (val: string) => {
if (this._isLogDrawer === true) {
this.setState(() => ({ isLoading: false, nniManagerLogStr: val }));
}
}
getdispatcherLogmessage = () => {
if (this._isLogDrawer === true) {
this.setState({ isLoadispatcher: true }, () => {
axios(`${DOWNLOAD_IP}/dispatcher.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
setTimeout(() => { this.setDispatcher(res.data); }, 300);
}
});
});
}
}
downloadNNImanager = () => {
const { nniManagerLogStr } = this.state;
downFile(nniManagerLogStr, 'nnimanager.log');
}
downloadDispatcher = () => {
const { dispatcherLogStr } = this.state;
downFile(dispatcherLogStr, 'dispatcher.log');
}
dispatcherHTML = () => {
return (
<div>
<span>Dispatcher Log</span>
<span className="refresh" onClick={this.getdispatcherLogmessage}>
<Icon type="sync" />
</span>
</div>
);
}
nnimanagerHTML = () => {
return (
<div>
<span>NNImanager Log</span>
<span className="refresh" onClick={this.getNNImanagerLogmessage}><Icon type="sync" /></span>
</div>
);
}
componentDidMount() {
this._isLogDrawer = true;
this.getNNImanagerLogmessage();
this.getdispatcherLogmessage();
}
componentWillReceiveProps(nextProps: LogDrawerProps) {
const { isVisble, activeTab } = nextProps;
if (isVisble === true) {
if (activeTab === 'nnimanager') {
this.getNNImanagerLogmessage();
}
if (activeTab === 'dispatcher') {
this.getdispatcherLogmessage();
}
}
}
componentWillUnmount() {
this._isLogDrawer = false;
}
render() {
const { isVisble, closeDrawer, activeTab } = this.props;
const { nniManagerLogStr, dispatcherLogStr, isLoadispatcher, isLoading } = this.state;
const heights: number = window.innerHeight - 48; // padding top and bottom
return (
<Row>
<Drawer
placement="right"
closable={false}
destroyOnClose={true}
onClose={closeDrawer}
visible={isVisble}
width="76%"
height={heights}
// className="logDrawer"
>
<div className="card-container log-tab-body" style={{ height: heights }}>
<Tabs type="card" defaultActiveKey={activeTab}>
{/* <Tabs type="card" onTabClick={this.selectwhichLog} defaultActiveKey={activeTab}> */}
{/* <TabPane tab="Dispatcher Log" key="dispatcher"> */}
<TabPane tab={this.dispatcherHTML()} key="dispatcher">
<div>
<MonacoHTML content={dispatcherLogStr} loading={isLoadispatcher} />
</div>
<Row className="buttons">
<Col span={12}>
<Button
type="primary"
onClick={this.downloadDispatcher}
>
Download
</Button>
</Col>
<Col span={12} className="close">
<Button
type="default"
onClick={closeDrawer}
>
Close
</Button>
</Col>
</Row>
</TabPane>
<TabPane tab={this.nnimanagerHTML()} key="nnimanager">
{/* <TabPane tab="NNImanager Log" key="nnimanager"> */}
<div>
<MonacoHTML content={nniManagerLogStr} loading={isLoading} />
</div>
<Row className="buttons">
<Col span={12} className="download">
<Button
type="primary"
onClick={this.downloadNNImanager}
>
Download
</Button>
</Col>
<Col span={12} className="close">
<Button
type="default"
onClick={closeDrawer}
>
Close
</Button>
</Col>
</Row>
</TabPane>
</Tabs>
</div>
</Drawer>
</Row>
);
}
}
export default LogDrawer;
...@@ -3,10 +3,11 @@ import { Link } from 'react-router'; ...@@ -3,10 +3,11 @@ import { Link } from 'react-router';
import axios from 'axios'; import axios from 'axios';
import { MANAGER_IP } from '../static/const'; import { MANAGER_IP } from '../static/const';
import MediaQuery from 'react-responsive'; import MediaQuery from 'react-responsive';
import { DOWNLOAD_IP } from '../static/const';
import { Row, Col, Menu, Dropdown, Icon, Select, Button } from 'antd'; import { Row, Col, Menu, Dropdown, Icon, Select, Button } from 'antd';
const { SubMenu } = Menu; const { SubMenu } = Menu;
const { Option } = Select; const { Option } = Select;
import LogDrawer from './Modal/LogDrawer';
import ExperimentDrawer from './Modal/ExperimentDrawer';
import '../static/style/slideBar.scss'; import '../static/style/slideBar.scss';
import '../static/style/button.scss'; import '../static/style/button.scss';
...@@ -15,6 +16,9 @@ interface SliderState { ...@@ -15,6 +16,9 @@ interface SliderState {
menuVisible: boolean; menuVisible: boolean;
navBarVisible: boolean; navBarVisible: boolean;
isdisabledFresh: boolean; isdisabledFresh: boolean;
isvisibleLogDrawer: boolean;
isvisibleExperimentDrawer: boolean;
activeKey: string;
} }
interface SliderProps { interface SliderProps {
...@@ -38,124 +42,13 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -38,124 +42,13 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
version: '', version: '',
menuVisible: false, menuVisible: false,
navBarVisible: false, navBarVisible: false,
isdisabledFresh: false isdisabledFresh: false,
isvisibleLogDrawer: false, // download button (nnimanager·dispatcher) click -> drawer
isvisibleExperimentDrawer: false,
activeKey: 'dispatcher'
}; };
} }
downExperimentContent = () => {
axios
.all([
axios.get(`${MANAGER_IP}/experiment`),
axios.get(`${MANAGER_IP}/trial-jobs`),
axios.get(`${MANAGER_IP}/metric-data`)
])
.then(axios.spread((res, res1, res2) => {
if (res.status === 200 && res1.status === 200 && res2.status === 200) {
if (res.data.params.searchSpace) {
res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace);
}
const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
let trialMessagesArr = res1.data;
const interResultList = res2.data;
Object.keys(trialMessagesArr).map(item => {
// transform hyperparameters as object to show elegantly
trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters);
const trialId = trialMessagesArr[item].id;
// add intermediate result message
trialMessagesArr[item].intermediate = [];
Object.keys(interResultList).map(key => {
const interId = interResultList[key].trialJobId;
if (trialId === interId) {
trialMessagesArr[item].intermediate.push(interResultList[key]);
}
});
});
const result = {
experimentParameters: res.data,
trialMessage: trialMessagesArr
};
const aTag = document.createElement('a');
const file = new Blob([JSON.stringify(result, null, 4)], { type: 'application/json' });
aTag.download = 'experiment.json';
aTag.href = URL.createObjectURL(file);
aTag.click();
if (!isEdge) {
URL.revokeObjectURL(aTag.href);
}
if (navigator.userAgent.indexOf('Firefox') > -1) {
const downTag = document.createElement('a');
downTag.addEventListener('click', function () {
downTag.download = 'experiment.json';
downTag.href = URL.createObjectURL(file);
});
let eventMouse = document.createEvent('MouseEvents');
eventMouse.initEvent('click', false, false);
downTag.dispatchEvent(eventMouse);
}
}
}));
}
downnnimanagerLog = () => {
axios(`${DOWNLOAD_IP}/nnimanager.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
const nniLogfile = res.data;
const aTag = document.createElement('a');
const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
const file = new Blob([nniLogfile], { type: 'application/json' });
aTag.download = 'nnimanagerLog.log';
aTag.href = URL.createObjectURL(file);
aTag.click();
if (!isEdge) {
URL.revokeObjectURL(aTag.href);
}
if (navigator.userAgent.indexOf('Firefox') > -1) {
const downTag = document.createElement('a');
downTag.addEventListener('click', function () {
downTag.download = 'nnimanagerLog.log';
downTag.href = URL.createObjectURL(file);
});
let eventMouse = document.createEvent('MouseEvents');
eventMouse.initEvent('click', false, false);
downTag.dispatchEvent(eventMouse);
}
}
});
}
downDispatcherlog = () => {
axios(`${DOWNLOAD_IP}/dispatcher.log`, {
method: 'GET'
})
.then(res => {
if (res.status === 200) {
const dispatchLogfile = res.data;
const aTag = document.createElement('a');
const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
const file = new Blob([dispatchLogfile], { type: 'application/json' });
aTag.download = 'dispatcherLog.log';
aTag.href = URL.createObjectURL(file);
aTag.click();
if (!isEdge) {
URL.revokeObjectURL(aTag.href);
}
if (navigator.userAgent.indexOf('Firefox') > -1) {
const downTag = document.createElement('a');
downTag.addEventListener('click', function () {
downTag.download = 'dispatcherLog.log';
downTag.href = URL.createObjectURL(file);
});
let eventMouse = document.createEvent('MouseEvents');
eventMouse.initEvent('click', false, false);
downTag.dispatchEvent(eventMouse);
}
}
});
}
getNNIversion = () => { getNNIversion = () => {
axios(`${MANAGER_IP}/version`, { axios(`${MANAGER_IP}/version`, {
method: 'GET' method: 'GET'
...@@ -170,17 +63,23 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -170,17 +63,23 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
handleMenuClick = (e: EventPer) => { handleMenuClick = (e: EventPer) => {
if (this._isMounted) { this.setState({ menuVisible: false }); } if (this._isMounted) { this.setState({ menuVisible: false }); }
switch (e.key) { switch (e.key) {
// download experiment related content // to see & download experiment parameters
case '1': case '1':
this.downExperimentContent(); if (this._isMounted === true) {
this.setState(() => ({ isvisibleExperimentDrawer: true }));
}
break; break;
// download nnimanager log file // to see & download nnimanager log
case '2': case '2':
this.downnnimanagerLog(); if (this._isMounted === true) {
this.setState(() => ({ activeKey: 'nnimanager', isvisibleLogDrawer: true }));
}
break; break;
// download dispatcher log file // to see & download dispatcher log
case '3': case '3':
this.downDispatcherlog(); if (this._isMounted === true) {
this.setState(() => ({ isvisibleLogDrawer: true, activeKey: 'dispatcher' }));
}
break; break;
case 'close': case 'close':
case '10': case '10':
...@@ -285,6 +184,20 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -285,6 +184,20 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
} }
} }
// close log drawer (nnimanager.dispatcher)
closeLogDrawer = () => {
if (this._isMounted === true) {
this.setState(() => ({ isvisibleLogDrawer: false, activeKey: '' }));
}
}
// close download experiment parameters drawer
closeExpDrawer = () => {
if (this._isMounted === true) {
this.setState(() => ({ isvisibleExperimentDrawer: false }));
}
}
componentDidMount() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
this.getNNIversion(); this.getNNIversion();
...@@ -295,79 +208,91 @@ class SlideBar extends React.Component<SliderProps, SliderState> { ...@@ -295,79 +208,91 @@ class SlideBar extends React.Component<SliderProps, SliderState> {
} }
render() { render() {
const { version, menuVisible } = this.state; const { version, menuVisible, isvisibleLogDrawer, activeKey, isvisibleExperimentDrawer } = this.state;
const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`; const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`;
return ( return (
<Row> <Row>
<Col span={18}> <Row>
<MediaQuery query="(min-width: 1299px)"> <Col span={18}>
<Row className="nav"> <MediaQuery query="(min-width: 1299px)">
<ul className="link"> <Row className="nav">
<li className="logo"> <ul className="link">
<li className="logo">
<Link to={'/oview'}>
<img
src={require('../static/img/logo2.png')}
style={{ width: 88 }}
alt="NNI logo"
/>
</Link>
</li>
<li className="tab firstTab">
<Link to={'/oview'} activeClassName="high">
Overview
</Link>
</li>
<li className="tab">
<Link to={'/detail'} activeClassName="high">
Trials detail
</Link>
</li>
<li className="feedback">
<Dropdown
className="dropdown"
overlay={this.menu()}
onVisibleChange={this.handleVisibleChange}
visible={menuVisible}
trigger={['click']}
>
<a className="ant-dropdown-link" href="#">
Download <Icon type="down" />
</a>
</Dropdown>
<a href={feed} target="_blank">
<img
src={require('../static/img/icon/issue.png')}
alt="NNI github issue"
/>
Feedback
</a>
<span className="version">Version: {version}</span>
</li>
</ul>
</Row>
</MediaQuery>
</Col>
<Col span={18}>
<MediaQuery query="(max-width: 1299px)">
<Row className="little">
<Col span={1} className="menu">
<Dropdown overlay={this.navigationBar()} trigger={['click']}>
<Icon type="unordered-list" className="more" />
</Dropdown>
</Col>
<Col span={14} className="logo">
<Link to={'/oview'}> <Link to={'/oview'}>
<img <img
src={require('../static/img/logo2.png')} src={require('../static/img/logo2.png')}
style={{ width: 88 }} style={{ width: 80 }}
alt="NNI logo" alt="NNI logo"
/> />
</Link> </Link>
</li> </Col>
<li className="tab firstTab"> </Row>
<Link to={'/oview'} activeClassName="high"> </MediaQuery>
Overview </Col>
</Link> <Col span={3}> {this.select()} </Col>
</li> </Row>
<li className="tab"> {/* the drawer for dispatcher & nnimanager log message */}
<Link to={'/detail'} activeClassName="high"> <LogDrawer
Trials detail isVisble={isvisibleLogDrawer}
</Link> closeDrawer={this.closeLogDrawer}
</li> activeTab={activeKey}
<li className="feedback"> />
<Dropdown <ExperimentDrawer
className="dropdown" isVisble={isvisibleExperimentDrawer}
overlay={this.menu()} closeExpDrawer={this.closeExpDrawer}
onVisibleChange={this.handleVisibleChange} />
visible={menuVisible}
trigger={['click']}
>
<a className="ant-dropdown-link" href="#">
Download <Icon type="down" />
</a>
</Dropdown>
<a href={feed} target="_blank">
<img
src={require('../static/img/icon/issue.png')}
alt="NNI github issue"
/>
Feedback
</a>
<span className="version">Version: {version}</span>
</li>
</ul>
</Row>
</MediaQuery>
</Col>
<Col span={18}>
<MediaQuery query="(max-width: 1299px)">
<Row className="little">
<Col span={1} className="menu">
<Dropdown overlay={this.navigationBar()} trigger={['click']}>
<Icon type="unordered-list" className="more" />
</Dropdown>
</Col>
<Col span={14} className="logo">
<Link to={'/oview'}>
<img
src={require('../static/img/logo2.png')}
style={{ width: 80 }}
alt="NNI logo"
/>
</Link>
</Col>
</Row>
</MediaQuery>
</Col>
<Col span={3}> {this.select()} </Col>
</Row> </Row>
); );
} }
......
...@@ -7,6 +7,7 @@ import { MANAGER_IP, CONTROLTYPE } from '../../static/const'; ...@@ -7,6 +7,7 @@ import { MANAGER_IP, CONTROLTYPE } from '../../static/const';
import { Experiment, TrialNumber } from '../../static/interface'; import { Experiment, TrialNumber } from '../../static/interface';
import { convertTime } from '../../static/function'; import { convertTime } from '../../static/function';
import ProgressBar from './ProgressItem'; import ProgressBar from './ProgressItem';
import LogDrawer from '../Modal/LogDrawer';
import '../../static/style/progress.scss'; import '../../static/style/progress.scss';
import '../../static/style/probar.scss'; import '../../static/style/probar.scss';
...@@ -24,6 +25,7 @@ interface ProgressState { ...@@ -24,6 +25,7 @@ interface ProgressState {
isEnable: boolean; isEnable: boolean;
userInputVal: string; // get user input userInputVal: string; // get user input
cancelSty: string; cancelSty: string;
isShowLogDrawer: boolean;
} }
class Progressed extends React.Component<ProgressProps, ProgressState> { class Progressed extends React.Component<ProgressProps, ProgressState> {
...@@ -36,7 +38,8 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -36,7 +38,8 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
btnName: 'Edit', btnName: 'Edit',
isEnable: true, isEnable: true,
userInputVal: this.props.trialProfile.runConcurren.toString(), userInputVal: this.props.trialProfile.runConcurren.toString(),
cancelSty: 'none' cancelSty: 'none',
isShowLogDrawer: false
}; };
} }
...@@ -139,6 +142,18 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -139,6 +142,18 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
} }
} }
isShowDrawer = () => {
if (this._isMounted === true) {
this.setState(() => ({ isShowLogDrawer: true }));
}
}
closeDrawer = () => {
if (this._isMounted === true) {
this.setState(() => ({ isShowLogDrawer: false }));
}
}
componentWillReceiveProps() { componentWillReceiveProps() {
const { trialProfile } = this.props; const { trialProfile } = this.props;
if (this.conInput !== null) { if (this.conInput !== null) {
...@@ -156,7 +171,7 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -156,7 +171,7 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
render() { render() {
const { trialProfile, trialNumber, bestAccuracy, status, errors } = this.props; const { trialProfile, trialNumber, bestAccuracy, status, errors } = this.props;
const { isEnable, btnName, cancelSty } = this.state; const { isEnable, btnName, cancelSty, isShowLogDrawer } = this.state;
const bar2 = trialNumber.totalCurrentTrial - trialNumber.waitTrial - trialNumber.unknowTrial; const bar2 = trialNumber.totalCurrentTrial - trialNumber.waitTrial - trialNumber.unknowTrial;
const bar2Percent = (bar2 / trialProfile.MaxTrialNum) * 100; const bar2Percent = (bar2 / trialProfile.MaxTrialNum) * 100;
const percent = (trialProfile.execDuration / trialProfile.maxDuration) * 100; const percent = (trialProfile.execDuration / trialProfile.maxDuration) * 100;
...@@ -173,6 +188,7 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -173,6 +188,7 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
errorContent = ( errorContent = (
<div className="errors"> <div className="errors">
{errors} {errors}
<div><a href="#" onClick={this.isShowDrawer}>Learn about</a></div>
</div> </div>
); );
} }
...@@ -282,8 +298,13 @@ class Progressed extends React.Component<ProgressProps, ProgressState> { ...@@ -282,8 +298,13 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
<div>{trialNumber.failTrial}</div> <div>{trialNumber.failTrial}</div>
</Row> </Row>
</Col> </Col>
</Row> </Row>
{/* learn about click -> default active key is dispatcher. */}
<LogDrawer
isVisble={isShowLogDrawer}
closeDrawer={this.closeDrawer}
activeTab="dispatcher"
/>
</Row> </Row>
); );
} }
......
import * as React from 'react';
import { Spin } from 'antd';
import { DRAWEROPTION } from '../../static/const';
import MonacoEditor from 'react-monaco-editor';
interface MonacoEditorProps {
content: string;
loading: boolean;
}
class MonacoHTML extends React.Component<MonacoEditorProps, {}> {
public _isMonacoMount: boolean;
constructor(props: MonacoEditorProps) {
super(props);
}
componentDidMount() {
this._isMonacoMount = true;
}
componentWillUnmount() {
this._isMonacoMount = false;
}
render() {
const { content, loading } = this.props;
const heights: number = window.innerHeight - 48;
return (
<div className="just-for-log">
<Spin
// tip="Loading..."
style={{ width: '100%', height: heights * 0.9 }}
spinning={loading}
>
<MonacoEditor
width="100%"
height={heights * 0.9}
language="json"
value={content}
options={DRAWEROPTION}
/>
</Spin>
</div>
);
}
}
export default MonacoHTML;
...@@ -19,6 +19,11 @@ const MONACO = { ...@@ -19,6 +19,11 @@ const MONACO = {
readOnly: true, readOnly: true,
automaticLayout: true automaticLayout: true
}; };
const DRAWEROPTION = {
minimap: { enabled: false },
readOnly: true,
automaticLayout: true
};
const COLUMN_INDEX = [ const COLUMN_INDEX = [
{ {
name: 'Trial No.', name: 'Trial No.',
...@@ -52,5 +57,5 @@ const COLUMN_INDEX = [ ...@@ -52,5 +57,5 @@ const COLUMN_INDEX = [
const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', 'Operation', 'Intermediate result']; const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', 'Operation', 'Intermediate result'];
export { export {
MANAGER_IP, DOWNLOAD_IP, trialJobStatus, MANAGER_IP, DOWNLOAD_IP, trialJobStatus,
CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX, DRAWEROPTION
}; };
...@@ -138,7 +138,29 @@ const filterDuration = (item: TableObj) => { ...@@ -138,7 +138,29 @@ const filterDuration = (item: TableObj) => {
return item.status !== 'WAITING'; return item.status !== 'WAITING';
}; };
const downFile = (content: string, fileName: string) => {
const aTag = document.createElement('a');
const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
const file = new Blob([content], { type: 'application/json' });
aTag.download = fileName;
aTag.href = URL.createObjectURL(file);
aTag.click();
if (!isEdge) {
URL.revokeObjectURL(aTag.href);
}
if (navigator.userAgent.indexOf('Firefox') > -1) {
const downTag = document.createElement('a');
downTag.addEventListener('click', function () {
downTag.download = fileName;
downTag.href = URL.createObjectURL(file);
});
let eventMouse = document.createEvent('MouseEvents');
eventMouse.initEvent('click', false, false);
downTag.dispatchEvent(eventMouse);
}
};
export { export {
convertTime, convertDuration, getFinalResult, getFinal, convertTime, convertDuration, getFinalResult, getFinal, downFile,
intermediateGraphOption, killJob, filterByStatus, filterDuration intermediateGraphOption, killJob, filterByStatus, filterDuration
}; };
$btnBgcolor: #0071bc; $btnBgcolor: #0071bc;
Button.tableButton{ Button.tableButton{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: $btnBgcolor; background: $btnBgcolor;
border-color: $btnBgcolor; border-color: $btnBgcolor;
height: 26px; height: 26px;
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.column{ .column{
max-width: 124px; max-width: 124px;
padding-left: 18px; padding-left: 18px;
font-weight: 700; font-weight: 600;
} }
.value{ .value{
max-width: 152px; max-width: 152px;
...@@ -20,6 +20,6 @@ ...@@ -20,6 +20,6 @@
text-align: left; text-align: left;
} }
.idList{ .idList{
font-weight: 700; font-weight: 600;
} }
} }
...@@ -55,7 +55,7 @@ div.addtitle{ ...@@ -55,7 +55,7 @@ div.addtitle{
font-size: 20px; font-size: 20px;
} }
.line{ .line{
font-weight: bold; font-weight: 600;
color: rgb(60,141,188); color: rgb(60,141,188);
padding-right: 20px; padding-right: 20px;
} }
......
.card-container > .ant-tabs-card > .ant-tabs-content {
margin-top: -16px;
}
.card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane {
background: #fff;
padding: 16px;
}
.card-container > .ant-tabs-card > .ant-tabs-bar {
border-color: #fff;
}
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
border-color: transparent;
background: transparent;
}
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
border-color: #fff;
background: #fff;
}
.logContainer{
height: 100%;
}
.buttons{
margin-top: 15px;
.close{
text-align: right;
}
}
/*
.logDrawer{
width: 100%;
.ant-drawer-body{
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
}
*/
.ant-drawer-body{
background: #333;
}
.card-container > .ant-tabs-card > .ant-tabs-bar{
border: none;
}
.card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane{
background-color: #333;
}
.close{
Button, Button:active, Button:hover{
background-color: #212121;
color: #fff;
border: none;
}
}
.download{
Button, Button:active, Button:hover{
background-color: #2772be;
color: #fff;
border: none;
}
}
.log-tab-body > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active{
background-color: #1e1e1e;
color: #fff;
border: none;
}
.log-tab-body .ant-tabs-nav .ant-tabs-tab:hover, .log-tab-body .ant-tabs-nav .ant-tabs-tab{
color: #fff;
border: none;
}
.ant-tabs.ant-tabs-card>.ant-tabs-bar .ant-tabs-tab{
border: none;
}
.log-tab-body{
.refresh{
margin-left: 10px;
display: none;
}
.ant-tabs-tab-active{
.refresh{
transition: 0.3s;
display: inline-block;
}
.refresh:hover{
transform: scale(1.2);
}
}
}
.just-for-log{
.monaco-editor{
.line-numbers{
color: #fff;
}
.current-line ~ .line-numbers{
color: #FFFAF0;
}
}
.view-lines{
background-color: #1e1e1e;
.mtk1{
color: #fff;
}
}
.margin-view-overlays{
background-color: #1e1e1e;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment