Skip to content

Commit

Permalink
Improve scope of values for every parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Nov 20, 2021
1 parent 93b84d2 commit a4469c0
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -5,6 +5,8 @@ grafanimate changelog

in progress
===========
- Improve scope of values for ``every`` parameter. It will now accept relative
humanized timestamps like ``2m30s``, ``1d12h`` or ``1.5 days``.


2021-11-17 0.6.0
Expand Down
9 changes: 9 additions & 0 deletions README.rst
Expand Up @@ -117,6 +117,14 @@ In order to run a built-in scenario, invoke::
grafanimate --scenario=playdemo --output=./animations


Details
=======

``grafanimate`` also supports relative timestamps, based on the fine
`pytimeparse2`_ library.

- Within ``every``, you will express a duration.

Help
====

Expand Down Expand Up @@ -317,6 +325,7 @@ License
.. _Grafana: https://grafana.com/
.. _Marionette Python Client: https://marionette-client.readthedocs.io/
.. _Playlists: http://docs.grafana.org/reference/playlist/
.. _pytimeparse2: https://github.com/onegreyonewhite/pytimeparse2
.. _scenarios.py: https://github.com/panodata/grafanimate/blob/main/grafanimate/scenarios.py
.. _Scripted Dashboards: http://docs.grafana.org/reference/scripting/
.. _set time range in Grafana: https://stackoverflow.com/questions/48264279/how-to-set-time-range-in-grafana-dashboard-from-text-panels/52492205#52492205
Expand Down
42 changes: 41 additions & 1 deletion grafanimate/animations.py
Expand Up @@ -5,6 +5,7 @@
import time
from datetime import timedelta
from operator import attrgetter
from typing import Tuple

from dateutil.relativedelta import relativedelta
from dateutil.rrule import (
Expand All @@ -18,9 +19,11 @@
rrule,
)
from munch import munchify
from pytimeparse2 import parse as parse_human_time

from grafanimate.grafana import GrafanaWrapper
from grafanimate.model import AnimationSequence, SequencingMode
from grafanimate.util import get_relativedelta

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -106,10 +109,44 @@ def run(self, step: AnimationSequence):

self.log("Animation finished")

def get_freq_delta(self, interval):
@staticmethod
def get_freq_delta(interval: str) -> Tuple[int, int, timedelta]:

rr_freq = MINUTELY
rr_interval = 1

# 1. Attempt to parse time using `pytimeparse` module.
# https://pypi.org/project/pytimeparse/
duration = parse_human_time(interval)
if duration:
delta = get_relativedelta(seconds=duration)

if delta.years:
rr_freq = YEARLY
rr_interval = delta.years
elif delta.months:
rr_freq = MONTHLY
rr_interval = delta.months
elif delta.days:
rr_freq = DAILY
rr_interval = delta.days
elif delta.hours:
rr_freq = HOURLY
rr_interval = delta.hours
elif delta.minutes:
rr_freq = MINUTELY
rr_interval = delta.minutes
else:
rr_freq = SECONDLY
rr_interval = delta.seconds

if rr_freq != SECONDLY:
delta -= relativedelta(seconds=1)

return rr_freq, rr_interval, delta

# 2. Compute parameters from specific labels, expression periods.

# Secondly
if interval == "secondly":
rr_freq = SECONDLY
Expand Down Expand Up @@ -166,6 +203,9 @@ def get_freq_delta(self, interval):
else:
raise ValueError('Unknown interval "{}"'.format(interval))

if isinstance(delta, timedelta):
delta = get_relativedelta(seconds=delta.total_seconds())

return rr_freq, rr_interval, delta

def render(self, start, stop, every):
Expand Down
32 changes: 9 additions & 23 deletions grafanimate/scenarios.py
Expand Up @@ -81,13 +81,13 @@ def playdemo_advanced():
AnimationSequence(
start="2021-11-15T02:12:05Z",
stop="2021-11-15T02:37:36Z",
every="5min",
every="3min",
mode=SequencingMode.CUMULATIVE,
),
AnimationSequence(
start=1637091011,
stop=1637091911,
every="5min",
every="4m5s",
mode=SequencingMode.CUMULATIVE,
),
],
Expand Down Expand Up @@ -142,9 +142,7 @@ def ldi_nye_2017_2018():
grafanimate --grafana-url=https://daq.example.org/grafana --dashboard-uid=1aOmc1sik --scenario=ldi_nye_2017_2018 --output=./animations
"""
logger.info("Running scenario ldi_nye_2017_2018")
return AnimationSequence(
start=datetime(2017, 12, 31, 21, 0, 0), stop=datetime(2018, 1, 1, 4, 0, 0), every="10min"
)
return AnimationSequence(start=datetime(2017, 12, 31, 21, 0, 0), stop=datetime(2018, 1, 1, 4, 0, 0), every="10min")


def ldi_nye_2018_2019():
Expand All @@ -157,21 +155,11 @@ def ldi_nye_2018_2019():
"""
logger.info("Running scenario ldi_nye_2018_2019")
return [
AnimationSequence(
start=datetime(2018, 12, 31, 15, 0, 0), stop=datetime(2018, 12, 31, 20, 0, 0), every="30min"
),
AnimationSequence(
start=datetime(2018, 12, 31, 20, 0, 0), stop=datetime(2018, 12, 31, 23, 0, 0), every="10min"
),
AnimationSequence(
start=datetime(2018, 12, 31, 23, 0, 0), stop=datetime(2019, 1, 1, 1, 0, 0), every="5min"
),
AnimationSequence(
start=datetime(2019, 1, 1, 1, 0, 0), stop=datetime(2019, 1, 1, 4, 0, 0), every="10min"
),
AnimationSequence(
start=datetime(2019, 1, 1, 4, 0, 0), stop=datetime(2019, 1, 1, 9, 0, 0), every="30min"
),
AnimationSequence(start=datetime(2018, 12, 31, 15, 0, 0), stop=datetime(2018, 12, 31, 20, 0, 0), every="30min"),
AnimationSequence(start=datetime(2018, 12, 31, 20, 0, 0), stop=datetime(2018, 12, 31, 23, 0, 0), every="10min"),
AnimationSequence(start=datetime(2018, 12, 31, 23, 0, 0), stop=datetime(2019, 1, 1, 1, 0, 0), every="5min"),
AnimationSequence(start=datetime(2019, 1, 1, 1, 0, 0), stop=datetime(2019, 1, 1, 4, 0, 0), every="10min"),
AnimationSequence(start=datetime(2019, 1, 1, 4, 0, 0), stop=datetime(2019, 1, 1, 9, 0, 0), every="30min"),
]


Expand All @@ -184,9 +172,7 @@ def cdc_maps():
grafanimate --grafana-url=https://daq.example.org/grafana --dashboard-uid=DLOlE_Rmz --scenario=cdc_maps --output=./animations
"""
logger.info("Running scenario cdc_maps")
return AnimationSequence(
start=datetime(2018, 3, 6, 5, 0, 0), stop=datetime(2018, 3, 10, 23, 59, 59), every="hourly"
)
return AnimationSequence(start=datetime(2018, 3, 6, 5, 0, 0), stop=datetime(2018, 3, 10, 23, 59, 59), every="hourly")

# Short sequence, for debugging processes.
# return AnimationSequence(start=datetime(2018, 3, 6, 5, 0, 0), stop=datetime(2018, 3, 6, 6, 59, 59), every='hourly')
Expand Down
25 changes: 25 additions & 0 deletions grafanimate/util.py
Expand Up @@ -9,6 +9,7 @@
from contextlib import closing
from pathlib import Path

from dateutil.relativedelta import relativedelta
from munch import munchify
from unidecode import unidecode

Expand Down Expand Up @@ -156,3 +157,27 @@ def import_module(name: str, path: str):
spec.loader.exec_module(mod)

return mod


def get_relativedelta(seconds: int):
# TODO: Add to `pytimeparse2`?
# https://stackoverflow.com/questions/16977768/elegant-way-to-convert-python-datetime-timedelta-to-dateutil-relativedelta

seconds_in = {
"year": 365 * 24 * 60 * 60,
"month": 30 * 24 * 60 * 60,
"day": 24 * 60 * 60,
"hour": 60 * 60,
"minute": 60,
}

years, rem = divmod(seconds, seconds_in["year"])
months, rem = divmod(rem, seconds_in["month"])
days, rem = divmod(rem, seconds_in["day"])
hours, rem = divmod(rem, seconds_in["hour"])
minutes, rem = divmod(rem, seconds_in["minute"])
seconds = rem

return relativedelta(
years=years, months=months, days=days, hours=hours, minutes=minutes, seconds=seconds
).normalized()
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -19,6 +19,7 @@
"marionette_driver>=3,<4",
"python-dateutil>=2.7,<3",
"datetime-interval==0.2",
"pytimeparse2>=1.3,<2",
]

extras = {
Expand Down
131 changes: 131 additions & 0 deletions tests/test_animations.py
@@ -0,0 +1,131 @@
from dateutil.relativedelta import relativedelta
from dateutil.rrule import DAILY, HOURLY, MINUTELY, MONTHLY, SECONDLY, WEEKLY, YEARLY

from grafanimate.animations import SequentialAnimation


def test_freq_delta_legacy():

get_freq_delta = SequentialAnimation.get_freq_delta

freq, interval, delta = get_freq_delta("secondly")
assert freq == SECONDLY
assert interval == 1
assert delta == relativedelta(seconds=+1)

freq, interval, delta = get_freq_delta("minutely")
assert freq == MINUTELY
assert interval == 1
assert delta == relativedelta(seconds=+59)

freq, interval, delta = get_freq_delta("5min")
assert freq == MINUTELY
assert interval == 5
assert delta == relativedelta(minutes=+5, seconds=-1)

freq, interval, delta = get_freq_delta("10min")
assert freq == MINUTELY
assert interval == 10
assert delta == relativedelta(minutes=+10, seconds=-1)

freq, interval, delta = get_freq_delta("30min")
assert freq == MINUTELY
assert interval == 30
assert delta == relativedelta(minutes=+30, seconds=-1)

freq, interval, delta = get_freq_delta("hourly")
assert freq == HOURLY
assert interval == 1
assert delta == relativedelta(minutes=+59, seconds=+59)

freq, interval, delta = get_freq_delta("daily")
assert freq == DAILY
assert interval == 1
assert delta == relativedelta(hours=+23, minutes=+59, seconds=+59)

freq, interval, delta = get_freq_delta("weekly")
assert freq == WEEKLY
assert interval == 1
assert delta == relativedelta(days=+6, hours=+23, minutes=+59, seconds=+59)

freq, interval, delta = get_freq_delta("monthly")
assert freq == MONTHLY
assert interval == 1
assert delta == relativedelta(months=+1, seconds=-1)

freq, interval, delta = get_freq_delta("yearly")
assert freq == YEARLY
assert interval == 1
assert delta == relativedelta(years=+1, seconds=-1)


def test_freq_delta_pytimeparse():

get_freq_delta = SequentialAnimation.get_freq_delta

freq, interval, delta = get_freq_delta("1s")
assert freq == SECONDLY
assert interval == 1
assert delta == relativedelta(seconds=+1)

freq, interval, delta = get_freq_delta("30s")
assert freq == SECONDLY
assert interval == 30
assert delta == relativedelta(seconds=+30)

freq, interval, delta = get_freq_delta("1m")
assert freq == MINUTELY
assert interval == 1
assert delta == relativedelta(minutes=+1, seconds=-1)

freq, interval, delta = get_freq_delta("2m30s")
assert freq == MINUTELY
assert interval == 2
assert delta == relativedelta(minutes=+2, seconds=29)

freq, interval, delta = get_freq_delta("30m")
assert freq == MINUTELY
assert interval == 30
assert delta == relativedelta(minutes=+30, seconds=-1)

freq, interval, delta = get_freq_delta("1h")
assert freq == HOURLY
assert interval == 1
assert delta == relativedelta(hours=+1, seconds=-1)

freq, interval, delta = get_freq_delta("12h")
assert freq == HOURLY
assert interval == 12
assert delta == relativedelta(hours=+12, seconds=-1)

freq, interval, delta = get_freq_delta("1d")
assert freq == DAILY
assert interval == 1
assert delta == relativedelta(days=+1, seconds=-1)

freq, interval, delta = get_freq_delta("1d12h")
assert freq == DAILY
assert interval == 1
assert delta == relativedelta(days=+1, hours=+12, seconds=-1)

freq, interval, delta = get_freq_delta("1.5 days")
assert freq == DAILY
assert interval == 1
assert delta == relativedelta(days=+1, hours=+12, seconds=-1)

freq, interval, delta = get_freq_delta("1w")
assert freq == DAILY
assert interval == 7
assert delta == relativedelta(days=+7, seconds=-1)

"""
freq, interval, delta = get_freq_delta("1mo")
assert freq == MONTHLY
assert interval == 1
assert delta == relativedelta(months=+1, seconds=-1)
freq, interval, delta = get_freq_delta("1y")
assert freq == YEARLY
assert interval == 1
assert delta == relativedelta(years=+1, seconds=-1)
"""

0 comments on commit a4469c0

Please sign in to comment.