Skip to content

Commit

Permalink
Granularity -> Rollup
Browse files Browse the repository at this point in the history
  • Loading branch information
dcramer committed May 24, 2013
1 parent e89c7b0 commit 913f981
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 70 deletions.
40 changes: 20 additions & 20 deletions src/sentry/tsdb/manager.py
Expand Up @@ -12,40 +12,40 @@
from sentry.manager import BaseManager
from sentry.utils.models import create_or_update

from .utils import Granularity
from .utils import Rollup


def get_optimal_granularity(start_timestamp, end_timestamp):
choices = [g for g, _ in reversed(Granularity.get_choices())]
def get_optimal_rollup(start_timestamp, end_timestamp):
choices = [g for g, _ in reversed(Rollup.get_choices())]

# calculate the highest granularity within time range
for idx, c_granularity in enumerate(choices):
start_norm = Granularity.normalize_to_epoch(c_granularity, start_timestamp)
end_norm = Granularity.normalize_to_epoch(c_granularity, end_timestamp)
# calculate the highest rollup within time range
for idx, c_rollup in enumerate(choices):
start_norm = Rollup.normalize_to_epoch(c_rollup, start_timestamp)
end_norm = Rollup.normalize_to_epoch(c_rollup, end_timestamp)
if start_norm != end_norm:
try:
return choices[idx + 1]
except IndexError:
return c_granularity
return c_rollup
return None


class PointManager(BaseManager):
def fetch(self, key, start, end, granularity=None):
def fetch(self, key, start, end, rollup=None):
"""
Return a list of points for ``key`` between ``start`` and ``end``.
If ``granularity`` is ommitted an optimal granularity is used to
If ``rollup`` is ommitted an optimal rollup is used to
minimize the number of data points returned.
>>> points = Point.objects.fetch(key, now, now - timedelta(days=7))
>>> for epoch, value in points:
>>> print epoch, value
"""
granularity = get_optimal_granularity(start, end)
rollup = get_optimal_rollup(start, end)
return sorted(self.filter(
key=key,
granularity=granularity,
rollup=rollup,
epoch__gte=start.strftime('%s'),
epoch__lte=end.strftime('%s'),
).values_list('epoch', 'value'), key=lambda x: x[0])
Expand All @@ -56,14 +56,14 @@ def incr(self, key, amount=1, timestamp=None):
"""
if timestamp is None:
timestamp = timezone.now()
for granularity, _ in Granularity.get_choices():
epoch = Granularity.normalize_to_epoch(granularity, timestamp)
for rollup, _ in Rollup.get_choices():
epoch = Rollup.normalize_to_epoch(rollup, timestamp)

create_or_update(
model=self.model,
key=key,
epoch=epoch,
granularity=granularity,
rollup=rollup,
defaults={
'value': F('value') + amount,
}
Expand All @@ -76,12 +76,12 @@ def trim(self, timestamp=None):
"""
if timestamp is None:
timestamp = timezone.now()
for granularity, _ in Granularity.get_choices():
min_value = Granularity.get_min_timestamp(granularity, timestamp)
if min_value is None:
for rollup, _ in Rollup.get_choices():
min_timestamp = Rollup.get_min_timestamp(rollup, timestamp)
if min_timestamp is None:
continue

self.filter(
granularity=granularity,
value__lt=min_value,
rollup=rollup,
epoch__lt=min_timestamp,
).delete()
4 changes: 2 additions & 2 deletions src/sentry/tsdb/migrations/0001_initial.py
Expand Up @@ -22,7 +22,7 @@ def forwards(self, orm):
('key', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['tsdb.Key'])),
('value', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('epoch', self.gf('django.db.models.fields.PositiveIntegerField')()),
('granularity', self.gf('django.db.models.fields.PositiveIntegerField')()),
('rollup', self.gf('django.db.models.fields.PositiveIntegerField')()),
))
db.send_create_signal(u'tsdb', ['Point'])

Expand All @@ -45,9 +45,9 @@ def backwards(self, orm):
u'tsdb.point': {
'Meta': {'object_name': 'Point'},
'epoch': ('django.db.models.fields.PositiveIntegerField', [], {}),
'granularity': ('django.db.models.fields.PositiveIntegerField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'key': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['tsdb.Key']"}),
'rollup': ('django.db.models.fields.PositiveIntegerField', [], {}),
'value': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/sentry/tsdb/models.py
Expand Up @@ -34,7 +34,7 @@
from sentry.manager import BaseManager

from .manager import PointManager
from .utils import Granularity
from .utils import Rollup


class Key(models.Model):
Expand All @@ -48,12 +48,12 @@ class Point(models.Model):
key = models.ForeignKey(Key)
value = models.PositiveIntegerField(default=0)
epoch = models.PositiveIntegerField()
granularity = models.PositiveIntegerField(choices=Granularity.get_choices())
rollup = models.PositiveIntegerField(choices=Rollup.get_choices())

objects = PointManager()

class Meta:
index_together = (
('key', 'granularity', 'value'),
('granularity', 'value'),
('key', 'rollup', 'epoch'),
('rollup', 'epoch'),
)
40 changes: 20 additions & 20 deletions src/sentry/tsdb/utils.py
Expand Up @@ -9,7 +9,7 @@
from datetime import timedelta


class Granularity(object):
class Rollup(object):
SECONDS = 0
MINUTES = 1
HOURS = 2
Expand All @@ -33,68 +33,68 @@ def get_choices(cls):
return cls.__choice_cache

@classmethod
def normalize_to_epoch(cls, granularity, timestamp):
def normalize_to_epoch(cls, rollup, timestamp):
"""
Given a ``timestamp`` (datetime object) normalize the datetime object
``timestamp`` to an epoch timestmap (integer).
i.e. if the granularity is minutes, the resulting timestamp would have
i.e. if the rollup is minutes, the resulting timestamp would have
the seconds and microseconds rounded down.
"""
timestamp = timestamp.replace(microsecond=0)
if granularity == cls.ALL_TIME:
if rollup == cls.ALL_TIME:
return 0

if granularity == cls.SECONDS:
if rollup == cls.SECONDS:
return int(timestamp.strftime('%s'))

timestamp = timestamp.replace(second=0)
if granularity == cls.MINUTES:
if rollup == cls.MINUTES:
return int(timestamp.strftime('%s'))

timestamp = timestamp.replace(minute=0)
if granularity == cls.HOURS:
if rollup == cls.HOURS:
return int(timestamp.strftime('%s'))

timestamp = timestamp.replace(hour=0)
if granularity == cls.DAYS:
if rollup == cls.DAYS:
return int(timestamp.strftime('%s'))

if granularity == cls.WEEKS:
if rollup == cls.WEEKS:
timestamp -= timedelta(days=timestamp.weekday())
else:
timestamp = timestamp.replace(day=1)

if granularity == cls.YEARS:
if rollup == cls.YEARS:
timestamp = timestamp.replace(month=1)

return int(timestamp.strftime('%s'))

@classmethod
def get_min_timestamp(cls, granularity, timestamp):
def get_min_timestamp(cls, rollup, timestamp):
"""
Return the minimum value (as an epoch timestamp) to keep in storage for
a granularity.
a rollup.
Timestamp should represent the current time.
i.e. if the granularity is seconds, the timestamp will be normalized to
i.e. if the rollup is seconds, the timestamp will be normalized to
the previous minute so only latest 60 points are stored (one per second)
"""
if granularity in (cls.ALL_TIME, cls.YEARS):
if rollup in (cls.ALL_TIME, cls.YEARS):
return None

if granularity == cls.SECONDS:
if rollup == cls.SECONDS:
timestamp -= timedelta(minutes=1)
elif granularity == cls.MINUTES:
elif rollup == cls.MINUTES:
timestamp -= timedelta(hours=1)
elif granularity == cls.HOURS:
elif rollup == cls.HOURS:
timestamp -= timedelta(days=1)
elif granularity == cls.DAYS:
elif rollup == cls.DAYS:
# days are stored for ~1 month
timestamp -= timedelta(days=30)
elif granularity == cls.WEEKS:
elif rollup == cls.WEEKS:
# weeks are stored for a full year
timestamp -= timedelta(days=1)

return cls.normalize_to_epoch(granularity, timestamp)
return cls.normalize_to_epoch(rollup, timestamp)
10 changes: 5 additions & 5 deletions tests/sentry/tsdb/manager/tests.py
Expand Up @@ -5,7 +5,7 @@

from sentry.testutils import TestCase
from sentry.tsdb.models import Point, Key
from sentry.tsdb.utils import Granularity
from sentry.tsdb.utils import Rollup

timestamp = datetime(2013, 5, 18, 15, 13, 58, 132928, tzinfo=pytz.UTC)

Expand All @@ -20,7 +20,7 @@ def test_simple(self):

points = list(Point.objects.filter(key=self.key))

assert len(points) == len(Granularity.get_choices())
assert len(points) == len(Rollup.get_choices())
for point in points:
assert point.value == 1

Expand All @@ -33,7 +33,7 @@ def key(self):
def test_simple(self):
Point.objects.create(
key=self.key,
granularity=Granularity.SECONDS,
rollup=Rollup.SECONDS,
value=1,
epoch=(timestamp - timedelta(seconds=120)).strftime('%s'),
)
Expand All @@ -51,13 +51,13 @@ def key(self):
def test_simple(self):
Point.objects.create(
key=self.key,
granularity=Granularity.SECONDS,
rollup=Rollup.SECONDS,
value=1,
epoch=timestamp.strftime('%s'),
)
Point.objects.create(
key=self.key,
granularity=Granularity.SECONDS,
rollup=Rollup.SECONDS,
value=1,
epoch=(timestamp - timedelta(seconds=10)).strftime('%s'),
)
Expand Down
38 changes: 19 additions & 19 deletions tests/sentry/tsdb/utils/tests.py
Expand Up @@ -3,76 +3,76 @@
from datetime import datetime

from sentry.testutils import TestCase
from sentry.tsdb.utils import Granularity
from sentry.tsdb.utils import Rollup

normalize_to_epoch = Granularity.normalize_to_epoch
get_min_timestamp = Granularity.get_min_timestamp
normalize_to_epoch = Rollup.normalize_to_epoch
get_min_timestamp = Rollup.get_min_timestamp
timestamp = datetime(2013, 5, 18, 15, 13, 58, 132928, tzinfo=pytz.UTC)


class NormalizeToEpochTest(TestCase):
def test_all_time(self):
result = normalize_to_epoch(Granularity.ALL_TIME, timestamp)
result = normalize_to_epoch(Rollup.ALL_TIME, timestamp)
assert result == 0

def test_seconds(self):
result = normalize_to_epoch(Granularity.SECONDS, timestamp)
result = normalize_to_epoch(Rollup.SECONDS, timestamp)
assert result == 1368890038

def test_minutes(self):
result = normalize_to_epoch(Granularity.MINUTES, timestamp)
result = normalize_to_epoch(Rollup.MINUTES, timestamp)
assert result == 1368889980

def test_hours(self):
result = normalize_to_epoch(Granularity.HOURS, timestamp)
result = normalize_to_epoch(Rollup.HOURS, timestamp)
assert result == 1368889200

def test_days(self):
result = normalize_to_epoch(Granularity.DAYS, timestamp)
result = normalize_to_epoch(Rollup.DAYS, timestamp)
assert result == 1368835200

def test_weeks(self):
result = normalize_to_epoch(Granularity.WEEKS, timestamp)
result = normalize_to_epoch(Rollup.WEEKS, timestamp)
assert result == 1368403200

def test_months(self):
result = normalize_to_epoch(Granularity.MONTHS, timestamp)
result = normalize_to_epoch(Rollup.MONTHS, timestamp)
assert result == 1367366400

def test_years(self):
result = normalize_to_epoch(Granularity.YEARS, timestamp)
result = normalize_to_epoch(Rollup.YEARS, timestamp)
assert result == 1356998400


class GetMinTimestampTest(TestCase):
def test_all_time(self):
result = get_min_timestamp(Granularity.ALL_TIME, timestamp)
result = get_min_timestamp(Rollup.ALL_TIME, timestamp)
assert result is None

def test_seconds(self):
result = get_min_timestamp(Granularity.SECONDS, timestamp)
result = get_min_timestamp(Rollup.SECONDS, timestamp)
assert result == 1368889978

def test_minutes(self):
result = get_min_timestamp(Granularity.MINUTES, timestamp)
result = get_min_timestamp(Rollup.MINUTES, timestamp)
assert result == 1368886380

def test_hours(self):
result = get_min_timestamp(Granularity.HOURS, timestamp)
result = get_min_timestamp(Rollup.HOURS, timestamp)
assert result == 1368802800

def test_days(self):
result = get_min_timestamp(Granularity.DAYS, timestamp)
result = get_min_timestamp(Rollup.DAYS, timestamp)
assert result == 1366243200

def test_weeks(self):
result = get_min_timestamp(Granularity.WEEKS, timestamp)
result = get_min_timestamp(Rollup.WEEKS, timestamp)
assert result == 1368403200

def test_months(self):
result = get_min_timestamp(Granularity.MONTHS, timestamp)
result = get_min_timestamp(Rollup.MONTHS, timestamp)
assert result == 1367366400

def test_years(self):
result = get_min_timestamp(Granularity.YEARS, timestamp)
result = get_min_timestamp(Rollup.YEARS, timestamp)
assert result is None

0 comments on commit 913f981

Please sign in to comment.