Skip to content

Commit

Permalink
Import
Browse files Browse the repository at this point in the history
  • Loading branch information
jd committed May 7, 2014
0 parents commit f5dc026
Show file tree
Hide file tree
Showing 59 changed files with 7,979 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
.testrepository
*.pyc
.tox
*.egg-info
4 changes: 4 additions & 0 deletions .testr.conf
@@ -0,0 +1,4 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover gnocchi $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
5 changes: 5 additions & 0 deletions README.rst
@@ -0,0 +1,5 @@
====
Gnocchi
====

REST API to store metrics in object storage.
Empty file added gnocchi/__init__.py
Empty file.
144 changes: 144 additions & 0 deletions gnocchi/carbonara.py
@@ -0,0 +1,144 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 eNovance
#
# Authors: Julien Danjou <julien@danjou.info>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Time series data manipulation, better with pancetta."""
import operator

import msgpack
import pandas


class TimeSerie(object):

def __init__(self, timestamps, values,
max_size=None,
sampling=None, aggregation_method='mean'):
self.aggregation_method = aggregation_method
self.sampling = pandas.tseries.frequencies.to_offset(sampling)
self.max_size = max_size
self.ts = pandas.Series(values, timestamps)
self._resample()
self._truncate()

def __eq__(self, other):
return (self.ts.all() == other.ts.all()
and self.max_size == other.max_size
and self.sampling == other.sampling
and self.aggregation_method == other.aggregation_method)

def __getitem__(self, key):
return self.ts[key]

def __setitem__(self, key, value):
self.ts[key] = value

def __len__(self):
return len(self.ts)

@classmethod
def from_dict(cls, d):
"""Build a time series from a dict.
The dict format must be datetime as key and values as values.
:param d: The dict.
:returns: A TimeSerie object
"""
return cls(*zip(*dict((pandas.Timestamp(k), v)
for k, v in d['values'].iteritems()).items())
or ([], []),
max_size=d.get('max_size'),
sampling=d.get('sampling'),
aggregation_method=d.get('aggregation_method', 'mean'))

def to_dict(self):
return {
'aggregation_method': self.aggregation_method,
'max_size': self.max_size,
'sampling': str(self.sampling.n) + self.sampling.rule_code,
'values': dict((str(k), v)
for k, v in self.ts[~self.ts.isnull()].iteritems()),
}

def _truncate(self):
if self.max_size is not None:
self.ts = self.ts[~self.ts.isnull()][-self.max_size:]

def _resample(self):
if self.sampling:
self.ts = self.ts.resample(self.sampling,
how=self.aggregation_method)

def update(self, ts):
self.ts = ts.ts.combine_first(self.ts)
self._resample()
self._truncate()

def update_from_dict(self, d):
self.update(self.__class__.from_dict(d))

def serialize(self):
return msgpack.dumps(self.to_dict())

@classmethod
def unserialize(cls, data):
return cls.from_dict(msgpack.loads(data))


class TimeSerieCollection(object):

def __init__(self, timeseries):
if timeseries:
agg = timeseries[0].aggregation_method
for ts in timeseries[1:]:
if ts.aggregation_method != agg:
raise ValueError(
"All time series must use the same aggregation method")
self.timeseries = sorted(timeseries,
key=operator.attrgetter('sampling'))

def fetch(self, from_timestamp=None, to_timestamp=None):
result = pandas.Series()
fts = pandas.Timestamp(from_timestamp,
unit='s') if from_timestamp else None
tts = pandas.Timestamp(to_timestamp,
unit='s') if to_timestamp else None
for ts in self.timeseries:
result = result.combine_first(ts[fts:tts])
return dict(result)

def __eq__(self, other):
return self.timeseries == other.timeseries

def serialize(self):
return msgpack.dumps([ts.to_dict() for ts in self.timeseries])

def __setitem__(self, timestamp, value):
timestamp = pandas.Timestamp(timestamp, unit='s')
for ts in self.timeseries:
ts[timestamp] = value

def __getitem__(self, key):
if isinstance(key, slice):
if key.step:
raise ValueError("Unable to use step on getitem %s",
self.__class__.__name__)
return self.fetch(key.start, key.stop)
return self.fetch(key)

@classmethod
def unserialize(cls, data):
return cls([TimeSerie.from_dict(ts) for ts in msgpack.loads(data)])
82 changes: 82 additions & 0 deletions gnocchi/indexer/__init__.py
@@ -0,0 +1,82 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 eNovance
#
# Authors: Julien Danjou <julien@danjou.info>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo.config import cfg
from stevedore import driver

OPTS = [
cfg.StrOpt('driver',
default='sqlalchemy',
help='Indexer driver to use'),
]

cfg.CONF.register_opts(OPTS, group="indexer")


def _get_driver(name, conf):
"""Return the driver named name.
:param name: The name of the driver.
:param conf: The conf to pass to the driver.
"""
d = driver.DriverManager('gnocchi.indexer',
name).driver
return d(conf)


def get_driver(conf):
"""Return the configured driver."""
return _get_driver(conf.indexer.driver,
conf)


class NoSuchEntity(Exception):
"""Error raised when an entitiy does not exist."""
def __init__(self, entity):
super(NoSuchEntity, self).__init__("Entity %s does not exist" %
str(entity))
self.entity = entity


class EntityAlreadyExists(Exception):
"""Error raised when an entity already exists."""
def __init__(self, entity):
super(EntityAlreadyExists, self).__init__("Entity %s already exists" %
entity)
self.entity = entity


class IndexerDriver(object):
@staticmethod
def __init__(conf):
pass

@staticmethod
def upgrade():
pass

@staticmethod
def create_resource(uuid, entities=[]):
raise NotImplementedError

@staticmethod
def create_entity(name):
raise NotImplementedError

@staticmethod
def delete_entity(name):
raise NotImplementedError
22 changes: 22 additions & 0 deletions gnocchi/indexer/null.py
@@ -0,0 +1,22 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 eNovance
#
# Authors: Julien Danjou <julien@danjou.info>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from gnocchi import indexer


class NullIndexer(indexer.IndexerDriver):
pass

0 comments on commit f5dc026

Please sign in to comment.