Source code for hyrax.tensorboardx_logger
import inspect
import time
from tensorboardX import SummaryWriter
[docs]
tensorboardx_logger = None
[docs]
tensorboard_start_ns = 0
[docs]
def get_tensorboard_logger():
"""
Get a Tensorboard logging object.
This is a way for code that just needs to log data to get a handle to the global tensorboard logger.
Client code does not need to handle initialization order, it can simply log.
If there is no tensorboard logger set up, messages will be dropped silently.
"""
return HyraxSummaryWriter()
[docs]
def init_tensorboard_logger(**kwargs):
"""
Initialize a Tensorboard SummaryWriter for use by the whole process.
Args are those to SummaryWriter's constructor.
If there already is a Tensorboard Logger initialized for this process, it will
be closed by this function call before the new one is initialized.
This should be called by code that controls overall hyrax execution e.g. a Verb's run() method.
"""
global tensorboardx_logger
global tensorboard_start_ns
close_tensorboard_logger()
tensorboardx_logger = SummaryWriter(**kwargs)
tensorboard_start_ns = time.monotonic_ns()
[docs]
def close_tensorboard_logger():
"""
Close the existing global tensorboard logger.
If there is no global tensorboard logger, this does nothing.
"""
global tensorboardx_logger
global tensorboard_start_ns
if tensorboardx_logger is not None:
tensorboardx_logger.close()
tensorboardx_logger = None
tensorboard_start_ns = 0
[docs]
class HyraxSummaryWriter:
"""
This is a wrapper class around TensorboardX SummaryWriter that allows definition
of convenience methods for commonly-used logging.
For client code that just wants to log Typical usage is:
from hyrax.tensorboardx_logger import get_tensorboard_logger
tensorboardx_logger = get_tensorboard_logger()
...in code...
tensorboardx_logger.log_scalar(...)
tensorboardx_logger.log_duration(...)
For code controlling overall process execution (e.g. a hyrax verb run method) usage looks like:
from hyrax.tensorboardx_logger import init_tensorboard_logger, close_tensorboard_logger
def run():
init_tensorboard_logger(log_dir="some/path/")
... do things ...
close_tensorboard_logger()
return
__dir__ and __getattr__ pass through function calls to the underlying tensorboardX SummaryWriter if
it exists. Otherwise empty/noop objects are returned. We don't use inheritance here because we want
consumers to not have to think about initialization order concerns, yet have a handle to a pile of
functions that all log to the one true tensorboard instance (if it exists)
All functions defined here need to be noops when global tensorboardx_logger is None.
We have the capacity to place information on instances of this class (e.g. a name prefix)
but its not implemented. One major issue is providing continuity of interface with tensorboard's
functions that don't recognize a name prefix. For now, the fully qualified tensorboard name of the
data is the common interface, since that follows what tensorboard functions expect.
"""
[docs]
def log_duration_ts(self, name: str, start_time: int):
"""
Log a duration to tensorboardX as a time series if configured.
Caller provides the start of the duration in time.monotonic_ns
End of the duration is assumed to be the moment the function is called.
Parameters
----------
name : str
The name of the scalar to log
start_time : int
Start time in nanoseconds from time.monotonic_ns()
"""
now = time.monotonic_ns()
if tensorboardx_logger:
since_tensorboard_start_us = (start_time - tensorboard_start_ns) / 1.0e3
duration_s = (now - start_time) / 1.0e9
self.log_scalar_ts(name, duration_s, since_tensorboard_start_us)
[docs]
def log_scalar_ts(self, name: str, scalar, since_tensorboard_start_ns=None):
"""
Log a scalar to tensorboardX as a time series if configured.
Parameters
----------
name : str
The name of the scalar to log
scalar: Any
The value to log. Really ought to be a number.
since_tensorboard_start_ns : int, Optional
Log time in nanoseconds from the beginning of tensorboard logging.
If not provided, this will be calculated at the moment this function is called
"""
now = time.monotonic_ns()
if tensorboardx_logger:
since_tensorboard_start_ns = (
(now - tensorboard_start_ns)
if since_tensorboard_start_ns is None
else since_tensorboard_start_ns
)
since_tensorboard_start_us = since_tensorboard_start_ns / 1.0e3
tensorboardx_logger.add_scalar(name, scalar, since_tensorboard_start_us)
[docs]
def __dir__(self):
methods = [i for i in dir(HyraxSummaryWriter) if inspect.isfunction(getattr(HyraxSummaryWriter, i))]
return sorted(set(methods + dir(SummaryWriter)))
[docs]
def __getattr__(self, name):
# Reminder of python behavior:
# __getattr__ is called when there's an AttributeError looking up
# an attribute on instances of HyraxSummaryWriter.
#
# It's job is to either return an object or raise AttributeError
# If we have a tensorboardX logger, just pass through access there
if tensorboardx_logger is not None:
return getattr(tensorboardx_logger, name)
# Otherwise if its a valid access of SummaryWriter's methods or members
elif name in dir(SummaryWriter):
# Function access returns a noop function
if inspect.isfunction(getattr(SummaryWriter, name)):
def noop(*args, **kwargs):
pass
return noop
# member access returns None
else:
return None
# All other access is an AttributeError
else:
raise AttributeError(name)