Compare commits

...

22 Commits

Author SHA1 Message Date
3b90318f83 Merge remote-tracking branch 'origin/packaging' 2013-01-31 21:54:41 -05:00
1fb37604d3 Rearrange documentation, clean up Makefile, README 2013-01-31 19:06:32 -05:00
018ecab310 Make setup.py executable 2013-01-31 17:26:55 -05:00
6a1d6017e2 Include datetime_tz module 2013-01-31 17:25:14 -05:00
e7406f8147 Add metadata 2013-01-31 17:14:47 -05:00
f316026592 Move datetime_tz package under nilmdb.utils
datetime_tz isn't readily available, so it's a lot easier to just
package it within the nilmdb tree.
2013-01-30 19:03:42 -05:00
a8db747768 More work on setup.py; fixed issues in setup.cfg
Adjusted setup.cfg so "python setup.py nosetests" now works correctly.
Also added a "test" alias so that "python setup.py test" works.
2013-01-30 18:35:12 -05:00
727af94722 Start working on setup.py 2013-01-29 20:21:03 -05:00
6c89659df7 Cleanup cmdline "create" help text 2013-01-28 19:07:48 -05:00
58c7c8f6ff Support "now" as a timestamp argument 2013-01-28 19:07:45 -05:00
225003f412 Huge cleanup of namespaces, modules, packages, imports.
Now nilmdb.client, nilmdb.server, nilmdb.cmdline, and nilmdb.utils
are each their own modules, and there is a little bit more of a
logical separation between them.  Various changes scattered throughout
to fix naming (for example, nilmdb.nilmdb.NilmDBError is now
nilmdb.server.errors.NilmDBError).

Reduced usage of "from __future__ import absolute_import" as much
as possible.  It's still needed for the functions in the nilmdb/server
directory to be able to import the nilmdb module rather than the
nilmdb.py script.

This should hopefully ease future packaging a bit.
2013-01-28 19:04:52 -05:00
40b966aef2 Add pycurl-specific hack to Iteratorizer
Inside the pycurl callback, we can't raise exceptions, because the
pycurl extension module will unconditionally print the exception
itself, and not pass it up to the caller.  Instead, we have the
callback return a value that tells curl to abort.  (-1 would be best,
in case we were given 0 bytes, but the extension doesn't support
that either).

This resolves the 'Exception("should die")' problem when interrupting
a streaming generator like stream_extract.
2013-01-24 19:06:20 -05:00
294ec6988b Rewrite Iteratorizer as a context manager
Relying on __del__ to clean up the thread isn't as reliable.
2013-01-24 19:04:25 -05:00
fad23ebb22 Add --timestamp-raw option to extract and list 2013-01-24 16:03:38 -05:00
b226dc4337 Properly handle test case where server doesn't start 2013-01-24 16:03:38 -05:00
e7af863017 httpclient: make sure we error out quickly if nested calls are made
Curl will give an error if we call .setopt() while a .perform() is
in progress, for example if we try to do a stream_insert() while
in the middle of a stream_extract().  Move the setopt() to the
beginning of the get/put functions to ensure that we hit this
error before we mess with the URLs or anything else.
2013-01-24 15:36:10 -05:00
af6ce5b79c Remove superfluous from iteratorizor callback exception 2013-01-23 15:42:27 -05:00
0a6fc943e2 Add some better documentation of layout parameter to create.py 2013-01-22 18:47:39 -05:00
67c6e178e1 Documentation updates 2013-01-22 18:36:05 -05:00
9bf213707c Properly return an error if two timestamps are equal 2013-01-22 18:35:18 -05:00
5cd7899e98 Send a Access-Control-Allow-Origin (CORS) header with all responses 2013-01-22 14:42:03 -05:00
ceec5fb9b3 Force /stream/interval and /stream/extract responses to be text/plain 2013-01-22 12:47:06 -05:00
55 changed files with 608 additions and 339 deletions

View File

@@ -7,3 +7,4 @@
exclude_lines = exclude_lines =
pragma: no cover pragma: no cover
if 0: if 0:
omit = nilmdb/utils/datetime_tz*

20
.gitignore vendored
View File

@@ -1,7 +1,23 @@
db/ # Tests
tests/*testdb/ tests/*testdb/
.coverage .coverage
db/
# Compiled / cythonized files
docs/*.html
build/
*.pyc *.pyc
design.html nilmdb/server/interval.c
nilmdb/server/interval.so
nilmdb/server/layout.c
nilmdb/server/layout.so
nilmdb/server/rbtree.c
nilmdb/server/rbtree.so
# Setup junk
dist/
nilmdb.egg-info/
# Misc
timeit*out timeit*out

View File

@@ -1,22 +1,11 @@
all: test all: test
tool:
python nilmtool.py --help
python nilmtool.py list --help
python nilmtool.py -u asfdadsf list
lint: lint:
pylint -f parseable nilmdb pylint -f parseable nilmdb
%.html: %.md
pandoc -s $< > $@
test: test:
python runtests.py python runtests.py
profile:
python runtests.py --with-profile
clean:: clean::
find . -name '*pyc' | xargs rm -f find . -name '*pyc' | xargs rm -f
rm -f .coverage rm -f .coverage

View File

@@ -1,3 +1,10 @@
sudo apt-get install python2.7 python-cherrypy3 python-decorator python-nose python-coverage nilmdb: Non-Intrusive Load Monitor Database
sudo apt-get install cython # 0.17.1-1 or newer by Jim Paris <jim@jtan.com>
Prerequisites:
sudo apt-get install python2.7 python-cherrypy3 python-decorator python-nose python-coverage python-setuptools
Install:
python setup.py install

5
TODO
View File

@@ -1,5 +0,0 @@
-- Clean up error responses. Specifically I'd like to be able to add
json-formatted data to OverflowError and DB parsing errors. It
seems like subclassing cherrypy.HTTPError and overriding
set_response is the best thing to do -- it would let me get rid
of the _be_ie_unfriendly and other hacks in the server.

9
docs/Makefile Normal file
View File

@@ -0,0 +1,9 @@
ALL_DOCS = $(wildcard *.md)
all: $(ALL_DOCS:.md=.html)
%.html: %.md
pandoc -s $< > $@
clean:
rm -f *.html

5
docs/TODO.md Normal file
View File

@@ -0,0 +1,5 @@
- Documentation
- Machine-readable information in OverflowError, parser errors.
Maybe subclass `cherrypy.HTTPError` and override `set_response`
to add another JSON field?

View File

@@ -1,12 +1,4 @@
"""Main NilmDB import""" """Main NilmDB import"""
from .nilmdb import NilmDB from server import NilmDB, Server
from .server import Server from client import Client
from .client import Client
import pyximport; pyximport.install()
import layout
import interval
import cmdline

View File

@@ -0,0 +1,4 @@
"""nilmdb.client"""
from .client import Client
from .errors import *

View File

@@ -2,7 +2,9 @@
"""Class for performing HTTP client requests via libcurl""" """Class for performing HTTP client requests via libcurl"""
from __future__ import absolute_import import nilmdb
import nilmdb.utils
import nilmdb.client.httpclient
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import time import time
@@ -12,12 +14,6 @@ import os
import simplejson as json import simplejson as json
import itertools import itertools
import nilmdb.utils
import nilmdb.httpclient
# Other functions expect to see these in the nilmdb.client namespace
from nilmdb.httpclient import ClientError, ServerError, Error
version = "1.0" version = "1.0"
def float_to_string(f): def float_to_string(f):
@@ -30,7 +26,7 @@ class Client(object):
client_version = version client_version = version
def __init__(self, url): def __init__(self, url):
self.http = nilmdb.httpclient.HTTPClient(url) self.http = nilmdb.client.httpclient.HTTPClient(url)
def _json_param(self, data): def _json_param(self, data):
"""Return compact json-encoded version of parameter""" """Return compact json-encoded version of parameter"""

33
nilmdb/client/errors.py Normal file
View File

@@ -0,0 +1,33 @@
"""HTTP client errors"""
from nilmdb.utils.printf import *
class Error(Exception):
"""Base exception for both ClientError and ServerError responses"""
def __init__(self,
status = "Unspecified error",
message = None,
url = None,
traceback = None):
Exception.__init__(self, status)
self.status = status # e.g. "400 Bad Request"
self.message = message # textual message from the server
self.url = url # URL we were requesting
self.traceback = traceback # server traceback, if available
def _format_error(self, show_url):
s = sprintf("[%s]", self.status)
if self.message:
s += sprintf(" %s", self.message)
if show_url and self.url: # pragma: no cover
s += sprintf(" (%s)", self.url)
if self.traceback: # pragma: no cover
s += sprintf("\nServer traceback:\n%s", self.traceback)
return s
def __str__(self):
return self._format_error(show_url = False)
def __repr__(self): # pragma: no cover
return self._format_error(show_url = True)
class ClientError(Error):
pass
class ServerError(Error):
pass

View File

@@ -1,8 +1,9 @@
"""HTTP client library""" """HTTP client library"""
from __future__ import absolute_import import nilmdb
from nilmdb.utils.printf import *
import nilmdb.utils import nilmdb.utils
from nilmdb.utils.printf import *
from nilmdb.client.errors import *
import time import time
import sys import sys
@@ -13,39 +14,6 @@ import urlparse
import pycurl import pycurl
import cStringIO import cStringIO
class Error(Exception):
"""Base exception for both ClientError and ServerError responses"""
def __init__(self,
status = "Unspecified error",
message = None,
url = None,
traceback = None):
Exception.__init__(self, status)
self.status = status # e.g. "400 Bad Request"
self.message = message # textual message from the server
self.url = url # URL we were requesting
self.traceback = traceback # server traceback, if available
def __str__(self):
s = sprintf("[%s]", self.status)
if self.message:
s += sprintf(" %s", self.message)
if self.traceback: # pragma: no cover
s += sprintf("\nServer traceback:\n%s", self.traceback)
return s
def __repr__(self): # pragma: no cover
s = sprintf("[%s]", self.status)
if self.message:
s += sprintf(" %s", self.message)
if self.url:
s += sprintf(" (%s)", self.url)
if self.traceback:
s += sprintf("\nServer traceback:\n%s", self.traceback)
return s
class ClientError(Error):
pass
class ServerError(Error):
pass
class HTTPClient(object): class HTTPClient(object):
"""Class to manage and perform HTTP requests from the client""" """Class to manage and perform HTTP requests from the client"""
def __init__(self, baseurl = ""): def __init__(self, baseurl = ""):
@@ -119,13 +87,14 @@ class HTTPClient(object):
self.curl.setopt(pycurl.WRITEFUNCTION, callback) self.curl.setopt(pycurl.WRITEFUNCTION, callback)
self.curl.perform() self.curl.perform()
try: try:
for i in nilmdb.utils.Iteratorizer(func): with nilmdb.utils.Iteratorizer(func, curl_hack = True) as it:
if self._status == 200: for i in it:
# If we had a 200 response, yield the data to the caller. if self._status == 200:
yield i # If we had a 200 response, yield the data to caller.
else: yield i
# Otherwise, collect it into an error string. else:
error_body += i # Otherwise, collect it into an error string.
error_body += i
except pycurl.error as e: except pycurl.error as e:
raise ServerError(status = "502 Error", raise ServerError(status = "502 Error",
url = self.url, url = self.url,
@@ -195,9 +164,9 @@ class HTTPClient(object):
def put(self, url, postdata, params = None, retjson = True): def put(self, url, postdata, params = None, retjson = True):
"""Simple PUT""" """Simple PUT"""
self.curl.setopt(pycurl.UPLOAD, 1)
self._setup_url(url, params) self._setup_url(url, params)
data = cStringIO.StringIO(postdata) data = cStringIO.StringIO(postdata)
self.curl.setopt(pycurl.UPLOAD, 1)
self.curl.setopt(pycurl.READFUNCTION, data.read) self.curl.setopt(pycurl.READFUNCTION, data.read)
return self._doreq(url, params, retjson) return self._doreq(url, params, retjson)
@@ -223,8 +192,8 @@ class HTTPClient(object):
def put_gen(self, url, postdata, params = None, retjson = True): def put_gen(self, url, postdata, params = None, retjson = True):
"""Simple PUT, returning a generator""" """Simple PUT, returning a generator"""
self.curl.setopt(pycurl.UPLOAD, 1)
self._setup_url(url, params) self._setup_url(url, params)
data = cStringIO.StringIO(postdata) data = cStringIO.StringIO(postdata)
self.curl.setopt(pycurl.UPLOAD, 1)
self.curl.setopt(pycurl.READFUNCTION, data.read) self.curl.setopt(pycurl.READFUNCTION, data.read)
return self._doreq_gen(url, params, retjson) return self._doreq_gen(url, params, retjson)

View File

@@ -1 +1,3 @@
"""nilmdb.cmdline"""
from .cmdline import Cmdline from .cmdline import Cmdline

View File

@@ -1,10 +1,9 @@
"""Command line client functionality""" """Command line client functionality"""
from __future__ import absolute_import import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb.client from nilmdb.utils import datetime_tz
import datetime_tz
import dateutil.parser import dateutil.parser
import sys import sys
import re import re
@@ -49,10 +48,10 @@ class Cmdline(object):
If the string doesn't contain a timestamp, the current local If the string doesn't contain a timestamp, the current local
timezone is assumed (e.g. from the TZ env var). timezone is assumed (e.g. from the TZ env var).
""" """
# If string doesn't contain at least 6 digits, consider it # If string isn't "now" and doesn't contain at least 4 digits,
# invalid. smartparse might otherwise accept empty strings # consider it invalid. smartparse might otherwise accept
# and strings with just separators. # empty strings and strings with just separators.
if len(re.findall(r"\d", toparse)) < 6: if toparse != "now" and len(re.findall(r"\d", toparse)) < 4:
raise ValueError("not enough digits for a timestamp") raise ValueError("not enough digits for a timestamp")
# Try to just parse the time as given # Try to just parse the time as given

View File

@@ -1,17 +1,27 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
import textwrap
from argparse import ArgumentDefaultsHelpFormatter as def_form from argparse import ArgumentDefaultsHelpFormatter as def_form
from argparse import RawDescriptionHelpFormatter as raw_form
def setup(self, sub): def setup(self, sub):
cmd = sub.add_parser("create", help="Create a new stream", cmd = sub.add_parser("create", help="Create a new stream",
formatter_class = def_form, formatter_class = raw_form,
description=""" description="""
Create a new empty stream at the Create a new empty stream at the specified path and with the specified
specified path and with the specifed layout type.
layout type.
""") Layout types are of the format: type_count
'type' is a data type like 'float32', 'float64', 'uint16', 'int32', etc.
'count' is the number of columns of this type.
For example, 'float32_8' means the data for this stream has 8 columns of
32-bit floating point values.
""")
cmd.set_defaults(handler = cmd_create) cmd.set_defaults(handler = cmd_create)
group = cmd.add_argument_group("Required arguments") group = cmd.add_argument_group("Required arguments")
group.add_argument("path", group.add_argument("path",

View File

@@ -1,5 +1,5 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
from argparse import ArgumentDefaultsHelpFormatter as def_form from argparse import ArgumentDefaultsHelpFormatter as def_form

View File

@@ -1,6 +1,6 @@
from __future__ import absolute_import
from __future__ import print_function from __future__ import print_function
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
import sys import sys
@@ -28,6 +28,8 @@ def setup(self, sub):
group.add_argument("-a", "--annotate", action="store_true", group.add_argument("-a", "--annotate", action="store_true",
help="Include comments with some information " help="Include comments with some information "
"about the stream") "about the stream")
group.add_argument("-T", "--timestamp-raw", action="store_true",
help="Show raw timestamps in annotated information")
group.add_argument("-c", "--count", action="store_true", group.add_argument("-c", "--count", action="store_true",
help="Just output a count of matched data points") help="Just output a count of matched data points")
@@ -42,11 +44,16 @@ def cmd_extract(self):
self.die("error getting stream info for path %s", self.args.path) self.die("error getting stream info for path %s", self.args.path)
layout = streams[0][1] layout = streams[0][1]
if self.args.timestamp_raw:
time_string = repr
else:
time_string = self.time_string
if self.args.annotate: if self.args.annotate:
printf("# path: %s\n", self.args.path) printf("# path: %s\n", self.args.path)
printf("# layout: %s\n", layout) printf("# layout: %s\n", layout)
printf("# start: %s\n", self.time_string(self.args.start)) printf("# start: %s\n", time_string(self.args.start))
printf("# end: %s\n", self.time_string(self.args.end)) printf("# end: %s\n", time_string(self.args.end))
printed = False printed = False
for dataline in self.client.stream_extract(self.args.path, for dataline in self.client.stream_extract(self.args.path,

View File

@@ -1,4 +1,3 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
from argparse import ArgumentDefaultsHelpFormatter as def_form from argparse import ArgumentDefaultsHelpFormatter as def_form

View File

@@ -1,7 +1,7 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
import nilmdb.timestamper import nilmdb.utils.timestamper as timestamper
import sys import sys
@@ -69,7 +69,7 @@ def cmd_insert(self):
# Build a timestamper for this file # Build a timestamper for this file
if self.args.none: if self.args.none:
ts = nilmdb.timestamper.TimestamperNull(infile) ts = timestamper.TimestamperNull(infile)
else: else:
if self.args.start: if self.args.start:
start = self.args.start start = self.args.start
@@ -84,7 +84,7 @@ def cmd_insert(self):
self.die("error: --rate is needed, but was not specified") self.die("error: --rate is needed, but was not specified")
rate = self.args.rate rate = self.args.rate
ts = nilmdb.timestamper.TimestamperRate(infile, start, rate) ts = timestamper.TimestamperRate(infile, start, rate)
# Print info # Print info
if not self.args.quiet: if not self.args.quiet:

View File

@@ -1,5 +1,5 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
import fnmatch import fnmatch
@@ -28,6 +28,8 @@ def setup(self, sub):
group = cmd.add_argument_group("Interval details") group = cmd.add_argument_group("Interval details")
group.add_argument("-d", "--detail", action="store_true", group.add_argument("-d", "--detail", action="store_true",
help="Show available data time intervals") help="Show available data time intervals")
group.add_argument("-T", "--timestamp-raw", action="store_true",
help="Show raw timestamps in time intervals")
group.add_argument("-s", "--start", group.add_argument("-s", "--start",
metavar="TIME", type=self.arg_time, metavar="TIME", type=self.arg_time,
help="Starting timestamp (free-form, inclusive)") help="Starting timestamp (free-form, inclusive)")
@@ -53,6 +55,12 @@ def cmd_list_verify(self):
def cmd_list(self): def cmd_list(self):
"""List available streams""" """List available streams"""
streams = self.client.stream_list() streams = self.client.stream_list()
if self.args.timestamp_raw:
time_string = repr
else:
time_string = self.time_string
for (path, layout) in streams: for (path, layout) in streams:
if not (fnmatch.fnmatch(path, self.args.path) and if not (fnmatch.fnmatch(path, self.args.path) and
fnmatch.fnmatch(layout, self.args.layout)): fnmatch.fnmatch(layout, self.args.layout)):
@@ -65,9 +73,7 @@ def cmd_list(self):
printed = False printed = False
for (start, end) in self.client.stream_intervals(path, self.args.start, for (start, end) in self.client.stream_intervals(path, self.args.start,
self.args.end): self.args.end):
printf(" [ %s -> %s ]\n", printf(" [ %s -> %s ]\n", time_string(start), time_string(end))
self.time_string(start),
self.time_string(end))
printed = True printed = True
if not printed: if not printed:
printf(" (no intervals)\n") printf(" (no intervals)\n")

View File

@@ -1,5 +1,5 @@
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
def setup(self, sub): def setup(self, sub):

View File

@@ -1,6 +1,5 @@
from __future__ import absolute_import
from __future__ import print_function
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb
import nilmdb.client import nilmdb.client
import sys import sys

15
nilmdb/server/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
"""nilmdb.server"""
# Try to set up pyximport to automatically rebuild Cython modules. If
# this doesn't work, it's OK, as long as the modules were built externally.
# (e.g. python setup.py build_ext --inplace)
try:
import pyximport
pyximport.install()
import layout
except: # pragma: no cover
pass
from .nilmdb import NilmDB
from .server import Server
from .errors import *

View File

@@ -1,5 +1,7 @@
# Fixed record size bulk data storage # Fixed record size bulk data storage
# Need absolute_import so that "import nilmdb" won't pull in
# nilmdb.py, but will pull the parent nilmdb module instead.
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import division from __future__ import division
import nilmdb import nilmdb
@@ -75,7 +77,7 @@ class BulkData(object):
# Get layout, and build format string for struct module # Get layout, and build format string for struct module
try: try:
layout = nilmdb.layout.get_named(layout_name) layout = nilmdb.server.layout.get_named(layout_name)
struct_fmt = '<d' # Little endian, double timestamp struct_fmt = '<d' # Little endian, double timestamp
struct_mapping = { struct_mapping = {
"int8": 'b', "int8": 'b',

12
nilmdb/server/errors.py Normal file
View File

@@ -0,0 +1,12 @@
"""Exceptions"""
class NilmDBError(Exception):
"""Base exception for NilmDB errors"""
def __init__(self, message = "Unspecified error"):
Exception.__init__(self, message)
class StreamError(NilmDBError):
pass
class OverlapError(NilmDBError):
pass

View File

@@ -170,7 +170,7 @@ class Parser(object):
if line[0] == '\#': if line[0] == '\#':
continue continue
(ts, row) = self.layout.parse(line) (ts, row) = self.layout.parse(line)
if ts < last_ts: if ts <= last_ts:
raise ValueError("timestamp is not " raise ValueError("timestamp is not "
"monotonically increasing") "monotonically increasing")
last_ts = ts last_ts = ts

View File

@@ -7,11 +7,15 @@ Object that represents a NILM database file.
Manages both the SQL database and the table storage backend. Manages both the SQL database and the table storage backend.
""" """
# Need absolute_import so that "import nilmdb" won't pull in nilmdb.py, # Need absolute_import so that "import nilmdb" won't pull in
# but will pull the nilmdb module instead. # nilmdb.py, but will pull the parent nilmdb module instead.
from __future__ import absolute_import from __future__ import absolute_import
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
from nilmdb.server.interval import (Interval, DBInterval,
IntervalSet, IntervalError)
from nilmdb.server import bulkdata
from nilmdb.server.errors import *
import sqlite3 import sqlite3
import time import time
@@ -20,12 +24,6 @@ import os
import errno import errno
import bisect import bisect
import pyximport
pyximport.install()
from nilmdb.interval import Interval, DBInterval, IntervalSet, IntervalError
from . import bulkdata
# Note about performance and transactions: # Note about performance and transactions:
# #
# Committing a transaction in the default sync mode (PRAGMA synchronous=FULL) # Committing a transaction in the default sync mode (PRAGMA synchronous=FULL)
@@ -77,17 +75,6 @@ _sql_schema_updates = {
""", """,
} }
class NilmDBError(Exception):
"""Base exception for NilmDB errors"""
def __init__(self, message = "Unspecified error"):
Exception.__init__(self, message)
class StreamError(NilmDBError):
pass
class OverlapError(NilmDBError):
pass
@nilmdb.utils.must_close() @nilmdb.utils.must_close()
class NilmDB(object): class NilmDB(object):
verbose = 0 verbose = 0

View File

@@ -1,10 +1,11 @@
"""CherryPy-based server for accessing NILM database via HTTP""" """CherryPy-based server for accessing NILM database via HTTP"""
# Need absolute_import so that "import nilmdb" won't pull in nilmdb.py, # Need absolute_import so that "import nilmdb" won't pull in
# but will pull the nilmdb module instead. # nilmdb.py, but will pull the nilmdb module instead.
from __future__ import absolute_import from __future__ import absolute_import
from nilmdb.utils.printf import *
import nilmdb import nilmdb
from nilmdb.utils.printf import *
from nilmdb.server.errors import *
import cherrypy import cherrypy
import sys import sys
@@ -14,8 +15,6 @@ import simplejson as json
import decorator import decorator
import traceback import traceback
from nilmdb.nilmdb import NilmDBError
try: try:
import cherrypy import cherrypy
cherrypy.tools.json_out cherrypy.tools.json_out
@@ -37,6 +36,14 @@ def chunked_response(func):
func._cp_config = { 'response.stream': True } func._cp_config = { 'response.stream': True }
return func return func
def response_type(content_type):
"""Return a decorator-generating function that sets the
response type to the specified string."""
def wrapper(func, *args, **kwargs):
cherrypy.response.headers['Content-Type'] = content_type
return func(*args, **kwargs)
return decorator.decorator(wrapper)
@decorator.decorator @decorator.decorator
def workaround_cp_bug_1200(func, *args, **kwargs): # pragma: no cover def workaround_cp_bug_1200(func, *args, **kwargs): # pragma: no cover
"""Decorator to work around CherryPy bug #1200 in a response """Decorator to work around CherryPy bug #1200 in a response
@@ -151,7 +158,7 @@ class Stream(NilmApp):
matching the given keys.""" matching the given keys."""
try: try:
data = self.db.stream_get_metadata(path) data = self.db.stream_get_metadata(path)
except nilmdb.nilmdb.StreamError as e: except nilmdb.server.nilmdb.StreamError as e:
raise cherrypy.HTTPError("404 Not Found", e.message) raise cherrypy.HTTPError("404 Not Found", e.message)
if key is None: # If no keys specified, return them all if key is None: # If no keys specified, return them all
key = data.keys() key = data.keys()
@@ -214,9 +221,9 @@ class Stream(NilmApp):
# Parse the input data # Parse the input data
try: try:
parser = nilmdb.layout.Parser(layout) parser = nilmdb.server.layout.Parser(layout)
parser.parse(body) parser.parse(body)
except nilmdb.layout.ParserError as e: except nilmdb.server.layout.ParserError as e:
raise cherrypy.HTTPError("400 Bad Request", raise cherrypy.HTTPError("400 Bad Request",
"error parsing input data: " + "error parsing input data: " +
e.message) e.message)
@@ -241,7 +248,7 @@ class Stream(NilmApp):
# Now do the nilmdb insert, passing it the parser full of data. # Now do the nilmdb insert, passing it the parser full of data.
try: try:
result = self.db.stream_insert(path, start, end, parser.data) result = self.db.stream_insert(path, start, end, parser.data)
except nilmdb.nilmdb.NilmDBError as e: except NilmDBError as e:
raise cherrypy.HTTPError("400 Bad Request", e.message) raise cherrypy.HTTPError("400 Bad Request", e.message)
# Done # Done
@@ -272,12 +279,17 @@ class Stream(NilmApp):
# /stream/intervals?path=/newton/prep&start=1234567890.0&end=1234567899.0 # /stream/intervals?path=/newton/prep&start=1234567890.0&end=1234567899.0
@cherrypy.expose @cherrypy.expose
@chunked_response @chunked_response
@response_type("text/plain")
def intervals(self, path, start = None, end = None): def intervals(self, path, start = None, end = None):
""" """
Get intervals from backend database. Streams the resulting Get intervals from backend database. Streams the resulting
intervals as JSON strings separated by newlines. This may intervals as JSON strings separated by newlines. This may
make multiple requests to the nilmdb backend to avoid causing make multiple requests to the nilmdb backend to avoid causing
it to block for too long. it to block for too long.
Note that the response type is set to 'text/plain' even
though we're sending back JSON; this is because we're not
really returning a single JSON object.
""" """
if start is not None: if start is not None:
start = float(start) start = float(start)
@@ -308,6 +320,7 @@ class Stream(NilmApp):
# /stream/extract?path=/newton/prep&start=1234567890.0&end=1234567899.0 # /stream/extract?path=/newton/prep&start=1234567890.0&end=1234567899.0
@cherrypy.expose @cherrypy.expose
@chunked_response @chunked_response
@response_type("text/plain")
def extract(self, path, start = None, end = None, count = False): def extract(self, path, start = None, end = None, count = False):
""" """
Extract data from backend database. Streams the resulting Extract data from backend database. Streams the resulting
@@ -335,7 +348,7 @@ class Stream(NilmApp):
layout = streams[0][1] layout = streams[0][1]
# Get formatter # Get formatter
formatter = nilmdb.layout.Formatter(layout) formatter = nilmdb.server.layout.Formatter(layout)
@workaround_cp_bug_1200 @workaround_cp_bug_1200
def content(start, end, count): def content(start, end, count):
@@ -390,6 +403,12 @@ class Server(object):
if self.embedded: if self.embedded:
cherrypy.config.update({ 'environment': 'embedded' }) cherrypy.config.update({ 'environment': 'embedded' })
# Send a permissive Access-Control-Allow-Origin (CORS) header
# with all responses so that browsers can send cross-domain
# requests to this server.
cherrypy.config.update({ 'response.headers.Access-Control-Allow-Origin':
'*' })
# Send tracebacks in error responses. They're hidden by the # Send tracebacks in error responses. They're hidden by the
# error_page function for client errors (code 400-499). # error_page function for client errors (code 400-499).
cherrypy.config.update({ 'request.show_tracebacks' : True }) cherrypy.config.update({ 'request.show_tracebacks' : True })
@@ -462,8 +481,10 @@ class Server(object):
cherrypy.engine.start() cherrypy.engine.start()
os._exit = real_exit os._exit = real_exit
# Signal that the engine has started successfully
if event is not None: if event is not None:
event.set() event.set()
if blocking: if blocking:
try: try:
cherrypy.engine.wait(cherrypy.engine.states.EXITING, cherrypy.engine.wait(cherrypy.engine.states.EXITING,

View File

@@ -1,4 +1,3 @@
import nilmdb
import os import os
from math import log from math import log

View File

@@ -1,14 +1,16 @@
import Queue import Queue
import threading import threading
import sys import sys
import contextlib
# This file provides a class that will convert a function that # This file provides a context manager that converts a function
# takes a callback into a generator that returns an iterator. # that takes a callback into a generator that returns an iterable.
# This is done by running the function in a new thread.
# Based partially on http://stackoverflow.com/questions/9968592/ # Based partially on http://stackoverflow.com/questions/9968592/
class IteratorizerThread(threading.Thread): class IteratorizerThread(threading.Thread):
def __init__(self, queue, function): def __init__(self, queue, function, curl_hack):
""" """
function: function to execute, which takes the function: function to execute, which takes the
callback (provided by this class) as an argument callback (provided by this class) as an argument
@@ -17,56 +19,81 @@ class IteratorizerThread(threading.Thread):
self.function = function self.function = function
self.queue = queue self.queue = queue
self.die = False self.die = False
self.curl_hack = curl_hack
def callback(self, data): def callback(self, data):
if self.die: try:
raise Exception("should die") if self.die:
self.queue.put((1, data)) raise Exception() # trigger termination
self.queue.put((1, data))
except:
if self.curl_hack:
# We can't raise exceptions, because the pycurl
# extension module will unconditionally print the
# exception itself, and not pass it up to the caller.
# Instead, just return a value that tells curl to
# abort. (-1 would be best, in case we were given 0
# bytes, but the extension doesn't support that).
self.queue.put((2, sys.exc_info()))
return 0
raise
def run(self): def run(self):
try: try:
result = self.function(self.callback) result = self.function(self.callback)
except: except:
if sys is not None: # can be None during unclean shutdown self.queue.put((2, sys.exc_info()))
self.queue.put((2, sys.exc_info()))
else: else:
self.queue.put((0, result)) self.queue.put((0, result))
class Iteratorizer(object): @contextlib.contextmanager
def __init__(self, function): def Iteratorizer(function, curl_hack = False):
""" """
function: function to execute, which takes the Context manager that takes a function expecting a callback,
callback (provided by this class) as an argument and provides an iterable that yields the values passed to that
""" callback instead.
self.function = function
self.queue = Queue.Queue(maxsize = 1)
self.thread = IteratorizerThread(self.queue, self.function)
self.thread.daemon = True
self.thread.start()
def __del__(self): function: function to execute, which takes a callback
# If we get garbage collected, try to get rid of the (provided by this context manager) as an argument
# thread too by asking it to raise an exception, then
# draining the queue until it's gone. with iteratorizer(func) as it:
self.thread.die = True for i in it:
while self.thread.isAlive(): print 'callback was passed:', i
print 'function returned:', it.retval
"""
queue = Queue.Queue(maxsize = 1)
thread = IteratorizerThread(queue, function, curl_hack)
thread.daemon = True
thread.start()
class iteratorizer_gen(object):
def __init__(self, queue):
self.queue = queue
self.retval = None
def __iter__(self):
return self
def next(self):
(typ, data) = self.queue.get()
if typ == 0:
# function has returned
self.retval = data
raise StopIteration
elif typ == 1:
# data is available
return data
else:
# callback raised an exception
raise data[0], data[1], data[2]
try:
yield iteratorizer_gen(queue)
finally:
# Ask the thread to die, if it's still running.
thread.die = True
while thread.isAlive():
try: try:
self.queue.get(True, 0.01) queue.get(True, 0.01)
except: # pragma: no cover except:
pass pass
def __iter__(self):
return self
def next(self):
(typ, data) = self.queue.get()
if typ == 0:
# function returned
self.retval = data
raise StopIteration
elif typ == 1:
# data available
return data
else:
# exception
raise data[0], data[1], data[2]

View File

@@ -1,11 +1,10 @@
"""File-like objects that add timestamps to the input lines""" """File-like objects that add timestamps to the input lines"""
from __future__ import absolute_import
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
from nilmdb.utils import datetime_tz
import time import time
import os import os
import datetime_tz
class Timestamper(object): class Timestamper(object):
"""A file-like object that adds timestamps to lines of an input file.""" """A file-like object that adds timestamps to lines of an input file."""

View File

@@ -20,9 +20,6 @@ def urlencode(query):
v = quote_plus(v) v = quote_plus(v)
l.append(k + '=' + v) l.append(k + '=' + v)
elif _is_unicode(v): elif _is_unicode(v):
# is there a reasonable way to convert to ASCII?
# encode generates a string, but "replace" or "ignore"
# lose information and "strict" can raise UnicodeError
v = quote_plus(v.encode("utf-8","strict")) v = quote_plus(v.encode("utf-8","strict"))
l.append(k + '=' + v) l.append(k + '=' + v)
else: else:

View File

@@ -1,16 +1,23 @@
[aliases]
test = nosetests
[nosetests] [nosetests]
# note: the value doesn't matter, that's why they're empty here # Note: values must be set to 1, and have no comments on the same line,
nocapture= # for "python setup.py nosetests" to work correctly.
nologcapture= # comment to see cherrypy logs on failure nocapture=1
with-coverage= # Comment this out to see CherryPy logs on failure:
cover-inclusive= nologcapture=1
with-coverage=1
cover-inclusive=1
cover-package=nilmdb cover-package=nilmdb
cover-erase= cover-erase=1
##cover-html= # this works, puts html output in cover/ dir # this works, puts html output in cover/ dir:
##cover-branches= # need nose 1.1.3 for this # cover-html=1
# need nose 1.1.3 for this:
# cover-branches=1
#debug=nose #debug=nose
#debug-log=nose.log #debug-log=nose.log
stop= stop=1
verbosity=2 verbosity=2
tests=tests tests=tests
#tests=tests/test_bulkdata.py #tests=tests/test_bulkdata.py
@@ -28,6 +35,6 @@ tests=tests
#tests=tests/test_iteratorizer.py #tests=tests/test_iteratorizer.py
#tests=tests/test_client.py:TestClient.test_client_nilmdb #tests=tests/test_client.py:TestClient.test_client_nilmdb
#tests=tests/test_nilmdb.py #tests=tests/test_nilmdb.py
#with-profile= #with-profile=1
#profile-sort=time #profile-sort=time
##profile-restrict=10 # doesn't work right, treated as string or something ##profile-restrict=10 # doesn't work right, treated as string or something

48
setup.py Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/python
# This is supposed to be using Distribute:
#
# distutils provides a "setup" method.
# setuptools is a set of monkeypatches on top of that.
# distribute is a particular version/implementation of setuptools.
#
# So we don't really know if this is using the old setuptools or the
# Distribute-provided version of setuptools.
from setuptools import setup, find_packages
from distutils.extension import Extension
from Cython.Build import cythonize
# Hack to workaround logging/multiprocessing issue:
# https://groups.google.com/d/msg/nose-users/fnJ-kAUbYHQ/_UsLN786ygcJ
try: import multiprocessing
except: pass
# Build cython modules.
cython_modules = cythonize("**/*.pyx")
# Run setup
setup(name='nilmdb',
version = '1.0',
url = 'https://git.jim.sh/jim/lees/nilmdb.git',
author = 'Jim Paris',
author_email = 'jim@jtan.com',
tests_require = [ 'nose',
'coverage',
],
setup_requires = [ 'cython',
],
install_requires = [ 'distribute',
'decorator',
],
packages = [ 'nilmdb',
'nilmdb.utils',
'nilmdb.utils.datetime_tz',
'nilmdb.server',
'nilmdb.client',
'nilmdb.cmdline',
],
ext_modules = cython_modules,
zip_safe = False,
)

124
tests/data/extract-7 Normal file
View File

@@ -0,0 +1,124 @@
# path: /newton/prep
# layout: PrepData
# start: 1332496830.0
# end: 1332496830.999
1332496830.000000 251774.000000 224241.000000 5688.100098 1915.530029 9329.219727 4183.709961 1212.349976 2641.790039
1332496830.008333 259567.000000 222698.000000 6207.600098 678.671997 9380.230469 4575.580078 2830.610107 2688.629883
1332496830.016667 263073.000000 223304.000000 4961.640137 2197.120117 7687.310059 4861.859863 2732.780029 3008.540039
1332496830.025000 257614.000000 223323.000000 5003.660156 3525.139893 7165.310059 4685.620117 1715.380005 3440.479980
1332496830.033333 255780.000000 221915.000000 6357.310059 2145.290039 8426.969727 3775.350098 1475.390015 3797.239990
1332496830.041667 260166.000000 223008.000000 6702.589844 1484.959961 9288.099609 3330.830078 1228.500000 3214.320068
1332496830.050000 261231.000000 226426.000000 4980.060059 2982.379883 8499.629883 4267.669922 994.088989 2292.889893
1332496830.058333 255117.000000 226642.000000 4584.410156 4656.439941 7860.149902 5317.310059 1473.599976 2111.689941
1332496830.066667 253300.000000 223554.000000 6455.089844 3036.649902 8869.750000 4986.310059 2607.360107 2839.590088
1332496830.075000 261061.000000 221263.000000 6951.979980 1500.239990 9386.099609 3791.679932 2677.010010 3980.629883
1332496830.083333 266503.000000 223198.000000 5189.609863 2594.560059 8571.530273 3175.000000 919.840027 3792.010010
1332496830.091667 260692.000000 225184.000000 3782.479980 4642.879883 7662.959961 3917.790039 -251.097000 2907.060059
1332496830.100000 253963.000000 225081.000000 5123.529785 3839.550049 8669.030273 4877.819824 943.723999 2527.449951
1332496830.108333 256555.000000 224169.000000 5930.600098 2298.540039 8906.709961 5331.680176 2549.909912 3053.560059
1332496830.116667 260889.000000 225010.000000 4681.129883 2971.870117 7900.040039 4874.080078 2322.429932 3649.120117
1332496830.125000 257944.000000 224923.000000 3291.139893 4357.089844 7131.589844 4385.560059 1077.050049 3664.040039
1332496830.133333 255009.000000 223018.000000 4584.819824 2864.000000 8469.490234 3625.580078 985.557007 3504.229980
1332496830.141667 260114.000000 221947.000000 5676.189941 1210.339966 9393.780273 3390.239990 1654.020020 3018.699951
1332496830.150000 264277.000000 224438.000000 4446.620117 2176.719971 8142.089844 4584.879883 2327.830078 2615.800049
1332496830.158333 259221.000000 226471.000000 2734.439941 4182.759766 6389.549805 5540.520020 1958.880005 2720.120117
1332496830.166667 252650.000000 224831.000000 4163.640137 2989.989990 7179.200195 5213.060059 1929.550049 3457.659912
1332496830.175000 257083.000000 222048.000000 5759.040039 702.440979 8566.549805 3552.020020 1832.939941 3956.189941
1332496830.183333 263130.000000 222967.000000 5141.140137 1166.119995 8666.959961 2720.370117 971.374023 3479.729980
1332496830.191667 260236.000000 225265.000000 3425.139893 3339.080078 7853.609863 3674.949951 525.908020 2443.310059
1332496830.200000 253503.000000 224527.000000 4398.129883 2927.429932 8110.279785 4842.470215 1513.869995 2467.100098
1332496830.208333 256126.000000 222693.000000 6043.529785 656.223999 8797.559570 4832.410156 2832.370117 3426.139893
1332496830.216667 261677.000000 223608.000000 5830.459961 1033.910034 8123.939941 3980.689941 1927.959961 4092.719971
1332496830.225000 259457.000000 225536.000000 4015.570068 2995.989990 7135.439941 3713.550049 307.220001 3849.429932
1332496830.233333 253352.000000 224216.000000 4650.560059 3196.620117 8131.279785 3586.159912 70.832298 3074.179932
1332496830.241667 256124.000000 221513.000000 6100.479980 821.979980 9757.540039 3474.510010 1647.520020 2559.860107
1332496830.250000 263024.000000 221559.000000 5789.959961 699.416992 9129.740234 4153.080078 2829.250000 2677.270020
1332496830.258333 261720.000000 224015.000000 4358.500000 2645.360107 7414.109863 4810.669922 2225.989990 3185.989990
1332496830.266667 254756.000000 224240.000000 4857.379883 3229.679932 7539.310059 4769.140137 1507.130005 3668.260010
1332496830.275000 256889.000000 222658.000000 6473.419922 1214.109985 9010.759766 3848.729980 1303.839966 3778.500000
1332496830.283333 264208.000000 223316.000000 5700.450195 1116.560059 9087.610352 3846.679932 1293.589966 2891.560059
1332496830.291667 263310.000000 225719.000000 3936.120117 3252.360107 7552.850098 4897.859863 1156.630005 2037.160034
1332496830.300000 255079.000000 225086.000000 4536.450195 3960.110107 7454.589844 5479.069824 1596.359985 2190.800049
1332496830.308333 254487.000000 222508.000000 6635.859863 1758.849976 8732.969727 4466.970215 2650.360107 3139.310059
1332496830.316667 261241.000000 222432.000000 6702.270020 1085.130005 8989.230469 3112.989990 1933.560059 3828.409912
1332496830.325000 262119.000000 225587.000000 4714.950195 2892.360107 8107.819824 2961.310059 239.977997 3273.719971
1332496830.333333 254999.000000 226514.000000 4532.089844 4126.899902 8200.129883 3872.590088 56.089001 2370.580078
1332496830.341667 254289.000000 224033.000000 6538.810059 2251.439941 9419.429688 4564.450195 2077.810059 2508.169922
1332496830.350000 261890.000000 221960.000000 6846.089844 1475.270020 9125.589844 4598.290039 3299.219971 3475.419922
1332496830.358333 264502.000000 223085.000000 5066.379883 3270.560059 7933.169922 4173.709961 1908.910034 3867.459961
1332496830.366667 257889.000000 223656.000000 4201.660156 4473.640137 7688.339844 4161.580078 687.578979 3653.689941
1332496830.375000 254270.000000 223151.000000 5715.140137 2752.139893 9273.320312 3772.949951 896.403992 3256.060059
1332496830.383333 258257.000000 224217.000000 6114.310059 1856.859985 9604.320312 4200.490234 1764.380005 2939.219971
1332496830.391667 260020.000000 226868.000000 4237.529785 3605.879883 8066.220215 5430.250000 2138.580078 2696.709961
1332496830.400000 255083.000000 225924.000000 3350.310059 4853.069824 7045.819824 5925.200195 1893.609985 2897.340088
1332496830.408333 254453.000000 222127.000000 5271.330078 2491.500000 8436.679688 5032.080078 2436.050049 3724.590088
1332496830.416667 262588.000000 219950.000000 5994.620117 789.273987 9029.650391 3515.739990 1953.569946 4014.520020
1332496830.425000 265610.000000 223333.000000 4391.410156 2400.959961 8146.459961 3536.959961 530.231995 3133.919922
1332496830.433333 257470.000000 226977.000000 2975.320068 4633.529785 7278.560059 4640.100098 -50.150200 2024.959961
1332496830.441667 250687.000000 226331.000000 4517.859863 3183.800049 8072.600098 5281.660156 1605.140015 2335.139893
1332496830.450000 255563.000000 224495.000000 5551.000000 1101.300049 8461.490234 4725.700195 2726.669922 3480.540039
1332496830.458333 261335.000000 224645.000000 4764.680176 1557.020020 7833.350098 3524.810059 1577.410034 4038.620117
1332496830.466667 260269.000000 224008.000000 3558.030029 2987.610107 7362.439941 3279.229980 562.442017 3786.550049
1332496830.475000 257435.000000 221777.000000 4972.600098 2166.879883 8481.440430 3328.719971 1037.130005 3271.370117
1332496830.483333 261046.000000 221550.000000 5816.180176 590.216980 9120.929688 3895.399902 2382.669922 2824.169922
1332496830.491667 262766.000000 224473.000000 4835.049805 1785.770020 7880.759766 4745.620117 2443.659912 3229.550049
1332496830.500000 256509.000000 226413.000000 3758.870117 3461.199951 6743.770020 4928.959961 1536.619995 3546.689941
1332496830.508333 250793.000000 224372.000000 5218.490234 2865.260010 7803.959961 4351.089844 1333.819946 3680.489990
1332496830.516667 256319.000000 222066.000000 6403.970215 732.344971 9627.759766 3089.300049 1516.780029 3653.689941
1332496830.525000 263343.000000 223235.000000 5200.430176 1388.579956 9372.849609 3371.229980 1450.390015 2678.909912
1332496830.533333 260903.000000 225110.000000 3722.580078 3246.659912 7876.540039 4716.810059 1498.439941 2116.520020
1332496830.541667 254416.000000 223769.000000 4841.649902 2956.399902 8115.919922 5392.359863 2142.810059 2652.320068
1332496830.550000 256698.000000 222172.000000 6471.229980 970.395996 8834.980469 4816.839844 2376.629883 3605.860107
1332496830.558333 261841.000000 223537.000000 5500.740234 1189.660034 8365.730469 4016.469971 1042.270020 3821.199951
1332496830.566667 259503.000000 225840.000000 3827.929932 3088.840088 7676.140137 3978.310059 -357.006989 3016.419922
1332496830.575000 253457.000000 224636.000000 4914.609863 3097.449951 8224.900391 4321.439941 171.373993 2412.360107
1332496830.583333 256029.000000 222221.000000 6841.799805 1028.500000 9252.299805 4387.569824 2418.139893 2510.100098
1332496830.591667 262840.000000 222550.000000 6210.250000 1410.729980 8538.900391 4152.580078 3009.300049 3219.760010
1332496830.600000 261633.000000 225065.000000 4284.529785 3357.209961 7282.169922 3823.590088 1402.839966 3644.669922
1332496830.608333 254591.000000 225109.000000 4693.160156 3647.739990 7745.160156 3686.379883 490.161011 3448.860107
1332496830.616667 254780.000000 223599.000000 6527.379883 1569.869995 9438.429688 3456.580078 1162.520020 3252.010010
1332496830.625000 260639.000000 224107.000000 6531.049805 1633.050049 9283.719727 4174.020020 2089.550049 2775.750000
1332496830.633333 261108.000000 225472.000000 4968.259766 3527.850098 7692.870117 5137.100098 2207.389893 2436.659912
1332496830.641667 255775.000000 223708.000000 4963.450195 4017.370117 7701.419922 5269.649902 2284.399902 2842.080078
1332496830.650000 257398.000000 220947.000000 6767.500000 1645.709961 9107.070312 4000.179932 2548.860107 3624.770020
1332496830.658333 264924.000000 221559.000000 6471.459961 1110.329956 9459.650391 3108.169922 1696.969971 3893.439941
1332496830.666667 265339.000000 225733.000000 4348.799805 3459.510010 8475.299805 4031.239990 573.346985 2910.270020
1332496830.675000 256814.000000 226995.000000 3479.540039 4949.790039 7499.910156 5624.709961 751.656006 2347.709961
1332496830.683333 253316.000000 225161.000000 5147.060059 3218.429932 8460.160156 5869.299805 2336.320068 2987.959961
1332496830.691667 259360.000000 223101.000000 5549.120117 1869.949951 8740.759766 4668.939941 2457.909912 3758.820068
1332496830.700000 262012.000000 224016.000000 4173.609863 3004.129883 8157.040039 3704.729980 987.963989 3652.750000
1332496830.708333 257176.000000 224420.000000 3517.300049 4118.750000 7822.240234 3718.229980 37.264900 2953.679932
1332496830.716667 255146.000000 223322.000000 4923.979980 2330.679932 9095.910156 3792.399902 1013.070007 2711.239990
1332496830.725000 260524.000000 223651.000000 5413.629883 1146.209961 8817.169922 4419.649902 2446.649902 2832.050049
1332496830.733333 262098.000000 225752.000000 4262.979980 2270.969971 7135.479980 5067.120117 2294.679932 3376.620117
1332496830.741667 256889.000000 225379.000000 3606.459961 3568.189941 6552.649902 4970.270020 1516.380005 3662.570068
1332496830.750000 253948.000000 222631.000000 5511.700195 2066.300049 7952.660156 4019.909912 1513.140015 3752.629883
1332496830.758333 259799.000000 222067.000000 5873.500000 608.583984 9253.780273 2870.739990 1348.239990 3344.199951
1332496830.766667 262547.000000 224901.000000 4346.080078 1928.099976 8590.969727 3455.459961 904.390991 2379.270020
1332496830.775000 256137.000000 226761.000000 3423.560059 3379.080078 7471.149902 4894.169922 1153.540039 2031.410034
1332496830.783333 250326.000000 225013.000000 5519.979980 2423.969971 7991.759766 5117.950195 2098.790039 3099.239990
1332496830.791667 255454.000000 222992.000000 6547.950195 496.496002 8751.339844 3900.560059 2132.290039 4076.810059
1332496830.800000 261286.000000 223489.000000 5152.850098 1501.510010 8425.610352 2888.030029 776.114014 3786.360107
1332496830.808333 258969.000000 224069.000000 3832.610107 3001.979980 7979.259766 3182.310059 52.716000 2874.800049
1332496830.816667 254946.000000 222035.000000 5317.879883 2139.800049 9103.139648 3955.610107 1235.170044 2394.149902
1332496830.825000 258676.000000 221205.000000 6594.910156 505.343994 9423.360352 4562.470215 2913.739990 2892.350098
1332496830.833333 262125.000000 223566.000000 5116.750000 1773.599976 8082.200195 4776.370117 2386.389893 3659.729980
1332496830.841667 257835.000000 225918.000000 3714.300049 3477.080078 7205.370117 4554.609863 711.539001 3878.419922
1332496830.850000 253660.000000 224371.000000 5022.450195 2592.429932 8277.200195 4119.370117 486.507996 3666.739990
1332496830.858333 259503.000000 222061.000000 6589.950195 659.935974 9596.919922 3598.100098 1702.489990 3036.600098
1332496830.866667 265495.000000 222843.000000 5541.850098 1728.430054 8459.959961 4492.000000 2231.969971 2430.620117
1332496830.875000 260929.000000 224996.000000 4000.949951 3745.989990 6983.790039 5430.859863 1855.260010 2533.379883
1332496830.883333 252716.000000 224335.000000 5086.560059 3401.149902 7597.970215 5196.120117 1755.719971 3079.760010
1332496830.891667 254110.000000 223111.000000 6822.189941 1229.079956 9164.339844 3761.229980 1679.390015 3584.879883
1332496830.900000 259969.000000 224693.000000 6183.950195 1538.500000 9222.080078 3139.169922 949.901978 3180.800049
1332496830.908333 259078.000000 226913.000000 4388.890137 3694.820068 8195.019531 3933.000000 426.079987 2388.449951
1332496830.916667 254563.000000 224760.000000 5168.439941 4020.939941 8450.269531 4758.910156 1458.900024 2286.429932
1332496830.925000 258059.000000 221217.000000 6883.459961 1649.530029 9232.780273 4457.649902 3057.820068 3031.949951
1332496830.933333 264667.000000 221177.000000 6218.509766 1645.729980 8657.179688 3663.500000 2528.280029 3978.340088
1332496830.941667 262925.000000 224382.000000 4627.500000 3635.929932 7892.799805 3431.320068 604.508972 3901.370117
1332496830.950000 254708.000000 225448.000000 4408.250000 4461.040039 8197.169922 3953.750000 -44.534599 3154.870117
1332496830.958333 253702.000000 224635.000000 5825.770020 2577.050049 9590.049805 4569.250000 1460.270020 2785.169922
1332496830.966667 260206.000000 224140.000000 5387.979980 1951.160034 8789.509766 5131.660156 2706.379883 2972.479980
1332496830.975000 261240.000000 224737.000000 3860.810059 3418.310059 7414.529785 5284.520020 2271.379883 3183.149902
1332496830.983333 256140.000000 223252.000000 3850.010010 3957.139893 7262.649902 4964.640137 1499.510010 3453.129883
1332496830.991667 256116.000000 221349.000000 5594.479980 2054.399902 8835.129883 3662.010010 1485.510010 3613.010010

View File

@@ -0,0 +1,11 @@
1332497040.000000 2.56439e+05 2.24775e+05 2.92897e+03 4.66646e+03 7.58491e+03 3.57351e+03 -4.34171e+02 2.98819e+03
1332497040.010000 2.51903e+05 2.23202e+05 4.23696e+03 3.49363e+03 8.53493e+03 4.29416e+03 8.49573e+02 2.38189e+03
1332497040.020000 2.57625e+05 2.20247e+05 5.47017e+03 1.35872e+03 9.18903e+03 4.56136e+03 2.65599e+03 2.60912e+03
1332497040.030000 2.63375e+05 2.20706e+05 4.51842e+03 1.80758e+03 8.17208e+03 4.17463e+03 2.57884e+03 3.32848e+03
1332497040.040000 2.59221e+05 2.22346e+05 2.98879e+03 3.66264e+03 6.87274e+03 3.94223e+03 1.25928e+03 3.51786e+03
1332497040.050000 2.51918e+05 2.22281e+05 4.22677e+03 2.84764e+03 7.78323e+03 3.81659e+03 8.04944e+02 3.46314e+03
1332497040.050000 2.54478e+05 2.21701e+05 5.61366e+03 1.02262e+03 9.26581e+03 3.50152e+03 1.29331e+03 3.07271e+03
1332497040.060000 2.59568e+05 2.22945e+05 4.97190e+03 1.28250e+03 8.62081e+03 4.06316e+03 1.85717e+03 2.61990e+03
1332497040.070000 2.57269e+05 2.23697e+05 3.60527e+03 3.05749e+03 7.22363e+03 4.90330e+03 1.93736e+03 2.35357e+03
1332497040.080000 2.52274e+05 2.21438e+05 5.01228e+03 2.86309e+03 7.87115e+03 4.80448e+03 2.18291e+03 2.93397e+03
1332497040.090000 2.56468e+05 2.19205e+05 6.29804e+03 8.09467e+02 9.12895e+03 3.52055e+03 2.16980e+03 3.88739e+03

View File

@@ -2,8 +2,6 @@
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb.bulkdata
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
import itertools import itertools
@@ -12,7 +10,8 @@ from testutil.helpers import *
testdb = "tests/bulkdata-testdb" testdb = "tests/bulkdata-testdb"
from nilmdb.bulkdata import BulkData import nilmdb.server.bulkdata
from nilmdb.server.bulkdata import BulkData
class TestBulkData(object): class TestBulkData(object):

View File

@@ -2,9 +2,9 @@
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
from nilmdb.utils import timestamper
from nilmdb.client import ClientError, ServerError from nilmdb.client import ClientError, ServerError
from nilmdb.utils import datetime_tz
import datetime_tz
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
@@ -158,13 +158,13 @@ class TestClient(object):
rate = 120 rate = 120
# First try a nonexistent path # First try a nonexistent path
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
result = client.stream_insert("/newton/no-such-path", data) result = client.stream_insert("/newton/no-such-path", data)
in_("404 Not Found", str(e.exception)) in_("404 Not Found", str(e.exception))
# Now try reversed timestamps # Now try reversed timestamps
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
data = reversed(list(data)) data = reversed(list(data))
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
result = client.stream_insert("/newton/prep", data) result = client.stream_insert("/newton/prep", data)
@@ -173,7 +173,7 @@ class TestClient(object):
# Now try empty data (no server request made) # Now try empty data (no server request made)
empty = cStringIO.StringIO("") empty = cStringIO.StringIO("")
data = nilmdb.timestamper.TimestamperRate(empty, start, 120) data = timestamper.TimestamperRate(empty, start, 120)
result = client.stream_insert("/newton/prep", data) result = client.stream_insert("/newton/prep", data)
eq_(result, None) eq_(result, None)
@@ -185,7 +185,7 @@ class TestClient(object):
in_("no data provided", str(e.exception)) in_("no data provided", str(e.exception))
# Specify start/end (starts too late) # Specify start/end (starts too late)
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
result = client.stream_insert("/newton/prep", data, result = client.stream_insert("/newton/prep", data,
start + 5, start + 120) start + 5, start + 120)
@@ -194,7 +194,7 @@ class TestClient(object):
str(e.exception)) str(e.exception))
# Specify start/end (ends too early) # Specify start/end (ends too early)
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
result = client.stream_insert("/newton/prep", data, result = client.stream_insert("/newton/prep", data,
start, start + 1) start, start + 1)
@@ -205,7 +205,7 @@ class TestClient(object):
str(e.exception)) str(e.exception))
# Now do the real load # Now do the real load
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
result = client.stream_insert("/newton/prep", data, result = client.stream_insert("/newton/prep", data,
start, start + 119.999777) start, start + 119.999777)
eq_(result, "ok") eq_(result, "ok")
@@ -216,7 +216,7 @@ class TestClient(object):
eq_(intervals, [[start, start + 119.999777]]) eq_(intervals, [[start, start + 119.999777]])
# Try some overlapping data -- just insert it again # Try some overlapping data -- just insert it again
data = nilmdb.timestamper.TimestamperRate(testfile, start, 120) data = timestamper.TimestamperRate(testfile, start, 120)
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
result = client.stream_insert("/newton/prep", data) result = client.stream_insert("/newton/prep", data)
in_("400 Bad Request", str(e.exception)) in_("400 Bad Request", str(e.exception))
@@ -227,7 +227,7 @@ class TestClient(object):
client = nilmdb.Client(url = "http://localhost:12380/") client = nilmdb.Client(url = "http://localhost:12380/")
for x in client.stream_extract("/newton/prep", 123, 123): for x in client.stream_extract("/newton/prep", 123, 123):
raise Exception("shouldn't be any data for this request") raise AssertionError("shouldn't be any data for this request")
with assert_raises(ClientError) as e: with assert_raises(ClientError) as e:
client.stream_remove("/newton/prep", 123, 120) client.stream_remove("/newton/prep", 123, 120)
@@ -281,28 +281,44 @@ class TestClient(object):
in_("404 Not Found", str(e.exception)) in_("404 Not Found", str(e.exception))
in_("No such stream", str(e.exception)) in_("No such stream", str(e.exception))
def test_client_7_chunked(self): def test_client_7_headers(self):
# Make sure that /stream/intervals and /stream/extract # Make sure that /stream/intervals and /stream/extract
# properly return streaming, chunked response. Pokes around # properly return streaming, chunked, text/plain response.
# in client.http internals a bit to look at the response # Pokes around in client.http internals a bit to look at the
# headers. # response headers.
client = nilmdb.Client(url = "http://localhost:12380/") client = nilmdb.Client(url = "http://localhost:12380/")
http = client.http
# Use a warning rather than returning a test failure, so that we can # Use a warning rather than returning a test failure, so that we can
# still disable chunked responses for debugging. # still disable chunked responses for debugging.
x = client.http.get("stream/intervals", { "path": "/newton/prep" },
# Intervals
x = http.get("stream/intervals", { "path": "/newton/prep" },
retjson=False) retjson=False)
lines_(x, 1) lines_(x, 1)
if "transfer-encoding: chunked" not in client.http._headers.lower(): if "Transfer-Encoding: chunked" not in http._headers:
warnings.warn("Non-chunked HTTP response for /stream/intervals") warnings.warn("Non-chunked HTTP response for /stream/intervals")
if "Content-Type: text/plain;charset=utf-8" not in http._headers:
raise AssertionError("/stream/intervals is not text/plain:\n" +
http._headers)
x = client.http.get("stream/extract", # Extract
x = http.get("stream/extract",
{ "path": "/newton/prep", { "path": "/newton/prep",
"start": "123", "start": "123",
"end": "123" }, retjson=False) "end": "123" }, retjson=False)
if "transfer-encoding: chunked" not in client.http._headers.lower(): if "Transfer-Encoding: chunked" not in http._headers:
warnings.warn("Non-chunked HTTP response for /stream/extract") warnings.warn("Non-chunked HTTP response for /stream/extract")
if "Content-Type: text/plain;charset=utf-8" not in http._headers:
raise AssertionError("/stream/extract is not text/plain:\n" +
http._headers)
# Make sure Access-Control-Allow-Origin gets set
if "Access-Control-Allow-Origin: " not in http._headers:
raise AssertionError("No Access-Control-Allow-Origin (CORS) "
"header in /stream/extract response:\n" +
http._headers)
def test_client_8_unicode(self): def test_client_8_unicode(self):
# Basic Unicode tests # Basic Unicode tests

View File

@@ -3,12 +3,12 @@
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import nilmdb.cmdline import nilmdb.cmdline
from nilmdb.utils import datetime_tz
import unittest import unittest
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
import itertools import itertools
import datetime_tz
import os import os
import re import re
import shutil import shutil
@@ -128,8 +128,8 @@ class TestCmdline(object):
with open(file) as f: with open(file) as f:
contents = f.read() contents = f.read()
if contents != self.captured: if contents != self.captured:
#print contents[1:1000] + "\n" print contents[1:1000] + "\n"
#print self.captured[1:1000] + "\n" print self.captured[1:1000] + "\n"
raise AssertionError("captured data doesn't match " + file) raise AssertionError("captured data doesn't match " + file)
def matchfilecount(self, file): def matchfilecount(self, file):
@@ -340,9 +340,10 @@ class TestCmdline(object):
eq_(cmd.parse_time("hi there 20120405 1400-0400 testing! 123"), test) eq_(cmd.parse_time("hi there 20120405 1400-0400 testing! 123"), test)
eq_(cmd.parse_time("20120405 1800 UTC"), test) eq_(cmd.parse_time("20120405 1800 UTC"), test)
eq_(cmd.parse_time("20120405 1400-0400 UTC"), test) eq_(cmd.parse_time("20120405 1400-0400 UTC"), test)
for badtime in [ "20120405 1400-9999", "hello", "-", "", "14:00" ]: for badtime in [ "20120405 1400-9999", "hello", "-", "", "4:00" ]:
with assert_raises(ValueError): with assert_raises(ValueError):
x = cmd.parse_time(badtime) x = cmd.parse_time(badtime)
x = cmd.parse_time("now")
eq_(cmd.parse_time("snapshot-20120405-140000.raw.gz"), test) eq_(cmd.parse_time("snapshot-20120405-140000.raw.gz"), test)
eq_(cmd.parse_time("prep-20120405T1400"), test) eq_(cmd.parse_time("prep-20120405T1400"), test)
@@ -369,6 +370,14 @@ class TestCmdline(object):
with open("tests/data/prep-20120323T1004-timestamped") as input: with open("tests/data/prep-20120323T1004-timestamped") as input:
self.ok("insert --none /newton/prep", input) self.ok("insert --none /newton/prep", input)
# insert pre-timestamped data, with bad times (non-monotonic)
os.environ['TZ'] = "UTC"
with open("tests/data/prep-20120323T1004-badtimes") as input:
self.fail("insert --none /newton/prep", input)
self.contain("error parsing input data")
self.contain("line 7:")
self.contain("timestamp is not monotonically increasing")
# insert data with normal timestamper from filename # insert data with normal timestamper from filename
os.environ['TZ'] = "UTC" os.environ['TZ'] = "UTC"
self.ok("insert --rate 120 /newton/prep " self.ok("insert --rate 120 /newton/prep "
@@ -440,6 +449,17 @@ class TestCmdline(object):
self.ok("list --detail") self.ok("list --detail")
lines_(self.captured, 8) lines_(self.captured, 8)
# Verify the "raw timestamp" output
self.ok("list --detail --path *prep --timestamp-raw "
"--start='23 Mar 2012 10:05:15.50'")
lines_(self.captured, 2)
self.contain("[ 1332497115.5 -> 1332497159.991668 ]")
self.ok("list --detail --path *prep -T "
"--start='23 Mar 2012 10:05:15.612'")
lines_(self.captured, 2)
self.contain("[ 1332497115.612 -> 1332497159.991668 ]")
def test_08_extract(self): def test_08_extract(self):
# nonexistent stream # nonexistent stream
self.fail("extract /no/such/foo --start 2000-01-01 --end 2020-01-01") self.fail("extract /no/such/foo --start 2000-01-01 --end 2020-01-01")
@@ -495,6 +515,8 @@ class TestCmdline(object):
test(4, "10:00:30.008333", "10:00:30.025") test(4, "10:00:30.008333", "10:00:30.025")
test(5, "10:00:30", "10:00:31", extra="--annotate --bare") test(5, "10:00:30", "10:00:31", extra="--annotate --bare")
test(6, "10:00:30", "10:00:31", extra="-b") test(6, "10:00:30", "10:00:31", extra="-b")
test(7, "10:00:30", "10:00:30.999", extra="-a -T")
test(7, "10:00:30", "10:00:30.999", extra="-a --timestamp-raw")
# all data put in by tests # all data put in by tests
self.ok("extract -a /newton/prep --start 2000-01-01 --end 2020-01-01") self.ok("extract -a /newton/prep --start 2000-01-01 --end 2020-01-01")

View File

@@ -2,13 +2,14 @@
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
import datetime_tz from nilmdb.utils import datetime_tz
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
import itertools import itertools
from nilmdb.interval import Interval, DBInterval, IntervalSet, IntervalError from nilmdb.server.interval import (Interval, DBInterval,
IntervalSet, IntervalError)
from testutil.helpers import * from testutil.helpers import *
import unittest import unittest

View File

@@ -13,6 +13,7 @@ def func_with_callback(a, b, callback):
callback(a) callback(a)
callback(b) callback(b)
callback(a+b) callback(a+b)
return "return value"
class TestIteratorizer(object): class TestIteratorizer(object):
def test(self): def test(self):
@@ -25,22 +26,21 @@ class TestIteratorizer(object):
eq_(self.result, "123") eq_(self.result, "123")
# Now make it an iterator # Now make it an iterator
it = nilmdb.utils.Iteratorizer(
lambda x:
func_with_callback(1, 2, x))
result = "" result = ""
for i in it: f = lambda x: func_with_callback(1, 2, x)
result += str(i) with nilmdb.utils.Iteratorizer(f) as it:
eq_(result, "123")
# Make sure things work when an exception occurs
it = nilmdb.utils.Iteratorizer(
lambda x:
func_with_callback(1, "a", x))
result = ""
with assert_raises(TypeError) as e:
for i in it: for i in it:
result += str(i) result += str(i)
eq_(result, "123")
eq_(it.retval, "return value")
# Make sure things work when an exception occurs
result = ""
with nilmdb.utils.Iteratorizer(
lambda x: func_with_callback(1, "a", x)) as it:
with assert_raises(TypeError) as e:
for i in it:
result += str(i)
eq_(result, "1a") eq_(result, "1a")
# Now try to trigger the case where we stop iterating # Now try to trigger the case where we stop iterating
@@ -48,8 +48,14 @@ class TestIteratorizer(object):
# itself. This doesn't have a particular result in the test, # itself. This doesn't have a particular result in the test,
# but gains coverage. # but gains coverage.
def foo(): def foo():
it = nilmdb.utils.Iteratorizer( with nilmdb.utils.Iteratorizer(f) as it:
lambda x: it.next()
func_with_callback(1, 2, x))
it.next()
foo() foo()
eq_(it.retval, None)
# Do the same thing when the curl hack is applied
def foo():
with nilmdb.utils.Iteratorizer(f, curl_hack = True) as it:
it.next()
foo()
eq_(it.retval, None)

View File

@@ -22,17 +22,17 @@ import unittest
from testutil.helpers import * from testutil.helpers import *
from nilmdb.layout import * from nilmdb.server.layout import *
class TestLayouts(object): class TestLayouts(object):
# Some nilmdb.layout tests. Not complete, just fills in missing # Some nilmdb.layout tests. Not complete, just fills in missing
# coverage. # coverage.
def test_layouts(self): def test_layouts(self):
x = nilmdb.layout.get_named("PrepData") x = nilmdb.server.layout.get_named("PrepData")
y = nilmdb.layout.get_named("float32_8") y = nilmdb.server.layout.get_named("float32_8")
eq_(x.count, y.count) eq_(x.count, y.count)
eq_(x.datatype, y.datatype) eq_(x.datatype, y.datatype)
y = nilmdb.layout.get_named("float32_7") y = nilmdb.server.layout.get_named("float32_7")
ne_(x.count, y.count) ne_(x.count, y.count)
eq_(x.datatype, y.datatype) eq_(x.datatype, y.datatype)
@@ -89,11 +89,23 @@ class TestLayouts(object):
# non-monotonic # non-monotonic
parser = Parser(name_raw) parser = Parser(name_raw)
data = ( "1234567890.100000 1 2 3 4 5 6\n" + data = ( "1234567890.100000 1 2 3 4 5 6\n" +
"1234567890.000000 1 2 3 4 5 6\n" ) "1234567890.099999 1 2 3 4 5 6\n" )
with assert_raises(ParserError) as e: with assert_raises(ParserError) as e:
parser.parse(data) parser.parse(data)
in_("not monotonically increasing", str(e.exception)) in_("not monotonically increasing", str(e.exception))
parser = Parser(name_raw)
data = ( "1234567890.100000 1 2 3 4 5 6\n" +
"1234567890.100000 1 2 3 4 5 6\n" )
with assert_raises(ParserError) as e:
parser.parse(data)
in_("not monotonically increasing", str(e.exception))
parser = Parser(name_raw)
data = ( "1234567890.100000 1 2 3 4 5 6\n" +
"1234567890.100001 1 2 3 4 5 6\n" )
parser.parse(data)
# RawData with values out of bounds # RawData with values out of bounds
parser = Parser(name_raw) parser = Parser(name_raw)
data = ( "1234567890.000000 1 2 3 4 500000 6\n" + data = ( "1234567890.000000 1 2 3 4 500000 6\n" +

View File

@@ -113,7 +113,8 @@ class TestBlockingServer(object):
self.server.start(blocking = True, event = event) self.server.start(blocking = True, event = event)
thread = threading.Thread(target = run_server) thread = threading.Thread(target = run_server)
thread.start() thread.start()
event.wait(timeout = 2) if not event.wait(timeout = 10):
raise AssertionError("server didn't start in 10 seconds")
# Send request to exit. # Send request to exit.
req = urlopen("http://127.0.0.1:12380/exit/", timeout = 1) req = urlopen("http://127.0.0.1:12380/exit/", timeout = 1)

View File

@@ -6,7 +6,7 @@ from nilmdb.utils.printf import *
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
from nilmdb.rbtree import RBTree, RBNode from nilmdb.server.rbtree import RBTree, RBNode
from testutil.helpers import * from testutil.helpers import *
import unittest import unittest

View File

@@ -1,7 +1,6 @@
import nilmdb import nilmdb
from nilmdb.utils.printf import * from nilmdb.utils.printf import *
from nilmdb.utils import datetime_tz
import datetime_tz
from nose.tools import * from nose.tools import *
from nose.tools import assert_raises from nose.tools import assert_raises
@@ -11,6 +10,8 @@ import cStringIO
from testutil.helpers import * from testutil.helpers import *
from nilmdb.utils import timestamper
class TestTimestamper(object): class TestTimestamper(object):
# Not a very comprehensive test, but it's good enough. # Not a very comprehensive test, but it's good enough.
@@ -27,20 +28,20 @@ class TestTimestamper(object):
# full # full
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000) ts = timestamper.TimestamperRate(input, start, 8000)
foo = ts.readlines() foo = ts.readlines()
eq_(foo, join(lines_out)) eq_(foo, join(lines_out))
in_("TimestamperRate(..., start=", str(ts)) in_("TimestamperRate(..., start=", str(ts))
# first 30 or so bytes means the first 2 lines # first 30 or so bytes means the first 2 lines
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000) ts = timestamper.TimestamperRate(input, start, 8000)
foo = ts.readlines(30) foo = ts.readlines(30)
eq_(foo, join(lines_out[0:2])) eq_(foo, join(lines_out[0:2]))
# stop iteration early # stop iteration early
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000, ts = timestamper.TimestamperRate(input, start, 8000,
1332561600.000200) 1332561600.000200)
foo = "" foo = ""
for line in ts: for line in ts:
@@ -49,21 +50,21 @@ class TestTimestamper(object):
# stop iteration early (readlines) # stop iteration early (readlines)
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000, ts = timestamper.TimestamperRate(input, start, 8000,
1332561600.000200) 1332561600.000200)
foo = ts.readlines() foo = ts.readlines()
eq_(foo, join(lines_out[0:2])) eq_(foo, join(lines_out[0:2]))
# stop iteration really early # stop iteration really early
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000, ts = timestamper.TimestamperRate(input, start, 8000,
1332561600.000000) 1332561600.000000)
foo = ts.readlines() foo = ts.readlines()
eq_(foo, "") eq_(foo, "")
# use iterator # use iterator
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperRate(input, start, 8000) ts = timestamper.TimestamperRate(input, start, 8000)
foo = "" foo = ""
for line in ts: for line in ts:
foo += line foo += line
@@ -71,21 +72,21 @@ class TestTimestamper(object):
# check that TimestamperNow gives similar result # check that TimestamperNow gives similar result
input = cStringIO.StringIO(join(lines_in)) input = cStringIO.StringIO(join(lines_in))
ts = nilmdb.timestamper.TimestamperNow(input) ts = timestamper.TimestamperNow(input)
foo = ts.readlines() foo = ts.readlines()
ne_(foo, join(lines_out)) ne_(foo, join(lines_out))
eq_(len(foo), len(join(lines_out))) eq_(len(foo), len(join(lines_out)))
eq_(str(ts), "TimestamperNow(...)") eq_(str(ts), "TimestamperNow(...)")
# Test passing a file (should be empty) # Test passing a file (should be empty)
ts = nilmdb.timestamper.TimestamperNow("/dev/null") ts = timestamper.TimestamperNow("/dev/null")
for line in ts: for line in ts:
raise AssertionError raise AssertionError
ts.close() ts.close()
# Test the null timestamper # Test the null timestamper
input = cStringIO.StringIO(join(lines_out)) # note: lines_out input = cStringIO.StringIO(join(lines_out)) # note: lines_out
ts = nilmdb.timestamper.TimestamperNull(input) ts = timestamper.TimestamperNull(input)
foo = ts.readlines() foo = ts.readlines()
eq_(foo, join(lines_out)) eq_(foo, join(lines_out))
eq_(str(ts), "TimestamperNull(...)") eq_(str(ts), "TimestamperNull(...)")

View File

@@ -1,54 +0,0 @@
nosetests
32: 386 μs (12.0625 μs each)
64: 672.102 μs (10.5016 μs each)
128: 1510.86 μs (11.8036 μs each)
256: 2782.11 μs (10.8676 μs each)
512: 5591.87 μs (10.9216 μs each)
1024: 12812.1 μs (12.5119 μs each)
2048: 21835.1 μs (10.6617 μs each)
4096: 46059.1 μs (11.2449 μs each)
8192: 114127 μs (13.9315 μs each)
16384: 181217 μs (11.0606 μs each)
32768: 419649 μs (12.8067 μs each)
65536: 804320 μs (12.2729 μs each)
131072: 1.73534e+06 μs (13.2396 μs each)
262144: 3.74451e+06 μs (14.2842 μs each)
524288: 8.8694e+06 μs (16.917 μs each)
1048576: 1.69993e+07 μs (16.2118 μs each)
2097152: 3.29387e+07 μs (15.7064 μs each)
|
+3.29387e+07 *
| ----
| -----
| ----
| -----
| -----
| ----
| -----
| -----
| ----
| -----
| ----
| -----
| ---
| ---
| ---
| -------
---+386---------------------------------------------------------------------+---
+32 +2.09715e+06
name #n tsub ttot tavg
..vl/lees/bucket/nilm/nilmdb/nilmdb/interval.py.__iadd__:184 4194272 10.025323 30.262723 0.000007
..evl/lees/bucket/nilm/nilmdb/nilmdb/interval.py.__init__:27 4194272 24.715377 24.715377 0.000006
../lees/bucket/nilm/nilmdb/nilmdb/interval.py.intersects:239 4194272 6.705053 12.577620 0.000003
..im/devl/lees/bucket/nilm/nilmdb/tests/aplotter.py.plot:404 1 0.000048 0.001412 0.001412
../lees/bucket/nilm/nilmdb/tests/aplotter.py.plot_double:311 1 0.000106 0.001346 0.001346
..vl/lees/bucket/nilm/nilmdb/tests/aplotter.py.plot_data:201 1 0.000098 0.000672 0.000672
..vl/lees/bucket/nilm/nilmdb/tests/aplotter.py.plot_line:241 16 0.000298 0.000496 0.000031
..jim/devl/lees/bucket/nilm/nilmdb/nilmdb/printf.py.printf:4 17 0.000252 0.000334 0.000020
..vl/lees/bucket/nilm/nilmdb/tests/aplotter.py.transposed:39 1 0.000229 0.000235 0.000235
..vl/lees/bucket/nilm/nilmdb/tests/aplotter.py.y_reversed:45 1 0.000151 0.000174 0.000174
name tid fname ttot scnt
_MainThread 47269783682784 ..b/python2.7/threading.py.setprofile:88 64.746000 1

View File

@@ -1,22 +0,0 @@
./nilmtool.py destroy /bpnilm/2/raw
./nilmtool.py create /bpnilm/2/raw RawData
if false; then
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s 20110513-110000 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s 20110513-120001 -r 8000 /bpnilm/2/raw
else
# 170 hours, about 98 gigs uncompressed:
for i in $(seq 2000 2016); do
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-010001 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-020002 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-030003 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-040004 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-050005 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-060006 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-070007 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-080008 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-090009 -r 8000 /bpnilm/2/raw
time zcat /home/jim/bpnilm-data/snapshot-1-20110513-110002.raw.gz | ./nilmtool.py insert -s ${i}0101-100010 -r 8000 /bpnilm/2/raw
done
fi