Skip to content
This repository was archived by the owner on Jul 7, 2020. It is now read-only.

Added: Docker state, in e.g. "up 14 hours" format #8

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ docker_monitor:
| --------------------------------- | ------------------------------- | ----- |
| version | Docker version | - |

| containers_total | Total number of containers | - |
| containers_paused | Number of paused containers | - |
| containers_running | Number of running containers | - |
| containers_stopped | Number of stopped containers | - |
| images_total | Total number of images | - |

| Container Conditions | Description | Unit |
| --------------------------------- | ------------------------------- | ----- |
| status | Container status | - |
Expand Down
2 changes: 0 additions & 2 deletions custom_components/docker_monitor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
)
from .helpers import DockerMonitorApi

VERSION = '0.1.0-b0'

_LOGGER = logging.getLogger(__name__)

CONF_MONITOR_UTILISATION_CONDITIONS_KEYS = list(
Expand Down
29 changes: 29 additions & 0 deletions custom_components/docker_monitor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
CONF_CONTAINER_SWITCH = 'switch'

CONF_MONITOR_UTILISATION_VERSION = 'version'
CONF_MONITOR_UTILISATION_CONTAINERS_TOTAL = 'containers_total'
CONF_MONITOR_UTILISATION_CONTAINERS_PAUSED = 'containers_paused'
CONF_MONITOR_UTILISATION_CONTAINERS_RUNNING = 'containers_running'
CONF_MONITOR_UTILISATION_CONTAINERS_STOPPED = 'containers_stopped'
CONF_MONITOR_UTILISATION_IMAGES_TOTAL = 'images_total'

CONF_MONITOR_CONTAINER_STATE = 'state'
CONF_MONITOR_CONTAINER_STATUS = 'status'
CONF_MONITOR_CONTAINER_UPTIME = 'uptime'
CONF_MONITOR_CONTAINER_IMAGE = 'image'
Expand All @@ -29,9 +35,21 @@
CONF_MONITOR_UTILISATION_CONDITIONS = {
CONF_MONITOR_UTILISATION_VERSION:
['Version', None, 'mdi:information-outline', None],
CONF_MONITOR_UTILISATION_CONTAINERS_TOTAL:
['Containers Total', None, 'mdi:docker', None],
CONF_MONITOR_UTILISATION_CONTAINERS_PAUSED:
['Containers Paused', None, 'mdi:docker', None],
CONF_MONITOR_UTILISATION_CONTAINERS_RUNNING:
['Containers Running', None, 'mdi:docker', None],
CONF_MONITOR_UTILISATION_CONTAINERS_STOPPED:
['Containers Stopped', None, 'mdi:docker', None],
CONF_MONITOR_UTILISATION_IMAGES_TOTAL:
['Images Total', None, 'mdi:information-outline', None],
}

CONF_MONITOR_CONTAINER_CONDITIONS = {
CONF_MONITOR_CONTAINER_STATE:
['State', None, 'mdi:checkbox-marked-circle-outline', None],
CONF_MONITOR_CONTAINER_STATUS:
['Status', None, 'mdi:checkbox-marked-circle-outline', None],
CONF_MONITOR_CONTAINER_UPTIME:
Expand Down Expand Up @@ -67,14 +85,25 @@
VERSION_INFO_OS = 'os'
VERSION_INFO_ARCHITECTURE = 'arch'
VERSION_INFO_KERNEL = 'kernel'
VERSION_INFO_CONTAINERS_TOTAL = 'containers_total'
VERSION_INFO_CONTAINERS_PAUSED = 'containers_paused'
VERSION_INFO_CONTAINERS_RUNNING = 'containers_running'
VERSION_INFO_CONTAINERS_STOPPED = 'containers_stopped'
VERSION_INFO_IMAGES = 'images_total'
VERSION_INFO_MEMTOTAL = 'memory_total'

CONTAINER_INFO = 'info'
CONTAINER_INFO_ID = 'id'
CONTAINER_INFO_IMAGE = 'image'
CONTAINER_INFO_STATE = 'state'
CONTAINER_INFO_STATUS = 'status'
CONTAINER_INFO_CREATED = 'created'
CONTAINER_INFO_STARTED = 'started'
CONTAINER_INFO_ENDED = 'ended'
CONTAINER_INFO_EXITCODE = 'exitcode'
CONTAINER_INFO_NETWORKMODE = 'networkmode'
CONTAINER_INFO_ENDED = 'ended'
CONTAINER_INFO_EXITCODE = 'exitcode'

EVENT_INFO_CONTAINER = 'Container'
EVENT_INFO_IMAGE = 'Image'
Expand Down
73 changes: 67 additions & 6 deletions custom_components/docker_monitor/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@
CONTAINER_INFO_CREATED,
CONTAINER_INFO_ID,
CONTAINER_INFO_IMAGE,
CONTAINER_INFO_NETWORKMODE,
CONTAINER_INFO_STARTED,
CONTAINER_INFO_ENDED,
CONTAINER_INFO_EXITCODE,
CONTAINER_INFO_STATE,
CONTAINER_INFO_STATUS,
CONTAINER_INFO_NETWORKMODE,
EVENT_INFO_CONTAINER,
EVENT_INFO_ID,
EVENT_INFO_IMAGE,
EVENT_INFO_STATUS,
VERSION_INFO_API_VERSION,
VERSION_INFO_ARCHITECTURE,
VERSION_INFO_CONTAINERS_PAUSED,
VERSION_INFO_CONTAINERS_RUNNING,
VERSION_INFO_CONTAINERS_STOPPED,
VERSION_INFO_CONTAINERS_TOTAL,
VERSION_INFO_IMAGES,
VERSION_INFO_KERNEL,
VERSION_INFO_MEMTOTAL,
VERSION_INFO_OS,
VERSION_INFO_VERSION
)
Expand Down Expand Up @@ -72,22 +81,29 @@ def watch_container(self, name, callback):
return self._containers[name]

def get_info(self):
version = {}
result = {}
try:
raw_stats = self._client.version()
version = {
info = self._client.info()
result = {
VERSION_INFO_VERSION: raw_stats.get('Version', None),
VERSION_INFO_API_VERSION: raw_stats.get('ApiVersion', None),
VERSION_INFO_OS: raw_stats.get('Os', None),
VERSION_INFO_ARCHITECTURE: raw_stats.get('Arch', None),
VERSION_INFO_KERNEL: raw_stats.get('KernelVersion', None),
VERSION_INFO_CONTAINERS_TOTAL : info.get('Containers', None),
VERSION_INFO_CONTAINERS_PAUSED : info.get('ContainersPaused', None),
VERSION_INFO_CONTAINERS_RUNNING : info.get('ContainersRunning', None),
VERSION_INFO_CONTAINERS_STOPPED : info.get('ContainersStopped', None),
VERSION_INFO_IMAGES : info.get('Images', None),
VERSION_INFO_MEMTOTAL : info.get('MemTotal', None),
}
except Exception as e:
_LOGGER.info("Cannot get docker daemon info")
_LOGGER.debug("Request exception: {}".format(e))
raise ConnectionError("Cannot request info")

return version
return result


class DockerContainerEventListener(threading.Thread):
Expand Down Expand Up @@ -185,10 +201,31 @@ def get_info(self):
parser.parse(self._container.attrs['Created']),
CONTAINER_INFO_STARTED:
parser.parse(self._container.attrs['State']['StartedAt']),
CONTAINER_INFO_ENDED:
parser.parse(self._container.attrs['State']['FinishedAt']),
CONTAINER_INFO_EXITCODE:
self._container.attrs['State']['ExitCode'],
CONTAINER_INFO_NETWORKMODE:
self._container.attrs['HostConfig']['NetworkMode'] == 'host'
self._container.attrs['HostConfig']['NetworkMode'] == 'host',
CONTAINER_INFO_ENDED:
parser.parse(self._container.attrs['State']['FinishedAt']),
CONTAINER_INFO_EXITCODE:
self._container.attrs['State']['ExitCode'],
}

if info[CONTAINER_INFO_STATUS] == 'running':
info[CONTAINER_INFO_STATE] = 'Up {}'.format(self.__calcdockerformat(info[CONTAINER_INFO_STARTED]))
elif info[CONTAINER_INFO_STATUS] == 'exited':
info[CONTAINER_INFO_STATE] = 'Exited ({}) {} ago'.format(info[CONTAINER_INFO_EXITCODE], self.__calcdockerformat(info[CONTAINER_INFO_ENDED]))
elif info[CONTAINER_INFO_STATUS] == 'created':
info[CONTAINER_INFO_STATE] = 'Created {} ago'.format(self.__calcdockerformat(info[CONTAINER_INFO_CREATED]))
elif info[CONTAINER_INFO_STATUS] == 'restarting':
info[CONTAINER_INFO_STATE] = 'Restarting'
elif info[CONTAINER_INFO_STATUS] == 'paused':
info[CONTAINER_INFO_STATE] = 'Up {} (Paused)'.format(self.__calcdockerformat(info[CONTAINER_INFO_STARTED]))
else:
info[CONTAINER_INFO_STATE] = 'None ({})'.format(info[CONTAINER_INFO_STATUS])

return info

def start(self):
Expand Down Expand Up @@ -231,6 +268,29 @@ def _notify(self):
for callback in self._subscribers:
callback()

def __calcdockerformat(self, dt):
"""Calculate datetime to Docker format."""
from datetime import datetime, timezone

if dt is None:
return 'None'

delta = round((datetime.now(timezone.utc) - dt).total_seconds())

if delta < (2 * 60):
return '{} seconds'.format(delta)
elif delta < (2 * 3600):
return '{} minutes'.format(round(delta / 60))
elif delta < (2 * 86400):
return '{} hours'.format(round(delta / 3600))
elif delta < (2 * 86400 * 7):
return '{} days'.format(round(delta / 86400))
elif delta < (2 * 86400 * 30):
return '{} weeks'.format(round(delta / 86400 * 7))
elif delta < (2 * 86400 * 365):
return '{} months'.format(round(delta / (86400 * 30)))
else:
return '{} years'.format(round(delta / (86400 * 365)))

class DockerContainerStats(threading.Thread):
"""Docker monitor container stats listener thread."""
Expand Down Expand Up @@ -288,7 +348,7 @@ def run(self):

container.set_stats(stats)
except Exception as ex:
_LOGGER.info("Cannot get docker container")
_LOGGER.error("Cannot get docker container", exc_info=True)
_LOGGER.debug("Request exception: {}".format(ex))

# Wait before read
Expand Down Expand Up @@ -372,3 +432,4 @@ def __parse_stats(self, name, containerinfo, raw):
self._old[name] = old

return stats

36 changes: 30 additions & 6 deletions custom_components/docker_monitor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
CONF_MONITOR_CONTAINER_MEMORY_USAGE,
CONF_MONITOR_CONTAINER_NETWORK_TOTAL_DOWN,
CONF_MONITOR_CONTAINER_NETWORK_TOTAL_UP,
CONF_MONITOR_CONTAINER_STATE,
CONF_MONITOR_CONTAINER_STATUS,
CONF_MONITOR_CONTAINER_UPTIME,
CONF_MONITOR_UTILISATION_CONDITIONS,
Expand All @@ -54,11 +55,19 @@
VERSION_INFO_ARCHITECTURE,
VERSION_INFO_KERNEL,
VERSION_INFO_OS,
VERSION_INFO_VERSION
VERSION_INFO_VERSION,
VERSION_INFO_CONTAINERS_TOTAL,
VERSION_INFO_CONTAINERS_PAUSED,
VERSION_INFO_CONTAINERS_RUNNING,
VERSION_INFO_CONTAINERS_STOPPED,
VERSION_INFO_IMAGES,
CONF_MONITOR_UTILISATION_CONTAINERS_TOTAL,
CONF_MONITOR_UTILISATION_CONTAINERS_PAUSED,
CONF_MONITOR_UTILISATION_CONTAINERS_RUNNING,
CONF_MONITOR_UTILISATION_CONTAINERS_STOPPED,
CONF_MONITOR_UTILISATION_IMAGES_TOTAL,
)

VERSION = '0.1.0-b0'

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -161,6 +170,16 @@ def update(self):
VERSION_INFO_OS, None)
self._attributes[ATTR_VERSION_ARCH] = version.get(
VERSION_INFO_ARCHITECTURE, None)
elif self._var_id == CONF_MONITOR_UTILISATION_CONTAINERS_TOTAL:
self._state = version.get(VERSION_INFO_CONTAINERS_TOTAL, None)
elif self._var_id == CONF_MONITOR_UTILISATION_CONTAINERS_PAUSED:
self._state = version.get(VERSION_INFO_CONTAINERS_PAUSED, None)
elif self._var_id == CONF_MONITOR_UTILISATION_CONTAINERS_RUNNING:
self._state = version.get(VERSION_INFO_CONTAINERS_RUNNING, None)
elif self._var_id == CONF_MONITOR_UTILISATION_CONTAINERS_STOPPED:
self._state = version.get(VERSION_INFO_CONTAINERS_STOPPED, None)
elif self._var_id == CONF_MONITOR_UTILISATION_IMAGES_TOTAL:
self._state = version.get(VERSION_INFO_IMAGES, None)
else:
self._state = None
self._attributes = None
Expand Down Expand Up @@ -189,6 +208,7 @@ def __init__(self, clientname, api, name, variable, interval):

self._state = None
self._available = False
self._running = None
self._attributes = {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION
}
Expand All @@ -201,8 +221,8 @@ def name(self):
@property
def icon(self):
"""Icon to use in the frontend, if any."""
if self._var_id == CONF_MONITOR_CONTAINER_STATUS:
if self._state == 'running':
if self._var_id in [CONF_MONITOR_CONTAINER_STATE, CONF_MONITOR_CONTAINER_STATUS]:
if self._running:
return 'mdi:checkbox-marked-circle-outline'
else:
return 'mdi:checkbox-blank-circle-outline'
Expand Down Expand Up @@ -247,7 +267,7 @@ async def async_added_to_hass(self):
def event_callback(self):
"""Update callback."""

_LOGGER.info("Sensor {} event".format(self._name))
_LOGGER.info("Sensor %s event %s", self._name, self._var_id)

info = None
stats = None
Expand All @@ -262,6 +282,10 @@ def event_callback(self):

if self._var_id == CONF_MONITOR_CONTAINER_STATUS:
state = info.get(CONTAINER_INFO_STATUS)
self._running = info.get(CONTAINER_INFO_STATUS) in ("running", "paused")
elif self._var_id == CONF_MONITOR_CONTAINER_STATE:
state = info.get(CONF_MONITOR_CONTAINER_STATE)
self._running = info.get(CONTAINER_INFO_STATUS) in ("running", "paused")
else:
if info[CONTAINER_INFO_STATUS] == 'running':
# Info
Expand Down
2 changes: 0 additions & 2 deletions custom_components/docker_monitor/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
ICON_SWITCH
)

VERSION = '0.1.0-b0'

_LOGGER = logging.getLogger(__name__)


Expand Down