Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Support nnictl tensorboard #268

Merged
merged 69 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
dc780cd
Merge pull request #1 from Microsoft/master
SparkSnail Sep 14, 2018
86243e7
Merge pull request #2 from Microsoft/master
SparkSnail Sep 14, 2018
3d1e4e9
fix nnictl bug
Sep 14, 2018
6d09780
Merge pull request #4 from Microsoft/master
SparkSnail Sep 17, 2018
0d24158
Merge branch 'master' of https://github.com/SparkSnail/nni
Sep 18, 2018
6d669c6
Merge pull request #6 from Microsoft/master
SparkSnail Sep 19, 2018
af2615d
Merge pull request #8 from Microsoft/master
SparkSnail Sep 20, 2018
f6b7c0a
Merge pull request #9 from Microsoft/master
SparkSnail Sep 24, 2018
a74febc
Merge pull request #10 from Microsoft/master
SparkSnail Sep 25, 2018
334b0a4
Merge pull request #12 from Microsoft/master
SparkSnail Sep 27, 2018
efe93df
Merge pull request #13 from Microsoft/master
SparkSnail Sep 27, 2018
0d9b074
Merge branch 'master' of https://github.com/SparkSnail/nni
Sep 28, 2018
421ad1a
Merge pull request #16 from Microsoft/master
SparkSnail Sep 30, 2018
660a8f8
Merge branch 'master' of https://github.com/SparkSnail/nni
Sep 30, 2018
2b01089
fix install.sh
Sep 30, 2018
951e80e
Merge pull request #17 from Microsoft/master
SparkSnail Oct 1, 2018
90fe674
Merge pull request #18 from Microsoft/master
SparkSnail Oct 7, 2018
2ccf0ed
Merge pull request #19 from Microsoft/master
SparkSnail Oct 8, 2018
77aacee
Merge pull request #20 from Microsoft/master
SparkSnail Oct 8, 2018
9e23dfe
Merge pull request #22 from Microsoft/master
SparkSnail Oct 8, 2018
ca7bbe4
Merge pull request #24 from Microsoft/master
SparkSnail Oct 10, 2018
346badd
add desc for Dockerfile.build.base
Oct 10, 2018
4af27d6
Merge pull request #27 from Microsoft/master
SparkSnail Oct 11, 2018
46a8350
update document for Dockerfile
Oct 11, 2018
4e3697f
Merge pull request #29 from Microsoft/master
SparkSnail Oct 12, 2018
4cd95aa
Merge pull request #30 from Microsoft/master
SparkSnail Oct 15, 2018
405ce45
Merge pull request #31 from Microsoft/master
SparkSnail Oct 15, 2018
c3949e6
Merge pull request #32 from Microsoft/master
SparkSnail Oct 16, 2018
22c78fd
Merge pull request #33 from Microsoft/master
SparkSnail Oct 16, 2018
a870817
update
Oct 16, 2018
b45268c
refactor port detect
Oct 16, 2018
59626ec
update
Oct 16, 2018
31ea28b
Merge pull request #34 from Microsoft/master
SparkSnail Oct 16, 2018
2ca84c5
refactor NNICTLDOC.md
Oct 17, 2018
ab02c93
add document for pai and nnictl
Oct 17, 2018
5ff7b45
add default value for port
Oct 17, 2018
c1e835d
Merge pull request #35 from Microsoft/master
SparkSnail Oct 18, 2018
5ae146d
add exception handling in trial_keeper.py
Oct 18, 2018
1dde461
fix port bug
Oct 18, 2018
fe6a188
Merge pull request #36 from Microsoft/master
SparkSnail Oct 18, 2018
f7a5228
Merge pull request #37 from Microsoft/master
SparkSnail Oct 19, 2018
9fdf6d4
fix resume
Oct 19, 2018
c1285f8
fix nnictl resume and fix nnictl stop
Oct 19, 2018
af0d081
fix document
Oct 19, 2018
7ce8fd8
update
Oct 19, 2018
b29aaed
refactor nnictl
Oct 19, 2018
683833b
update
Oct 19, 2018
6149bf9
update doc
Oct 22, 2018
73bef2f
update
Oct 22, 2018
5c397f6
update nnictl
Oct 23, 2018
2c68171
fix comment
Oct 23, 2018
d0659d6
fix conflict
Oct 23, 2018
e64abbf
Merge branch 'Microsoft-master'
Oct 23, 2018
a70f7e7
Merge pull request #39 from Microsoft/master
SparkSnail Oct 23, 2018
8d14ca9
revert dockerfile
Oct 23, 2018
aeb7c66
update
Oct 23, 2018
a256549
update
Oct 23, 2018
3fb0bca
update
Oct 23, 2018
e116af4
Merge pull request #40 from Microsoft/master
SparkSnail Oct 23, 2018
d8dde9c
update
Oct 23, 2018
7b39064
add nnictl tensorboard
Oct 24, 2018
f4edebc
Merge pull request #41 from Microsoft/master
SparkSnail Oct 24, 2018
0f49936
Merge branch 'master' of https://github.com/SparkSnail/nni into dev-n…
Oct 24, 2018
d6e5009
update
Oct 24, 2018
3eeef25
add remote mode
Oct 25, 2018
46aa1a5
add tips
Oct 25, 2018
0993826
update tensorboard
Oct 26, 2018
41cc147
update
Oct 26, 2018
aa1a705
update
Oct 26, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/NNICTLDOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,48 @@ nnictl webui

Options:

| Name, shorthand | Required|Default | Description |
| ------ | ------ | ------ |------ |
| id| False| |ID of the experiment you want to set|


### Manage tensorboard
* __nnictl tensorboard start__
* Description

Start the tensorboard process.

* Usage

nnictl tensorboard start

Options:

| Name, shorthand | Required|Default | Description |
| ------ | ------ | ------ |------ |
| id| False| |ID of the experiment you want to set|
| --trialid| False| |ID of the trial|
| --port| False| 6006|The port of the tensorboard process|

* Detail

1. NNICTL support tensorboard function in local and remote platform for the moment, other platforms will be supported later.
2. If you want to use tensorboard, you need to write your tensorboard log data to environment variable [NNI_OUTPUT_DIR] path.
3. In local mode, nnictl will set --logdir=[NNI_OUTPUT_DIR] directly and start a tensorboard process.
4. In remote mode, nnictl will create a ssh client to copy log data from remote machine to local temp directory firstly, and then start a tensorboard process in your local machine. You need to notice that nnictl only copy the log data one time when you use the command, if you want to see the later result of tensorboard, you should execute nnictl tensorboard command again.
5. If there is only one trial job, you don't need to set trialid. If there are multiple trial jobs running, you should set the trialid, or you could use [nnictl tensorboard start --trialid all] to map --logdir to all trial log paths.

* __nnictl tensorboard stop__
* Description

Stop all of the tensorboard process.

* Usage

nnictl tensorboard stop

Options:

| Name, shorthand | Required|Default | Description |
| ------ | ------ | ------ |------ |
| id| False| |ID of the experiment you want to set|
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def run(self):
'requests',
'scipy',
'schema',
'pyhdfs'
'pyhdfs',
'paramiko'
],

cmdclass={
Expand Down
6 changes: 3 additions & 3 deletions tools/nnicmd/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@
from nni_annotation import *
from .launcher_utils import validate_all_content
from .rest_utils import rest_put, rest_post, check_rest_server, check_rest_server_quick, check_response
from .url_utils import cluster_metadata_url, experiment_url
from .url_utils import cluster_metadata_url, experiment_url, get_local_urls
from .config_utils import Config, Experiments
from .common_utils import get_yml_content, get_json_content, print_error, print_normal, print_warning, detect_process, detect_port
from .constants import *
from .webui_utils import *
import time
import random
import string
Expand Down Expand Up @@ -288,7 +287,8 @@ def launch_experiment(args, experiment_config, mode, config_file_name, experimen
except Exception:
raise Exception(ERROR_INFO % 'Restful server stopped!')
exit(1)
web_ui_url_list = get_web_ui_urls(args.port, config_file_name)
web_ui_url_list = get_local_urls(args.port)
nni_config.set_config('webuiUrl', web_ui_url_list)

#save experiment information
experiment_config = Experiments()
Expand Down
13 changes: 13 additions & 0 deletions tools/nnicmd/nnictl.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .nnictl_utils import *
from .package_management import *
from .constants import *
from .tensorboard_utils import *

def nni_help_info(*args):
print('please run "nnictl {positional argument} --help" to see nnictl guidance')
Expand Down Expand Up @@ -148,6 +149,18 @@ def parse_args():
parser_package_show = parser_package_subparsers.add_parser('show', help='show the information of packages')
parser_package_show.set_defaults(func=package_show)

#parse tensorboard command
parser_tensorboard = subparsers.add_parser('tensorboard', help='manage tensorboard')
parser_tensorboard_subparsers = parser_tensorboard.add_subparsers()
parser_tensorboard_start = parser_tensorboard_subparsers.add_parser('start', help='start tensorboard')
parser_tensorboard_start.add_argument('id', nargs='?', help='the id of experiment')
parser_tensorboard_start.add_argument('--trialid', dest='trialid', help='the id of trial')
parser_tensorboard_start.add_argument('--port', dest='port', default=6006, help='the port to start tensorboard')
parser_tensorboard_start.set_defaults(func=start_tensorboard)
parser_tensorboard_start = parser_tensorboard_subparsers.add_parser('stop', help='stop tensorboard')
parser_tensorboard_start.add_argument('id', nargs='?', help='the id of experiment')
parser_tensorboard_start.set_defaults(func=stop_tensorboard)

args = parser.parse_args()
args.func(args)

Expand Down
14 changes: 12 additions & 2 deletions tools/nnicmd/nnictl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,17 @@ def stop_experiment(args):
time.sleep(3)
rest_pid = nni_config.get_config('restServerPid')
if rest_pid:
cmds = ['pkill', '-P', str(rest_pid)]
call(cmds)
stop_rest_cmds = ['pkill', '-P', str(rest_pid)]
call(stop_rest_cmds)
tensorboard_pid_list = nni_config.get_config('tensorboardPidList')
if tensorboard_pid_list:
for tensorboard_pid in tensorboard_pid_list:
try:
cmds = ['kill', '-9', str(tensorboard_pid)]
call(cmds)
except Exception as exception:
print_error(exception)
nni_config.set_config('tensorboardPidList', [])
if stop_rest_result:
print_normal('Stop experiment success!')
experiment_config.update_experiment(experiment_id, 'status', 'stopped')
Expand Down Expand Up @@ -343,3 +352,4 @@ def experiment_list(args):
experiment_information += (EXPERIMENT_DETAIL_FORMAT % (key, experiment_dict[key]['status'], \
experiment_dict[key]['startTime'], experiment_dict[key]['endTime']))
print(EXPERIMENT_INFORMATION_FORMAT % experiment_information)

40 changes: 28 additions & 12 deletions tools/nnicmd/webui_utils.py → tools/nnicmd/ssh_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,32 @@
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import psutil
from socket import AddressFamily
from .config_utils import Config
import paramiko
import os
from .common_utils import print_error

def get_web_ui_urls(port, CONFIG_FILE_NAME):
webui_url_list = []
for name, info in psutil.net_if_addrs().items():
for addr in info:
if AddressFamily.AF_INET == addr.family:
webui_url_list.append('http://{}:{}'.format(addr.address, port))
nni_config = Config(CONFIG_FILE_NAME)
nni_config.set_config('webuiUrl', webui_url_list)
return webui_url_list
def copy_remote_directory_to_local(sftp, remote_path, local_path):
'''copy remote directory to local machine'''
try:
os.makedirs(local_path, exist_ok=True)
files = sftp.listdir(remote_path)
for file in files:
remote_full_path = os.path.join(remote_path, file)
local_full_path = os.path.join(local_path, file)
try:
if sftp.listdir(remote_full_path):
copy_remote_directory_to_local(sftp, remote_full_path, local_full_path)
except:
sftp.get(remote_full_path, local_full_path)
except Exception:
pass

def create_ssh_sftp_client(host_ip, port, username, password):
'''create ssh client'''
try:
conn = paramiko.Transport(host_ip, port)
conn.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(conn)
return sftp
except Exception as exception:
print_error('Create ssh client error %s\n' % exception)
165 changes: 165 additions & 0 deletions tools/nnicmd/tensorboard_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright (c) Microsoft Corporation
# All rights reserved.
#
# MIT License
#
# Permission is hereby granted, free of charge,
# to any person obtaining a copy of this software and associated
# documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and
# to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os
import psutil
import json
import datetime
import time
from subprocess import call, check_output, Popen, PIPE
from .rest_utils import rest_get, rest_delete, check_rest_server_quick, check_response
from .config_utils import Config, Experiments
from .url_utils import trial_jobs_url, experiment_url, trial_job_id_url, get_local_urls
from .constants import NNICTL_HOME_DIR, EXPERIMENT_INFORMATION_FORMAT, EXPERIMENT_DETAIL_FORMAT, COLOR_GREEN_FORMAT
import time
from .common_utils import print_normal, print_error, print_warning, detect_process, detect_port
from .nnictl_utils import *
import re
from .ssh_utils import create_ssh_sftp_client, copy_remote_directory_to_local
import tempfile

def parse_log_path(args, trial_content):
'''parse log path'''
path_list = []
host_list = []
for trial in trial_content:
if args.trialid and args.trialid != 'all' and trial.get('id') != args.trialid:
continue
pattern = r'(?P<head>.+)://(?P<host>.+):(?P<path>.*)'
match = re.search(pattern,trial['logPath'])
if match:
path_list.append(match.group('path'))
host_list.append(match.group('host'))
if not path_list:
print_error('Trial id %s error!' % args.trialid)
exit(1)
return path_list, host_list

def copy_data_from_remote(args, nni_config, trial_content, path_list, host_list, temp_nni_path):
'''use ssh client to copy data from remote machine to local machien'''
machine_list = nni_config.get_config('experimentConfig').get('machineList')
machine_dict = {}
local_path_list = []
for machine in machine_list:
machine_dict[machine['ip']] = {'port': machine['port'], 'passwd': machine['passwd'], 'username': machine['username']}
for index, host in enumerate(host_list):
local_path = os.path.join(temp_nni_path, trial_content[index].get('id'))
local_path_list.append(local_path)
print_normal('Copying log data from %s to %s' % (host + ':' + path_list[index], local_path))
sftp = create_ssh_sftp_client(host, machine_dict[host]['port'], machine_dict[host]['username'], machine_dict[host]['passwd'])
copy_remote_directory_to_local(sftp, path_list[index], local_path)
print_normal('Copy done!')
return local_path_list

def get_path_list(args, nni_config, trial_content, temp_nni_path):
'''get path list according to different platform'''
path_list, host_list = parse_log_path(args, trial_content)
platform = nni_config.get_config('experimentConfig').get('trainingServicePlatform')
if platform == 'local':
print_normal('Log path: %s' % ' '.join(path_list))
return path_list
elif platform == 'remote':
path_list = copy_data_from_remote(args, nni_config, trial_content, path_list, host_list, temp_nni_path)
print_normal('Log path: %s' % ' '.join(path_list))
return path_list
else:
print_error('Not supported platform!')
exit(1)

def format_tensorboard_log_path(path_list):
new_path_list = []
for index, value in enumerate(path_list):
new_path_list.append('name%d:%s' % (index + 1, value))
return ','.join(new_path_list)

def start_tensorboard_process(args, nni_config, path_list, temp_nni_path):
'''call cmds to start tensorboard process in local machine'''
if detect_port(args.port):
print_error('Port %s is used by another process, please reset port!' % str(args.port))
exit(1)

stdout_file = open(os.path.join(temp_nni_path, 'tensorboard_stdout'), 'a+')
stderr_file = open(os.path.join(temp_nni_path, 'tensorboard_stderr'), 'a+')
cmds = ['tensorboard', '--logdir', format_tensorboard_log_path(path_list), '--port', str(args.port)]
tensorboard_process = Popen(cmds, stdout=stdout_file, stderr=stderr_file)
url_list = get_local_urls(args.port)
print_normal(COLOR_GREEN_FORMAT % 'Start tensorboard success!\n' + 'Tensorboard urls: ' + ' '.join(url_list))
tensorboard_process_pid_list = nni_config.get_config('tensorboardPidList')
if tensorboard_process_pid_list is None:
tensorboard_process_pid_list = [tensorboard_process.pid]
else:
tensorboard_process_pid_list.append(tensorboard_process.pid)
nni_config.set_config('tensorboardPidList', tensorboard_process_pid_list)

def stop_tensorboard(args):
'''stop tensorboard'''
experiment_id = check_experiment_id(args)
experiment_config = Experiments()
experiment_dict = experiment_config.get_all_experiments()
config_file_name = experiment_dict[experiment_id]['fileName']
nni_config = Config(config_file_name)
tensorboard_pid_list = nni_config.get_config('tensorboardPidList')
if tensorboard_pid_list:
for tensorboard_pid in tensorboard_pid_list:
try:
cmds = ['kill', '-9', str(tensorboard_pid)]
call(cmds)
except Exception as exception:
print_error(exception)
nni_config.set_config('tensorboardPidList', [])
print_normal('Stop tensorboard success!')
else:
print_error('No tensorboard configuration!')


def start_tensorboard(args):
'''start tensorboard'''
experiment_id = check_experiment_id(args)
experiment_config = Experiments()
experiment_dict = experiment_config.get_all_experiments()
config_file_name = experiment_dict[experiment_id]['fileName']
nni_config = Config(config_file_name)
rest_port = nni_config.get_config('restServerPort')
rest_pid = nni_config.get_config('restServerPid')
if not detect_process(rest_pid):
print_error('Experiment is not running...')
return
running, response = check_rest_server_quick(rest_port)
trial_content = None
if running:
response = rest_get(trial_jobs_url(rest_port), 20)
if response and check_response(response):
trial_content = json.loads(response.text)
else:
print_error('List trial failed...')
else:
print_error('Restful server is not running...')
if not trial_content:
print_error('No trial information!')
exit(1)
if len(trial_content) > 1 and not args.trialid:
print_error('There are multiple trials, please set trial id!')
exit(1)
experiment_id = nni_config.get_config('experimentId')
temp_nni_path = os.path.join(tempfile.gettempdir(), 'nni', experiment_id)
os.makedirs(temp_nni_path, exist_ok=True)

path_list = get_path_list(args, nni_config, trial_content, temp_nni_path)
start_tensorboard_process(args, nni_config, path_list, temp_nni_path)
13 changes: 13 additions & 0 deletions tools/nnicmd/url_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import psutil
from socket import AddressFamily

BASE_URL = 'http://localhost'

Expand Down Expand Up @@ -53,6 +55,7 @@ def trial_jobs_url(port):
'''get trial_jobs url'''
return '{0}:{1}{2}{3}'.format(BASE_URL, port, API_ROOT_URL, TRIAL_JOBS_API)


def trial_job_id_url(port, job_id):
'''get trial_jobs with id url'''
return '{0}:{1}{2}{3}/:{4}'.format(BASE_URL, port, API_ROOT_URL, TRIAL_JOBS_API, job_id)
Expand All @@ -61,3 +64,13 @@ def trial_job_id_url(port, job_id):
def tensorboard_url(port):
'''get tensorboard url'''
return '{0}:{1}{2}{3}'.format(BASE_URL, port, API_ROOT_URL, TENSORBOARD_API)


def get_local_urls(port):
'''get urls of local machine'''
url_list = []
for name, info in psutil.net_if_addrs().items():
for addr in info:
if AddressFamily.AF_INET == addr.family:
url_list.append('http://{}:{}'.format(addr.address, port))
return url_list
3 changes: 2 additions & 1 deletion tools/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
'psutil',
'astor',
'schema',
'pyhdfs'
'pyhdfs',
'paramiko'
],

author = 'Microsoft NNI Team',
Expand Down