Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Collect CPU, GPU and memory resource metrics.
* Automatically delete temporary files used in offline mode once runs have entered a terminal state.
* Warn users if their access token has expired.
* Remove dependency on the randomname module, instead handle name generation server side.

## v0.6.0

Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
setuptools.setup(
name="simvue",
version=version,
author="Andrew Lahiff",
author_email="andrew.lahiff@ukaea.uk",
author_email="info@simvue.io",
description="Simulation tracking and monitoring",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/simvue-io/client",
platforms=["any"],
install_requires=["requests", "randomname", "msgpack", "tenacity", "pyjwt", "psutil"],
install_requires=["requests", "msgpack", "tenacity", "pyjwt", "psutil"],
package_dir={'': '.'},
packages=["simvue"],
package_data={"": ["README.md"]},
Expand Down
2 changes: 1 addition & 1 deletion simvue/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from simvue.run import Run
from simvue.client import Client
from simvue.handler import Handler
__version__ = '0.0.1'
__version__ = '0.0.7'
10 changes: 7 additions & 3 deletions simvue/offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
import os
import time

from .utilities import get_offline_directory, get_directory_name, create_file
from .utilities import get_offline_directory, create_file

logger = logging.getLogger(__name__)

class Offline(object):
"""
Class for offline runs
"""
def __init__(self, name, suppress_errors=False):
def __init__(self, name, uuid, suppress_errors=False):
self._name = name
self._directory = os.path.join(get_offline_directory(), get_directory_name(name))
self._uuid = uuid
self._directory = os.path.join(get_offline_directory(), self._uuid)
self._suppress_errors = suppress_errors

def _error(self, message):
Expand Down Expand Up @@ -45,6 +46,9 @@ def create_run(self, data):
logger.error('Unable to create directory %s due to: %s', self._directory, str(err))

filename = f"{self._directory}/run.json"
if 'name' not in data:
data['name'] = None

self._write_json(filename, data)

status = data['status']
Expand Down
8 changes: 6 additions & 2 deletions simvue/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class Remote(object):
"""
Class which interacts with Simvue REST API
"""
def __init__(self, name, suppress_errors=False):
def __init__(self, name, uuid, suppress_errors=False):
self._name = name
self._uuid = uuid
self._suppress_errors = suppress_errors
self._url, self._token = get_auth()
self._headers = {"Authorization": f"Bearer {self._token}"}
Expand Down Expand Up @@ -49,7 +50,10 @@ def create_run(self, data):
self._error(f"Got status code {response.status_code} when creating run")
return False

return True
if 'name' in response.json():
self._name = response.json()['name']

return self._name

def update(self, data):
"""
Expand Down
60 changes: 37 additions & 23 deletions simvue/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import sys
import time as tm
import platform
import uuid
import requests
import randomname

from .worker import Worker
from .simvue import Simvue
Expand Down Expand Up @@ -127,6 +127,7 @@ class Run(object):
Track simulation details based on token and URL
"""
def __init__(self, mode='online'):
self._uuid = str(uuid.uuid4())
self._mode = mode
self._name = None
self._suppress_errors = False
Expand Down Expand Up @@ -186,6 +187,7 @@ def _start(self, reconnect=False):
self._events_queue = multiprocessing.Manager().Queue(maxsize=self._queue_size)
self._worker = Worker(self._metrics_queue,
self._events_queue,
self._uuid,
self._name,
self._url,
self._headers,
Expand Down Expand Up @@ -217,14 +219,12 @@ def init(self, name=None, metadata={}, tags=[], description=None, folder='/', ru
if self._mode == 'disabled':
return True

if not name:
name = randomname.get_name()

if not self._token or not self._url:
self._error('Unable to get URL and token from environment variables or config file')

if not re.match(r'^[a-zA-Z0-9\-\_\s\/\.:]+$', name):
self._error('specified name is invalid')
if name:
if not re.match(r'^[a-zA-Z0-9\-\_\s\/\.:]+$', name):
self._error('specified name is invalid')

if not isinstance(tags, list):
self._error('tags must be a list')
Expand All @@ -239,14 +239,16 @@ def init(self, name=None, metadata={}, tags=[], description=None, folder='/', ru
else:
self._status = 'created'

data = {'name': name,
'metadata': metadata,
data = {'metadata': metadata,
'tags': tags,
'system': {'cpu': {},
'gpu': {},
'platform': {}},
'status': self._status}

if name:
data['name'] = name

if description:
data['description'] = description

Expand All @@ -260,13 +262,16 @@ def init(self, name=None, metadata={}, tags=[], description=None, folder='/', ru

self._check_token()

self._simvue = Simvue(self._name, self._mode, self._suppress_errors)
if not self._simvue.create_run(data):
self._simvue = Simvue(self._name, self._uuid, self._mode, self._suppress_errors)
name = self._simvue.create_run(data)

if not name:
return False
elif name is not True:
self._name = name

if self._status == 'running':
self._start()

return True

@property
Expand All @@ -276,7 +281,14 @@ def name(self):
"""
return self._name

def reconnect(self, name):
@property
def uid(self):
"""
Return the local unique identifier of the run
"""
return self._uuid

def reconnect(self, name=None, uid=None):
"""
Reconnect to a run in the created state
"""
Expand All @@ -285,7 +297,9 @@ def reconnect(self, name):

self._status = 'running'
self._name = name
self._simvue = Simvue(self._name, self._mode, self._suppress_errors)
self._uuid = uid

self._simvue = Simvue(self._name, self._uuid, self._mode, self._suppress_errors)
self._start(reconnect=True)

def set_pid(self, pid):
Expand Down Expand Up @@ -332,7 +346,7 @@ def update_metadata(self, metadata):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand All @@ -358,7 +372,7 @@ def update_tags(self, tags):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand All @@ -380,7 +394,7 @@ def log_event(self, message, timestamp=None):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down Expand Up @@ -417,7 +431,7 @@ def log_metrics(self, metrics, step=None, time=None, timestamp=None):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down Expand Up @@ -468,7 +482,7 @@ def save(self, filename, category, filetype=None, preserve_path=False):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down Expand Up @@ -527,7 +541,7 @@ def save_directory(self, directory, category, filetype=None, preserve_path=False
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down Expand Up @@ -577,7 +591,7 @@ def set_status(self, status):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand All @@ -603,7 +617,7 @@ def close(self):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand All @@ -620,7 +634,7 @@ def set_folder_details(self, path, metadata={}, tags=[], description=None):
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down Expand Up @@ -670,7 +684,7 @@ def add_alert(self,
if self._mode == 'disabled':
return True

if not self._name:
if not self._uuid and not self._name:
self._error(INIT_MISSING)
return False

Expand Down
48 changes: 32 additions & 16 deletions simvue/sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,39 @@
import shutil
import time

import msgpack

from .remote import Remote
from .utilities import get_offline_directory, create_file, remove_file

logger = logging.getLogger(__name__)

def get_json(filename):
def add_name(name, data, filename):
"""
Get JSON from a file
Update name in JSON
"""
with open(filename, 'r') as fh:
data = json.load(fh)
if not data['name']:
data['name'] = name
with open(filename, 'w') as fh:
json.dump(data, fh)

return data

def get_binary(filename):
def get_json(filename, name=None):
"""
Get binary content from a file
Get JSON from a file
"""
with open(filename, 'rb') as fh:
data = fh.read()
with open(filename, 'r') as fh:
data = json.load(fh)
if name:
if 'name' in data:
if not data['name']:
data['name'] = name
else:
for item in data:
if 'run' in item:
if not item['run']:
item['run'] = name
return data

def sender():
Expand Down Expand Up @@ -75,16 +89,18 @@ def sender():

logger.info('Considering run with name %s and id %s', run_init['name'], id)

remote = Remote(run_init['name'], suppress_errors=True)
remote = Remote(run_init['name'], id, suppress_errors=True)

# Check token
remote.check_token()

# Create run if it hasn't previously been created
created_file = f"{current}/init"
name = None
if not os.path.isfile(created_file):
name = remote.create_run(run_init)
logger.info('Creating run with name %s', run_init['name'])
remote.create_run(run_init)
run_init = add_name(name, run_init, f"{current}/run.json")
create_file(created_file)

if status == 'running':
Expand Down Expand Up @@ -131,37 +147,37 @@ def sender():
# Handle metrics
if '/metrics-' in record:
logger.info('Sending metrics for run %s', run_init['name'])
remote.send_metrics(get_binary(record))
remote.send_metrics(msgpack.packb(get_json(record, name), use_bin_type=True))
rename = True

# Handle events
if '/event-' in record:
logger.info('Sending event for run %s', run_init['name'])
remote.send_event(get_binary(record))
remote.send_event(msgpack.packb(get_json(record, name), use_bin_type=True))
rename = True

# Handle updates
if '/update-' in record:
logger.info('Sending update for run %s', run_init['name'])
remote.update(get_json(record))
remote.update(get_json(record, name))
rename = True

# Handle folders
if '/folder-' in record:
logger.info('Sending folder details for run %s', run_init['name'])
remote.set_folder_details(get_json(record))
remote.set_folder_details(get_json(record, name))
rename = True

# Handle alerts
if '/alert-' in record:
logger.info('Sending alert details for run %s', run_init['name'])
remote.add_alert(get_json(record))
remote.add_alert(get_json(record, name))
rename = True

# Handle files
if '/file-' in record:
logger.info('Saving file for run %s', run_init['name'])
remote.save_file(get_json(record))
remote.save_file(get_json(record, name))
rename = True

# Rename processed files
Expand Down
Loading