Compare commits
113 Commits
nilmdb-1.3
...
nilmdb-1.5
Author | SHA1 | Date | |
---|---|---|---|
bfb09a189f | |||
416a499866 | |||
637d193807 | |||
b7fa5745ce | |||
0104c8edd9 | |||
cf3b8e787d | |||
83d022016c | |||
43b740ecaa | |||
4ce059b920 | |||
99a4228285 | |||
230ec72609 | |||
d36ece3767 | |||
231963538e | |||
b4d6aad6de | |||
e95142eabf | |||
d21c3470bc | |||
7576883f49 | |||
cc211542f8 | |||
8292dcf70b | |||
b362fd37f6 | |||
41ec13ee17 | |||
efa9aa9097 | |||
d9afb48f45 | |||
d1140e0f16 | |||
6091e44561 | |||
e233ba790f | |||
f0304b4c00 | |||
60594ca58e | |||
c7f2df4abc | |||
5b7409f802 | |||
06038062a2 | |||
ae9fe89759 | |||
04def60021 | |||
9ce0f69dff | |||
90c3be91c4 | |||
ebccfb3531 | |||
e006f1d02e | |||
5292319802 | |||
173121ca87 | |||
26bab031bd | |||
b5fefffa09 | |||
dccb3e370a | |||
95ca55aa7e | |||
e01813f29d | |||
7f41e117a2 | |||
dd5fc806e5 | |||
f8ca8d31e6 | |||
ed89d803f0 | |||
3d24092cd2 | |||
304bb43d85 | |||
59a79a30a5 | |||
c0d450d39e | |||
6f14d609b2 | |||
77ef87456f | |||
32d6af935c | |||
6af3a6fc41 | |||
f8a06fb3b7 | |||
e790bb9e8a | |||
89be6f5931 | |||
4cdef3285d | |||
bcd82c4d59 | |||
caf63ab01f | |||
2d72891162 | |||
cda2ac3e77 | |||
57d3d60f6a | |||
d6b5befe76 | |||
7429c1788d | |||
0ef71c193b | |||
4a50dd015e | |||
22274550ab | |||
4f06d6ae68 | |||
c54d8041c3 | |||
52ae397d7d | |||
d05b6f6348 | |||
049375d30e | |||
88eb0123f5 | |||
a547ddbbba | |||
28e72fd53e | |||
f63107b334 | |||
955d7aa871 | |||
b8d2cf1b78 | |||
7c465730de | |||
aca130272d | |||
76e5e9883f | |||
fb4f4519ff | |||
30328714a7 | |||
759466de4a | |||
d3efb829b5 | |||
90b96799ac | |||
56679ad770 | |||
b5541722c2 | |||
aaea105861 | |||
e6a081d639 | |||
1835d03412 | |||
c7a712d8d8 | |||
20d315b4f7 | |||
a44a5e3135 | |||
039b2a0557 | |||
cd1dfe7dcd | |||
fb35517dfa | |||
b9f0b35bbe | |||
b1b09f8cd0 | |||
d467df7980 | |||
09bc7eb48c | |||
b77f07a4cd | |||
59f0076306 | |||
83bc5bc775 | |||
6b1dfec828 | |||
d827f41fa5 | |||
7eca587fdf | |||
a351bc1b10 | |||
1d61d61a81 | |||
755255030b |
2
Makefile
2
Makefile
@@ -43,4 +43,4 @@ clean::
|
||||
gitclean::
|
||||
git clean -dXf
|
||||
|
||||
.PHONY: all version build dist sdist install docs lint test clean
|
||||
.PHONY: all version build dist sdist install docs lint test clean gitclean
|
||||
|
@@ -10,6 +10,9 @@ Prerequisites:
|
||||
sudo apt-get install python-cherrypy3 python-decorator python-simplejson
|
||||
sudo apt-get install python-requests python-dateutil python-tz python-psutil
|
||||
|
||||
# Other dependencies (required by some modules)
|
||||
sudo apt-get install python-numpy
|
||||
|
||||
# Tools for running tests
|
||||
sudo apt-get install python-nose python-coverage
|
||||
|
||||
@@ -24,3 +27,5 @@ Usage:
|
||||
|
||||
nilmdb-server --help
|
||||
nilmtool --help
|
||||
|
||||
See docs/wsgi.md for info on setting up a WSGI application in Apache.
|
||||
|
@@ -140,7 +140,7 @@ Speed
|
||||
|
||||
- Next slowdown target is nilmdb.layout.Parser.parse().
|
||||
- Rewrote parsers using cython and sscanf
|
||||
- Stats (rev 10831), with _add_interval disabled
|
||||
- Stats (rev 10831), with `_add_interval` disabled
|
||||
|
||||
layout.pyx.Parser.parse:128 6303 sec, 262k calls
|
||||
layout.pyx.parse:63 13913 sec, 5.1g calls
|
||||
@@ -186,6 +186,19 @@ IntervalSet speed
|
||||
- rbtree and interval converted to cython:
|
||||
8.4 μS, total 12 s, 134 MB RAM
|
||||
|
||||
- Would like to move Interval itself back to Python so other
|
||||
non-cythonized code like client code can use it more easily.
|
||||
Testing speed with just `test_interval` being tested, with
|
||||
`range(5,22)`, using `/usr/bin/time -v python tests/runtests.py`,
|
||||
times recorded for 2097152:
|
||||
- 52ae397 (Interval in cython):
|
||||
12.6133 μs each, ratio 0.866533, total 47 sec, 399 MB RAM
|
||||
- 9759dcf (Interval in python):
|
||||
21.2937 μs each, ratio 1.462870, total 83 sec, 1107 MB RAM
|
||||
That's a huge difference! Instead, will keep Interval and DBInterval
|
||||
cythonized inside nilmdb, and just have an additional copy in
|
||||
nilmdb.utils for clients to use.
|
||||
|
||||
Layouts
|
||||
-------
|
||||
Current/old design has specific layouts: RawData, PrepData, RawNotchedData.
|
||||
@@ -328,3 +341,83 @@ Current places where we use lines:
|
||||
- Finished. Just a single insert() that takes any length string and
|
||||
does very little processing until it's time to send it to the
|
||||
server.
|
||||
|
||||
Timestamps
|
||||
----------
|
||||
|
||||
Timestamps are currently double-precision floats (64 bit). Since the
|
||||
mantissa is 53-bit, this can only represent about 15-17 significant
|
||||
figures, and microsecond Unix timestamps like 1222333444.000111 are
|
||||
already 16 significant figures. Rounding is therefore an issue;
|
||||
it's hard to sure that converting from ASCII, then back to ASCII,
|
||||
will always give the same result.
|
||||
|
||||
Also, if the client provides a floating point value like 1.9999999999,
|
||||
we need to be careful that we don't store it as 1.9999999999 but later
|
||||
print it as 2.000000, because then round-trips change the data.
|
||||
|
||||
Possible solutions:
|
||||
|
||||
- When the client provides a floating point value to the server,
|
||||
always round to the 6th decimal digit before verifying & storing.
|
||||
Good for compatibility and simplicity. But still might have rounding
|
||||
issues, and clients will also need to round when doing their own
|
||||
verification. Having every piece of code need to know which digit
|
||||
to round at is not ideal.
|
||||
|
||||
- Always store int64 timestamps on the server, representing
|
||||
microseconds since epoch. int64 timestamps are used in all HTTP
|
||||
parameters, in insert/extract ASCII strings, client API, commandline
|
||||
raw timestamps, etc. Pretty big change.
|
||||
|
||||
This is what we'll go with...
|
||||
|
||||
- Client programs that interpret the timestamps as doubles instead
|
||||
of ints will remain accurate until 2^53 microseconds, or year
|
||||
2255.
|
||||
|
||||
- On insert, maybe it's OK to send floating point microsecond values
|
||||
(1234567890123456.0), just to cope with clients that want to print
|
||||
everything as a double. Server could try parsing as int64, and if
|
||||
that fails, parse as double and truncate to int64. However, this
|
||||
wouldn't catch imprecise inputs like "1.23456789012e+15". But
|
||||
maybe that can just be ignored; it's likely to cause a
|
||||
non-monotonic error at the client.
|
||||
|
||||
- Timestamps like 1234567890.123456 never show up anywhere, except
|
||||
for interfacing to datetime_tz etc. Command line "raw timestamps"
|
||||
are always printed as int64 values, and a new format
|
||||
"@1234567890123456" is added to the parser for specifying them
|
||||
exactly.
|
||||
|
||||
Binary interface
|
||||
----------------
|
||||
|
||||
The ASCII interface is too slow for high-bandwidth processing, like
|
||||
sinefits, prep, etc. A binary interface was added so that you can
|
||||
extract the raw binary out of the bulkdata storage. This binary is
|
||||
a little-endian format, e.g. in C a uint16_6 stream would be:
|
||||
|
||||
#include <endian.h>
|
||||
#include <stdint.h>
|
||||
struct {
|
||||
int64_t timestamp_le;
|
||||
uint16_t data_le[6];
|
||||
} __attribute__((packed));
|
||||
|
||||
Remember to byteswap (with e.g. `letoh` in C)!
|
||||
|
||||
This interface is used by the new `nilmdb.client.numpyclient.NumpyClient`
|
||||
class, which is a subclass of the normal `nilmcb.client.client.Client`
|
||||
and has all of the same functions. It adds three new functions:
|
||||
|
||||
- `stream_extract_numpy` to extract data as a Numpy array
|
||||
|
||||
- `stream_insert_numpy` to insert data as a Numpy array
|
||||
|
||||
- `stream_insert_numpy_context` is the context manager for
|
||||
incrementally inserting data
|
||||
|
||||
It is significantly faster! It is about 20 times faster to decimate a
|
||||
stream with `nilm-decimate` when the filter code is using the new
|
||||
binary/numpy interface.
|
||||
|
32
docs/wsgi.md
Normal file
32
docs/wsgi.md
Normal file
@@ -0,0 +1,32 @@
|
||||
WSGI Application in Apache
|
||||
--------------------------
|
||||
|
||||
Install `apache2` and `libapache2-mod-wsgi`
|
||||
|
||||
We'll set up the database server at URL `http://myhost.com/nilmdb`.
|
||||
The database will be stored in `/home/nilm/db`, and the process will
|
||||
run as user `nilm`, group `nilm`.
|
||||
|
||||
First, create a WSGI script `/home/nilm/nilmdb.wsgi` containing:
|
||||
|
||||
import nilmdb.server
|
||||
application = nilmdb.server.wsgi_application("/home/nilm/db", "/nilmdb")
|
||||
|
||||
The first parameter is the local filesystem path, and the second
|
||||
parameter is the path part of the URL.
|
||||
|
||||
Then, set up Apache with a configuration like:
|
||||
|
||||
<VirtualHost>
|
||||
WSGIScriptAlias /nilmdb /home/nilm/nilmdb.wsgi
|
||||
WSGIApplicationGroup nilmdb-appgroup
|
||||
WSGIProcessGroup nilmdb-procgroup
|
||||
WSGIDaemonProcess nilmdb-procgroup threads=32 user=nilm group=nilm
|
||||
|
||||
# Access control example:
|
||||
<Location /nilmdb>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
Allow from 1.2.3.4
|
||||
</Location>
|
||||
</VirtualHost>
|
20
extras/nilmtool-bash-completion.sh
Normal file
20
extras/nilmtool-bash-completion.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
# To enable bash completion:
|
||||
#
|
||||
# 1. Ensure python-argcomplete is installed:
|
||||
# pip install argcomplete
|
||||
# 2. Source this file:
|
||||
# . nilmtool-bash-completion.sh
|
||||
|
||||
_nilmtool_argcomplete() {
|
||||
local IFS=$(printf "\013")
|
||||
COMPREPLY=( $(IFS="$IFS" \
|
||||
COMP_LINE="$COMP_LINE" \
|
||||
COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
||||
COMP_POINT="$COMP_POINT" \
|
||||
_ARGCOMPLETE=1 \
|
||||
"$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
|
||||
if [[ $? != 0 ]]; then
|
||||
unset COMPREPLY
|
||||
fi
|
||||
}
|
||||
complete -o nospace -F _nilmtool_argcomplete nilmtool
|
@@ -6,15 +6,16 @@ import nilmdb.utils
|
||||
import nilmdb.client.httpclient
|
||||
from nilmdb.client.errors import ClientError
|
||||
|
||||
import re
|
||||
import time
|
||||
import simplejson as json
|
||||
import contextlib
|
||||
|
||||
from nilmdb.utils.time import float_time_to_string
|
||||
from nilmdb.utils.time import timestamp_to_string, string_to_timestamp
|
||||
|
||||
def extract_timestamp(line):
|
||||
"""Extract just the timestamp from a line of data text"""
|
||||
return float(line.split()[0])
|
||||
return string_to_timestamp(line.split()[0])
|
||||
|
||||
class Client(object):
|
||||
"""Main client interface to the Nilm database."""
|
||||
@@ -65,7 +66,12 @@ class Client(object):
|
||||
params["layout"] = layout
|
||||
if extended:
|
||||
params["extended"] = 1
|
||||
return self.http.get("stream/list", params)
|
||||
def sort_streams_nicely(x):
|
||||
"""Human-friendly sort (/stream/2 before /stream/10)"""
|
||||
num = lambda t: int(t) if t.isdigit() else t
|
||||
key = lambda k: [ num(c) for c in re.split('([0-9]+)', k[0]) ]
|
||||
return sorted(x, key = key)
|
||||
return sort_streams_nicely(self.http.get("stream/list", params))
|
||||
|
||||
def stream_get_metadata(self, path, keys = None):
|
||||
params = { "path": path }
|
||||
@@ -97,28 +103,35 @@ class Client(object):
|
||||
return self.http.post("stream/create", params)
|
||||
|
||||
def stream_destroy(self, path):
|
||||
"""Delete stream and its contents"""
|
||||
"""Delete stream. Fails if any data is still present."""
|
||||
params = { "path": path }
|
||||
return self.http.post("stream/destroy", params)
|
||||
|
||||
def stream_rename(self, oldpath, newpath):
|
||||
"""Rename a stream."""
|
||||
params = { "oldpath": oldpath,
|
||||
"newpath": newpath }
|
||||
return self.http.post("stream/rename", params)
|
||||
|
||||
def stream_remove(self, path, start = None, end = None):
|
||||
"""Remove data from the specified time range"""
|
||||
params = {
|
||||
"path": path
|
||||
}
|
||||
if start is not None:
|
||||
params["start"] = float_time_to_string(start)
|
||||
params["start"] = timestamp_to_string(start)
|
||||
if end is not None:
|
||||
params["end"] = float_time_to_string(end)
|
||||
params["end"] = timestamp_to_string(end)
|
||||
return self.http.post("stream/remove", params)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def stream_insert_context(self, path, start = None, end = None):
|
||||
"""Return a context manager that allows data to be efficiently
|
||||
inserted into a stream in a piecewise manner. Data is be provided
|
||||
as single lines, and is aggregated and sent to the server in larger
|
||||
chunks as necessary. Data lines must match the database layout for
|
||||
the given path, and end with a newline.
|
||||
inserted into a stream in a piecewise manner. Data is
|
||||
provided as ASCII lines, and is aggregated and sent to the
|
||||
server in larger or smaller chunks as necessary. Data lines
|
||||
must match the database layout for the given path, and end
|
||||
with a newline.
|
||||
|
||||
Example:
|
||||
with client.stream_insert_context('/path', start, end) as ctx:
|
||||
@@ -130,15 +143,16 @@ class Client(object):
|
||||
This may make multiple requests to the server, if the data is
|
||||
large enough or enough time has passed between insertions.
|
||||
"""
|
||||
ctx = StreamInserter(self.http, path, start, end)
|
||||
ctx = StreamInserter(self, path, start, end)
|
||||
yield ctx
|
||||
ctx.finalize()
|
||||
|
||||
def stream_insert(self, path, data, start = None, end = None):
|
||||
"""Insert rows of data into a stream. data should be a string
|
||||
or iterable that provides ASCII data that matches the database
|
||||
layout for path. See stream_insert_context for details on the
|
||||
'start' and 'end' parameters."""
|
||||
layout for path. Data is passed through stream_insert_context,
|
||||
so it will be broken into reasonably-sized chunks and
|
||||
start/end will be deduced if missing."""
|
||||
with self.stream_insert_context(path, start, end) as ctx:
|
||||
if isinstance(data, basestring):
|
||||
ctx.insert(data)
|
||||
@@ -147,38 +161,73 @@ class Client(object):
|
||||
ctx.insert(chunk)
|
||||
return ctx.last_response
|
||||
|
||||
def stream_intervals(self, path, start = None, end = None):
|
||||
def stream_insert_block(self, path, data, start, end, binary = False):
|
||||
"""Insert a single fixed block of data into the stream. It is
|
||||
sent directly to the server in one block with no further
|
||||
processing.
|
||||
|
||||
If 'binary' is True, provide raw binary data in little-endian
|
||||
format matching the path layout, including an int64 timestamp.
|
||||
Otherwise, provide ASCII data matching the layout."""
|
||||
params = {
|
||||
"path": path,
|
||||
"start": timestamp_to_string(start),
|
||||
"end": timestamp_to_string(end),
|
||||
}
|
||||
if binary:
|
||||
params["binary"] = 1
|
||||
return self.http.put("stream/insert", data, params, binary = binary)
|
||||
|
||||
def stream_intervals(self, path, start = None, end = None, diffpath = None):
|
||||
"""
|
||||
Return a generator that yields each stream interval.
|
||||
|
||||
If 'diffpath' is not None, yields only interval ranges that are
|
||||
present in 'path' but not in 'diffpath'.
|
||||
"""
|
||||
params = {
|
||||
"path": path
|
||||
}
|
||||
if diffpath is not None:
|
||||
params["diffpath"] = diffpath
|
||||
if start is not None:
|
||||
params["start"] = float_time_to_string(start)
|
||||
params["start"] = timestamp_to_string(start)
|
||||
if end is not None:
|
||||
params["end"] = float_time_to_string(end)
|
||||
params["end"] = timestamp_to_string(end)
|
||||
return self.http.get_gen("stream/intervals", params)
|
||||
|
||||
def stream_extract(self, path, start = None, end = None, count = False):
|
||||
def stream_extract(self, path, start = None, end = None,
|
||||
count = False, markup = False, binary = False):
|
||||
"""
|
||||
Extract data from a stream. Returns a generator that yields
|
||||
lines of ASCII-formatted data that matches the database
|
||||
layout for the given path.
|
||||
|
||||
Specify count = True to return a count of matching data points
|
||||
If 'count' is True, return a count of matching data points
|
||||
rather than the actual data. The output format is unchanged.
|
||||
|
||||
If 'markup' is True, include comments in the returned data
|
||||
that indicate interval starts and ends.
|
||||
|
||||
If 'binary' is True, return chunks of raw binary data, rather
|
||||
than lines of ASCII-formatted data. Raw binary data is
|
||||
little-endian and matches the database types (including an
|
||||
int64 timestamp).
|
||||
"""
|
||||
params = {
|
||||
"path": path,
|
||||
}
|
||||
if start is not None:
|
||||
params["start"] = float_time_to_string(start)
|
||||
params["start"] = timestamp_to_string(start)
|
||||
if end is not None:
|
||||
params["end"] = float_time_to_string(end)
|
||||
params["end"] = timestamp_to_string(end)
|
||||
if count:
|
||||
params["count"] = 1
|
||||
return self.http.get_gen("stream/extract", params)
|
||||
if markup:
|
||||
params["markup"] = 1
|
||||
if binary:
|
||||
params["binary"] = 1
|
||||
return self.http.get_gen("stream/extract", params, binary = binary)
|
||||
|
||||
def stream_count(self, path, start = None, end = None):
|
||||
"""
|
||||
@@ -225,17 +274,15 @@ class StreamInserter(object):
|
||||
# See design.md for a discussion of how much data to send. This
|
||||
# is a soft limit -- we might send up to twice as much or so
|
||||
_max_data = 2 * 1024 * 1024
|
||||
_max_data_after_send = 64 * 1024
|
||||
|
||||
# Delta to add to the final timestamp, if "end" wasn't given
|
||||
_end_epsilon = 1e-6
|
||||
|
||||
def __init__(self, http, path, start = None, end = None):
|
||||
"""'http' is the httpclient object. 'path' is the database
|
||||
def __init__(self, client, path, start, end):
|
||||
"""'client' is the client object. 'path' is the database
|
||||
path to insert to. 'start' and 'end' are used for the first
|
||||
contiguous interval."""
|
||||
contiguous interval and may be None."""
|
||||
self.last_response = None
|
||||
|
||||
self._http = http
|
||||
self._client = client
|
||||
self._path = path
|
||||
|
||||
# Start and end for the overall contiguous interval we're
|
||||
@@ -270,6 +317,10 @@ class StreamInserter(object):
|
||||
# Send the block once we have enough data
|
||||
if self._block_len >= maxdata:
|
||||
self._send_block(final = False)
|
||||
if self._block_len >= self._max_data_after_send: # pragma: no cover
|
||||
raise ValueError("too much data left over after trying"
|
||||
" to send intermediate block; is it"
|
||||
" missing newlines or malformed?")
|
||||
|
||||
def update_start(self, start):
|
||||
"""Update the start time for the next contiguous interval.
|
||||
@@ -294,6 +345,11 @@ class StreamInserter(object):
|
||||
part of a new interval and there may be a gap left in-between."""
|
||||
self._send_block(final = True)
|
||||
|
||||
def send(self):
|
||||
"""Send any data that we might have buffered up. Does not affect
|
||||
any other treatment of timestamps or endpoints."""
|
||||
self._send_block(final = False)
|
||||
|
||||
def _get_first_noncomment(self, block):
|
||||
"""Return the (start, end) indices of the first full line in
|
||||
block that isn't a comment, or raise IndexError if
|
||||
@@ -348,7 +404,7 @@ class StreamInserter(object):
|
||||
if end_ts is None:
|
||||
(spos, epos) = self._get_last_noncomment(block)
|
||||
end_ts = extract_timestamp(block[spos:epos])
|
||||
end_ts += self._end_epsilon
|
||||
end_ts += nilmdb.utils.time.epsilon
|
||||
except (ValueError, IndexError):
|
||||
pass # no timestamp is OK, if we have no data
|
||||
self._block_data = []
|
||||
@@ -366,7 +422,7 @@ class StreamInserter(object):
|
||||
(spos, epos) = self._get_last_noncomment(block)
|
||||
end_ts = extract_timestamp(block[spos:epos])
|
||||
except (ValueError, IndexError):
|
||||
# If we found no timestamp, give up; we'll send this
|
||||
# If we found no timestamp, give up; we could send this
|
||||
# block later when we have more data.
|
||||
return
|
||||
if spos == 0:
|
||||
@@ -394,7 +450,7 @@ class StreamInserter(object):
|
||||
raise ClientError("have data to send, but no start/end times")
|
||||
|
||||
# Send it
|
||||
params = { "path": self._path,
|
||||
"start": float_time_to_string(start_ts),
|
||||
"end": float_time_to_string(end_ts) }
|
||||
self.last_response = self._http.put("stream/insert", block, params)
|
||||
self.last_response = self._client.stream_insert_block(
|
||||
self._path, block, start_ts, end_ts, binary = False)
|
||||
|
||||
return
|
||||
|
@@ -16,7 +16,7 @@ class HTTPClient(object):
|
||||
reparsed = urlparse.urlparse(baseurl).geturl()
|
||||
if '://' not in reparsed:
|
||||
reparsed = urlparse.urlparse("http://" + baseurl).geturl()
|
||||
self.baseurl = reparsed
|
||||
self.baseurl = reparsed.rstrip('/') + '/'
|
||||
|
||||
# Build Requests session object, enable SSL verification
|
||||
self.session = requests.Session()
|
||||
@@ -105,12 +105,17 @@ class HTTPClient(object):
|
||||
else:
|
||||
return self._req("POST", url, None, params)
|
||||
|
||||
def put(self, url, data, params = None):
|
||||
def put(self, url, data, params = None, binary = False):
|
||||
"""Simple PUT (parameters in URL, data in body)"""
|
||||
return self._req("PUT", url, params, data)
|
||||
if binary:
|
||||
h = { 'Content-type': 'application/octet-stream' }
|
||||
else:
|
||||
h = { 'Content-type': 'text/plain; charset=utf-8' }
|
||||
return self._req("PUT", url, query = params, body = data, headers = h)
|
||||
|
||||
# Generator versions that return data one line at a time.
|
||||
def _req_gen(self, method, url, query = None, body = None, headers = None):
|
||||
def _req_gen(self, method, url, query = None, body = None,
|
||||
headers = None, binary = False):
|
||||
"""
|
||||
Make a request and return a generator that gives back strings
|
||||
or JSON decoded lines of the body data, or raise an error if
|
||||
@@ -118,16 +123,19 @@ class HTTPClient(object):
|
||||
"""
|
||||
(response, isjson) = self._do_req(method, url, query, body,
|
||||
stream = True, headers = headers)
|
||||
if isjson:
|
||||
if binary:
|
||||
for chunk in response.iter_content(chunk_size = 65536):
|
||||
yield chunk
|
||||
elif isjson:
|
||||
for line in response.iter_lines():
|
||||
yield json.loads(line)
|
||||
else:
|
||||
for line in response.iter_lines():
|
||||
yield line
|
||||
|
||||
def get_gen(self, url, params = None):
|
||||
def get_gen(self, url, params = None, binary = False):
|
||||
"""Simple GET (parameters in URL) returning a generator"""
|
||||
return self._req_gen("GET", url, params)
|
||||
return self._req_gen("GET", url, params, binary = binary)
|
||||
|
||||
# Not much use for a POST or PUT generator, since they don't
|
||||
# return much data.
|
||||
|
262
nilmdb/client/numpyclient.py
Normal file
262
nilmdb/client/numpyclient.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Provide a NumpyClient class that is based on normal Client, but has
|
||||
additional methods for extracting and inserting data via Numpy arrays."""
|
||||
|
||||
import nilmdb.utils
|
||||
import nilmdb.client.client
|
||||
import nilmdb.client.httpclient
|
||||
from nilmdb.client.errors import ClientError
|
||||
|
||||
import contextlib
|
||||
from nilmdb.utils.time import timestamp_to_string, string_to_timestamp
|
||||
|
||||
import numpy
|
||||
import cStringIO
|
||||
|
||||
def layout_to_dtype(layout):
|
||||
ltype = layout.split('_')[0]
|
||||
lcount = int(layout.split('_')[1])
|
||||
if ltype.startswith('int'):
|
||||
atype = '<i' + str(int(ltype[3:]) / 8)
|
||||
elif ltype.startswith('uint'):
|
||||
atype = '<u' + str(int(ltype[4:]) / 8)
|
||||
elif ltype.startswith('float'):
|
||||
atype = '<f' + str(int(ltype[5:]) / 8)
|
||||
else:
|
||||
raise ValueError("bad layout")
|
||||
return numpy.dtype([('timestamp', '<i8'), ('data', atype, lcount)])
|
||||
|
||||
class NumpyClient(nilmdb.client.client.Client):
|
||||
"""Subclass of nilmdb.client.Client that adds additional methods for
|
||||
extracting and inserting data via Numpy arrays."""
|
||||
|
||||
def _get_dtype(self, path, layout):
|
||||
if layout is None:
|
||||
streams = self.stream_list(path)
|
||||
if len(streams) != 1:
|
||||
raise ClientError("can't get layout for path: " + path)
|
||||
layout = streams[0][1]
|
||||
return layout_to_dtype(layout)
|
||||
|
||||
def stream_extract_numpy(self, path, start = None, end = None,
|
||||
layout = None, maxrows = 100000,
|
||||
structured = False):
|
||||
"""
|
||||
Extract data from a stream. Returns a generator that yields
|
||||
Numpy arrays of up to 'maxrows' of data each.
|
||||
|
||||
If 'layout' is None, it is read using stream_info.
|
||||
|
||||
If 'structured' is False, all data is converted to float64
|
||||
and returned in a flat 2D array. Otherwise, data is returned
|
||||
as a structured dtype in a 1D array.
|
||||
"""
|
||||
dtype = self._get_dtype(path, layout)
|
||||
|
||||
def to_numpy(data):
|
||||
a = numpy.fromstring(data, dtype)
|
||||
if structured:
|
||||
return a
|
||||
return numpy.c_[a['timestamp'], a['data']]
|
||||
|
||||
chunks = []
|
||||
total_len = 0
|
||||
maxsize = dtype.itemsize * maxrows
|
||||
for data in self.stream_extract(path, start, end, binary = True):
|
||||
# Add this block of binary data
|
||||
chunks.append(data)
|
||||
total_len += len(data)
|
||||
|
||||
# See if we have enough to make the requested Numpy array
|
||||
while total_len >= maxsize:
|
||||
assembled = "".join(chunks)
|
||||
total_len -= maxsize
|
||||
chunks = [ assembled[maxsize:] ]
|
||||
block = assembled[:maxsize]
|
||||
yield to_numpy(block)
|
||||
|
||||
if total_len:
|
||||
yield to_numpy("".join(chunks))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def stream_insert_numpy_context(self, path, start = None, end = None,
|
||||
layout = None):
|
||||
"""Return a context manager that allows data to be efficiently
|
||||
inserted into a stream in a piecewise manner. Data is
|
||||
provided as Numpy arrays, and is aggregated and sent to the
|
||||
server in larger or smaller chunks as necessary. Data format
|
||||
must match the database layout for the given path.
|
||||
|
||||
For more details, see help for
|
||||
nilmdb.client.numpyclient.StreamInserterNumpy
|
||||
|
||||
If 'layout' is not None, use it as the layout rather than
|
||||
querying the database.
|
||||
"""
|
||||
dtype = self._get_dtype(path, layout)
|
||||
ctx = StreamInserterNumpy(self, path, start, end, dtype)
|
||||
yield ctx
|
||||
ctx.finalize()
|
||||
|
||||
def stream_insert_numpy(self, path, data, start = None, end = None,
|
||||
layout = None):
|
||||
"""Insert data into a stream. data should be a Numpy array
|
||||
which will be passed through stream_insert_numpy_context to
|
||||
break it into chunks etc. See the help for that function
|
||||
for details."""
|
||||
with self.stream_insert_numpy_context(path, start, end, layout) as ctx:
|
||||
if isinstance(data, numpy.ndarray):
|
||||
ctx.insert(data)
|
||||
else:
|
||||
for chunk in data:
|
||||
ctx.insert(chunk)
|
||||
return ctx.last_response
|
||||
|
||||
class StreamInserterNumpy(nilmdb.client.client.StreamInserter):
|
||||
"""Object returned by stream_insert_numpy_context() that manages
|
||||
the insertion of rows of data into a particular path.
|
||||
|
||||
See help for nilmdb.client.client.StreamInserter for details.
|
||||
The only difference is that, instead of ASCII formatted data,
|
||||
this context manager can take Numpy arrays, which are either
|
||||
structured (1D with complex dtype) or flat (2D with simple dtype).
|
||||
"""
|
||||
|
||||
# Soft limit of how many bytes to send per HTTP request.
|
||||
_max_data = 2 * 1024 * 1024
|
||||
|
||||
def __init__(self, client, path, start, end, dtype):
|
||||
"""
|
||||
'client' is the client object. 'path' is the database path
|
||||
to insert to. 'start' and 'end' are used for the first
|
||||
contiguous interval and may be None. 'dtype' is the Numpy
|
||||
dtype for this stream.
|
||||
"""
|
||||
self.last_response = None
|
||||
|
||||
self._dtype = dtype
|
||||
self._client = client
|
||||
self._path = path
|
||||
|
||||
# Start and end for the overall contiguous interval we're
|
||||
# filling
|
||||
self._interval_start = start
|
||||
self._interval_end = end
|
||||
|
||||
# Max rows to send at once
|
||||
self._max_rows = self._max_data // self._dtype.itemsize
|
||||
|
||||
# List of the current arrays we're building up to send
|
||||
self._block_arrays = []
|
||||
self._block_rows = 0
|
||||
|
||||
def insert(self, array):
|
||||
"""Insert Numpy data, which must match the layout type."""
|
||||
if type(array) != numpy.ndarray:
|
||||
array = numpy.array(array)
|
||||
if array.ndim == 1:
|
||||
# Already a structured array; just verify the type
|
||||
if array.dtype != self._dtype:
|
||||
raise ValueError("wrong dtype for 1D (structured) array")
|
||||
elif array.ndim == 2:
|
||||
# Convert to structured array
|
||||
sarray = numpy.zeros(array.shape[0], dtype=self._dtype)
|
||||
try:
|
||||
sarray['timestamp'] = array[:,0]
|
||||
# Need the squeeze in case sarray['data'] is 1 dimensional
|
||||
sarray['data'] = numpy.squeeze(array[:,1:])
|
||||
except (IndexError, ValueError):
|
||||
raise ValueError("wrong number of fields for this data type")
|
||||
array = sarray
|
||||
else:
|
||||
raise ValueError("wrong number of dimensions in array")
|
||||
|
||||
length = len(array)
|
||||
maxrows = self._max_rows
|
||||
|
||||
if length == 0:
|
||||
return
|
||||
if length > maxrows:
|
||||
# This is more than twice what we wanted to send, so split
|
||||
# it up. This is a bit inefficient, but the user really
|
||||
# shouldn't be providing this much data at once.
|
||||
for cut in range(0, length, maxrows):
|
||||
self.insert(array[cut:(cut + maxrows)])
|
||||
return
|
||||
|
||||
# Add this array to our list
|
||||
self._block_arrays.append(array)
|
||||
self._block_rows += length
|
||||
|
||||
# Send if it's too long
|
||||
if self._block_rows >= maxrows:
|
||||
self._send_block(final = False)
|
||||
|
||||
def _send_block(self, final = False):
|
||||
"""Send the data current stored up. One row might be left
|
||||
over if we need its timestamp saved."""
|
||||
|
||||
# Build the full array to send
|
||||
if self._block_rows == 0:
|
||||
array = numpy.zeros(0, dtype = self._dtype)
|
||||
else:
|
||||
array = numpy.hstack(self._block_arrays)
|
||||
|
||||
# Get starting timestamp
|
||||
start_ts = self._interval_start
|
||||
if start_ts is None:
|
||||
# Pull start from the first row
|
||||
try:
|
||||
start_ts = array['timestamp'][0]
|
||||
except IndexError:
|
||||
pass # no timestamp is OK, if we have no data
|
||||
|
||||
# Get ending timestamp
|
||||
if final:
|
||||
# For a final block, the timestamp is either the
|
||||
# user-provided end, or the timestamp of the last line
|
||||
# plus epsilon.
|
||||
end_ts = self._interval_end
|
||||
if end_ts is None:
|
||||
try:
|
||||
end_ts = array['timestamp'][-1]
|
||||
end_ts += nilmdb.utils.time.epsilon
|
||||
except IndexError:
|
||||
pass # no timestamp is OK, if we have no data
|
||||
self._block_arrays = []
|
||||
self._block_rows = 0
|
||||
|
||||
# Next block is completely fresh
|
||||
self._interval_start = None
|
||||
self._interval_end = None
|
||||
else:
|
||||
# An intermediate block. We need to save the last row
|
||||
# for the next block, and use its timestamp as the ending
|
||||
# timestamp for this one.
|
||||
if len(array) < 2:
|
||||
# Not enough data to send an intermediate block
|
||||
return
|
||||
end_ts = array['timestamp'][-1]
|
||||
if self._interval_end is not None and end_ts > self._interval_end:
|
||||
# User gave us bad endpoints; send it anyway, and let
|
||||
# the server complain so that the error is the same
|
||||
# as if we hadn't done this chunking.
|
||||
end_ts = self._interval_end
|
||||
self._block_arrays = [ array[-1:] ]
|
||||
self._block_rows = 1
|
||||
array = array[:-1]
|
||||
|
||||
# Next block continues where this one ended
|
||||
self._interval_start = end_ts
|
||||
|
||||
# If we have no endpoints, it's because we had no data to send.
|
||||
if start_ts is None or end_ts is None:
|
||||
return
|
||||
|
||||
# Send it
|
||||
data = array.tostring()
|
||||
self.last_response = self._client.stream_insert_block(
|
||||
self._path, data, start_ts, end_ts, binary = True)
|
||||
|
||||
return
|
@@ -10,11 +10,18 @@ import sys
|
||||
import os
|
||||
import argparse
|
||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||
import signal
|
||||
|
||||
try: # pragma: no cover
|
||||
import argcomplete
|
||||
except ImportError: # pragma: no cover
|
||||
argcomplete = None
|
||||
|
||||
# Valid subcommands. Defined in separate files just to break
|
||||
# things up -- they're still called with Cmdline as self.
|
||||
subcommands = [ "help", "info", "create", "list", "metadata",
|
||||
"insert", "extract", "remove", "destroy" ]
|
||||
"insert", "extract", "remove", "destroy",
|
||||
"intervals", "rename" ]
|
||||
|
||||
# Import the subcommand modules
|
||||
subcmd_mods = {}
|
||||
@@ -26,22 +33,74 @@ class JimArgumentParser(argparse.ArgumentParser):
|
||||
self.print_usage(sys.stderr)
|
||||
self.exit(2, sprintf("error: %s\n", message))
|
||||
|
||||
class Complete(object): # pragma: no cover
|
||||
# Completion helpers, for using argcomplete (see
|
||||
# extras/nilmtool-bash-completion.sh)
|
||||
def escape(self, s):
|
||||
quote_chars = [ "\\", "\"", "'", " " ]
|
||||
for char in quote_chars:
|
||||
s = s.replace(char, "\\" + char)
|
||||
return s
|
||||
|
||||
def none(self, prefix, parsed_args, **kwargs):
|
||||
return []
|
||||
rate = none
|
||||
time = none
|
||||
url = none
|
||||
|
||||
def path(self, prefix, parsed_args, **kwargs):
|
||||
client = nilmdb.client.Client(parsed_args.url)
|
||||
return ( self.escape(s[0])
|
||||
for s in client.stream_list()
|
||||
if s[0].startswith(prefix) )
|
||||
|
||||
def layout(self, prefix, parsed_args, **kwargs):
|
||||
types = [ "int8", "int16", "int32", "int64",
|
||||
"uint8", "uint16", "uint32", "uint64",
|
||||
"float32", "float64" ]
|
||||
layouts = []
|
||||
for i in range(1,10):
|
||||
layouts.extend([(t + "_" + str(i)) for t in types])
|
||||
return ( l for l in layouts if l.startswith(prefix) )
|
||||
|
||||
def meta_key(self, prefix, parsed_args, **kwargs):
|
||||
return (kv.split('=')[0] for kv
|
||||
in self.meta_keyval(prefix, parsed_args, **kwargs))
|
||||
|
||||
def meta_keyval(self, prefix, parsed_args, **kwargs):
|
||||
client = nilmdb.client.Client(parsed_args.url)
|
||||
path = parsed_args.path
|
||||
if not path:
|
||||
return []
|
||||
return ( self.escape(k + '=' + v)
|
||||
for (k,v) in client.stream_get_metadata(path).iteritems()
|
||||
if k.startswith(prefix) )
|
||||
|
||||
|
||||
class Cmdline(object):
|
||||
|
||||
def __init__(self, argv = None):
|
||||
self.argv = argv or sys.argv[1:]
|
||||
try:
|
||||
# Assume command line arguments are encoded with stdin's encoding,
|
||||
# and reverse it. Won't be needed in Python 3, but for now..
|
||||
self.argv = [ x.decode(sys.stdin.encoding) for x in self.argv ]
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
self.client = None
|
||||
self.def_url = os.environ.get("NILMDB_URL", "http://localhost:12380")
|
||||
self.def_url = os.environ.get("NILMDB_URL", "http://localhost/nilmdb/")
|
||||
self.subcmd = {}
|
||||
self.complete = Complete()
|
||||
|
||||
def arg_time(self, toparse):
|
||||
"""Parse a time string argument"""
|
||||
try:
|
||||
return nilmdb.utils.time.parse_time(toparse).totimestamp()
|
||||
return nilmdb.utils.time.parse_time(toparse)
|
||||
except ValueError as e:
|
||||
raise argparse.ArgumentTypeError(sprintf("%s \"%s\"",
|
||||
str(e), toparse))
|
||||
|
||||
# Set up the parser
|
||||
def parser_setup(self):
|
||||
self.parser = JimArgumentParser(add_help = False,
|
||||
formatter_class = def_form)
|
||||
@@ -55,7 +114,8 @@ class Cmdline(object):
|
||||
group = self.parser.add_argument_group("Server")
|
||||
group.add_argument("-u", "--url", action="store",
|
||||
default=self.def_url,
|
||||
help="NilmDB server URL (default: %(default)s)")
|
||||
help="NilmDB server URL (default: %(default)s)"
|
||||
).completer = self.complete.url
|
||||
|
||||
sub = self.parser.add_subparsers(
|
||||
title="Commands", dest="command",
|
||||
@@ -73,12 +133,21 @@ class Cmdline(object):
|
||||
sys.exit(-1)
|
||||
|
||||
def run(self):
|
||||
# Set SIGPIPE to its default handler -- we don't need Python
|
||||
# to catch it for us.
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except ValueError: # pragma: no cover
|
||||
pass
|
||||
|
||||
# Clear cached timezone, so that we can pick up timezone changes
|
||||
# while running this from the test suite.
|
||||
datetime_tz._localtz = None
|
||||
|
||||
# Run parser
|
||||
self.parser_setup()
|
||||
if argcomplete: # pragma: no cover
|
||||
argcomplete.autocomplete(self.parser)
|
||||
self.args = self.parser.parse_args(self.argv)
|
||||
|
||||
# Run arg verify handler if there is one
|
||||
|
@@ -22,9 +22,11 @@ Layout types are of the format: type_count
|
||||
cmd.set_defaults(handler = cmd_create)
|
||||
group = cmd.add_argument_group("Required arguments")
|
||||
group.add_argument("path",
|
||||
help="Path (in database) of new stream, e.g. /foo/bar")
|
||||
help="Path (in database) of new stream, e.g. /foo/bar",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("layout",
|
||||
help="Layout type for new stream, e.g. float32_8")
|
||||
help="Layout type for new stream, e.g. float32_8",
|
||||
).completer = self.complete.layout
|
||||
return cmd
|
||||
|
||||
def cmd_create(self):
|
||||
|
@@ -1,5 +1,6 @@
|
||||
from nilmdb.utils.printf import *
|
||||
import nilmdb.client
|
||||
import fnmatch
|
||||
|
||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||
|
||||
@@ -7,19 +8,42 @@ def setup(self, sub):
|
||||
cmd = sub.add_parser("destroy", help="Delete a stream and all data",
|
||||
formatter_class = def_form,
|
||||
description="""
|
||||
Destroy the stream at the specified path. All
|
||||
data and metadata related to the stream is
|
||||
permanently deleted.
|
||||
Destroy the stream at the specified path.
|
||||
The stream must be empty. All metadata
|
||||
related to the stream is permanently deleted.
|
||||
|
||||
Wildcards and multiple paths are supported.
|
||||
""")
|
||||
cmd.set_defaults(handler = cmd_destroy)
|
||||
group = cmd.add_argument_group("Options")
|
||||
group.add_argument("-R", "--remove", action="store_true",
|
||||
help="Remove all data before destroying stream")
|
||||
group.add_argument("-q", "--quiet", action="store_true",
|
||||
help="Don't display names when destroying "
|
||||
"multiple paths")
|
||||
group = cmd.add_argument_group("Required arguments")
|
||||
group.add_argument("path",
|
||||
help="Path of the stream to delete, e.g. /foo/bar")
|
||||
group.add_argument("path", nargs='+',
|
||||
help="Path of the stream to delete, e.g. /foo/bar/*",
|
||||
).completer = self.complete.path
|
||||
return cmd
|
||||
|
||||
def cmd_destroy(self):
|
||||
"""Destroy stream"""
|
||||
try:
|
||||
self.client.stream_destroy(self.args.path)
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error destroying stream: %s", str(e))
|
||||
streams = [ s[0] for s in self.client.stream_list() ]
|
||||
paths = []
|
||||
for path in self.args.path:
|
||||
new = fnmatch.filter(streams, path)
|
||||
if not new:
|
||||
self.die("error: no stream matched path: %s", path)
|
||||
paths.extend(new)
|
||||
|
||||
for path in paths:
|
||||
if not self.args.quiet and len(paths) > 1:
|
||||
printf("Destroying %s\n", path)
|
||||
|
||||
try:
|
||||
if self.args.remove:
|
||||
count = self.client.stream_remove(path)
|
||||
self.client.stream_destroy(path)
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error destroying stream: %s", str(e))
|
||||
|
@@ -12,13 +12,16 @@ def setup(self, sub):
|
||||
|
||||
group = cmd.add_argument_group("Data selection")
|
||||
group.add_argument("path",
|
||||
help="Path of stream, e.g. /foo/bar")
|
||||
help="Path of stream, e.g. /foo/bar",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("-s", "--start", required=True,
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Starting timestamp (free-form, inclusive)")
|
||||
help="Starting timestamp (free-form, inclusive)",
|
||||
).completer = self.complete.time
|
||||
group.add_argument("-e", "--end", required=True,
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp (free-form, noninclusive)")
|
||||
help="Ending timestamp (free-form, noninclusive)",
|
||||
).completer = self.complete.time
|
||||
|
||||
group = cmd.add_argument_group("Output format")
|
||||
group.add_argument("-b", "--bare", action="store_true",
|
||||
@@ -26,6 +29,8 @@ def setup(self, sub):
|
||||
group.add_argument("-a", "--annotate", action="store_true",
|
||||
help="Include comments with some information "
|
||||
"about the stream")
|
||||
group.add_argument("-m", "--markup", action="store_true",
|
||||
help="Include comments with interval starts and ends")
|
||||
group.add_argument("-T", "--timestamp-raw", action="store_true",
|
||||
help="Show raw timestamps in annotated information")
|
||||
group.add_argument("-c", "--count", action="store_true",
|
||||
@@ -44,9 +49,9 @@ def cmd_extract(self):
|
||||
layout = streams[0][1]
|
||||
|
||||
if self.args.timestamp_raw:
|
||||
time_string = nilmdb.utils.time.float_time_to_string
|
||||
time_string = nilmdb.utils.time.timestamp_to_string
|
||||
else:
|
||||
time_string = nilmdb.utils.time.format_time
|
||||
time_string = nilmdb.utils.time.timestamp_to_human
|
||||
|
||||
if self.args.annotate:
|
||||
printf("# path: %s\n", self.args.path)
|
||||
@@ -58,7 +63,8 @@ def cmd_extract(self):
|
||||
for dataline in self.client.stream_extract(self.args.path,
|
||||
self.args.start,
|
||||
self.args.end,
|
||||
self.args.count):
|
||||
self.args.count,
|
||||
self.args.markup):
|
||||
if self.args.bare and not self.args.count:
|
||||
# Strip timestamp (first element). Doesn't make sense
|
||||
# if we are only returning a count.
|
||||
|
@@ -25,7 +25,8 @@ def setup(self, sub):
|
||||
group.add_argument("-t", "--timestamp", action="store_true",
|
||||
help="Add timestamps to each line")
|
||||
group.add_argument("-r", "--rate", type=float,
|
||||
help="Data rate, in Hz")
|
||||
help="Data rate, in Hz",
|
||||
).completer = self.complete.rate
|
||||
|
||||
group = cmd.add_argument_group("Start time",
|
||||
description="""
|
||||
@@ -39,7 +40,8 @@ def setup(self, sub):
|
||||
exc = group.add_mutually_exclusive_group()
|
||||
exc.add_argument("-s", "--start",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Starting timestamp (free-form)")
|
||||
help="Starting timestamp (free-form)",
|
||||
).completer = self.complete.time
|
||||
exc.add_argument("-f", "--filename", action="store_true",
|
||||
help="Use filename to determine start time")
|
||||
|
||||
@@ -52,11 +54,13 @@ def setup(self, sub):
|
||||
timezone.""")
|
||||
group.add_argument("-e", "--end",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp (free-form)")
|
||||
help="Ending timestamp (free-form)",
|
||||
).completer = self.complete.time
|
||||
|
||||
group = cmd.add_argument_group("Required parameters")
|
||||
group.add_argument("path",
|
||||
help="Path of stream, e.g. /foo/bar")
|
||||
help="Path of stream, e.g. /foo/bar",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("file", nargs = '?', default='-',
|
||||
help="File to insert (default: - (stdin))")
|
||||
return cmd
|
||||
@@ -92,7 +96,7 @@ def cmd_insert(self):
|
||||
|
||||
if arg.start is None:
|
||||
try:
|
||||
arg.start = nilmdb.utils.time.parse_time(filename).totimestamp()
|
||||
arg.start = nilmdb.utils.time.parse_time(filename)
|
||||
except ValueError:
|
||||
self.die("error extracting start time from filename '%s'",
|
||||
filename)
|
||||
@@ -106,10 +110,10 @@ def cmd_insert(self):
|
||||
if not arg.quiet:
|
||||
printf(" Input file: %s\n", filename)
|
||||
printf(" Start time: %s\n",
|
||||
nilmdb.utils.time.format_time(arg.start))
|
||||
nilmdb.utils.time.timestamp_to_human(arg.start))
|
||||
if arg.end:
|
||||
printf(" End time: %s\n",
|
||||
nilmdb.utils.time.format_time(arg.end))
|
||||
nilmdb.utils.time.timestamp_to_human(arg.end))
|
||||
if arg.timestamp:
|
||||
printf("Timestamper: %s\n", str(data))
|
||||
|
||||
|
66
nilmdb/cmdline/intervals.py
Normal file
66
nilmdb/cmdline/intervals.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from nilmdb.utils.printf import *
|
||||
import nilmdb.utils.time
|
||||
|
||||
import fnmatch
|
||||
import argparse
|
||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||
|
||||
def setup(self, sub):
|
||||
cmd = sub.add_parser("intervals", help="List intervals",
|
||||
formatter_class = def_form,
|
||||
description="""
|
||||
List intervals in a stream, similar to
|
||||
'list --detail path'.
|
||||
|
||||
If '--diff diffpath' is provided, only
|
||||
interval ranges that are present in 'path'
|
||||
and not present in 'diffpath' are printed.
|
||||
""")
|
||||
cmd.set_defaults(verify = cmd_intervals_verify,
|
||||
handler = cmd_intervals)
|
||||
|
||||
group = cmd.add_argument_group("Stream selection")
|
||||
group.add_argument("path", metavar="PATH",
|
||||
help="List intervals for this path",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("-d", "--diff", metavar="PATH",
|
||||
help="Subtract intervals from this path",
|
||||
).completer = self.complete.path
|
||||
|
||||
group = cmd.add_argument_group("Interval details")
|
||||
group.add_argument("-s", "--start",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Starting timestamp for intervals "
|
||||
"(free-form, inclusive)",
|
||||
).completer = self.complete.time
|
||||
group.add_argument("-e", "--end",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp for intervals "
|
||||
"(free-form, noninclusive)",
|
||||
).completer = self.complete.time
|
||||
|
||||
group = cmd.add_argument_group("Misc options")
|
||||
group.add_argument("-T", "--timestamp-raw", action="store_true",
|
||||
help="Show raw timestamps when printing times")
|
||||
|
||||
return cmd
|
||||
|
||||
def cmd_intervals_verify(self):
|
||||
if self.args.start is not None and self.args.end is not None:
|
||||
if self.args.start >= self.args.end:
|
||||
self.parser.error("start must precede end")
|
||||
|
||||
def cmd_intervals(self):
|
||||
"""List intervals in a stream"""
|
||||
if self.args.timestamp_raw:
|
||||
time_string = nilmdb.utils.time.timestamp_to_string
|
||||
else:
|
||||
time_string = nilmdb.utils.time.timestamp_to_human
|
||||
|
||||
try:
|
||||
for (start, end) in self.client.stream_intervals(
|
||||
self.args.path, self.args.start, self.args.end, self.args.diff):
|
||||
printf("[ %s -> %s ]\n", time_string(start), time_string(end))
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error listing intervals: %s", str(e))
|
||||
|
@@ -10,19 +10,16 @@ def setup(self, sub):
|
||||
formatter_class = def_form,
|
||||
description="""
|
||||
List streams available in the database,
|
||||
optionally filtering by layout or path. Wildcards
|
||||
are accepted.
|
||||
optionally filtering by path. Wildcards
|
||||
are accepted; non-matching paths or wildcards
|
||||
are ignored.
|
||||
""")
|
||||
cmd.set_defaults(verify = cmd_list_verify,
|
||||
handler = cmd_list)
|
||||
|
||||
group = cmd.add_argument_group("Stream filtering")
|
||||
group.add_argument("-p", "--path", metavar="PATH", default="*",
|
||||
help="Match only this path (-p can be omitted)")
|
||||
group.add_argument("path_positional", default="*",
|
||||
nargs="?", help=argparse.SUPPRESS)
|
||||
group.add_argument("-l", "--layout", default="*",
|
||||
help="Match only this stream layout")
|
||||
group.add_argument("path", metavar="PATH", default=["*"], nargs='*',
|
||||
).completer = self.complete.path
|
||||
|
||||
group = cmd.add_argument_group("Interval info")
|
||||
group.add_argument("-E", "--ext", action="store_true",
|
||||
@@ -35,29 +32,23 @@ def setup(self, sub):
|
||||
group.add_argument("-s", "--start",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Starting timestamp for intervals "
|
||||
"(free-form, inclusive)")
|
||||
"(free-form, inclusive)",
|
||||
).completer = self.complete.time
|
||||
group.add_argument("-e", "--end",
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp for intervals "
|
||||
"(free-form, noninclusive)")
|
||||
"(free-form, noninclusive)",
|
||||
).completer = self.complete.time
|
||||
|
||||
group = cmd.add_argument_group("Misc options")
|
||||
group.add_argument("-T", "--timestamp-raw", action="store_true",
|
||||
help="Show raw timestamps when printing times")
|
||||
group.add_argument("-l", "--layout", action="store_true",
|
||||
help="Show layout type next to path name")
|
||||
|
||||
return cmd
|
||||
|
||||
def cmd_list_verify(self):
|
||||
# A hidden "path_positional" argument lets the user leave off the
|
||||
# "-p" when specifying the path. Handle it here.
|
||||
got_opt = self.args.path != "*"
|
||||
got_pos = self.args.path_positional != "*"
|
||||
if got_pos:
|
||||
if got_opt:
|
||||
self.parser.error("too many paths specified")
|
||||
else:
|
||||
self.args.path = self.args.path_positional
|
||||
|
||||
if self.args.start is not None and self.args.end is not None:
|
||||
if self.args.start >= self.args.end:
|
||||
self.parser.error("start must precede end")
|
||||
@@ -71,32 +62,37 @@ def cmd_list(self):
|
||||
streams = self.client.stream_list(extended = True)
|
||||
|
||||
if self.args.timestamp_raw:
|
||||
time_string = nilmdb.utils.time.float_time_to_string
|
||||
time_string = nilmdb.utils.time.timestamp_to_string
|
||||
else:
|
||||
time_string = nilmdb.utils.time.format_time
|
||||
time_string = nilmdb.utils.time.timestamp_to_human
|
||||
|
||||
for stream in streams:
|
||||
(path, layout, int_min, int_max, rows, seconds) = stream[:6]
|
||||
if not (fnmatch.fnmatch(path, self.args.path) and
|
||||
fnmatch.fnmatch(layout, self.args.layout)):
|
||||
continue
|
||||
for argpath in self.args.path:
|
||||
for stream in streams:
|
||||
(path, layout, int_min, int_max, rows, time) = stream[:6]
|
||||
if not fnmatch.fnmatch(path, argpath):
|
||||
continue
|
||||
|
||||
printf("%s %s\n", path, layout)
|
||||
|
||||
if self.args.ext:
|
||||
if int_min is None or int_max is None:
|
||||
printf(" interval extents: (no data)\n")
|
||||
if self.args.layout:
|
||||
printf("%s %s\n", path, layout)
|
||||
else:
|
||||
printf(" interval extents: %s -> %s\n",
|
||||
time_string(int_min), time_string(int_max))
|
||||
printf(" total data: %d rows, %.6f seconds\n",
|
||||
rows or 0, seconds or 0);
|
||||
printf("%s\n", path)
|
||||
|
||||
if self.args.detail:
|
||||
printed = False
|
||||
for (start, end) in self.client.stream_intervals(
|
||||
path, self.args.start, self.args.end):
|
||||
printf(" [ %s -> %s ]\n", time_string(start), time_string(end))
|
||||
printed = True
|
||||
if not printed:
|
||||
printf(" (no intervals)\n")
|
||||
if self.args.ext:
|
||||
if int_min is None or int_max is None:
|
||||
printf(" interval extents: (no data)\n")
|
||||
else:
|
||||
printf(" interval extents: %s -> %s\n",
|
||||
time_string(int_min), time_string(int_max))
|
||||
printf(" total data: %d rows, %.6f seconds\n",
|
||||
rows or 0,
|
||||
nilmdb.utils.time.timestamp_to_seconds(time or 0))
|
||||
|
||||
if self.args.detail:
|
||||
printed = False
|
||||
for (start, end) in self.client.stream_intervals(
|
||||
path, self.args.start, self.args.end):
|
||||
printf(" [ %s -> %s ]\n",
|
||||
time_string(start), time_string(end))
|
||||
printed = True
|
||||
if not printed:
|
||||
printf(" (no intervals)\n")
|
||||
|
@@ -9,23 +9,31 @@ def setup(self, sub):
|
||||
a stream.
|
||||
""",
|
||||
usage="%(prog)s path [-g [key ...] | "
|
||||
"-s key=value [...] | -u key=value [...]]")
|
||||
"-s key=value [...] | -u key=value [...]] | "
|
||||
"-d [key ...]")
|
||||
cmd.set_defaults(handler = cmd_metadata)
|
||||
|
||||
group = cmd.add_argument_group("Required arguments")
|
||||
group.add_argument("path",
|
||||
help="Path of stream, e.g. /foo/bar")
|
||||
help="Path of stream, e.g. /foo/bar",
|
||||
).completer = self.complete.path
|
||||
|
||||
group = cmd.add_argument_group("Actions")
|
||||
exc = group.add_mutually_exclusive_group()
|
||||
exc.add_argument("-g", "--get", nargs="*", metavar="key",
|
||||
help="Get metadata for specified keys (default all)")
|
||||
help="Get metadata for specified keys (default all)",
|
||||
).completer = self.complete.meta_key
|
||||
exc.add_argument("-s", "--set", nargs="+", metavar="key=value",
|
||||
help="Replace all metadata with provided "
|
||||
"key=value pairs")
|
||||
"key=value pairs",
|
||||
).completer = self.complete.meta_keyval
|
||||
exc.add_argument("-u", "--update", nargs="+", metavar="key=value",
|
||||
help="Update metadata using provided "
|
||||
"key=value pairs")
|
||||
"key=value pairs",
|
||||
).completer = self.complete.meta_keyval
|
||||
exc.add_argument("-d", "--delete", nargs="*", metavar="key",
|
||||
help="Delete metadata for specified keys (default all)",
|
||||
).completer = self.complete.meta_key
|
||||
return cmd
|
||||
|
||||
def cmd_metadata(self):
|
||||
@@ -52,6 +60,16 @@ def cmd_metadata(self):
|
||||
handler(self.args.path, data)
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error setting/updating metadata: %s", str(e))
|
||||
elif self.args.delete is not None:
|
||||
# Delete (by setting values to empty strings)
|
||||
keys = self.args.delete or None
|
||||
try:
|
||||
data = self.client.stream_get_metadata(self.args.path, keys)
|
||||
for key in data:
|
||||
data[key] = ""
|
||||
self.client.stream_update_metadata(self.args.path, data)
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error deleting metadata: %s", str(e))
|
||||
else:
|
||||
# Get (or unspecified)
|
||||
keys = self.args.get or None
|
||||
@@ -60,7 +78,7 @@ def cmd_metadata(self):
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error getting metadata: %s", str(e))
|
||||
for key, value in sorted(data.items()):
|
||||
# Omit nonexistant keys
|
||||
# Print nonexistant keys as having empty value
|
||||
if value is None:
|
||||
value = ""
|
||||
printf("%s=%s\n", key, value)
|
||||
|
@@ -1,37 +1,55 @@
|
||||
from nilmdb.utils.printf import *
|
||||
import nilmdb.client
|
||||
import fnmatch
|
||||
|
||||
def setup(self, sub):
|
||||
cmd = sub.add_parser("remove", help="Remove data",
|
||||
description="""
|
||||
Remove all data from a specified time range within a
|
||||
stream.
|
||||
stream. If multiple streams or wildcards are provided,
|
||||
the same time range is removed from all streams.
|
||||
""")
|
||||
cmd.set_defaults(handler = cmd_remove)
|
||||
|
||||
group = cmd.add_argument_group("Data selection")
|
||||
group.add_argument("path",
|
||||
help="Path of stream, e.g. /foo/bar")
|
||||
group.add_argument("path", nargs='+',
|
||||
help="Path of stream, e.g. /foo/bar/*",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("-s", "--start", required=True,
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Starting timestamp (free-form, inclusive)")
|
||||
help="Starting timestamp (free-form, inclusive)",
|
||||
).completer = self.complete.time
|
||||
group.add_argument("-e", "--end", required=True,
|
||||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp (free-form, noninclusive)")
|
||||
help="Ending timestamp (free-form, noninclusive)",
|
||||
).completer = self.complete.time
|
||||
|
||||
group = cmd.add_argument_group("Output format")
|
||||
group.add_argument("-q", "--quiet", action="store_true",
|
||||
help="Don't display names when removing "
|
||||
"from multiple paths")
|
||||
group.add_argument("-c", "--count", action="store_true",
|
||||
help="Output number of data points removed")
|
||||
return cmd
|
||||
|
||||
def cmd_remove(self):
|
||||
streams = [ s[0] for s in self.client.stream_list() ]
|
||||
paths = []
|
||||
for path in self.args.path:
|
||||
new = fnmatch.filter(streams, path)
|
||||
if not new:
|
||||
self.die("error: no stream matched path: %s", path)
|
||||
paths.extend(new)
|
||||
|
||||
try:
|
||||
count = self.client.stream_remove(self.args.path,
|
||||
self.args.start, self.args.end)
|
||||
for path in paths:
|
||||
if not self.args.quiet and len(paths) > 1:
|
||||
printf("Removing from %s\n", path)
|
||||
count = self.client.stream_remove(path,
|
||||
self.args.start, self.args.end)
|
||||
if self.args.count:
|
||||
printf("%d\n", count);
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error removing data: %s", str(e))
|
||||
|
||||
if self.args.count:
|
||||
printf("%d\n", count)
|
||||
|
||||
return 0
|
||||
|
31
nilmdb/cmdline/rename.py
Normal file
31
nilmdb/cmdline/rename.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from nilmdb.utils.printf import *
|
||||
import nilmdb.client
|
||||
|
||||
from argparse import ArgumentDefaultsHelpFormatter as def_form
|
||||
|
||||
def setup(self, sub):
|
||||
cmd = sub.add_parser("rename", help="Rename a stream",
|
||||
formatter_class = def_form,
|
||||
description="""
|
||||
Rename a stream.
|
||||
|
||||
Only the stream's path is renamed; no
|
||||
metadata is changed.
|
||||
""")
|
||||
cmd.set_defaults(handler = cmd_rename)
|
||||
group = cmd.add_argument_group("Required arguments")
|
||||
group.add_argument("oldpath",
|
||||
help="Old path, e.g. /foo/old",
|
||||
).completer = self.complete.path
|
||||
group.add_argument("newpath",
|
||||
help="New path, e.g. /foo/bar/new",
|
||||
).completer = self.complete.path
|
||||
|
||||
return cmd
|
||||
|
||||
def cmd_rename(self):
|
||||
"""Rename a stream"""
|
||||
try:
|
||||
self.client.stream_rename(self.args.oldpath, self.args.newpath)
|
||||
except nilmdb.client.ClientError as e:
|
||||
self.die("error renaming stream: %s", str(e))
|
@@ -22,7 +22,7 @@ def main():
|
||||
group.add_argument('-p', '--port', help = 'Listen on the given port',
|
||||
type = int, default = 12380)
|
||||
group.add_argument('-d', '--database', help = 'Database directory',
|
||||
default = os.path.join(os.getcwd(), "db"))
|
||||
default = "./db")
|
||||
group.add_argument('-q', '--quiet', help = 'Silence output',
|
||||
action = 'store_true')
|
||||
group.add_argument('-t', '--traceback',
|
||||
|
@@ -17,5 +17,5 @@ except (ImportError, TypeError): # pragma: no cover
|
||||
pass
|
||||
|
||||
from nilmdb.server.nilmdb import NilmDB
|
||||
from nilmdb.server.server import Server
|
||||
from nilmdb.server.server import Server, wsgi_application
|
||||
from nilmdb.server.errors import NilmDBError, StreamError, OverlapError
|
||||
|
@@ -5,15 +5,16 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.utils.time import float_time_to_string as ftts
|
||||
from nilmdb.utils.time import timestamp_to_string as timestamp_to_string
|
||||
import nilmdb.utils
|
||||
|
||||
import os
|
||||
import cPickle as pickle
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
#from . import pyrocket as rocket
|
||||
import nilmdb.utils.lock
|
||||
from . import rocket
|
||||
|
||||
# Up to 256 open file descriptors at any given time.
|
||||
@@ -26,6 +27,8 @@ class BulkData(object):
|
||||
def __init__(self, basepath, **kwargs):
|
||||
self.basepath = basepath
|
||||
self.root = os.path.join(self.basepath, "data")
|
||||
self.lock = self.root + ".lock"
|
||||
self.lockfile = None
|
||||
|
||||
# Tuneables
|
||||
if "file_size" in kwargs:
|
||||
@@ -44,8 +47,22 @@ class BulkData(object):
|
||||
if not os.path.isdir(self.root):
|
||||
os.mkdir(self.root)
|
||||
|
||||
# Create the lock
|
||||
self.lockfile = open(self.lock, "w")
|
||||
if not nilmdb.utils.lock.exclusive_lock(self.lockfile):
|
||||
raise IOError('database at "' + self.basepath +
|
||||
'" is already locked by another process')
|
||||
|
||||
def close(self):
|
||||
self.getnode.cache_remove_all()
|
||||
if self.lockfile:
|
||||
nilmdb.utils.lock.exclusive_unlock(self.lockfile)
|
||||
self.lockfile.close()
|
||||
try:
|
||||
os.unlink(self.lock)
|
||||
except OSError: # pragma: no cover
|
||||
pass
|
||||
self.lockfile = None
|
||||
|
||||
def _encode_filename(self, path):
|
||||
# Encode all paths to UTF-8, regardless of sys.getfilesystemencoding(),
|
||||
@@ -56,6 +73,64 @@ class BulkData(object):
|
||||
return path.encode('utf-8')
|
||||
return path
|
||||
|
||||
def _create_check_ospath(self, ospath):
|
||||
if ospath[-1] == '/':
|
||||
raise ValueError("invalid path; should not end with a /")
|
||||
if Table.exists(ospath):
|
||||
raise ValueError("stream already exists at this path")
|
||||
if os.path.isdir(ospath):
|
||||
# Look for any files in subdirectories. Fully empty subdirectories
|
||||
# are OK; they might be there during a rename
|
||||
for (root, dirs, files) in os.walk(ospath):
|
||||
if len(files):
|
||||
raise ValueError(
|
||||
"non-empty subdirs of this path already exist")
|
||||
|
||||
def _create_parents(self, unicodepath):
|
||||
"""Verify the path name, and create parent directories if they
|
||||
don't exist. Returns a list of elements that got created."""
|
||||
path = self._encode_filename(unicodepath)
|
||||
|
||||
if path[0] != '/':
|
||||
raise ValueError("paths must start with /")
|
||||
[ group, node ] = path.rsplit("/", 1)
|
||||
if group == '':
|
||||
raise ValueError("invalid path; path must contain at least one "
|
||||
"folder")
|
||||
if node == '':
|
||||
raise ValueError("invalid path; should not end with a /")
|
||||
if not Table.valid_path(path):
|
||||
raise ValueError("path name is invalid or contains reserved words")
|
||||
|
||||
# Create the table's base dir. Note that we make a
|
||||
# distinction here between NilmDB paths (always Unix style,
|
||||
# split apart manually) and OS paths (built up with
|
||||
# os.path.join)
|
||||
|
||||
# Make directories leading up to this one
|
||||
elements = path.lstrip('/').split('/')
|
||||
made_dirs = []
|
||||
try:
|
||||
# Make parent elements
|
||||
for i in range(len(elements)):
|
||||
ospath = os.path.join(self.root, *elements[0:i])
|
||||
if Table.exists(ospath):
|
||||
raise ValueError("path is subdir of existing node")
|
||||
if not os.path.isdir(ospath):
|
||||
os.mkdir(ospath)
|
||||
made_dirs.append(ospath)
|
||||
except Exception as e:
|
||||
# Try to remove paths that we created; ignore errors
|
||||
exc_info = sys.exc_info()
|
||||
for ospath in reversed(made_dirs): # pragma: no cover (hard to hit)
|
||||
try:
|
||||
os.rmdir(ospath)
|
||||
except OSError:
|
||||
pass
|
||||
raise exc_info[1], None, exc_info[2]
|
||||
|
||||
return elements
|
||||
|
||||
def create(self, unicodepath, layout_name):
|
||||
"""
|
||||
unicodepath: path to the data (e.g. u'/newton/prep').
|
||||
@@ -67,32 +142,11 @@ class BulkData(object):
|
||||
|
||||
layout_name: string for nilmdb.layout.get_named(), e.g. 'float32_8'
|
||||
"""
|
||||
path = self._encode_filename(unicodepath)
|
||||
|
||||
if path[0] != '/':
|
||||
raise ValueError("paths must start with /")
|
||||
[ group, node ] = path.rsplit("/", 1)
|
||||
if group == '':
|
||||
raise ValueError("invalid path; path must contain at least one "
|
||||
"folder")
|
||||
|
||||
# Create the table. Note that we make a distinction here
|
||||
# between NilmDB paths (always Unix style, split apart
|
||||
# manually) and OS paths (built up with os.path.join)
|
||||
|
||||
# Make directories leading up to this one
|
||||
elements = path.lstrip('/').split('/')
|
||||
for i in range(len(elements)):
|
||||
ospath = os.path.join(self.root, *elements[0:i])
|
||||
if Table.exists(ospath):
|
||||
raise ValueError("path is subdir of existing node")
|
||||
if not os.path.isdir(ospath):
|
||||
os.mkdir(ospath)
|
||||
elements = self._create_parents(unicodepath)
|
||||
|
||||
# Make the final dir
|
||||
ospath = os.path.join(self.root, *elements)
|
||||
if os.path.isdir(ospath):
|
||||
raise ValueError("subdirs of this path already exist")
|
||||
self._create_check_ospath(ospath)
|
||||
os.mkdir(ospath)
|
||||
|
||||
try:
|
||||
@@ -102,7 +156,7 @@ class BulkData(object):
|
||||
|
||||
# Open and cache it
|
||||
self.getnode(unicodepath)
|
||||
except:
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
try:
|
||||
os.rmdir(ospath)
|
||||
@@ -113,6 +167,57 @@ class BulkData(object):
|
||||
# Success
|
||||
return
|
||||
|
||||
def _remove_leaves(self, unicodepath):
|
||||
"""Remove empty directories starting at the leaves of unicodepath"""
|
||||
path = self._encode_filename(unicodepath)
|
||||
elements = path.lstrip('/').split('/')
|
||||
for i in reversed(range(len(elements))):
|
||||
ospath = os.path.join(self.root, *elements[0:i+1])
|
||||
try:
|
||||
os.rmdir(ospath)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def rename(self, oldunicodepath, newunicodepath):
|
||||
"""Move entire tree from 'oldunicodepath' to
|
||||
'newunicodepath'"""
|
||||
oldpath = self._encode_filename(oldunicodepath)
|
||||
newpath = self._encode_filename(newunicodepath)
|
||||
|
||||
# Get OS paths
|
||||
oldelements = oldpath.lstrip('/').split('/')
|
||||
oldospath = os.path.join(self.root, *oldelements)
|
||||
newelements = newpath.lstrip('/').split('/')
|
||||
newospath = os.path.join(self.root, *newelements)
|
||||
|
||||
# Basic checks
|
||||
if oldospath == newospath:
|
||||
raise ValueError("old and new paths are the same")
|
||||
|
||||
# Move the table to a temporary location
|
||||
tmpdir = tempfile.mkdtemp(prefix = "rename-", dir = self.root)
|
||||
tmppath = os.path.join(tmpdir, "table")
|
||||
os.rename(oldospath, tmppath)
|
||||
|
||||
try:
|
||||
# Check destination path
|
||||
self._create_check_ospath(newospath)
|
||||
|
||||
# Create parent dirs for new location
|
||||
self._create_parents(newunicodepath)
|
||||
|
||||
# Move table into new location
|
||||
os.rename(tmppath, newospath)
|
||||
except Exception:
|
||||
# On failure, move the table back to original path
|
||||
os.rename(tmppath, oldospath)
|
||||
os.rmdir(tmpdir)
|
||||
raise
|
||||
|
||||
# Prune old dirs
|
||||
self._remove_leaves(oldunicodepath)
|
||||
os.rmdir(tmpdir)
|
||||
|
||||
def destroy(self, unicodepath):
|
||||
"""Fully remove all data at a particular path. No way to undo
|
||||
it! The group/path structure is removed, too."""
|
||||
@@ -134,13 +239,8 @@ class BulkData(object):
|
||||
for name in dirs:
|
||||
os.rmdir(os.path.join(root, name))
|
||||
|
||||
# Remove empty parent directories
|
||||
for i in reversed(range(len(elements))):
|
||||
ospath = os.path.join(self.root, *elements[0:i+1])
|
||||
try:
|
||||
os.rmdir(ospath)
|
||||
except OSError:
|
||||
break
|
||||
# Remove leftover empty directories
|
||||
self._remove_leaves(unicodepath)
|
||||
|
||||
# Cache open tables
|
||||
@nilmdb.utils.lru_cache(size = table_cache_size,
|
||||
@@ -159,6 +259,11 @@ class Table(object):
|
||||
# See design.md for design details
|
||||
|
||||
# Class methods, to help keep format details in this class.
|
||||
@classmethod
|
||||
def valid_path(cls, root):
|
||||
"""Return True if a root path is a valid name"""
|
||||
return "_format" not in root.split("/")
|
||||
|
||||
@classmethod
|
||||
def exists(cls, root):
|
||||
"""Return True if a table appears to exist at this OS path"""
|
||||
@@ -178,7 +283,7 @@ class Table(object):
|
||||
fmt = { "rows_per_file": rows_per_file,
|
||||
"files_per_dir": files_per_dir,
|
||||
"layout": layout,
|
||||
"version": 2 }
|
||||
"version": 3 }
|
||||
with open(os.path.join(root, "_format"), "wb") as f:
|
||||
pickle.dump(fmt, f, 2)
|
||||
|
||||
@@ -191,21 +296,11 @@ class Table(object):
|
||||
with open(os.path.join(self.root, "_format"), "rb") as f:
|
||||
fmt = pickle.load(f)
|
||||
|
||||
if fmt["version"] == 1: # pragma: no cover
|
||||
# We can handle this old version by converting from
|
||||
# struct_fmt back to layout name.
|
||||
compat = { "<dHHHHHH": "uint16_6",
|
||||
"<dHHHHHHHHH": "uint16_9",
|
||||
"<dffffffff": "float32_8" }
|
||||
if fmt["struct_fmt"] in compat:
|
||||
fmt["version"] = 2
|
||||
fmt["layout"] = compat[fmt["struct_fmt"]]
|
||||
else:
|
||||
raise NotImplementedError("old version 1 data with format "
|
||||
+ fmt["struct_fmt"] + " is no good")
|
||||
elif fmt["version"] != 2: # pragma: no cover (just future proofing)
|
||||
raise NotImplementedError("version " + str(fmt["version"]) +
|
||||
" bulk data store not supported")
|
||||
if fmt["version"] != 3: # pragma: no cover
|
||||
# Old versions used floating point timestamps, which aren't
|
||||
# valid anymore.
|
||||
raise NotImplementedError("old version " + str(fmt["version"]) +
|
||||
" bulk data store is not supported")
|
||||
|
||||
self.rows_per_file = fmt["rows_per_file"]
|
||||
self.files_per_dir = fmt["files_per_dir"]
|
||||
@@ -300,7 +395,7 @@ class Table(object):
|
||||
# Try deleting subdir, too
|
||||
try:
|
||||
os.rmdir(os.path.join(self.root, subdir))
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Cache open files
|
||||
@@ -318,38 +413,21 @@ class Table(object):
|
||||
return rocket.Rocket(self.layout,
|
||||
os.path.join(self.root, subdir, filename))
|
||||
|
||||
def append(self, data):
|
||||
"""Append the data and flush it to disk.
|
||||
data is a nested Python list [[row],[row],[...]]"""
|
||||
remaining = len(data)
|
||||
dataiter = iter(data)
|
||||
while remaining:
|
||||
# See how many rows we can fit into the current file, and open it
|
||||
(subdir, fname, offset, count) = self._offset_from_row(self.nrows)
|
||||
if count > remaining:
|
||||
count = remaining
|
||||
|
||||
f = self.file_open(subdir, fname)
|
||||
|
||||
# Write the data
|
||||
written = f.append_iter(count, dataiter)
|
||||
if written != count: # pragma: no cover
|
||||
raise Exception("Didn't write the expected number of rows: "
|
||||
+ str(written) + " != " + str(count))
|
||||
remaining -= count
|
||||
self.nrows += count
|
||||
|
||||
def append_string(self, data, start, end):
|
||||
def append_data(self, data, start, end, binary = False):
|
||||
"""Parse the formatted string in 'data', according to the
|
||||
current layout, and append it to the table. If any timestamps
|
||||
are non-monotonic, or don't fall between 'start' and 'end',
|
||||
a ValueError is raised.
|
||||
|
||||
If 'binary' is True, the data should be in raw binary format
|
||||
instead: little-endian, matching the current table's layout,
|
||||
including the int64 timestamp.
|
||||
|
||||
If this function succeeds, it returns normally. Otherwise,
|
||||
the table is reverted back to its original state by truncating
|
||||
or deleting files as necessary."""
|
||||
data_offset = 0
|
||||
last_timestamp = -1e12
|
||||
last_timestamp = nilmdb.utils.time.min_timestamp
|
||||
tot_rows = self.nrows
|
||||
count = 0
|
||||
linenum = 0
|
||||
@@ -363,26 +441,42 @@ class Table(object):
|
||||
# Ask the rocket object to parse and append up to "count"
|
||||
# rows of data, verifying things along the way.
|
||||
try:
|
||||
if binary:
|
||||
appender = f.append_binary
|
||||
else:
|
||||
appender = f.append_string
|
||||
(added_rows, data_offset, last_timestamp, linenum
|
||||
) = f.append_string(count, data, data_offset, linenum,
|
||||
start, end, last_timestamp)
|
||||
) = appender(count, data, data_offset, linenum,
|
||||
start, end, last_timestamp)
|
||||
except rocket.ParseError as e:
|
||||
(linenum, errtype, obj) = e.args
|
||||
(linenum, colnum, errtype, obj) = e.args
|
||||
if binary:
|
||||
where = "byte %d: " % (linenum)
|
||||
else:
|
||||
where = "line %d, column %d: " % (linenum, colnum)
|
||||
# Extract out the error line, add column marker
|
||||
try:
|
||||
if binary:
|
||||
raise IndexError
|
||||
bad = data.splitlines()[linenum-1]
|
||||
bad += '\n' + ' ' * (colnum - 1) + '^'
|
||||
except IndexError:
|
||||
bad = ""
|
||||
if errtype == rocket.ERR_NON_MONOTONIC:
|
||||
err = sprintf("line %d: timestamp is not monotonically "
|
||||
"increasing", linenum)
|
||||
err = "timestamp is not monotonically increasing"
|
||||
elif errtype == rocket.ERR_OUT_OF_INTERVAL:
|
||||
if obj < start:
|
||||
err = sprintf("line %d: Data timestamp %s < "
|
||||
"start time %s", linenum,
|
||||
ftts(obj), ftts(start))
|
||||
err = sprintf("Data timestamp %s < start time %s",
|
||||
timestamp_to_string(obj),
|
||||
timestamp_to_string(start))
|
||||
else:
|
||||
err = sprintf("line %d: Data timestamp %s >= "
|
||||
"end time %s", linenum,
|
||||
ftts(obj), ftts(end))
|
||||
err = sprintf("Data timestamp %s >= end time %s",
|
||||
timestamp_to_string(obj),
|
||||
timestamp_to_string(end))
|
||||
else:
|
||||
err = sprintf("line %d: %s", linenum, str(obj))
|
||||
raise ValueError("error parsing input data: " + err)
|
||||
err = str(obj)
|
||||
raise ValueError("error parsing input data: " +
|
||||
where + err + "\n" + bad)
|
||||
tot_rows += added_rows
|
||||
except Exception:
|
||||
# Some failure, so try to roll things back by truncating or
|
||||
@@ -398,10 +492,9 @@ class Table(object):
|
||||
# Success, so update self.nrows accordingly
|
||||
self.nrows = tot_rows
|
||||
|
||||
def _get_data(self, start, stop, as_string):
|
||||
def get_data(self, start, stop, binary = False):
|
||||
"""Extract data corresponding to Python range [n:m],
|
||||
and returns a numeric list or formatted string,
|
||||
depending on as_string."""
|
||||
and returns a formatted string"""
|
||||
if (start is None or
|
||||
stop is None or
|
||||
start > stop or
|
||||
@@ -417,42 +510,21 @@ class Table(object):
|
||||
if count > remaining:
|
||||
count = remaining
|
||||
f = self.file_open(subdir, filename)
|
||||
if as_string:
|
||||
ret.append(f.extract_string(offset, count))
|
||||
if binary:
|
||||
ret.append(f.extract_binary(offset, count))
|
||||
else:
|
||||
ret.extend(f.extract_list(offset, count))
|
||||
ret.append(f.extract_string(offset, count))
|
||||
remaining -= count
|
||||
row += count
|
||||
if as_string:
|
||||
return "".join(ret)
|
||||
return ret
|
||||
return b"".join(ret)
|
||||
|
||||
def get_as_text(self, start, stop):
|
||||
"""Extract data corresponding to Python range [n:m],
|
||||
and returns a formatted string"""
|
||||
return self._get_data(start, stop, True)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Extract data and return it. Supports simple indexing
|
||||
(table[n]) and range slices (table[n:m]). Returns a nested
|
||||
Python list [[row],[row],[...]]"""
|
||||
|
||||
# Handle simple slices
|
||||
if isinstance(key, slice):
|
||||
# Fall back to brute force if the slice isn't simple
|
||||
try:
|
||||
if (key.step is not None and key.step != 1):
|
||||
raise IndexError
|
||||
return self._get_data(key.start, key.stop, False)
|
||||
except IndexError:
|
||||
return [ self[x] for x in xrange(*key.indices(self.nrows)) ]
|
||||
|
||||
# Handle single points (inefficiently!)
|
||||
if key < 0 or key >= self.nrows:
|
||||
def __getitem__(self, row):
|
||||
"""Extract timestamps from a row, with table[n] notation."""
|
||||
if row < 0 or row >= self.nrows:
|
||||
raise IndexError("Index out of range")
|
||||
(subdir, filename, offset, count) = self._offset_from_row(key)
|
||||
(subdir, filename, offset, count) = self._offset_from_row(row)
|
||||
f = self.file_open(subdir, filename)
|
||||
return f.extract_list(offset, 1)[0]
|
||||
return f.extract_timestamp(offset)
|
||||
|
||||
def _remove_rows(self, subdir, filename, start, stop):
|
||||
"""Helper to mark specific rows as being removed from a
|
||||
@@ -472,7 +544,7 @@ class Table(object):
|
||||
with open(cachefile, "rb") as f:
|
||||
ranges = pickle.load(f)
|
||||
cachefile_present = True
|
||||
except:
|
||||
except Exception:
|
||||
ranges = []
|
||||
cachefile_present = False
|
||||
|
||||
@@ -543,11 +615,3 @@ class Table(object):
|
||||
self._remove_rows(subdir, filename, row_offset, row_offset + count)
|
||||
remaining -= count
|
||||
row += count
|
||||
|
||||
class TimestampOnlyTable(object):
|
||||
"""Helper that lets us pass a Tables object into bisect, by
|
||||
returning only the timestamp when a particular row is requested."""
|
||||
def __init__(self, table):
|
||||
self.table = table
|
||||
def __getitem__(self, index):
|
||||
return self.table[index][0]
|
||||
|
@@ -1,5 +1,9 @@
|
||||
"""Interval, IntervalSet
|
||||
|
||||
The Interval implemented here is just like
|
||||
nilmdb.utils.interval.Interval, except implemented in Cython for
|
||||
speed.
|
||||
|
||||
Represents an interval of time, and a set of such intervals.
|
||||
|
||||
Intervals are half-open, ie. they include data points with timestamps
|
||||
@@ -19,51 +23,44 @@ Intervals are half-open, ie. they include data points with timestamps
|
||||
# Fourth version is an optimized rb-tree that stores interval starts
|
||||
# and ends directly in the tree, like bxinterval did.
|
||||
|
||||
from ..utils.time import float_time_to_string as ftts
|
||||
from ..utils.time import min_timestamp as nilmdb_min_timestamp
|
||||
from ..utils.time import max_timestamp as nilmdb_max_timestamp
|
||||
from ..utils.time import timestamp_to_string
|
||||
from ..utils.iterator import imerge
|
||||
from ..utils.interval import IntervalError
|
||||
import itertools
|
||||
|
||||
cimport rbtree
|
||||
cdef extern from "stdint.h":
|
||||
ctypedef unsigned long long uint64_t
|
||||
from libc.stdint cimport uint64_t, int64_t
|
||||
|
||||
class IntervalError(Exception):
|
||||
"""Error due to interval overlap, etc"""
|
||||
pass
|
||||
ctypedef int64_t timestamp_t
|
||||
|
||||
cdef class Interval:
|
||||
"""Represents an interval of time."""
|
||||
|
||||
cdef public double start, end
|
||||
cdef public timestamp_t start, end
|
||||
|
||||
def __init__(self, double start, double end):
|
||||
def __init__(self, timestamp_t start, timestamp_t end):
|
||||
"""
|
||||
'start' and 'end' are arbitrary floats that represent time
|
||||
'start' and 'end' are arbitrary numbers that represent time
|
||||
"""
|
||||
if start >= end:
|
||||
# Explicitly disallow zero-width intervals (since they're half-open)
|
||||
raise IntervalError("start %s must precede end %s" % (start, end))
|
||||
self.start = float(start)
|
||||
self.end = float(end)
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
s = repr(self.start) + ", " + repr(self.end)
|
||||
return self.__class__.__name__ + "(" + s + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "[" + ftts(self.start) + " -> " + ftts(self.end) + ")"
|
||||
return ("[" + timestamp_to_string(self.start) +
|
||||
" -> " + timestamp_to_string(self.end) + ")")
|
||||
|
||||
def __cmp__(self, Interval other):
|
||||
"""Compare two intervals. If non-equal, order by start then end"""
|
||||
if not isinstance(other, Interval):
|
||||
raise TypeError("bad type")
|
||||
if self.start == other.start:
|
||||
if self.end < other.end:
|
||||
return -1
|
||||
if self.end > other.end:
|
||||
return 1
|
||||
return 0
|
||||
if self.start < other.start:
|
||||
return -1
|
||||
return 1
|
||||
return cmp(self.start, other.start) or cmp(self.end, other.end)
|
||||
|
||||
cpdef intersects(self, Interval other):
|
||||
"""Return True if two Interval objects intersect"""
|
||||
@@ -71,7 +68,7 @@ cdef class Interval:
|
||||
return False
|
||||
return True
|
||||
|
||||
cpdef subset(self, double start, double end):
|
||||
cpdef subset(self, timestamp_t start, timestamp_t end):
|
||||
"""Return a new Interval that is a subset of this one"""
|
||||
# A subclass that tracks additional data might override this.
|
||||
if start < self.start or end > self.end:
|
||||
@@ -93,14 +90,14 @@ cdef class DBInterval(Interval):
|
||||
db_end = 200, db_endpos = 20000
|
||||
"""
|
||||
|
||||
cpdef public double db_start, db_end
|
||||
cpdef public timestamp_t db_start, db_end
|
||||
cpdef public uint64_t db_startpos, db_endpos
|
||||
|
||||
def __init__(self, start, end,
|
||||
db_start, db_end,
|
||||
db_startpos, db_endpos):
|
||||
"""
|
||||
'db_start' and 'db_end' are arbitrary floats that represent
|
||||
'db_start' and 'db_end' are arbitrary numbers that represent
|
||||
time. They must be a strict superset of the time interval
|
||||
covered by 'start' and 'end'. The 'db_startpos' and
|
||||
'db_endpos' are arbitrary database position indicators that
|
||||
@@ -120,7 +117,7 @@ cdef class DBInterval(Interval):
|
||||
s += ", " + repr(self.db_startpos) + ", " + repr(self.db_endpos)
|
||||
return self.__class__.__name__ + "(" + s + ")"
|
||||
|
||||
cpdef subset(self, double start, double end):
|
||||
cpdef subset(self, timestamp_t start, timestamp_t end):
|
||||
"""
|
||||
Return a new DBInterval that is a subset of this one
|
||||
"""
|
||||
@@ -264,21 +261,15 @@ cdef class IntervalSet:
|
||||
|
||||
def __and__(self, other not None):
|
||||
"""
|
||||
Compute a new IntervalSet from the intersection of two others
|
||||
Compute a new IntervalSet from the intersection of this
|
||||
IntervalSet with one other interval.
|
||||
|
||||
Output intervals are built as subsets of the intervals in the
|
||||
first argument (self).
|
||||
"""
|
||||
out = IntervalSet()
|
||||
|
||||
if not isinstance(other, IntervalSet):
|
||||
for i in self.intersection(other):
|
||||
out.tree.insert(rbtree.RBNode(i.start, i.end, i))
|
||||
else:
|
||||
for x in other:
|
||||
for i in self.intersection(x):
|
||||
out.tree.insert(rbtree.RBNode(i.start, i.end, i))
|
||||
|
||||
for i in self.intersection(other):
|
||||
out.tree.insert(rbtree.RBNode(i.start, i.end, i))
|
||||
return out
|
||||
|
||||
def intersection(self, Interval interval not None, orig = False):
|
||||
@@ -295,23 +286,18 @@ cdef class IntervalSet:
|
||||
(potentially) subsetted to make the one that is being
|
||||
returned.
|
||||
"""
|
||||
if not isinstance(interval, Interval):
|
||||
raise TypeError("bad type")
|
||||
for n in self.tree.intersect(interval.start, interval.end):
|
||||
i = n.obj
|
||||
if i:
|
||||
if i.start >= interval.start and i.end <= interval.end:
|
||||
if orig:
|
||||
yield (i, i)
|
||||
else:
|
||||
yield i
|
||||
else:
|
||||
subset = i.subset(max(i.start, interval.start),
|
||||
min(i.end, interval.end))
|
||||
if orig:
|
||||
yield (subset, i)
|
||||
else:
|
||||
yield subset
|
||||
if orig:
|
||||
for n in self.tree.intersect(interval.start, interval.end):
|
||||
i = n.obj
|
||||
subset = i.subset(max(i.start, interval.start),
|
||||
min(i.end, interval.end))
|
||||
yield (subset, i)
|
||||
else:
|
||||
for n in self.tree.intersect(interval.start, interval.end):
|
||||
i = n.obj
|
||||
subset = i.subset(max(i.start, interval.start),
|
||||
min(i.end, interval.end))
|
||||
yield subset
|
||||
|
||||
cpdef intersects(self, Interval other):
|
||||
"""Return True if this IntervalSet intersects another interval"""
|
||||
@@ -320,7 +306,7 @@ cdef class IntervalSet:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_end(self, double t):
|
||||
def find_end(self, timestamp_t t):
|
||||
"""
|
||||
Return an Interval from this tree that ends at time t, or
|
||||
None if it doesn't exist.
|
||||
|
@@ -1,201 +0,0 @@
|
||||
# cython: profile=False
|
||||
|
||||
import time
|
||||
import sys
|
||||
import inspect
|
||||
import cStringIO
|
||||
|
||||
cdef enum:
|
||||
max_value_count = 64
|
||||
|
||||
cimport cython
|
||||
cimport libc.stdlib
|
||||
cimport libc.stdio
|
||||
cimport libc.string
|
||||
|
||||
class ParserError(Exception):
|
||||
def __init__(self, line, message):
|
||||
self.message = "line " + str(line) + ": " + message
|
||||
Exception.__init__(self, self.message)
|
||||
|
||||
class FormatterError(Exception):
|
||||
pass
|
||||
|
||||
class Layout:
|
||||
"""Represents a NILM database layout"""
|
||||
|
||||
def __init__(self, typestring):
|
||||
"""Initialize this Layout object to handle the specified
|
||||
type string"""
|
||||
try:
|
||||
[ datatype, count ] = typestring.split("_")
|
||||
except:
|
||||
raise KeyError("invalid layout string")
|
||||
|
||||
try:
|
||||
self.count = int(count)
|
||||
except ValueError:
|
||||
raise KeyError("invalid count")
|
||||
if self.count < 1 or self.count > max_value_count:
|
||||
raise KeyError("invalid count")
|
||||
|
||||
if datatype == 'uint16':
|
||||
self.parse = self.parse_uint16
|
||||
self.format_str = "%.6f" + " %d" * self.count
|
||||
self.format = self.format_generic
|
||||
elif datatype == 'float32':
|
||||
self.parse = self.parse_float64
|
||||
self.format_str = "%.6f" + " %.6e" * self.count
|
||||
self.format = self.format_generic
|
||||
elif datatype == 'float64':
|
||||
self.parse = self.parse_float64
|
||||
self.format_str = "%.6f" + " %.16e" * self.count
|
||||
self.format = self.format_generic
|
||||
else:
|
||||
raise KeyError("invalid type")
|
||||
|
||||
self.datatype = datatype
|
||||
|
||||
# Parsers
|
||||
def parse_float64(self, char *text):
|
||||
cdef int n
|
||||
cdef double ts
|
||||
# Return doubles even in float32 case, since they're going into
|
||||
# a Python array which would upconvert to double anyway.
|
||||
result = [0] * (self.count + 1)
|
||||
cdef char *end
|
||||
ts = libc.stdlib.strtod(text, &end)
|
||||
if end == text:
|
||||
raise ValueError("bad timestamp")
|
||||
result[0] = ts
|
||||
for n in range(self.count):
|
||||
text = end
|
||||
result[n+1] = libc.stdlib.strtod(text, &end)
|
||||
if end == text:
|
||||
raise ValueError("wrong number of values")
|
||||
n = 0
|
||||
while end[n] == ' ':
|
||||
n += 1
|
||||
if end[n] != '\n' and end[n] != '#' and end[n] != '\0':
|
||||
raise ValueError("extra data on line")
|
||||
return (ts, result)
|
||||
|
||||
def parse_uint16(self, char *text):
|
||||
cdef int n
|
||||
cdef double ts
|
||||
cdef int v
|
||||
cdef char *end
|
||||
result = [0] * (self.count + 1)
|
||||
ts = libc.stdlib.strtod(text, &end)
|
||||
if end == text:
|
||||
raise ValueError("bad timestamp")
|
||||
result[0] = ts
|
||||
for n in range(self.count):
|
||||
text = end
|
||||
v = libc.stdlib.strtol(text, &end, 10)
|
||||
if v < 0 or v > 65535:
|
||||
raise ValueError("value out of range")
|
||||
result[n+1] = v
|
||||
if end == text:
|
||||
raise ValueError("wrong number of values")
|
||||
n = 0
|
||||
while end[n] == ' ':
|
||||
n += 1
|
||||
if end[n] != '\n' and end[n] != '#' and end[n] != '\0':
|
||||
raise ValueError("extra data on line")
|
||||
return (ts, result)
|
||||
|
||||
# Formatters
|
||||
def format_generic(self, d):
|
||||
n = len(d) - 1
|
||||
if n != self.count:
|
||||
raise ValueError("wrong number of values for layout type: "
|
||||
"got %d, wanted %d" % (n, self.count))
|
||||
return (self.format_str % tuple(d)) + "\n"
|
||||
|
||||
# Get a layout by name
|
||||
def get_named(typestring):
|
||||
try:
|
||||
return Layout(typestring)
|
||||
except KeyError:
|
||||
compat = { "PrepData": "float32_8",
|
||||
"RawData": "uint16_6",
|
||||
"RawNotchedData": "uint16_9" }
|
||||
return Layout(compat[typestring])
|
||||
|
||||
class Parser(object):
|
||||
"""Object that parses and stores ASCII data for inclusion into the
|
||||
database"""
|
||||
|
||||
def __init__(self, layout):
|
||||
if issubclass(layout.__class__, Layout):
|
||||
self.layout = layout
|
||||
else:
|
||||
try:
|
||||
self.layout = get_named(layout)
|
||||
except KeyError:
|
||||
raise TypeError("unknown layout")
|
||||
|
||||
self.data = []
|
||||
self.min_timestamp = None
|
||||
self.max_timestamp = None
|
||||
|
||||
def parse(self, textdata):
|
||||
"""
|
||||
Parse the data, provided as lines of text, using the current
|
||||
layout, into an internal data structure suitable for a
|
||||
pytables 'table.append(parser.data)'.
|
||||
"""
|
||||
cdef double last_ts = -1e12, ts
|
||||
cdef int n = 0, i
|
||||
cdef char *line
|
||||
|
||||
indata = cStringIO.StringIO(textdata)
|
||||
# Assume any parsing error is a real error.
|
||||
# In the future we might want to skip completely empty lines,
|
||||
# or partial lines right before EOF?
|
||||
try:
|
||||
self.data = []
|
||||
for pyline in indata:
|
||||
line = pyline
|
||||
n += 1
|
||||
if line[0] == '\#':
|
||||
continue
|
||||
(ts, row) = self.layout.parse(line)
|
||||
if ts <= last_ts:
|
||||
raise ValueError("timestamp is not "
|
||||
"monotonically increasing")
|
||||
last_ts = ts
|
||||
self.data.append(row)
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
raise ParserError(n, "error: " + e.message)
|
||||
|
||||
# Mark timestamp ranges
|
||||
if len(self.data):
|
||||
self.min_timestamp = self.data[0][0]
|
||||
self.max_timestamp = self.data[-1][0]
|
||||
|
||||
class Formatter(object):
|
||||
"""Object that formats database data into ASCII"""
|
||||
|
||||
def __init__(self, layout):
|
||||
if issubclass(layout.__class__, Layout):
|
||||
self.layout = layout
|
||||
else:
|
||||
try:
|
||||
self.layout = get_named(layout)
|
||||
except KeyError:
|
||||
raise TypeError("unknown layout")
|
||||
|
||||
def format(self, data):
|
||||
"""
|
||||
Format raw data from the database, using the current layout,
|
||||
as lines of ACSII text.
|
||||
"""
|
||||
text = cStringIO.StringIO()
|
||||
try:
|
||||
for row in data:
|
||||
text.write(self.layout.format(row))
|
||||
except (ValueError, IndexError, TypeError) as e:
|
||||
raise FormatterError("formatting error: " + e.message)
|
||||
return text.getvalue()
|
@@ -12,8 +12,11 @@ Manages both the SQL database and the table storage backend.
|
||||
from __future__ import absolute_import
|
||||
import nilmdb.utils
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.server.interval import (Interval, DBInterval,
|
||||
IntervalSet, IntervalError)
|
||||
from nilmdb.utils.time import timestamp_to_string
|
||||
|
||||
from nilmdb.utils.interval import IntervalError
|
||||
from nilmdb.server.interval import Interval, DBInterval, IntervalSet
|
||||
|
||||
from nilmdb.server import bulkdata
|
||||
from nilmdb.server.errors import NilmDBError, StreamError, OverlapError
|
||||
|
||||
@@ -35,10 +38,10 @@ import bisect
|
||||
# seems that 'PRAGMA synchronous=NORMAL' and 'PRAGMA journal_mode=WAL'
|
||||
# give an equivalent speedup more safely. That is what is used here.
|
||||
_sql_schema_updates = {
|
||||
0: """
|
||||
0: { "next": 1, "sql": """
|
||||
-- All streams
|
||||
CREATE TABLE streams(
|
||||
id INTEGER PRIMARY KEY, -- stream ID
|
||||
id INTEGER PRIMARY KEY, -- stream ID
|
||||
path TEXT UNIQUE NOT NULL, -- path, e.g. '/newton/prep'
|
||||
layout TEXT NOT NULL -- layout name, e.g. float32_8
|
||||
);
|
||||
@@ -59,16 +62,21 @@ _sql_schema_updates = {
|
||||
end_pos INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX _ranges_index ON ranges (stream_id, start_time, end_time);
|
||||
""",
|
||||
""" },
|
||||
|
||||
1: """
|
||||
1: { "next": 3, "sql": """
|
||||
-- Generic dictionary-type metadata that can be associated with a stream
|
||||
CREATE TABLE metadata(
|
||||
stream_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT
|
||||
);
|
||||
""",
|
||||
""" },
|
||||
|
||||
2: { "error": "old format with floating-point timestamps requires "
|
||||
"nilmdb 1.3.1 or older" },
|
||||
|
||||
3: { "next": None },
|
||||
}
|
||||
|
||||
@nilmdb.utils.must_close()
|
||||
@@ -76,7 +84,18 @@ class NilmDB(object):
|
||||
verbose = 0
|
||||
|
||||
def __init__(self, basepath, max_results=None,
|
||||
bulkdata_args=None):
|
||||
max_removals=None, bulkdata_args=None):
|
||||
"""Initialize NilmDB at the given basepath.
|
||||
Other arguments are for debugging / testing:
|
||||
|
||||
'max_results' is the max rows to send in a single
|
||||
stream_intervals or stream_extract response.
|
||||
|
||||
'max_removals' is the max rows to delete at once
|
||||
in stream_move.
|
||||
|
||||
'bulkdata_args' is kwargs for the bulkdata module.
|
||||
"""
|
||||
if bulkdata_args is None:
|
||||
bulkdata_args = {}
|
||||
|
||||
@@ -87,7 +106,9 @@ class NilmDB(object):
|
||||
try:
|
||||
os.makedirs(self.basepath)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
if e.errno != errno.EEXIST: # pragma: no cover
|
||||
# (no coverage, because it's hard to trigger this case
|
||||
# if tests are run as root)
|
||||
raise IOError("can't create tree " + self.basepath)
|
||||
|
||||
# Our data goes inside it
|
||||
@@ -96,18 +117,22 @@ class NilmDB(object):
|
||||
# SQLite database too
|
||||
sqlfilename = os.path.join(self.basepath, "data.sql")
|
||||
self.con = sqlite3.connect(sqlfilename, check_same_thread = True)
|
||||
self._sql_schema_update()
|
||||
try:
|
||||
self._sql_schema_update()
|
||||
except Exception: # pragma: no cover
|
||||
self.data.close()
|
||||
raise
|
||||
|
||||
# See big comment at top about the performance implications of this
|
||||
self.con.execute("PRAGMA synchronous=NORMAL")
|
||||
self.con.execute("PRAGMA journal_mode=WAL")
|
||||
|
||||
# Approximate largest number of elements that we want to send
|
||||
# in a single reply (for stream_intervals, stream_extract)
|
||||
if max_results:
|
||||
self.max_results = max_results
|
||||
else:
|
||||
self.max_results = 16384
|
||||
# in a single reply (for stream_intervals, stream_extract).
|
||||
self.max_results = max_results or 16384
|
||||
|
||||
# Remove up to this many rows per call to stream_remove.
|
||||
self.max_removals = max_removals or 1048576
|
||||
|
||||
def get_basepath(self):
|
||||
return self.basepath
|
||||
@@ -123,11 +148,20 @@ class NilmDB(object):
|
||||
version = cur.execute("PRAGMA user_version").fetchone()[0]
|
||||
oldversion = version
|
||||
|
||||
while version in _sql_schema_updates:
|
||||
cur.executescript(_sql_schema_updates[version])
|
||||
version = version + 1
|
||||
while True:
|
||||
if version not in _sql_schema_updates: # pragma: no cover
|
||||
raise Exception(self.basepath + ": unknown database version "
|
||||
+ str(version))
|
||||
update = _sql_schema_updates[version]
|
||||
if "error" in update: # pragma: no cover
|
||||
raise Exception(self.basepath + ": can't use database version "
|
||||
+ str(version) + ": " + update["error"])
|
||||
if update["next"] is None:
|
||||
break
|
||||
cur.executescript(update["sql"])
|
||||
version = update["next"]
|
||||
if self.verbose: # pragma: no cover
|
||||
printf("Schema updated to %d\n", version)
|
||||
printf("Database schema updated to %d\n", version)
|
||||
|
||||
if version != oldversion:
|
||||
with self.con:
|
||||
@@ -135,9 +169,9 @@ class NilmDB(object):
|
||||
|
||||
def _check_user_times(self, start, end):
|
||||
if start is None:
|
||||
start = -1e12
|
||||
start = nilmdb.utils.time.min_timestamp
|
||||
if end is None:
|
||||
end = 1e12
|
||||
end = nilmdb.utils.time.max_timestamp
|
||||
if start >= end:
|
||||
raise NilmDBError("start must precede end")
|
||||
return (start, end)
|
||||
@@ -284,14 +318,14 @@ class NilmDB(object):
|
||||
interval_min (earliest interval start)
|
||||
interval_max (latest interval end)
|
||||
rows (total number of rows of data)
|
||||
seconds (total time covered by this stream)
|
||||
time (total time covered by this stream, in timestamp units)
|
||||
"""
|
||||
params = ()
|
||||
query = "SELECT streams.path, streams.layout"
|
||||
if extended:
|
||||
query += ", min(ranges.start_time), max(ranges.end_time) "
|
||||
query += ", sum(ranges.end_pos - ranges.start_pos) "
|
||||
query += ", sum(ranges.end_time - ranges.start_time) "
|
||||
query += ", coalesce(sum(ranges.end_pos - ranges.start_pos), 0) "
|
||||
query += ", coalesce(sum(ranges.end_time - ranges.start_time), 0) "
|
||||
query += " FROM streams"
|
||||
if extended:
|
||||
query += " LEFT JOIN ranges ON streams.id = ranges.stream_id"
|
||||
@@ -306,31 +340,45 @@ class NilmDB(object):
|
||||
result = self.con.execute(query, params).fetchall()
|
||||
return [ list(x) for x in result ]
|
||||
|
||||
def stream_intervals(self, path, start = None, end = None):
|
||||
def stream_intervals(self, path, start = None, end = None, diffpath = None):
|
||||
"""
|
||||
List all intervals in 'path' between 'start' and 'end'. If
|
||||
'diffpath' is not none, list instead the set-difference
|
||||
between the intervals in the two streams; i.e. all interval
|
||||
ranges that are present in 'path' but not 'diffpath'.
|
||||
|
||||
Returns (intervals, restart) tuple.
|
||||
|
||||
intervals is a list of [start,end] timestamps of all intervals
|
||||
'intervals' is a list of [start,end] timestamps of all intervals
|
||||
that exist for path, between start and end.
|
||||
|
||||
restart, if nonzero, means that there were too many results to
|
||||
return in a single request. The data is complete from the
|
||||
starting timestamp to the point at which it was truncated,
|
||||
and a new request with a start time of 'restart' will fetch
|
||||
the next block of data.
|
||||
'restart', if not None, means that there were too many results
|
||||
to return in a single request. The data is complete from the
|
||||
starting timestamp to the point at which it was truncated, and
|
||||
a new request with a start time of 'restart' will fetch the
|
||||
next block of data.
|
||||
"""
|
||||
stream_id = self._stream_id(path)
|
||||
intervals = self._get_intervals(stream_id)
|
||||
if diffpath:
|
||||
diffstream_id = self._stream_id(diffpath)
|
||||
diffintervals = self._get_intervals(diffstream_id)
|
||||
(start, end) = self._check_user_times(start, end)
|
||||
requested = Interval(start, end)
|
||||
result = []
|
||||
for n, i in enumerate(intervals.intersection(requested)):
|
||||
if diffpath:
|
||||
getter = nilmdb.utils.interval.set_difference(
|
||||
intervals.intersection(requested),
|
||||
diffintervals.intersection(requested))
|
||||
else:
|
||||
getter = intervals.intersection(requested)
|
||||
for n, i in enumerate(getter):
|
||||
if n >= self.max_results:
|
||||
restart = i.start
|
||||
break
|
||||
result.append([i.start, i.end])
|
||||
else:
|
||||
restart = 0
|
||||
restart = None
|
||||
return (result, restart)
|
||||
|
||||
def stream_create(self, path, layout_name):
|
||||
@@ -393,29 +441,50 @@ class NilmDB(object):
|
||||
data.update(newdata)
|
||||
self.stream_set_metadata(path, data)
|
||||
|
||||
def stream_rename(self, oldpath, newpath):
|
||||
"""Rename a stream."""
|
||||
stream_id = self._stream_id(oldpath)
|
||||
|
||||
# Rename the data
|
||||
self.data.rename(oldpath, newpath)
|
||||
|
||||
# Rename the stream in the database
|
||||
with self.con as con:
|
||||
con.execute("UPDATE streams SET path=? WHERE id=?",
|
||||
(newpath, stream_id))
|
||||
|
||||
def stream_destroy(self, path):
|
||||
"""Fully remove a table and all of its data from the database.
|
||||
No way to undo it! Metadata is removed."""
|
||||
"""Fully remove a table from the database. Fails if there are
|
||||
any intervals data present; remove them first. Metadata is
|
||||
also removed."""
|
||||
stream_id = self._stream_id(path)
|
||||
|
||||
# Delete the cached interval data (if it was cached)
|
||||
# Verify that no intervals are present, and clear the cache
|
||||
iset = self._get_intervals(stream_id)
|
||||
if len(iset):
|
||||
raise NilmDBError("all intervals must be removed before "
|
||||
"destroying a stream")
|
||||
self._get_intervals.cache_remove(self, stream_id)
|
||||
|
||||
# Delete the data
|
||||
# Delete the bulkdata storage
|
||||
self.data.destroy(path)
|
||||
|
||||
# Delete metadata, stream, intervals
|
||||
# Delete metadata, stream, intervals (should be none)
|
||||
with self.con as con:
|
||||
con.execute("DELETE FROM metadata WHERE stream_id=?", (stream_id,))
|
||||
con.execute("DELETE FROM ranges WHERE stream_id=?", (stream_id,))
|
||||
con.execute("DELETE FROM streams WHERE id=?", (stream_id,))
|
||||
|
||||
def stream_insert(self, path, start, end, data):
|
||||
def stream_insert(self, path, start, end, data, binary = False):
|
||||
"""Insert new data into the database.
|
||||
path: Path at which to add the data
|
||||
start: Starting timestamp
|
||||
end: Ending timestamp
|
||||
data: Textual data, formatted according to the layout of path
|
||||
|
||||
'binary', if True, means that 'data' is raw binary:
|
||||
little-endian, matching the current table's layout,
|
||||
including the int64 timestamp.
|
||||
"""
|
||||
# First check for basic overlap using timestamp info given.
|
||||
stream_id = self._stream_id(path)
|
||||
@@ -429,7 +498,7 @@ class NilmDB(object):
|
||||
# there are any parse errors.
|
||||
table = self.data.getnode(path)
|
||||
row_start = table.nrows
|
||||
table.append_string(data, start, end)
|
||||
table.append_data(data, start, end, binary)
|
||||
row_end = table.nrows
|
||||
|
||||
# Insert the record into the sql database.
|
||||
@@ -448,7 +517,7 @@ class NilmDB(object):
|
||||
# Optimization for the common case where an interval wasn't truncated
|
||||
if dbinterval.start == dbinterval.db_start:
|
||||
return dbinterval.db_startpos
|
||||
return bisect.bisect_left(bulkdata.TimestampOnlyTable(table),
|
||||
return bisect.bisect_left(table,
|
||||
dbinterval.start,
|
||||
dbinterval.db_startpos,
|
||||
dbinterval.db_endpos)
|
||||
@@ -467,28 +536,36 @@ class NilmDB(object):
|
||||
# want to include the given timestamp in the results. This is
|
||||
# so a queries like 1:00 -> 2:00 and 2:00 -> 3:00 return
|
||||
# non-overlapping data.
|
||||
return bisect.bisect_left(bulkdata.TimestampOnlyTable(table),
|
||||
return bisect.bisect_left(table,
|
||||
dbinterval.end,
|
||||
dbinterval.db_startpos,
|
||||
dbinterval.db_endpos)
|
||||
|
||||
def stream_extract(self, path, start = None, end = None, count = False):
|
||||
def stream_extract(self, path, start = None, end = None,
|
||||
count = False, markup = False, binary = False):
|
||||
"""
|
||||
Returns (data, restart) tuple.
|
||||
|
||||
data is ASCII-formatted data from the database, formatted
|
||||
'data' is ASCII-formatted data from the database, formatted
|
||||
according to the layout of the stream.
|
||||
|
||||
restart, if nonzero, means that there were too many results to
|
||||
'restart', if not None, means that there were too many results to
|
||||
return in a single request. The data is complete from the
|
||||
starting timestamp to the point at which it was truncated,
|
||||
and a new request with a start time of 'restart' will fetch
|
||||
the next block of data.
|
||||
|
||||
count, if true, means to not return raw data, but just the count
|
||||
'count', if true, means to not return raw data, but just the count
|
||||
of rows that would have been returned. This is much faster
|
||||
than actually fetching the data. It is not limited by
|
||||
max_results.
|
||||
|
||||
'markup', if true, indicates that returned data should be
|
||||
marked with a comment denoting when a particular interval
|
||||
starts, and another comment when an interval ends.
|
||||
|
||||
'binary', if true, means to return raw binary rather than
|
||||
ASCII-formatted data.
|
||||
"""
|
||||
stream_id = self._stream_id(path)
|
||||
table = self.data.getnode(path)
|
||||
@@ -498,7 +575,9 @@ class NilmDB(object):
|
||||
result = []
|
||||
matched = 0
|
||||
remaining = self.max_results
|
||||
restart = 0
|
||||
restart = None
|
||||
if binary and (markup or count):
|
||||
raise NilmDBError("binary mode can't be used with markup or count")
|
||||
for interval in intervals.intersection(requested):
|
||||
# Reading single rows from the table is too slow, so
|
||||
# we use two bisections to find both the starting and
|
||||
@@ -515,16 +594,28 @@ class NilmDB(object):
|
||||
row_max = row_start + remaining
|
||||
if row_max < row_end:
|
||||
row_end = row_max
|
||||
restart = table[row_max][0]
|
||||
restart = table[row_max]
|
||||
|
||||
# Add markup
|
||||
if markup:
|
||||
result.append("# interval-start " +
|
||||
timestamp_to_string(interval.start) + "\n")
|
||||
|
||||
# Gather these results up
|
||||
result.append(table.get_as_text(row_start, row_end))
|
||||
result.append(table.get_data(row_start, row_end, binary))
|
||||
|
||||
# Count them
|
||||
remaining -= row_end - row_start
|
||||
|
||||
if restart:
|
||||
# Add markup, and exit if restart is set.
|
||||
if restart is not None:
|
||||
if markup:
|
||||
result.append("# interval-end " +
|
||||
timestamp_to_string(restart) + "\n")
|
||||
break
|
||||
if markup:
|
||||
result.append("# interval-end " +
|
||||
timestamp_to_string(interval.end) + "\n")
|
||||
|
||||
if count:
|
||||
return matched
|
||||
@@ -533,9 +624,17 @@ class NilmDB(object):
|
||||
def stream_remove(self, path, start = None, end = None):
|
||||
"""
|
||||
Remove data from the specified time interval within a stream.
|
||||
Removes all data in the interval [start, end), and intervals
|
||||
are truncated or split appropriately. Returns the number of
|
||||
data points removed.
|
||||
|
||||
Removes data in the interval [start, end), and intervals are
|
||||
truncated or split appropriately.
|
||||
|
||||
Returns a (removed, restart) tuple.
|
||||
|
||||
'removed' is the number of data points that were removed.
|
||||
|
||||
'restart', if not None, means there were too many rows to
|
||||
remove in a single request. This function should be called
|
||||
again with a start time of 'restart' to complete the removal.
|
||||
"""
|
||||
stream_id = self._stream_id(path)
|
||||
table = self.data.getnode(path)
|
||||
@@ -543,6 +642,8 @@ class NilmDB(object):
|
||||
(start, end) = self._check_user_times(start, end)
|
||||
to_remove = Interval(start, end)
|
||||
removed = 0
|
||||
remaining = self.max_removals
|
||||
restart = None
|
||||
|
||||
# Can't remove intervals from within the iterator, so we need to
|
||||
# remember what's currently in the intersection now.
|
||||
@@ -553,6 +654,13 @@ class NilmDB(object):
|
||||
row_start = self._find_start(table, dbint)
|
||||
row_end = self._find_end(table, dbint)
|
||||
|
||||
# Shorten it if we'll hit the maximum number of removals
|
||||
row_max = row_start + remaining
|
||||
if row_max < row_end:
|
||||
row_end = row_max
|
||||
dbint.end = table[row_max]
|
||||
restart = dbint.end
|
||||
|
||||
# Adjust the DBInterval to match the newly found ends
|
||||
dbint.db_start = dbint.start
|
||||
dbint.db_end = dbint.end
|
||||
@@ -568,4 +676,7 @@ class NilmDB(object):
|
||||
# Count how many were removed
|
||||
removed += row_end - row_start
|
||||
|
||||
return removed
|
||||
if restart is not None:
|
||||
break
|
||||
|
||||
return (removed, restart)
|
||||
|
@@ -1,143 +0,0 @@
|
||||
# Python implementation of the "rocket" data parsing interface.
|
||||
# This interface translates between the binary format on disk
|
||||
# and the ASCII format used when communicating with clients.
|
||||
|
||||
# This is slow! Use the C version instead.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import struct
|
||||
import cStringIO
|
||||
import itertools
|
||||
from . import layout as _layout
|
||||
import nilmdb.utils
|
||||
from nilmdb.utils.time import float_time_to_string as ftts
|
||||
|
||||
ERR_UNKNOWN = 0
|
||||
ERR_NON_MONOTONIC = 1
|
||||
ERR_OUT_OF_INTERVAL = 2
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
@nilmdb.utils.must_close(wrap_verify = False)
|
||||
class Rocket(object):
|
||||
def __init__(self, layout, filename):
|
||||
self.layout = layout
|
||||
if filename:
|
||||
self.file = open(filename, "a+b")
|
||||
else:
|
||||
self.file = None
|
||||
|
||||
# For packing/unpacking into a binary file.
|
||||
# This will change in the C version
|
||||
try:
|
||||
(self.ltype, lcount) = layout.split('_', 2)
|
||||
self.lcount = int(lcount)
|
||||
except:
|
||||
raise ValueError("no such layout: badly formatted string")
|
||||
if self.lcount < 1:
|
||||
raise ValueError("no such layout: bad count")
|
||||
try:
|
||||
struct_fmt = '<d' # Little endian, double timestamp
|
||||
struct_mapping = {
|
||||
"int8": 'b',
|
||||
"uint8": 'B',
|
||||
"int16": 'h',
|
||||
"uint16": 'H',
|
||||
"int32": 'i',
|
||||
"uint32": 'I',
|
||||
"int64": 'q',
|
||||
"uint64": 'Q',
|
||||
"float32": 'f',
|
||||
"float64": 'd',
|
||||
}
|
||||
struct_fmt += struct_mapping[self.ltype] * self.lcount
|
||||
except KeyError:
|
||||
raise ValueError("no such layout: bad data type")
|
||||
self.packer = struct.Struct(struct_fmt)
|
||||
|
||||
# For packing/unpacking from strings.
|
||||
self.layoutparser = _layout.Layout(self.layout)
|
||||
self.formatter = _layout.Formatter(self.layout)
|
||||
|
||||
def close(self):
|
||||
if self.file:
|
||||
self.file.close()
|
||||
|
||||
@property
|
||||
def binary_size(self):
|
||||
"""Return size of one row of data in the binary file, in bytes"""
|
||||
return self.packer.size
|
||||
|
||||
def append_iter(self, maxrows, data):
|
||||
"""Append the list data to the file"""
|
||||
# We assume the file is opened in append mode,
|
||||
# so all writes go to the end.
|
||||
written = 0
|
||||
for row in itertools.islice(data, maxrows):
|
||||
self.file.write(self.packer.pack(*row))
|
||||
written += 1
|
||||
self.file.flush()
|
||||
return written
|
||||
|
||||
def append_string(self, count, data, data_offset, linenum,
|
||||
start, end, last_timestamp):
|
||||
"""Parse string and append data.
|
||||
|
||||
count: maximum number of rows to add
|
||||
data: string data
|
||||
data_offset: byte offset into data to start parsing
|
||||
linenum: current line number of data
|
||||
start: starting timestamp for interval
|
||||
end: end timestamp for interval
|
||||
last_timestamp: last timestamp that was previously parsed
|
||||
|
||||
Raises ParseError if timestamps are non-monotonic, outside the
|
||||
start/end interval, etc.
|
||||
|
||||
On success, return a tuple with three values:
|
||||
added_rows: how many rows were added from the file
|
||||
data_offset: current offset into the data string
|
||||
last_timestamp: last timestamp we parsed
|
||||
"""
|
||||
# Parse the input data
|
||||
indata = cStringIO.StringIO(data)
|
||||
indata.seek(data_offset)
|
||||
written = 0
|
||||
while written < count:
|
||||
line = indata.readline()
|
||||
linenum += 1
|
||||
if line == "":
|
||||
break
|
||||
comment = line.find('#')
|
||||
if comment >= 0:
|
||||
line = line.split('#', 1)[0]
|
||||
line = line.strip()
|
||||
if line == "":
|
||||
continue
|
||||
try:
|
||||
(ts, row) = self.layoutparser.parse(line)
|
||||
except ValueError as e:
|
||||
raise ParseError(linenum, ERR_UNKNOWN, e)
|
||||
if ts <= last_timestamp:
|
||||
raise ParseError(linenum, ERR_NON_MONOTONIC, ts)
|
||||
last_timestamp = ts
|
||||
if ts < start or ts >= end:
|
||||
raise ParseError(linenum, ERR_OUT_OF_INTERVAL, ts)
|
||||
self.append_iter(1, [row])
|
||||
written += 1
|
||||
return (written, indata.tell(), last_timestamp, linenum)
|
||||
|
||||
def extract_list(self, offset, count):
|
||||
"""Extract count rows of data from the file at offset offset.
|
||||
Return a list of lists [[row],[row],...]"""
|
||||
ret = []
|
||||
self.file.seek(offset)
|
||||
for i in xrange(count):
|
||||
data = self.file.read(self.binary_size)
|
||||
ret.append(list(self.packer.unpack(data)))
|
||||
return ret
|
||||
|
||||
def extract_string(self, offset, count):
|
||||
"""Extract count rows of data from the file at offset offset.
|
||||
Return an ascii formatted string according to the layout"""
|
||||
return self.formatter.format(self.extract_list(offset, count))
|
@@ -2,6 +2,7 @@
|
||||
#include <structmember.h>
|
||||
#include <endian.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Values missing from stdint.h */
|
||||
@@ -16,9 +17,18 @@
|
||||
#define FLOAT64_MIN 0
|
||||
#define FLOAT64_MAX 0
|
||||
|
||||
typedef int64_t timestamp_t;
|
||||
|
||||
/* This code probably needs to be double-checked for the case where
|
||||
sizeof(long) != 8, so enforce that here with something that will
|
||||
fail at build time. We assume that the python integer type can
|
||||
hold an int64_t. */
|
||||
const static char __long_ok[1 - 2*!(sizeof(int64_t) ==
|
||||
sizeof(long int))] = { 0 };
|
||||
|
||||
/* Somewhat arbitrary, just so we can use fixed sizes for strings
|
||||
etc. */
|
||||
static const int MAX_LAYOUT_COUNT = 64;
|
||||
static const int MAX_LAYOUT_COUNT = 128;
|
||||
|
||||
/* Error object and constants */
|
||||
static PyObject *ParseError;
|
||||
@@ -35,20 +45,20 @@ static void add_parseerror_codes(PyObject *module)
|
||||
}
|
||||
|
||||
/* Helpers to raise ParseErrors. Use "return raise_str(...)" etc. */
|
||||
static PyObject *raise_str(int linenum, int code, const char *string)
|
||||
static PyObject *raise_str(int line, int col, int code, const char *string)
|
||||
{
|
||||
PyObject *o;
|
||||
o = Py_BuildValue("(iis)", linenum, code, string);
|
||||
o = Py_BuildValue("(iiis)", line, col, code, string);
|
||||
if (o != NULL) {
|
||||
PyErr_SetObject(ParseError, o);
|
||||
Py_DECREF(o);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
static PyObject *raise_num(int linenum, int code, double num)
|
||||
static PyObject *raise_int(int line, int col, int code, int64_t num)
|
||||
{
|
||||
PyObject *o;
|
||||
o = Py_BuildValue("(iid)", linenum, code, num);
|
||||
o = Py_BuildValue("(iiil)", line, col, code, num);
|
||||
if (o != NULL) {
|
||||
PyErr_SetObject(ParseError, o);
|
||||
Py_DECREF(o);
|
||||
@@ -236,106 +246,6 @@ static PyObject *Rocket_get_file_size(Rocket *self)
|
||||
return PyInt_FromLong(self->file_size);
|
||||
}
|
||||
|
||||
/****
|
||||
* Append from iterator
|
||||
*/
|
||||
|
||||
/* Helper for writing Python objects to the file */
|
||||
static inline void append_pyobject(FILE *out, PyObject *val, layout_type_t type)
|
||||
{
|
||||
union8_t t8;
|
||||
union16_t t16;
|
||||
union32_t t32;
|
||||
union64_t t64;
|
||||
int ret = 0;
|
||||
|
||||
switch (type) {
|
||||
#define CASE(type, pyconvert, pytype, disktype, htole, bytes) \
|
||||
case LAYOUT_TYPE_##type: \
|
||||
pytype = pyconvert(val); \
|
||||
if (PyErr_Occurred()) \
|
||||
return; \
|
||||
disktype = htole(disktype); \
|
||||
ret = fwrite(&disktype, bytes, 1, out); \
|
||||
break
|
||||
CASE(INT8, PyInt_AsLong, t8.i, t8.u, , 1);
|
||||
CASE(UINT8, PyInt_AsLong, t8.u, t8.u, , 1);
|
||||
CASE(INT16, PyInt_AsLong, t16.i, t16.u, htole16, 2);
|
||||
CASE(UINT16, PyInt_AsLong, t16.u, t16.u, htole16, 2);
|
||||
CASE(INT32, PyInt_AsLong, t32.i, t32.u, htole32, 4);
|
||||
CASE(UINT32, PyInt_AsLong, t32.u, t32.u, htole32, 4);
|
||||
CASE(INT64, PyInt_AsLong, t64.i, t64.u, htole64, 8);
|
||||
CASE(UINT64, PyInt_AsLong, t64.u, t64.u, htole64, 8);
|
||||
CASE(FLOAT32, PyFloat_AsDouble, t32.f, t32.u, htole32, 4);
|
||||
CASE(FLOAT64, PyFloat_AsDouble, t64.d, t64.u, htole64, 8);
|
||||
#undef CASE
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "unknown type");
|
||||
return;
|
||||
}
|
||||
if (ret <= 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
}
|
||||
/* .append_iter(maxrows, dataiter) */
|
||||
static PyObject *Rocket_append_iter(Rocket *self, PyObject *args)
|
||||
{
|
||||
int maxrows;
|
||||
PyObject *iter;
|
||||
PyObject *rowlist;
|
||||
if (!PyArg_ParseTuple(args, "iO:append_iter", &maxrows, &iter))
|
||||
return NULL;
|
||||
if (!PyIter_Check(iter)) {
|
||||
PyErr_SetString(PyExc_TypeError, "need an iterable");
|
||||
return NULL;
|
||||
}
|
||||
if (!self->file) {
|
||||
PyErr_SetString(PyExc_Exception, "no file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark file size so that it will get updated next time it's read */
|
||||
self->file_size = -1;
|
||||
|
||||
int row;
|
||||
for (row = 0; row < maxrows; row++) {
|
||||
rowlist = PyIter_Next(iter);
|
||||
if (!rowlist)
|
||||
break;
|
||||
if (!PyList_Check(rowlist)) {
|
||||
PyErr_SetString(PyExc_TypeError, "rows must be lists");
|
||||
goto row_err;
|
||||
}
|
||||
if (PyList_Size(rowlist) != self->layout_count + 1) {
|
||||
PyErr_SetString(PyExc_TypeError, "short row");
|
||||
goto row_err;
|
||||
}
|
||||
|
||||
/* Extract and write timestamp */
|
||||
append_pyobject(self->file, PyList_GetItem(rowlist, 0),
|
||||
LAYOUT_TYPE_FLOAT64);
|
||||
if (PyErr_Occurred())
|
||||
goto row_err;
|
||||
|
||||
/* Extract and write values */
|
||||
int i;
|
||||
for (i = 0; i < self->layout_count; i++) {
|
||||
append_pyobject(self->file,
|
||||
PyList_GetItem(rowlist, i+1),
|
||||
self->layout_type);
|
||||
if (PyErr_Occurred())
|
||||
goto row_err;
|
||||
}
|
||||
}
|
||||
fflush(self->file);
|
||||
/* All done */
|
||||
return PyLong_FromLong(row);
|
||||
row_err:
|
||||
fflush(self->file);
|
||||
Py_DECREF(rowlist);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****
|
||||
* Append from string
|
||||
*/
|
||||
@@ -352,10 +262,11 @@ static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
|
||||
int count;
|
||||
const char *data;
|
||||
int offset;
|
||||
const char *linestart;
|
||||
int linenum;
|
||||
double start;
|
||||
double end;
|
||||
double last_timestamp;
|
||||
timestamp_t start;
|
||||
timestamp_t end;
|
||||
timestamp_t last_timestamp;
|
||||
|
||||
int written = 0;
|
||||
char *endptr;
|
||||
@@ -369,19 +280,27 @@ static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
|
||||
but we need the null termination for strto*. If we had
|
||||
strnto* that took a length, we could use t# and not require
|
||||
a copy. */
|
||||
if (!PyArg_ParseTuple(args, "isiiddd:append_string", &count,
|
||||
if (!PyArg_ParseTuple(args, "isiilll:append_string", &count,
|
||||
&data, &offset, &linenum,
|
||||
&start, &end, &last_timestamp))
|
||||
return NULL;
|
||||
|
||||
/* Skip spaces, but don't skip over a newline. */
|
||||
#define SKIP_BLANK(buf) do { \
|
||||
while (isspace(*buf)) { \
|
||||
if (*buf == '\n') \
|
||||
break; \
|
||||
buf++; \
|
||||
} } while(0)
|
||||
|
||||
const char *buf = &data[offset];
|
||||
while (written < count && *buf)
|
||||
{
|
||||
linestart = buf;
|
||||
linenum++;
|
||||
|
||||
/* Skip leading whitespace and commented lines */
|
||||
while (*buf == ' ' || *buf == '\t')
|
||||
buf++;
|
||||
SKIP_BLANK(buf);
|
||||
if (*buf == '#') {
|
||||
while (*buf && *buf != '\n')
|
||||
buf++;
|
||||
@@ -391,14 +310,23 @@ static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
|
||||
}
|
||||
|
||||
/* Extract timestamp */
|
||||
t64.d = strtod(buf, &endptr);
|
||||
if (endptr == buf)
|
||||
return raise_str(linenum, ERR_OTHER, "bad timestamp");
|
||||
if (t64.d <= last_timestamp)
|
||||
return raise_num(linenum, ERR_NON_MONOTONIC, t64.d);
|
||||
last_timestamp = t64.d;
|
||||
if (t64.d < start || t64.d >= end)
|
||||
return raise_num(linenum, ERR_OUT_OF_INTERVAL, t64.d);
|
||||
t64.i = strtoll(buf, &endptr, 10);
|
||||
if (endptr == buf || !isspace(*endptr)) {
|
||||
/* Try parsing as a double instead */
|
||||
t64.d = strtod(buf, &endptr);
|
||||
if (endptr == buf)
|
||||
goto bad_timestamp;
|
||||
if (!isspace(*endptr))
|
||||
goto cant_parse_value;
|
||||
t64.i = round(t64.d);
|
||||
}
|
||||
if (t64.i <= last_timestamp)
|
||||
return raise_int(linenum, buf - linestart + 1,
|
||||
ERR_NON_MONOTONIC, t64.i);
|
||||
last_timestamp = t64.i;
|
||||
if (t64.i < start || t64.i >= end)
|
||||
return raise_int(linenum, buf - linestart + 1,
|
||||
ERR_OUT_OF_INTERVAL, t64.i);
|
||||
t64.u = le64toh(t64.u);
|
||||
if (fwrite(&t64.u, 8, 1, self->file) != 1)
|
||||
goto err;
|
||||
@@ -410,23 +338,31 @@ static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
|
||||
case LAYOUT_TYPE_##type: \
|
||||
/* parse and write in a loop */ \
|
||||
for (i = 0; i < self->layout_count; i++) { \
|
||||
parsetype = parsefunc(buf, &endptr); \
|
||||
if (endptr == buf) \
|
||||
/* skip non-newlines */ \
|
||||
SKIP_BLANK(buf); \
|
||||
if (*buf == '\n') \
|
||||
goto wrong_number_of_values; \
|
||||
/* parse number */ \
|
||||
parsetype = parsefunc(buf, &endptr); \
|
||||
if (*endptr && !isspace(*endptr)) \
|
||||
goto cant_parse_value; \
|
||||
/* check limits */ \
|
||||
if (type##_MIN != type##_MAX && \
|
||||
(parsetype < type##_MIN || \
|
||||
parsetype > type##_MAX)) \
|
||||
goto value_out_of_range; \
|
||||
/* convert to disk representation */ \
|
||||
realtype = parsetype; \
|
||||
disktype = letoh(disktype); \
|
||||
/* write it */ \
|
||||
if (fwrite(&disktype, bytes, \
|
||||
1, self->file) != 1) \
|
||||
goto err; \
|
||||
/* advance buf */ \
|
||||
buf = endptr; \
|
||||
} \
|
||||
/* Skip trailing whitespace and comments */ \
|
||||
while (*buf == ' ' || *buf == '\t') \
|
||||
buf++; \
|
||||
SKIP_BLANK(buf); \
|
||||
if (*buf == '#') \
|
||||
while (*buf && *buf != '\n') \
|
||||
buf++; \
|
||||
@@ -458,130 +394,91 @@ static PyObject *Rocket_append_string(Rocket *self, PyObject *args)
|
||||
|
||||
fflush(self->file);
|
||||
|
||||
/* Build return value and return*/
|
||||
/* Build return value and return */
|
||||
offset = buf - data;
|
||||
PyObject *o;
|
||||
o = Py_BuildValue("(iidi)", written, offset, last_timestamp, linenum);
|
||||
o = Py_BuildValue("(iili)", written, offset, last_timestamp, linenum);
|
||||
return o;
|
||||
err:
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
bad_timestamp:
|
||||
return raise_str(linenum, buf - linestart + 1,
|
||||
ERR_OTHER, "bad timestamp");
|
||||
cant_parse_value:
|
||||
return raise_str(linenum, buf - linestart + 1,
|
||||
ERR_OTHER, "can't parse value");
|
||||
wrong_number_of_values:
|
||||
return raise_str(linenum, ERR_OTHER, "wrong number of values");
|
||||
return raise_str(linenum, buf - linestart + 1,
|
||||
ERR_OTHER, "wrong number of values");
|
||||
value_out_of_range:
|
||||
return raise_str(linenum, ERR_OTHER, "value out of range");
|
||||
return raise_str(linenum, buf - linestart + 1,
|
||||
ERR_OTHER, "value out of range");
|
||||
extra_data_on_line:
|
||||
return raise_str(linenum, ERR_OTHER, "extra data on line");
|
||||
return raise_str(linenum, buf - linestart + 1,
|
||||
ERR_OTHER, "extra data on line");
|
||||
}
|
||||
|
||||
/****
|
||||
* Extract to Python list
|
||||
* Append from binary data
|
||||
*/
|
||||
|
||||
static int _extract_handle_params(Rocket *self, PyObject *args, long *count)
|
||||
/* .append_binary(count, data, offset, linenum, start, end, last_timestamp) */
|
||||
static PyObject *Rocket_append_binary(Rocket *self, PyObject *args)
|
||||
{
|
||||
long offset;
|
||||
if (!PyArg_ParseTuple(args, "ll", &offset, count))
|
||||
return -1;
|
||||
if (!self->file) {
|
||||
PyErr_SetString(PyExc_Exception, "no file");
|
||||
return -1;
|
||||
}
|
||||
/* Seek to target location */
|
||||
if (fseek(self->file, offset, SEEK_SET) < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int count;
|
||||
const uint8_t *data;
|
||||
int data_len;
|
||||
int linenum;
|
||||
int offset;
|
||||
timestamp_t start;
|
||||
timestamp_t end;
|
||||
timestamp_t last_timestamp;
|
||||
|
||||
/* Helper for extracting data from a file as a Python object */
|
||||
static inline void *extract_pyobject(FILE *in, layout_type_t type)
|
||||
{
|
||||
union8_t t8;
|
||||
union16_t t16;
|
||||
union32_t t32;
|
||||
union64_t t64;
|
||||
|
||||
switch (type) {
|
||||
#define CASE(type, pyconvert, pytype, disktype, letoh, bytes) \
|
||||
case LAYOUT_TYPE_##type: \
|
||||
if (fread(&disktype, bytes, 1, in) <= 0) \
|
||||
break; \
|
||||
disktype = letoh(disktype); \
|
||||
return pyconvert(pytype); \
|
||||
break
|
||||
CASE(INT8, PyInt_FromLong, t8.i, t8.u, , 1);
|
||||
CASE(UINT8, PyInt_FromLong, t8.u, t8.u, , 1);
|
||||
CASE(INT16, PyInt_FromLong, t16.i, t16.u, le16toh, 2);
|
||||
CASE(UINT16, PyInt_FromLong, t16.u, t16.u, le16toh, 2);
|
||||
CASE(INT32, PyInt_FromLong, t32.i, t32.u, le32toh, 4);
|
||||
CASE(UINT32, PyInt_FromLong, t32.u, t32.u, le32toh, 4);
|
||||
CASE(INT64, PyInt_FromLong, t64.i, t64.u, le64toh, 8);
|
||||
CASE(UINT64, PyInt_FromLong, t64.u, t64.u, le64toh, 8);
|
||||
CASE(FLOAT32, PyFloat_FromDouble, t32.f, t32.u, le32toh, 4);
|
||||
CASE(FLOAT64, PyFloat_FromDouble, t64.d, t64.u, le64toh, 8);
|
||||
#undef CASE
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "unknown type");
|
||||
return NULL;
|
||||
}
|
||||
PyErr_SetString(PyExc_OSError, "failed to read from file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject *Rocket_extract_list(Rocket *self, PyObject *args)
|
||||
{
|
||||
long count;
|
||||
if (_extract_handle_params(self, args, &count) < 0)
|
||||
if (!PyArg_ParseTuple(args, "it#iilll:append_binary",
|
||||
&count, &data, &data_len, &offset,
|
||||
&linenum, &start, &end, &last_timestamp))
|
||||
return NULL;
|
||||
|
||||
/* Make a list to return */
|
||||
PyObject *retlist = PyList_New(0);
|
||||
if (!retlist)
|
||||
return NULL;
|
||||
/* Advance to offset */
|
||||
if (offset > data_len)
|
||||
return raise_str(0, 0, ERR_OTHER, "bad offset");
|
||||
data += offset;
|
||||
data_len -= offset;
|
||||
|
||||
/* Read data into new Python lists */
|
||||
int row;
|
||||
for (row = 0; row < count; row++)
|
||||
{
|
||||
PyObject *rowlist = PyList_New(self->layout_count + 1);
|
||||
if (!rowlist) {
|
||||
Py_DECREF(retlist);
|
||||
return NULL;
|
||||
}
|
||||
/* Figure out max number of rows to insert */
|
||||
int rows = data_len / self->binary_size;
|
||||
if (rows > count)
|
||||
rows = count;
|
||||
|
||||
/* Timestamp */
|
||||
PyObject *entry = extract_pyobject(self->file,
|
||||
LAYOUT_TYPE_FLOAT64);
|
||||
if (!entry || (PyList_SetItem(rowlist, 0, entry) < 0)) {
|
||||
Py_DECREF(rowlist);
|
||||
Py_DECREF(retlist);
|
||||
return NULL;
|
||||
}
|
||||
/* Check timestamps */
|
||||
timestamp_t ts;
|
||||
int i;
|
||||
for (i = 0; i < rows; i++) {
|
||||
/* Read raw timestamp, byteswap if needed */
|
||||
memcpy(&ts, &data[i * self->binary_size], 8);
|
||||
ts = le64toh(ts);
|
||||
|
||||
/* Data */
|
||||
int i;
|
||||
for (i = 0; i < self->layout_count; i++) {
|
||||
PyObject *ent = extract_pyobject(self->file,
|
||||
self->layout_type);
|
||||
if (!ent || (PyList_SetItem(rowlist, i+1, ent) < 0)) {
|
||||
Py_DECREF(rowlist);
|
||||
Py_DECREF(retlist);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
/* Check limits */
|
||||
if (ts <= last_timestamp)
|
||||
return raise_int(i, 0, ERR_NON_MONOTONIC, ts);
|
||||
last_timestamp = ts;
|
||||
if (ts < start || ts >= end)
|
||||
return raise_int(i, 0, ERR_OUT_OF_INTERVAL, ts);
|
||||
}
|
||||
|
||||
/* Add row to return value */
|
||||
if (PyList_Append(retlist, rowlist) < 0) {
|
||||
Py_DECREF(rowlist);
|
||||
Py_DECREF(retlist);
|
||||
return NULL;
|
||||
}
|
||||
/* Write binary data */
|
||||
if (fwrite(data, data_len, 1, self->file) != 1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
fflush(self->file);
|
||||
|
||||
Py_DECREF(rowlist);
|
||||
}
|
||||
return retlist;
|
||||
/* Build return value and return */
|
||||
PyObject *o;
|
||||
o = Py_BuildValue("(iili)", rows, offset + rows * self->binary_size,
|
||||
last_timestamp, linenum);
|
||||
return o;
|
||||
}
|
||||
|
||||
/****
|
||||
@@ -591,8 +488,19 @@ static PyObject *Rocket_extract_list(Rocket *self, PyObject *args)
|
||||
static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
|
||||
{
|
||||
long count;
|
||||
if (_extract_handle_params(self, args, &count) < 0)
|
||||
long offset;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ll", &offset, &count))
|
||||
return NULL;
|
||||
if (!self->file) {
|
||||
PyErr_SetString(PyExc_Exception, "no file");
|
||||
return NULL;
|
||||
}
|
||||
/* Seek to target location */
|
||||
if (fseek(self->file, offset, SEEK_SET) < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *str = NULL, *new;
|
||||
long len_alloc = 0;
|
||||
@@ -626,8 +534,7 @@ static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
|
||||
if (fread(&t64.u, 8, 1, self->file) != 1)
|
||||
goto err;
|
||||
t64.u = le64toh(t64.u);
|
||||
/* Timestamps are always printed to the microsecond */
|
||||
ret = sprintf(&str[len], "%.6f", t64.d);
|
||||
ret = sprintf(&str[len], "%ld", t64.i);
|
||||
if (ret <= 0)
|
||||
goto err;
|
||||
len += ret;
|
||||
@@ -639,7 +546,7 @@ static PyObject *Rocket_extract_string(Rocket *self, PyObject *args)
|
||||
/* read and format in a loop */ \
|
||||
for (i = 0; i < self->layout_count; i++) { \
|
||||
if (fread(&disktype, bytes, \
|
||||
1, self->file) < 0) \
|
||||
1, self->file) != 1) \
|
||||
goto err; \
|
||||
disktype = letoh(disktype); \
|
||||
ret = sprintf(&str[len], " " fmt, \
|
||||
@@ -682,6 +589,73 @@ err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****
|
||||
* Extract to binary string containing raw little-endian binary data
|
||||
*/
|
||||
static PyObject *Rocket_extract_binary(Rocket *self, PyObject *args)
|
||||
{
|
||||
long count;
|
||||
long offset;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ll", &offset, &count))
|
||||
return NULL;
|
||||
if (!self->file) {
|
||||
PyErr_SetString(PyExc_Exception, "no file");
|
||||
return NULL;
|
||||
}
|
||||
/* Seek to target location */
|
||||
if (fseek(self->file, offset, SEEK_SET) < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *str;
|
||||
int len = count * self->binary_size;
|
||||
str = malloc(len);
|
||||
if (str == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Data in the file is already in the desired little-endian
|
||||
binary format, so just read it directly. */
|
||||
if (fread(str, self->binary_size, count, self->file) != count) {
|
||||
free(str);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *pystr = PyBytes_FromStringAndSize((char *)str, len);
|
||||
free(str);
|
||||
return pystr;
|
||||
}
|
||||
|
||||
/****
|
||||
* Extract timestamp
|
||||
*/
|
||||
static PyObject *Rocket_extract_timestamp(Rocket *self, PyObject *args)
|
||||
{
|
||||
long offset;
|
||||
union64_t t64;
|
||||
if (!PyArg_ParseTuple(args, "l", &offset))
|
||||
return NULL;
|
||||
if (!self->file) {
|
||||
PyErr_SetString(PyExc_Exception, "no file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Seek to target location and read timestamp */
|
||||
if ((fseek(self->file, offset, SEEK_SET) < 0) ||
|
||||
(fread(&t64.u, 8, 1, self->file) != 1)) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert and return */
|
||||
t64.u = le64toh(t64.u);
|
||||
return Py_BuildValue("l", t64.i);
|
||||
}
|
||||
|
||||
/****
|
||||
* Module and type setup
|
||||
*/
|
||||
@@ -699,15 +673,13 @@ static PyMemberDef Rocket_members[] = {
|
||||
};
|
||||
|
||||
static PyMethodDef Rocket_methods[] = {
|
||||
{ "close", (PyCFunction)Rocket_close, METH_NOARGS,
|
||||
{ "close",
|
||||
(PyCFunction)Rocket_close, METH_NOARGS,
|
||||
"close(self)\n\n"
|
||||
"Close file handle" },
|
||||
|
||||
{ "append_iter", (PyCFunction)Rocket_append_iter, METH_VARARGS,
|
||||
"append_iter(self, maxrows, iterable)\n\n"
|
||||
"Append up to maxrows of data from iter to the file" },
|
||||
|
||||
{ "append_string", (PyCFunction)Rocket_append_string, METH_VARARGS,
|
||||
{ "append_string",
|
||||
(PyCFunction)Rocket_append_string, METH_VARARGS,
|
||||
"append_string(self, count, data, offset, line, start, end, ts)\n\n"
|
||||
"Parse string and append data.\n"
|
||||
"\n"
|
||||
@@ -722,21 +694,51 @@ static PyMethodDef Rocket_methods[] = {
|
||||
"Raises ParseError if timestamps are non-monotonic, outside\n"
|
||||
"the start/end interval etc.\n"
|
||||
"\n"
|
||||
"On success, return a tuple with three values:\n"
|
||||
"On success, return a tuple:\n"
|
||||
" added_rows: how many rows were added from the file\n"
|
||||
" data_offset: current offset into the data string\n"
|
||||
" last_timestamp: last timestamp we parsed" },
|
||||
" last_timestamp: last timestamp we parsed\n"
|
||||
" linenum: current line number" },
|
||||
|
||||
{ "extract_list", (PyCFunction)Rocket_extract_list, METH_VARARGS,
|
||||
"extract_list(self, offset, count)\n\n"
|
||||
"Extract count rows of data from the file at offset offset.\n"
|
||||
"Return a list of lists [[row],[row],...]" },
|
||||
{ "append_binary",
|
||||
(PyCFunction)Rocket_append_binary, METH_VARARGS,
|
||||
"append_binary(self, count, data, offset, line, start, end, ts)\n\n"
|
||||
"Append binary data, which must match the data layout.\n"
|
||||
"\n"
|
||||
" count: maximum number of rows to add\n"
|
||||
" data: binary data\n"
|
||||
" offset: byte offset into data to start adding\n"
|
||||
" line: current line number (unused)\n"
|
||||
" start: starting timestamp for interval\n"
|
||||
" end: end timestamp for interval\n"
|
||||
" ts: last timestamp that was previously parsed\n"
|
||||
"\n"
|
||||
"Raises ParseError if timestamps are non-monotonic, outside\n"
|
||||
"the start/end interval etc.\n"
|
||||
"\n"
|
||||
"On success, return a tuple:\n"
|
||||
" added_rows: how many rows were added from the file\n"
|
||||
" data_offset: current offset into the data string\n"
|
||||
" last_timestamp: last timestamp we parsed\n"
|
||||
" linenum: current line number (copied from argument)" },
|
||||
|
||||
{ "extract_string", (PyCFunction)Rocket_extract_string, METH_VARARGS,
|
||||
{ "extract_string",
|
||||
(PyCFunction)Rocket_extract_string, METH_VARARGS,
|
||||
"extract_string(self, offset, count)\n\n"
|
||||
"Extract count rows of data from the file at offset offset.\n"
|
||||
"Return an ascii formatted string according to the layout" },
|
||||
|
||||
{ "extract_binary",
|
||||
(PyCFunction)Rocket_extract_binary, METH_VARARGS,
|
||||
"extract_binary(self, offset, count)\n\n"
|
||||
"Extract count rows of data from the file at offset offset.\n"
|
||||
"Return a raw binary string of data matching the data layout." },
|
||||
|
||||
{ "extract_timestamp",
|
||||
(PyCFunction)Rocket_extract_timestamp, METH_VARARGS,
|
||||
"extract_timestamp(self, offset)\n\n"
|
||||
"Extract a single timestamp from the file" },
|
||||
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
|
@@ -6,13 +6,16 @@ from __future__ import absolute_import
|
||||
import nilmdb.server
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.server.errors import NilmDBError
|
||||
from nilmdb.utils.time import string_to_timestamp
|
||||
|
||||
import cherrypy
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import simplejson as json
|
||||
import decorator
|
||||
import psutil
|
||||
import traceback
|
||||
|
||||
class NilmApp(object):
|
||||
def __init__(self, db):
|
||||
@@ -171,6 +174,21 @@ class Root(NilmApp):
|
||||
class Stream(NilmApp):
|
||||
"""Stream-specific operations"""
|
||||
|
||||
# Helpers
|
||||
def _get_times(self, start_param, end_param):
|
||||
(start, end) = (None, None)
|
||||
if start_param is not None:
|
||||
start = string_to_timestamp(start_param)
|
||||
if end_param is not None:
|
||||
end = string_to_timestamp(end_param)
|
||||
if start is not None and end is not None:
|
||||
if start >= end:
|
||||
raise cherrypy.HTTPError(
|
||||
"400 Bad Request",
|
||||
sprintf("start must precede end (%s >= %s)",
|
||||
start_param, end_param))
|
||||
return (start, end)
|
||||
|
||||
# /stream/list
|
||||
# /stream/list?layout=float32_8
|
||||
# /stream/list?path=/newton/prep&extended=1
|
||||
@@ -209,9 +227,19 @@ class Stream(NilmApp):
|
||||
@exception_to_httperror(NilmDBError)
|
||||
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||
def destroy(self, path):
|
||||
"""Delete a stream and its associated data."""
|
||||
"""Delete a stream. Fails if any data is still present."""
|
||||
return self.db.stream_destroy(path)
|
||||
|
||||
# /stream/rename?oldpath=/newton/prep&newpath=/newton/prep/1
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_in()
|
||||
@cherrypy.tools.json_out()
|
||||
@exception_to_httperror(NilmDBError, ValueError)
|
||||
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||
def rename(self, oldpath, newpath):
|
||||
"""Rename a stream."""
|
||||
return self.db.stream_rename(oldpath, newpath)
|
||||
|
||||
# /stream/get_metadata?path=/newton/prep
|
||||
# /stream/get_metadata?path=/newton/prep&key=foo&key=bar
|
||||
@cherrypy.expose
|
||||
@@ -277,10 +305,15 @@ class Stream(NilmApp):
|
||||
@cherrypy.tools.json_out()
|
||||
@exception_to_httperror(NilmDBError, ValueError)
|
||||
@cherrypy.tools.CORS_allow(methods = ["PUT"])
|
||||
def insert(self, path, start, end):
|
||||
def insert(self, path, start, end, binary = False):
|
||||
"""
|
||||
Insert new data into the database. Provide textual data
|
||||
(matching the path's layout) as a HTTP PUT.
|
||||
|
||||
If 'binary' is True, expect raw binary data, rather than lines
|
||||
of ASCII-formatted data. Raw binary data is always
|
||||
little-endian and matches the database types (including an
|
||||
int64 timestamp).
|
||||
"""
|
||||
# Important that we always read the input before throwing any
|
||||
# errors, to keep lengths happy for persistent connections.
|
||||
@@ -288,21 +321,24 @@ class Stream(NilmApp):
|
||||
# requests, if we ever want to handle those (issue #1134)
|
||||
body = cherrypy.request.body.read()
|
||||
|
||||
# Verify content type for binary data
|
||||
content_type = cherrypy.request.headers.get('content-type')
|
||||
if binary and content_type:
|
||||
if content_type != "application/octet-stream":
|
||||
raise cherrypy.HTTPError("400", "Content type must be "
|
||||
"application/octet-stream for "
|
||||
"binary data, not " + content_type)
|
||||
|
||||
# Check path and get layout
|
||||
streams = self.db.stream_list(path = path)
|
||||
if len(streams) != 1:
|
||||
raise cherrypy.HTTPError("404 Not Found", "No such stream")
|
||||
if len(self.db.stream_list(path = path)) != 1:
|
||||
raise cherrypy.HTTPError("404", "No such stream: " + path)
|
||||
|
||||
# Check limits
|
||||
start = float(start)
|
||||
end = float(end)
|
||||
if start >= end:
|
||||
raise cherrypy.HTTPError("400 Bad Request",
|
||||
"start must precede end")
|
||||
(start, end) = self._get_times(start, end)
|
||||
|
||||
# Pass the data directly to nilmdb, which will parse it and
|
||||
# raise a ValueError if there are any problems.
|
||||
self.db.stream_insert(path, start, end, body)
|
||||
self.db.stream_insert(path, start, end, body, binary)
|
||||
|
||||
# Done
|
||||
return
|
||||
@@ -320,53 +356,54 @@ class Stream(NilmApp):
|
||||
the interval [start, end). Returns the number of data points
|
||||
removed.
|
||||
"""
|
||||
if start is not None:
|
||||
start = float(start)
|
||||
if end is not None:
|
||||
end = float(end)
|
||||
if start is not None and end is not None:
|
||||
if start >= end:
|
||||
raise cherrypy.HTTPError("400 Bad Request",
|
||||
"start must precede end")
|
||||
return self.db.stream_remove(path, start, end)
|
||||
(start, end) = self._get_times(start, end)
|
||||
total_removed = 0
|
||||
while True:
|
||||
(removed, restart) = self.db.stream_remove(path, start, end)
|
||||
total_removed += removed
|
||||
if restart is None:
|
||||
break
|
||||
start = restart
|
||||
return total_removed
|
||||
|
||||
# /stream/intervals?path=/newton/prep
|
||||
# /stream/intervals?path=/newton/prep&start=1234567890.0&end=1234567899.0
|
||||
# /stream/intervals?path=/newton/prep&diffpath=/newton/prep2
|
||||
@cherrypy.expose
|
||||
@chunked_response
|
||||
@response_type("application/x-json-stream")
|
||||
def intervals(self, path, start = None, end = None):
|
||||
def intervals(self, path, start = None, end = None, diffpath = None):
|
||||
"""
|
||||
Get intervals from backend database. Streams the resulting
|
||||
intervals as JSON strings separated by CR LF pairs. This may
|
||||
make multiple requests to the nilmdb backend to avoid causing
|
||||
it to block for too long.
|
||||
|
||||
Returns intervals between 'start' and 'end' belonging to
|
||||
'path'. If 'diff' is provided, the set-difference between
|
||||
intervals in 'path' and intervals in 'diffpath' are
|
||||
returned instead.
|
||||
|
||||
Note that the response type is the non-standard
|
||||
'application/x-json-stream' for lack of a better option.
|
||||
"""
|
||||
if start is not None:
|
||||
start = float(start)
|
||||
if end is not None:
|
||||
end = float(end)
|
||||
(start, end) = self._get_times(start, end)
|
||||
|
||||
if start is not None and end is not None:
|
||||
if start >= end:
|
||||
raise cherrypy.HTTPError("400 Bad Request",
|
||||
"start must precede end")
|
||||
if len(self.db.stream_list(path = path)) != 1:
|
||||
raise cherrypy.HTTPError("404", "No such stream: " + path)
|
||||
|
||||
streams = self.db.stream_list(path = path)
|
||||
if len(streams) != 1:
|
||||
raise cherrypy.HTTPError("404 Not Found", "No such stream")
|
||||
if diffpath and len(self.db.stream_list(path = diffpath)) != 1:
|
||||
raise cherrypy.HTTPError("404", "No such stream: " + diffpath)
|
||||
|
||||
@workaround_cp_bug_1200
|
||||
def content(start, end):
|
||||
# Note: disable chunked responses to see tracebacks from here.
|
||||
while True:
|
||||
(ints, restart) = self.db.stream_intervals(path, start, end)
|
||||
(ints, restart) = self.db.stream_intervals(path, start, end,
|
||||
diffpath)
|
||||
response = ''.join([ json.dumps(i) + "\r\n" for i in ints ])
|
||||
yield response
|
||||
if restart == 0:
|
||||
if restart is None:
|
||||
break
|
||||
start = restart
|
||||
return content(start, end)
|
||||
@@ -374,48 +411,58 @@ class Stream(NilmApp):
|
||||
# /stream/extract?path=/newton/prep&start=1234567890.0&end=1234567899.0
|
||||
@cherrypy.expose
|
||||
@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, markup = False, binary = False):
|
||||
"""
|
||||
Extract data from backend database. Streams the resulting
|
||||
entries as ASCII text lines separated by newlines. This may
|
||||
make multiple requests to the nilmdb backend to avoid causing
|
||||
it to block for too long.
|
||||
|
||||
Add count=True to return a count rather than actual data.
|
||||
"""
|
||||
if start is not None:
|
||||
start = float(start)
|
||||
if end is not None:
|
||||
end = float(end)
|
||||
If 'count' is True, returns a count rather than actual data.
|
||||
|
||||
# Check parameters
|
||||
if start is not None and end is not None:
|
||||
if start >= end:
|
||||
raise cherrypy.HTTPError("400 Bad Request",
|
||||
"start must precede end")
|
||||
If 'markup' is True, adds comments to the stream denoting each
|
||||
interval's start and end timestamp.
|
||||
|
||||
If 'binary' is True, return raw binary data, rather than lines
|
||||
of ASCII-formatted data. Raw binary data is always
|
||||
little-endian and matches the database types (including an
|
||||
int64 timestamp).
|
||||
"""
|
||||
(start, end) = self._get_times(start, end)
|
||||
|
||||
# Check path and get layout
|
||||
streams = self.db.stream_list(path = path)
|
||||
if len(streams) != 1:
|
||||
raise cherrypy.HTTPError("404 Not Found", "No such stream")
|
||||
if len(self.db.stream_list(path = path)) != 1:
|
||||
raise cherrypy.HTTPError("404", "No such stream: " + path)
|
||||
|
||||
if binary:
|
||||
content_type = "application/octet-stream"
|
||||
if markup or count:
|
||||
raise cherrypy.HTTPError("400", "can't mix binary and "
|
||||
"markup or count modes")
|
||||
else:
|
||||
content_type = "text/plain"
|
||||
cherrypy.response.headers['Content-Type'] = content_type
|
||||
|
||||
@workaround_cp_bug_1200
|
||||
def content(start, end, count):
|
||||
def content(start, end):
|
||||
# Note: disable chunked responses to see tracebacks from here.
|
||||
if count:
|
||||
matched = self.db.stream_extract(path, start, end, count)
|
||||
matched = self.db.stream_extract(path, start, end,
|
||||
count = True)
|
||||
yield sprintf("%d\n", matched)
|
||||
return
|
||||
|
||||
while True:
|
||||
(data, restart) = self.db.stream_extract(path, start, end)
|
||||
(data, restart) = self.db.stream_extract(
|
||||
path, start, end, count = False,
|
||||
markup = markup, binary = binary)
|
||||
yield data
|
||||
|
||||
if restart == 0:
|
||||
if restart is None:
|
||||
return
|
||||
start = restart
|
||||
return content(start, end, count)
|
||||
return content(start, end)
|
||||
|
||||
class Exiter(object):
|
||||
"""App that exits the server, for testing"""
|
||||
@@ -433,7 +480,8 @@ class Server(object):
|
||||
stoppable = False, # whether /exit URL exists
|
||||
embedded = True, # hide diagnostics and output, etc
|
||||
fast_shutdown = False, # don't wait for clients to disconn.
|
||||
force_traceback = False # include traceback in all errors
|
||||
force_traceback = False, # include traceback in all errors
|
||||
basepath = '', # base URL path for cherrypy.tree
|
||||
):
|
||||
# Save server version, just for verification during tests
|
||||
self.version = nilmdb.__version__
|
||||
@@ -493,7 +541,7 @@ class Server(object):
|
||||
if stoppable:
|
||||
root.exit = Exiter()
|
||||
cherrypy.tree.apps = {}
|
||||
cherrypy.tree.mount(root, "/", config = { "/" : app_config })
|
||||
cherrypy.tree.mount(root, basepath, config = { "/" : app_config })
|
||||
|
||||
# Shutdowns normally wait for clients to disconnect. To speed
|
||||
# up tests, set fast_shutdown = True
|
||||
@@ -503,6 +551,9 @@ class Server(object):
|
||||
else:
|
||||
cherrypy.server.shutdown_timeout = 5
|
||||
|
||||
# Set up the WSGI application pointer for external programs
|
||||
self.wsgi_application = cherrypy.tree
|
||||
|
||||
def json_error_page(self, status, message, traceback, version):
|
||||
"""Return a custom error page in JSON so the client can parse it"""
|
||||
errordata = { "status" : status,
|
||||
@@ -569,3 +620,55 @@ class Server(object):
|
||||
|
||||
def stop(self):
|
||||
cherrypy.engine.exit()
|
||||
|
||||
# Use a single global nilmdb.server.NilmDB and nilmdb.server.Server
|
||||
# instance since the database can only be opened once. For this to
|
||||
# work, the web server must use only a single process and single
|
||||
# Python interpreter. Multiple threads are OK.
|
||||
_wsgi_server = None
|
||||
def wsgi_application(dbpath, basepath): # pragma: no cover
|
||||
"""Return a WSGI application object with a database at the
|
||||
specified path.
|
||||
|
||||
'dbpath' is a filesystem location, e.g. /home/nilm/db
|
||||
|
||||
'basepath' is the URL path of the application base, which
|
||||
is the same as the first argument to Apache's WSGIScriptAlias
|
||||
directive.
|
||||
"""
|
||||
def application(environ, start_response):
|
||||
global _wsgi_server
|
||||
if _wsgi_server is None:
|
||||
# Try to start the server
|
||||
try:
|
||||
db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(dbpath)
|
||||
_wsgi_server = nilmdb.server.Server(
|
||||
db, embedded = True,
|
||||
basepath = basepath.rstrip('/'))
|
||||
except Exception:
|
||||
# Build an error message on failure
|
||||
import pprint
|
||||
err = sprintf("Initializing database at path '%s' failed:\n\n",
|
||||
dbpath)
|
||||
err += traceback.format_exc()
|
||||
try:
|
||||
import pwd
|
||||
import grp
|
||||
err += sprintf("\nRunning as: uid=%d (%s), gid=%d (%s) "
|
||||
"on host %s, pid %d\n",
|
||||
os.getuid(), pwd.getpwuid(os.getuid())[0],
|
||||
os.getgid(), grp.getgrgid(os.getgid())[0],
|
||||
socket.gethostname(), os.getpid())
|
||||
except ImportError:
|
||||
pass
|
||||
err += sprintf("\nEnvironment:\n%s\n", pprint.pformat(environ))
|
||||
if _wsgi_server is None:
|
||||
# Serve up the error with our own mini WSGI app.
|
||||
headers = [ ('Content-type', 'text/plain'),
|
||||
('Content-length', str(len(err))) ]
|
||||
start_response("500 Internal Server Error", headers)
|
||||
return [err]
|
||||
|
||||
# Call the normal application
|
||||
return _wsgi_server.wsgi_application(environ, start_response)
|
||||
return application
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""NilmDB utilities"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from nilmdb.utils.timer import Timer
|
||||
from nilmdb.utils.iteratorizer import Iteratorizer
|
||||
from nilmdb.utils.serializer import serializer_proxy
|
||||
from nilmdb.utils.lrucache import lru_cache
|
||||
from nilmdb.utils.diskusage import du, human_size
|
||||
@@ -10,3 +10,6 @@ from nilmdb.utils import atomic
|
||||
import nilmdb.utils.threadsafety
|
||||
import nilmdb.utils.fallocate
|
||||
import nilmdb.utils.time
|
||||
import nilmdb.utils.iterator
|
||||
import nilmdb.utils.interval
|
||||
import nilmdb.utils.lock
|
||||
|
106
nilmdb/utils/interval.py
Normal file
106
nilmdb/utils/interval.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Interval. Like nilmdb.server.interval, but re-implemented here
|
||||
in plain Python so clients have easier access to it.
|
||||
|
||||
Intervals are half-open, ie. they include data points with timestamps
|
||||
[start, end)
|
||||
"""
|
||||
|
||||
import nilmdb.utils.time
|
||||
import nilmdb.utils.iterator
|
||||
|
||||
class IntervalError(Exception):
|
||||
"""Error due to interval overlap, etc"""
|
||||
pass
|
||||
|
||||
# Interval
|
||||
class Interval:
|
||||
"""Represents an interval of time."""
|
||||
|
||||
def __init__(self, start, end):
|
||||
"""
|
||||
'start' and 'end' are arbitrary numbers that represent time
|
||||
"""
|
||||
if start >= end:
|
||||
# Explicitly disallow zero-width intervals (since they're half-open)
|
||||
raise IntervalError("start %s must precede end %s" % (start, end))
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def __repr__(self):
|
||||
s = repr(self.start) + ", " + repr(self.end)
|
||||
return self.__class__.__name__ + "(" + s + ")"
|
||||
|
||||
def __str__(self):
|
||||
return ("[" + nilmdb.utils.time.timestamp_to_string(self.start) +
|
||||
" -> " + nilmdb.utils.time.timestamp_to_string(self.end) + ")")
|
||||
|
||||
def __cmp__(self, other):
|
||||
"""Compare two intervals. If non-equal, order by start then end"""
|
||||
return cmp(self.start, other.start) or cmp(self.end, other.end)
|
||||
|
||||
def intersects(self, other):
|
||||
"""Return True if two Interval objects intersect"""
|
||||
if not isinstance(other, Interval):
|
||||
raise TypeError("need an Interval")
|
||||
if self.end <= other.start or self.start >= other.end:
|
||||
return False
|
||||
return True
|
||||
|
||||
def subset(self, start, end):
|
||||
"""Return a new Interval that is a subset of this one"""
|
||||
# A subclass that tracks additional data might override this.
|
||||
if start < self.start or end > self.end:
|
||||
raise IntervalError("not a subset")
|
||||
return Interval(start, end)
|
||||
|
||||
def set_difference(a, b):
|
||||
"""
|
||||
Compute the difference (a \\ b) between the intervals in 'a' and
|
||||
the intervals in 'b'; i.e., the ranges that are present in 'self'
|
||||
but not 'other'.
|
||||
|
||||
'a' and 'b' must both be iterables.
|
||||
|
||||
Returns a generator that yields each interval in turn.
|
||||
Output intervals are built as subsets of the intervals in the
|
||||
first argument (a).
|
||||
"""
|
||||
# Iterate through all starts and ends in sorted order. Add a
|
||||
# tag to the iterator so that we can figure out which one they
|
||||
# were, after sorting.
|
||||
def decorate(it, key_start, key_end):
|
||||
for i in it:
|
||||
yield i.start, key_start, i
|
||||
yield i.end, key_end, i
|
||||
a_iter = decorate(iter(a), 0, 2)
|
||||
b_iter = decorate(iter(b), 1, 3)
|
||||
|
||||
# Now iterate over the timestamps of each start and end.
|
||||
# At each point, evaluate which type of end it is, to determine
|
||||
# how to build up the output intervals.
|
||||
a_interval = None
|
||||
b_interval = None
|
||||
out_start = None
|
||||
for (ts, k, i) in nilmdb.utils.iterator.imerge(a_iter, b_iter):
|
||||
if k == 0:
|
||||
# start a interval
|
||||
a_interval = i
|
||||
if b_interval is None:
|
||||
out_start = ts
|
||||
elif k == 1:
|
||||
# start b interval
|
||||
b_interval = i
|
||||
if out_start is not None and out_start != ts:
|
||||
yield a_interval.subset(out_start, ts)
|
||||
out_start = None
|
||||
elif k == 2:
|
||||
# end a interval
|
||||
if out_start is not None and out_start != ts:
|
||||
yield a_interval.subset(out_start, ts)
|
||||
out_start = None
|
||||
a_interval = None
|
||||
elif k == 3:
|
||||
# end b interval
|
||||
b_interval = None
|
||||
if a_interval:
|
||||
out_start = ts
|
36
nilmdb/utils/iterator.py
Normal file
36
nilmdb/utils/iterator.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Misc iterator tools
|
||||
|
||||
# Iterator merging, based on http://code.activestate.com/recipes/491285/
|
||||
import heapq
|
||||
def imerge(*iterables):
|
||||
'''Merge multiple sorted inputs into a single sorted output.
|
||||
|
||||
Equivalent to: sorted(itertools.chain(*iterables))
|
||||
|
||||
>>> list(imerge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25]))
|
||||
[0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25]
|
||||
|
||||
'''
|
||||
heappop, siftup, _Stop = heapq.heappop, heapq._siftup, StopIteration
|
||||
|
||||
h = []
|
||||
h_append = h.append
|
||||
for it in map(iter, iterables):
|
||||
try:
|
||||
next = it.next
|
||||
h_append([next(), next])
|
||||
except _Stop:
|
||||
pass
|
||||
heapq.heapify(h)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
while 1:
|
||||
v, next = s = h[0] # raises IndexError when h is empty
|
||||
yield v
|
||||
s[0] = next() # raises StopIteration when exhausted
|
||||
siftup(h, 0) # restore heap condition
|
||||
except _Stop:
|
||||
heappop(h) # remove empty iterator
|
||||
except IndexError:
|
||||
return
|
@@ -1,100 +0,0 @@
|
||||
import Queue
|
||||
import threading
|
||||
import sys
|
||||
import contextlib
|
||||
|
||||
# This file provides a context manager that converts a function
|
||||
# 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/
|
||||
|
||||
class IteratorizerThread(threading.Thread):
|
||||
def __init__(self, queue, function, curl_hack):
|
||||
"""
|
||||
function: function to execute, which takes the
|
||||
callback (provided by this class) as an argument
|
||||
"""
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "Iteratorizer-" + function.__name__ + "-" + self.name
|
||||
self.function = function
|
||||
self.queue = queue
|
||||
self.die = False
|
||||
self.curl_hack = curl_hack
|
||||
|
||||
def callback(self, data):
|
||||
try:
|
||||
if self.die:
|
||||
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):
|
||||
try:
|
||||
result = self.function(self.callback)
|
||||
except:
|
||||
self.queue.put((2, sys.exc_info()))
|
||||
else:
|
||||
self.queue.put((0, result))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def Iteratorizer(function, curl_hack = False):
|
||||
"""
|
||||
Context manager that takes a function expecting a callback,
|
||||
and provides an iterable that yields the values passed to that
|
||||
callback instead.
|
||||
|
||||
function: function to execute, which takes a callback
|
||||
(provided by this context manager) as an argument
|
||||
|
||||
with iteratorizer(func) as it:
|
||||
for i in it:
|
||||
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:
|
||||
queue.get(True, 0.01)
|
||||
except: # pragma: no cover
|
||||
pass
|
33
nilmdb/utils/lock.py
Normal file
33
nilmdb/utils/lock.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# File locking
|
||||
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import fcntl
|
||||
import errno
|
||||
|
||||
def exclusive_lock(f):
|
||||
"""Acquire an exclusive lock. Returns True on successful
|
||||
lock, or False on error."""
|
||||
try:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError as e:
|
||||
if e.errno in (errno.EACCES, errno.EAGAIN):
|
||||
return False
|
||||
else: # pragma: no cover
|
||||
raise
|
||||
return True
|
||||
|
||||
def exclusive_unlock(f):
|
||||
"""Release an exclusive lock."""
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
|
||||
except ImportError: # pragma: no cover
|
||||
def exclusive_lock(f):
|
||||
"""Dummy lock function -- does not lock!"""
|
||||
warnings.warn("Pretending to lock " + str(f))
|
||||
return True
|
||||
|
||||
def exclusive_unlock(f):
|
||||
"""Release an exclusive lock."""
|
||||
return
|
@@ -15,7 +15,7 @@ def must_close(errorfile = sys.stderr, wrap_verify = False):
|
||||
def wrap_class_method(wrapper):
|
||||
try:
|
||||
orig = getattr(cls, wrapper.__name__).im_func
|
||||
except:
|
||||
except Exception:
|
||||
orig = lambda x: None
|
||||
setattr(cls, wrapper.__name__, decorator.decorator(wrapper, orig))
|
||||
|
||||
|
@@ -1,12 +1,82 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
from nilmdb.utils import datetime_tz
|
||||
import re
|
||||
import time
|
||||
|
||||
# Range
|
||||
min_timestamp = (-2**63)
|
||||
max_timestamp = (2**63 - 1)
|
||||
|
||||
# Smallest representable step
|
||||
epsilon = 1
|
||||
|
||||
def string_to_timestamp(str):
|
||||
"""Convert a string that represents an integer number of microseconds
|
||||
since epoch."""
|
||||
try:
|
||||
# Parse a string like "1234567890123456" and return an integer
|
||||
return int(str)
|
||||
except ValueError:
|
||||
# Try parsing as a float, in case it's "1234567890123456.0"
|
||||
return int(round(float(str)))
|
||||
|
||||
def timestamp_to_string(timestamp):
|
||||
"""Convert a timestamp (integer microseconds since epoch) to a string"""
|
||||
if isinstance(timestamp, float):
|
||||
return str(int(round(timestamp)))
|
||||
else:
|
||||
return str(timestamp)
|
||||
|
||||
def timestamp_to_human(timestamp):
|
||||
"""Convert a timestamp (integer microseconds since epoch) to a
|
||||
human-readable string, using the local timezone for display
|
||||
(e.g. from the TZ env var)."""
|
||||
if timestamp == min_timestamp:
|
||||
return "(minimum)"
|
||||
if timestamp == max_timestamp:
|
||||
return "(maximum)"
|
||||
dt = datetime_tz.datetime_tz.fromtimestamp(timestamp_to_unix(timestamp))
|
||||
return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z")
|
||||
|
||||
def unix_to_timestamp(unix):
|
||||
"""Convert a Unix timestamp (floating point seconds since epoch)
|
||||
into a NILM timestamp (integer microseconds since epoch)"""
|
||||
return int(round(unix * 1e6))
|
||||
seconds_to_timestamp = unix_to_timestamp
|
||||
|
||||
def timestamp_to_unix(timestamp):
|
||||
"""Convert a NILM timestamp (integer microseconds since epoch)
|
||||
into a Unix timestamp (floating point seconds since epoch)"""
|
||||
return timestamp / 1e6
|
||||
timestamp_to_seconds = timestamp_to_unix
|
||||
|
||||
def rate_to_period(hz, cycles = 1):
|
||||
"""Convert a rate (in Hz) to a period (in timestamp units).
|
||||
Returns an integer."""
|
||||
period = unix_to_timestamp(cycles) / float(hz)
|
||||
return int(round(period))
|
||||
|
||||
def parse_time(toparse):
|
||||
"""
|
||||
Parse a free-form time string and return a datetime_tz object.
|
||||
If the string doesn't contain a timestamp, the current local
|
||||
timezone is assumed (e.g. from the TZ env var).
|
||||
Parse a free-form time string and return a nilmdb timestamp
|
||||
(integer seconds since epoch). If the string doesn't contain a
|
||||
timestamp, the current local timezone is assumed (e.g. from the TZ
|
||||
env var).
|
||||
"""
|
||||
if toparse == "min":
|
||||
return min_timestamp
|
||||
if toparse == "max":
|
||||
return max_timestamp
|
||||
|
||||
# If it starts with @, treat it as a NILM timestamp
|
||||
# (integer microseconds since epoch)
|
||||
try:
|
||||
if toparse[0] == '@':
|
||||
return int(toparse[1:])
|
||||
except (ValueError, KeyError, IndexError):
|
||||
pass
|
||||
|
||||
# If string isn't "now" and doesn't contain at least 4 digits,
|
||||
# consider it invalid. smartparse might otherwise accept
|
||||
# empty strings and strings with just separators.
|
||||
@@ -15,17 +85,20 @@ def parse_time(toparse):
|
||||
|
||||
# Try to just parse the time as given
|
||||
try:
|
||||
return datetime_tz.datetime_tz.smartparse(toparse)
|
||||
except ValueError:
|
||||
return unix_to_timestamp(datetime_tz.datetime_tz.
|
||||
smartparse(toparse).totimestamp())
|
||||
except (ValueError, OverflowError):
|
||||
pass
|
||||
|
||||
# Try to treat it as a single double
|
||||
# If it's parseable as a float, treat it as a Unix or NILM
|
||||
# timestamp based on its range.
|
||||
try:
|
||||
timestamp = float(toparse)
|
||||
# range is from about year 2001 - 2065
|
||||
if timestamp < 1e9 or timestamp > 3e9:
|
||||
raise ValueError
|
||||
return datetime_tz.datetime_tz.fromtimestamp(timestamp)
|
||||
val = float(toparse)
|
||||
# range is from about year 2001 - 2128
|
||||
if val > 1e9 and val < 5e9:
|
||||
return unix_to_timestamp(val)
|
||||
if val > 1e15 and val < 5e15:
|
||||
return val
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -47,7 +120,8 @@ def parse_time(toparse):
|
||||
r")", toparse)
|
||||
if res is not None:
|
||||
try:
|
||||
return datetime_tz.datetime_tz.smartparse(res.group(2))
|
||||
return unix_to_timestamp(datetime_tz.datetime_tz.
|
||||
smartparse(res.group(2)).totimestamp())
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -55,15 +129,6 @@ def parse_time(toparse):
|
||||
# just give up for now.
|
||||
raise ValueError("unable to parse timestamp")
|
||||
|
||||
def format_time(timestamp):
|
||||
"""
|
||||
Convert a Unix timestamp to a string for printing, using the
|
||||
local timezone for display (e.g. from the TZ env var).
|
||||
"""
|
||||
dt = datetime_tz.datetime_tz.fromtimestamp(timestamp)
|
||||
return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z")
|
||||
|
||||
def float_time_to_string(timestamp):
|
||||
"""Convert a floating-point Unix timestamp to a string,
|
||||
like '1234567890.000000'"""
|
||||
return "%.6f" % timestamp
|
||||
def now():
|
||||
"""Return current timestamp"""
|
||||
return unix_to_timestamp(time.time())
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"""File-like objects that add timestamps to the input lines"""
|
||||
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.utils import datetime_tz
|
||||
import nilmdb.utils.time
|
||||
|
||||
class Timestamper(object):
|
||||
"""A file-like object that adds timestamps to lines of an input file."""
|
||||
@@ -61,31 +61,33 @@ class TimestamperRate(Timestamper):
|
||||
|
||||
end: If specified, raise StopIteration before outputting a value
|
||||
greater than this."""
|
||||
timestamp_to_string = nilmdb.utils.time.timestamp_to_string
|
||||
rate_to_period = nilmdb.utils.time.rate_to_period
|
||||
def iterator(start, rate, end):
|
||||
n = 0
|
||||
rate = float(rate)
|
||||
while True:
|
||||
now = start + n / rate
|
||||
now = start + rate_to_period(rate, n)
|
||||
if end and now >= end:
|
||||
raise StopIteration
|
||||
yield sprintf("%.6f ", start + n / rate)
|
||||
yield timestamp_to_string(now) + " "
|
||||
n += 1
|
||||
Timestamper.__init__(self, infile, iterator(start, rate, end))
|
||||
self.start = start
|
||||
self.rate = rate
|
||||
def __str__(self):
|
||||
start = datetime_tz.datetime_tz.fromtimestamp(self.start)
|
||||
start = start.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
||||
return sprintf("TimestamperRate(..., start=\"%s\", rate=%g)",
|
||||
str(start), self.rate)
|
||||
nilmdb.utils.time.timestamp_to_human(self.start),
|
||||
self.rate)
|
||||
|
||||
class TimestamperNow(Timestamper):
|
||||
"""Timestamper that uses current time"""
|
||||
def __init__(self, infile):
|
||||
timestamp_to_string = nilmdb.utils.time.timestamp_to_string
|
||||
get_now = nilmdb.utils.time.now
|
||||
def iterator():
|
||||
while True:
|
||||
now = datetime_tz.datetime_tz.utcnow().totimestamp()
|
||||
yield sprintf("%.6f ", now)
|
||||
yield timestamp_to_string(get_now()) + " "
|
||||
Timestamper.__init__(self, infile, iterator())
|
||||
def __str__(self):
|
||||
return "TimestamperNow(...)"
|
||||
|
8
setup.py
8
setup.py
@@ -39,11 +39,10 @@ versioneer.parentdir_prefix = 'nilmdb-'
|
||||
# Hack to workaround logging/multiprocessing issue:
|
||||
# https://groups.google.com/d/msg/nose-users/fnJ-kAUbYHQ/_UsLN786ygcJ
|
||||
try: import multiprocessing
|
||||
except: pass
|
||||
except Exception: pass
|
||||
|
||||
# Use Cython if it's new enough, otherwise use preexisting C files.
|
||||
cython_modules = [ 'nilmdb.server.interval',
|
||||
'nilmdb.server.layout',
|
||||
'nilmdb.server.rbtree' ]
|
||||
try:
|
||||
import Cython
|
||||
@@ -91,6 +90,9 @@ include tests/test.order
|
||||
|
||||
# Docs
|
||||
recursive-include docs Makefile *.md
|
||||
|
||||
# Extras
|
||||
recursive-include extras *
|
||||
""")
|
||||
|
||||
# Run setup
|
||||
@@ -105,13 +107,13 @@ setup(name='nilmdb',
|
||||
author_email = 'jim@jtan.com',
|
||||
tests_require = [ 'nose',
|
||||
'coverage',
|
||||
'numpy',
|
||||
],
|
||||
setup_requires = [ 'distribute',
|
||||
],
|
||||
install_requires = [ 'decorator',
|
||||
'cherrypy >= 3.2',
|
||||
'simplejson',
|
||||
'pycurl',
|
||||
'python-dateutil',
|
||||
'pytz',
|
||||
'psutil >= 0.3.0',
|
||||
|
@@ -2,123 +2,123 @@
|
||||
# layout: float32_8
|
||||
# start: Fri, 23 Mar 2012 10:00:30.000000 +0000
|
||||
# end: Fri, 23 Mar 2012 10:00:31.000000 +0000
|
||||
1332496830.000000 2.517740e+05 2.242410e+05 5.688100e+03 1.915530e+03 9.329220e+03 4.183710e+03 1.212350e+03 2.641790e+03
|
||||
1332496830.008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830.016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830.025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830.033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830.041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830.050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830.058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830.066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830.075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830.083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830.091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830.100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830.108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830.116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830.125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830.133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830.141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830.150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830.158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830.166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830.175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830.183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830.191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830.200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830.208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830.216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830.225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830.233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830.241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830.250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830.258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830.266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830.275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830.283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830.291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830.300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830.308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830.316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830.325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830.333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830.341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830.350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830.358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830.366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830.375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830.383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830.391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830.400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830.408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830.416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830.425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830.433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830.441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830.450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830.458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830.466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830.475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830.483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830.491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830.500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830.508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830.516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830.525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830.533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830.541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830.550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830.558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830.566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830.575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830.583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830.591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830.600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830.608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830.616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830.625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830.633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830.641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830.650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830.658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830.666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830.675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830.683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830.691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830.700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830.708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830.716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830.725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830.733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830.741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830.750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830.758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830.766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830.775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830.783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830.791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830.800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830.808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830.816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830.825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830.833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830.841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830.850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830.858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830.866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830.875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830.883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830.891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830.900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830.908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830.916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830.925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830.933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830.941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830.950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830.958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830.966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830.975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830.983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830.991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
1332496830000000 2.517740e+05 2.242410e+05 5.688100e+03 1.915530e+03 9.329220e+03 4.183710e+03 1.212350e+03 2.641790e+03
|
||||
1332496830008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
|
@@ -1,119 +1,119 @@
|
||||
1332496830.008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830.016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830.025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830.033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830.041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830.050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830.058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830.066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830.075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830.083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830.091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830.100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830.108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830.116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830.125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830.133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830.141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830.150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830.158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830.166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830.175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830.183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830.191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830.200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830.208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830.216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830.225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830.233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830.241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830.250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830.258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830.266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830.275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830.283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830.291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830.300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830.308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830.316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830.325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830.333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830.341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830.350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830.358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830.366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830.375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830.383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830.391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830.400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830.408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830.416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830.425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830.433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830.441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830.450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830.458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830.466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830.475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830.483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830.491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830.500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830.508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830.516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830.525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830.533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830.541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830.550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830.558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830.566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830.575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830.583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830.591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830.600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830.608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830.616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830.625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830.633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830.641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830.650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830.658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830.666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830.675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830.683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830.691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830.700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830.708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830.716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830.725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830.733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830.741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830.750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830.758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830.766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830.775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830.783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830.791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830.800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830.808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830.816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830.825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830.833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830.841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830.850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830.858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830.866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830.875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830.883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830.891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830.900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830.908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830.916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830.925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830.933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830.941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830.950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830.958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830.966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830.975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830.983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830.991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
1332496830008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
|
@@ -1 +1 @@
|
||||
1332496830.008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
|
@@ -1,2 +1,2 @@
|
||||
1332496830.008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830.016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
|
@@ -1,124 +1,124 @@
|
||||
# path: /newton/prep
|
||||
# layout: float32_8
|
||||
# start: 1332496830.000000
|
||||
# end: 1332496830.999000
|
||||
1332496830.000000 2.517740e+05 2.242410e+05 5.688100e+03 1.915530e+03 9.329220e+03 4.183710e+03 1.212350e+03 2.641790e+03
|
||||
1332496830.008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830.016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830.025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830.033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830.041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830.050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830.058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830.066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830.075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830.083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830.091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830.100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830.108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830.116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830.125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830.133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830.141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830.150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830.158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830.166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830.175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830.183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830.191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830.200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830.208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830.216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830.225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830.233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830.241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830.250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830.258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830.266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830.275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830.283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830.291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830.300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830.308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830.316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830.325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830.333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830.341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830.350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830.358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830.366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830.375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830.383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830.391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830.400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830.408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830.416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830.425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830.433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830.441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830.450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830.458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830.466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830.475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830.483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830.491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830.500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830.508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830.516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830.525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830.533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830.541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830.550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830.558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830.566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830.575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830.583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830.591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830.600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830.608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830.616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830.625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830.633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830.641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830.650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830.658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830.666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830.675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830.683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830.691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830.700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830.708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830.716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830.725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830.733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830.741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830.750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830.758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830.766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830.775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830.783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830.791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830.800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830.808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830.816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830.825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830.833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830.841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830.850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830.858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830.866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830.875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830.883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830.891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830.900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830.908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830.916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830.925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830.933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830.941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830.950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830.958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830.966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830.975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830.983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830.991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
# start: 1332496830000000
|
||||
# end: 1332496830999000
|
||||
1332496830000000 2.517740e+05 2.242410e+05 5.688100e+03 1.915530e+03 9.329220e+03 4.183710e+03 1.212350e+03 2.641790e+03
|
||||
1332496830008333 2.595670e+05 2.226980e+05 6.207600e+03 6.786720e+02 9.380230e+03 4.575580e+03 2.830610e+03 2.688630e+03
|
||||
1332496830016667 2.630730e+05 2.233040e+05 4.961640e+03 2.197120e+03 7.687310e+03 4.861860e+03 2.732780e+03 3.008540e+03
|
||||
1332496830025000 2.576140e+05 2.233230e+05 5.003660e+03 3.525140e+03 7.165310e+03 4.685620e+03 1.715380e+03 3.440480e+03
|
||||
1332496830033333 2.557800e+05 2.219150e+05 6.357310e+03 2.145290e+03 8.426970e+03 3.775350e+03 1.475390e+03 3.797240e+03
|
||||
1332496830041667 2.601660e+05 2.230080e+05 6.702590e+03 1.484960e+03 9.288100e+03 3.330830e+03 1.228500e+03 3.214320e+03
|
||||
1332496830050000 2.612310e+05 2.264260e+05 4.980060e+03 2.982380e+03 8.499630e+03 4.267670e+03 9.940890e+02 2.292890e+03
|
||||
1332496830058333 2.551170e+05 2.266420e+05 4.584410e+03 4.656440e+03 7.860150e+03 5.317310e+03 1.473600e+03 2.111690e+03
|
||||
1332496830066667 2.533000e+05 2.235540e+05 6.455090e+03 3.036650e+03 8.869750e+03 4.986310e+03 2.607360e+03 2.839590e+03
|
||||
1332496830075000 2.610610e+05 2.212630e+05 6.951980e+03 1.500240e+03 9.386100e+03 3.791680e+03 2.677010e+03 3.980630e+03
|
||||
1332496830083333 2.665030e+05 2.231980e+05 5.189610e+03 2.594560e+03 8.571530e+03 3.175000e+03 9.198400e+02 3.792010e+03
|
||||
1332496830091667 2.606920e+05 2.251840e+05 3.782480e+03 4.642880e+03 7.662960e+03 3.917790e+03 -2.510970e+02 2.907060e+03
|
||||
1332496830100000 2.539630e+05 2.250810e+05 5.123530e+03 3.839550e+03 8.669030e+03 4.877820e+03 9.437240e+02 2.527450e+03
|
||||
1332496830108333 2.565550e+05 2.241690e+05 5.930600e+03 2.298540e+03 8.906710e+03 5.331680e+03 2.549910e+03 3.053560e+03
|
||||
1332496830116667 2.608890e+05 2.250100e+05 4.681130e+03 2.971870e+03 7.900040e+03 4.874080e+03 2.322430e+03 3.649120e+03
|
||||
1332496830125000 2.579440e+05 2.249230e+05 3.291140e+03 4.357090e+03 7.131590e+03 4.385560e+03 1.077050e+03 3.664040e+03
|
||||
1332496830133333 2.550090e+05 2.230180e+05 4.584820e+03 2.864000e+03 8.469490e+03 3.625580e+03 9.855570e+02 3.504230e+03
|
||||
1332496830141667 2.601140e+05 2.219470e+05 5.676190e+03 1.210340e+03 9.393780e+03 3.390240e+03 1.654020e+03 3.018700e+03
|
||||
1332496830150000 2.642770e+05 2.244380e+05 4.446620e+03 2.176720e+03 8.142090e+03 4.584880e+03 2.327830e+03 2.615800e+03
|
||||
1332496830158333 2.592210e+05 2.264710e+05 2.734440e+03 4.182760e+03 6.389550e+03 5.540520e+03 1.958880e+03 2.720120e+03
|
||||
1332496830166667 2.526500e+05 2.248310e+05 4.163640e+03 2.989990e+03 7.179200e+03 5.213060e+03 1.929550e+03 3.457660e+03
|
||||
1332496830175000 2.570830e+05 2.220480e+05 5.759040e+03 7.024410e+02 8.566550e+03 3.552020e+03 1.832940e+03 3.956190e+03
|
||||
1332496830183333 2.631300e+05 2.229670e+05 5.141140e+03 1.166120e+03 8.666960e+03 2.720370e+03 9.713740e+02 3.479730e+03
|
||||
1332496830191667 2.602360e+05 2.252650e+05 3.425140e+03 3.339080e+03 7.853610e+03 3.674950e+03 5.259080e+02 2.443310e+03
|
||||
1332496830200000 2.535030e+05 2.245270e+05 4.398130e+03 2.927430e+03 8.110280e+03 4.842470e+03 1.513870e+03 2.467100e+03
|
||||
1332496830208333 2.561260e+05 2.226930e+05 6.043530e+03 6.562240e+02 8.797560e+03 4.832410e+03 2.832370e+03 3.426140e+03
|
||||
1332496830216667 2.616770e+05 2.236080e+05 5.830460e+03 1.033910e+03 8.123940e+03 3.980690e+03 1.927960e+03 4.092720e+03
|
||||
1332496830225000 2.594570e+05 2.255360e+05 4.015570e+03 2.995990e+03 7.135440e+03 3.713550e+03 3.072200e+02 3.849430e+03
|
||||
1332496830233333 2.533520e+05 2.242160e+05 4.650560e+03 3.196620e+03 8.131280e+03 3.586160e+03 7.083230e+01 3.074180e+03
|
||||
1332496830241667 2.561240e+05 2.215130e+05 6.100480e+03 8.219800e+02 9.757540e+03 3.474510e+03 1.647520e+03 2.559860e+03
|
||||
1332496830250000 2.630240e+05 2.215590e+05 5.789960e+03 6.994170e+02 9.129740e+03 4.153080e+03 2.829250e+03 2.677270e+03
|
||||
1332496830258333 2.617200e+05 2.240150e+05 4.358500e+03 2.645360e+03 7.414110e+03 4.810670e+03 2.225990e+03 3.185990e+03
|
||||
1332496830266667 2.547560e+05 2.242400e+05 4.857380e+03 3.229680e+03 7.539310e+03 4.769140e+03 1.507130e+03 3.668260e+03
|
||||
1332496830275000 2.568890e+05 2.226580e+05 6.473420e+03 1.214110e+03 9.010760e+03 3.848730e+03 1.303840e+03 3.778500e+03
|
||||
1332496830283333 2.642080e+05 2.233160e+05 5.700450e+03 1.116560e+03 9.087610e+03 3.846680e+03 1.293590e+03 2.891560e+03
|
||||
1332496830291667 2.633100e+05 2.257190e+05 3.936120e+03 3.252360e+03 7.552850e+03 4.897860e+03 1.156630e+03 2.037160e+03
|
||||
1332496830300000 2.550790e+05 2.250860e+05 4.536450e+03 3.960110e+03 7.454590e+03 5.479070e+03 1.596360e+03 2.190800e+03
|
||||
1332496830308333 2.544870e+05 2.225080e+05 6.635860e+03 1.758850e+03 8.732970e+03 4.466970e+03 2.650360e+03 3.139310e+03
|
||||
1332496830316667 2.612410e+05 2.224320e+05 6.702270e+03 1.085130e+03 8.989230e+03 3.112990e+03 1.933560e+03 3.828410e+03
|
||||
1332496830325000 2.621190e+05 2.255870e+05 4.714950e+03 2.892360e+03 8.107820e+03 2.961310e+03 2.399780e+02 3.273720e+03
|
||||
1332496830333333 2.549990e+05 2.265140e+05 4.532090e+03 4.126900e+03 8.200130e+03 3.872590e+03 5.608900e+01 2.370580e+03
|
||||
1332496830341667 2.542890e+05 2.240330e+05 6.538810e+03 2.251440e+03 9.419430e+03 4.564450e+03 2.077810e+03 2.508170e+03
|
||||
1332496830350000 2.618900e+05 2.219600e+05 6.846090e+03 1.475270e+03 9.125590e+03 4.598290e+03 3.299220e+03 3.475420e+03
|
||||
1332496830358333 2.645020e+05 2.230850e+05 5.066380e+03 3.270560e+03 7.933170e+03 4.173710e+03 1.908910e+03 3.867460e+03
|
||||
1332496830366667 2.578890e+05 2.236560e+05 4.201660e+03 4.473640e+03 7.688340e+03 4.161580e+03 6.875790e+02 3.653690e+03
|
||||
1332496830375000 2.542700e+05 2.231510e+05 5.715140e+03 2.752140e+03 9.273320e+03 3.772950e+03 8.964040e+02 3.256060e+03
|
||||
1332496830383333 2.582570e+05 2.242170e+05 6.114310e+03 1.856860e+03 9.604320e+03 4.200490e+03 1.764380e+03 2.939220e+03
|
||||
1332496830391667 2.600200e+05 2.268680e+05 4.237530e+03 3.605880e+03 8.066220e+03 5.430250e+03 2.138580e+03 2.696710e+03
|
||||
1332496830400000 2.550830e+05 2.259240e+05 3.350310e+03 4.853070e+03 7.045820e+03 5.925200e+03 1.893610e+03 2.897340e+03
|
||||
1332496830408333 2.544530e+05 2.221270e+05 5.271330e+03 2.491500e+03 8.436680e+03 5.032080e+03 2.436050e+03 3.724590e+03
|
||||
1332496830416667 2.625880e+05 2.199500e+05 5.994620e+03 7.892740e+02 9.029650e+03 3.515740e+03 1.953570e+03 4.014520e+03
|
||||
1332496830425000 2.656100e+05 2.233330e+05 4.391410e+03 2.400960e+03 8.146460e+03 3.536960e+03 5.302320e+02 3.133920e+03
|
||||
1332496830433333 2.574700e+05 2.269770e+05 2.975320e+03 4.633530e+03 7.278560e+03 4.640100e+03 -5.015020e+01 2.024960e+03
|
||||
1332496830441667 2.506870e+05 2.263310e+05 4.517860e+03 3.183800e+03 8.072600e+03 5.281660e+03 1.605140e+03 2.335140e+03
|
||||
1332496830450000 2.555630e+05 2.244950e+05 5.551000e+03 1.101300e+03 8.461490e+03 4.725700e+03 2.726670e+03 3.480540e+03
|
||||
1332496830458333 2.613350e+05 2.246450e+05 4.764680e+03 1.557020e+03 7.833350e+03 3.524810e+03 1.577410e+03 4.038620e+03
|
||||
1332496830466667 2.602690e+05 2.240080e+05 3.558030e+03 2.987610e+03 7.362440e+03 3.279230e+03 5.624420e+02 3.786550e+03
|
||||
1332496830475000 2.574350e+05 2.217770e+05 4.972600e+03 2.166880e+03 8.481440e+03 3.328720e+03 1.037130e+03 3.271370e+03
|
||||
1332496830483333 2.610460e+05 2.215500e+05 5.816180e+03 5.902170e+02 9.120930e+03 3.895400e+03 2.382670e+03 2.824170e+03
|
||||
1332496830491667 2.627660e+05 2.244730e+05 4.835050e+03 1.785770e+03 7.880760e+03 4.745620e+03 2.443660e+03 3.229550e+03
|
||||
1332496830500000 2.565090e+05 2.264130e+05 3.758870e+03 3.461200e+03 6.743770e+03 4.928960e+03 1.536620e+03 3.546690e+03
|
||||
1332496830508333 2.507930e+05 2.243720e+05 5.218490e+03 2.865260e+03 7.803960e+03 4.351090e+03 1.333820e+03 3.680490e+03
|
||||
1332496830516667 2.563190e+05 2.220660e+05 6.403970e+03 7.323450e+02 9.627760e+03 3.089300e+03 1.516780e+03 3.653690e+03
|
||||
1332496830525000 2.633430e+05 2.232350e+05 5.200430e+03 1.388580e+03 9.372850e+03 3.371230e+03 1.450390e+03 2.678910e+03
|
||||
1332496830533333 2.609030e+05 2.251100e+05 3.722580e+03 3.246660e+03 7.876540e+03 4.716810e+03 1.498440e+03 2.116520e+03
|
||||
1332496830541667 2.544160e+05 2.237690e+05 4.841650e+03 2.956400e+03 8.115920e+03 5.392360e+03 2.142810e+03 2.652320e+03
|
||||
1332496830550000 2.566980e+05 2.221720e+05 6.471230e+03 9.703960e+02 8.834980e+03 4.816840e+03 2.376630e+03 3.605860e+03
|
||||
1332496830558333 2.618410e+05 2.235370e+05 5.500740e+03 1.189660e+03 8.365730e+03 4.016470e+03 1.042270e+03 3.821200e+03
|
||||
1332496830566667 2.595030e+05 2.258400e+05 3.827930e+03 3.088840e+03 7.676140e+03 3.978310e+03 -3.570070e+02 3.016420e+03
|
||||
1332496830575000 2.534570e+05 2.246360e+05 4.914610e+03 3.097450e+03 8.224900e+03 4.321440e+03 1.713740e+02 2.412360e+03
|
||||
1332496830583333 2.560290e+05 2.222210e+05 6.841800e+03 1.028500e+03 9.252300e+03 4.387570e+03 2.418140e+03 2.510100e+03
|
||||
1332496830591667 2.628400e+05 2.225500e+05 6.210250e+03 1.410730e+03 8.538900e+03 4.152580e+03 3.009300e+03 3.219760e+03
|
||||
1332496830600000 2.616330e+05 2.250650e+05 4.284530e+03 3.357210e+03 7.282170e+03 3.823590e+03 1.402840e+03 3.644670e+03
|
||||
1332496830608333 2.545910e+05 2.251090e+05 4.693160e+03 3.647740e+03 7.745160e+03 3.686380e+03 4.901610e+02 3.448860e+03
|
||||
1332496830616667 2.547800e+05 2.235990e+05 6.527380e+03 1.569870e+03 9.438430e+03 3.456580e+03 1.162520e+03 3.252010e+03
|
||||
1332496830625000 2.606390e+05 2.241070e+05 6.531050e+03 1.633050e+03 9.283720e+03 4.174020e+03 2.089550e+03 2.775750e+03
|
||||
1332496830633333 2.611080e+05 2.254720e+05 4.968260e+03 3.527850e+03 7.692870e+03 5.137100e+03 2.207390e+03 2.436660e+03
|
||||
1332496830641667 2.557750e+05 2.237080e+05 4.963450e+03 4.017370e+03 7.701420e+03 5.269650e+03 2.284400e+03 2.842080e+03
|
||||
1332496830650000 2.573980e+05 2.209470e+05 6.767500e+03 1.645710e+03 9.107070e+03 4.000180e+03 2.548860e+03 3.624770e+03
|
||||
1332496830658333 2.649240e+05 2.215590e+05 6.471460e+03 1.110330e+03 9.459650e+03 3.108170e+03 1.696970e+03 3.893440e+03
|
||||
1332496830666667 2.653390e+05 2.257330e+05 4.348800e+03 3.459510e+03 8.475300e+03 4.031240e+03 5.733470e+02 2.910270e+03
|
||||
1332496830675000 2.568140e+05 2.269950e+05 3.479540e+03 4.949790e+03 7.499910e+03 5.624710e+03 7.516560e+02 2.347710e+03
|
||||
1332496830683333 2.533160e+05 2.251610e+05 5.147060e+03 3.218430e+03 8.460160e+03 5.869300e+03 2.336320e+03 2.987960e+03
|
||||
1332496830691667 2.593600e+05 2.231010e+05 5.549120e+03 1.869950e+03 8.740760e+03 4.668940e+03 2.457910e+03 3.758820e+03
|
||||
1332496830700000 2.620120e+05 2.240160e+05 4.173610e+03 3.004130e+03 8.157040e+03 3.704730e+03 9.879640e+02 3.652750e+03
|
||||
1332496830708333 2.571760e+05 2.244200e+05 3.517300e+03 4.118750e+03 7.822240e+03 3.718230e+03 3.726490e+01 2.953680e+03
|
||||
1332496830716667 2.551460e+05 2.233220e+05 4.923980e+03 2.330680e+03 9.095910e+03 3.792400e+03 1.013070e+03 2.711240e+03
|
||||
1332496830725000 2.605240e+05 2.236510e+05 5.413630e+03 1.146210e+03 8.817170e+03 4.419650e+03 2.446650e+03 2.832050e+03
|
||||
1332496830733333 2.620980e+05 2.257520e+05 4.262980e+03 2.270970e+03 7.135480e+03 5.067120e+03 2.294680e+03 3.376620e+03
|
||||
1332496830741667 2.568890e+05 2.253790e+05 3.606460e+03 3.568190e+03 6.552650e+03 4.970270e+03 1.516380e+03 3.662570e+03
|
||||
1332496830750000 2.539480e+05 2.226310e+05 5.511700e+03 2.066300e+03 7.952660e+03 4.019910e+03 1.513140e+03 3.752630e+03
|
||||
1332496830758333 2.597990e+05 2.220670e+05 5.873500e+03 6.085840e+02 9.253780e+03 2.870740e+03 1.348240e+03 3.344200e+03
|
||||
1332496830766667 2.625470e+05 2.249010e+05 4.346080e+03 1.928100e+03 8.590970e+03 3.455460e+03 9.043910e+02 2.379270e+03
|
||||
1332496830775000 2.561370e+05 2.267610e+05 3.423560e+03 3.379080e+03 7.471150e+03 4.894170e+03 1.153540e+03 2.031410e+03
|
||||
1332496830783333 2.503260e+05 2.250130e+05 5.519980e+03 2.423970e+03 7.991760e+03 5.117950e+03 2.098790e+03 3.099240e+03
|
||||
1332496830791667 2.554540e+05 2.229920e+05 6.547950e+03 4.964960e+02 8.751340e+03 3.900560e+03 2.132290e+03 4.076810e+03
|
||||
1332496830800000 2.612860e+05 2.234890e+05 5.152850e+03 1.501510e+03 8.425610e+03 2.888030e+03 7.761140e+02 3.786360e+03
|
||||
1332496830808333 2.589690e+05 2.240690e+05 3.832610e+03 3.001980e+03 7.979260e+03 3.182310e+03 5.271600e+01 2.874800e+03
|
||||
1332496830816667 2.549460e+05 2.220350e+05 5.317880e+03 2.139800e+03 9.103140e+03 3.955610e+03 1.235170e+03 2.394150e+03
|
||||
1332496830825000 2.586760e+05 2.212050e+05 6.594910e+03 5.053440e+02 9.423360e+03 4.562470e+03 2.913740e+03 2.892350e+03
|
||||
1332496830833333 2.621250e+05 2.235660e+05 5.116750e+03 1.773600e+03 8.082200e+03 4.776370e+03 2.386390e+03 3.659730e+03
|
||||
1332496830841667 2.578350e+05 2.259180e+05 3.714300e+03 3.477080e+03 7.205370e+03 4.554610e+03 7.115390e+02 3.878420e+03
|
||||
1332496830850000 2.536600e+05 2.243710e+05 5.022450e+03 2.592430e+03 8.277200e+03 4.119370e+03 4.865080e+02 3.666740e+03
|
||||
1332496830858333 2.595030e+05 2.220610e+05 6.589950e+03 6.599360e+02 9.596920e+03 3.598100e+03 1.702490e+03 3.036600e+03
|
||||
1332496830866667 2.654950e+05 2.228430e+05 5.541850e+03 1.728430e+03 8.459960e+03 4.492000e+03 2.231970e+03 2.430620e+03
|
||||
1332496830875000 2.609290e+05 2.249960e+05 4.000950e+03 3.745990e+03 6.983790e+03 5.430860e+03 1.855260e+03 2.533380e+03
|
||||
1332496830883333 2.527160e+05 2.243350e+05 5.086560e+03 3.401150e+03 7.597970e+03 5.196120e+03 1.755720e+03 3.079760e+03
|
||||
1332496830891667 2.541100e+05 2.231110e+05 6.822190e+03 1.229080e+03 9.164340e+03 3.761230e+03 1.679390e+03 3.584880e+03
|
||||
1332496830900000 2.599690e+05 2.246930e+05 6.183950e+03 1.538500e+03 9.222080e+03 3.139170e+03 9.499020e+02 3.180800e+03
|
||||
1332496830908333 2.590780e+05 2.269130e+05 4.388890e+03 3.694820e+03 8.195020e+03 3.933000e+03 4.260800e+02 2.388450e+03
|
||||
1332496830916667 2.545630e+05 2.247600e+05 5.168440e+03 4.020940e+03 8.450270e+03 4.758910e+03 1.458900e+03 2.286430e+03
|
||||
1332496830925000 2.580590e+05 2.212170e+05 6.883460e+03 1.649530e+03 9.232780e+03 4.457650e+03 3.057820e+03 3.031950e+03
|
||||
1332496830933333 2.646670e+05 2.211770e+05 6.218510e+03 1.645730e+03 8.657180e+03 3.663500e+03 2.528280e+03 3.978340e+03
|
||||
1332496830941667 2.629250e+05 2.243820e+05 4.627500e+03 3.635930e+03 7.892800e+03 3.431320e+03 6.045090e+02 3.901370e+03
|
||||
1332496830950000 2.547080e+05 2.254480e+05 4.408250e+03 4.461040e+03 8.197170e+03 3.953750e+03 -4.453460e+01 3.154870e+03
|
||||
1332496830958333 2.537020e+05 2.246350e+05 5.825770e+03 2.577050e+03 9.590050e+03 4.569250e+03 1.460270e+03 2.785170e+03
|
||||
1332496830966667 2.602060e+05 2.241400e+05 5.387980e+03 1.951160e+03 8.789510e+03 5.131660e+03 2.706380e+03 2.972480e+03
|
||||
1332496830975000 2.612400e+05 2.247370e+05 3.860810e+03 3.418310e+03 7.414530e+03 5.284520e+03 2.271380e+03 3.183150e+03
|
||||
1332496830983333 2.561400e+05 2.232520e+05 3.850010e+03 3.957140e+03 7.262650e+03 4.964640e+03 1.499510e+03 3.453130e+03
|
||||
1332496830991667 2.561160e+05 2.213490e+05 5.594480e+03 2.054400e+03 8.835130e+03 3.662010e+03 1.485510e+03 3.613010e+03
|
||||
|
28
tests/data/extract-8
Normal file
28
tests/data/extract-8
Normal file
@@ -0,0 +1,28 @@
|
||||
# interval-start 1332496919900000
|
||||
1332496919900000 2.523050e+05 2.254020e+05 4.779410e+03 3.638030e+03 8.138070e+03 4.334460e+03 1.083780e+03 3.743730e+03
|
||||
1332496919908333 2.551190e+05 2.237870e+05 5.965640e+03 2.076350e+03 9.468790e+03 3.693880e+03 1.247860e+03 3.393680e+03
|
||||
1332496919916667 2.616370e+05 2.247980e+05 4.848970e+03 2.315620e+03 9.323300e+03 4.225460e+03 1.805780e+03 2.593050e+03
|
||||
1332496919925000 2.606460e+05 2.251300e+05 3.061360e+03 3.951840e+03 7.662910e+03 5.341410e+03 1.986520e+03 2.276780e+03
|
||||
1332496919933333 2.559710e+05 2.235030e+05 4.096030e+03 3.296970e+03 7.827080e+03 5.452120e+03 2.492520e+03 2.929450e+03
|
||||
1332496919941667 2.579260e+05 2.217080e+05 5.472320e+03 1.555700e+03 8.495760e+03 4.491140e+03 2.379780e+03 3.741710e+03
|
||||
1332496919950000 2.610180e+05 2.242350e+05 4.669770e+03 1.876190e+03 8.366680e+03 3.677510e+03 9.021690e+02 3.549040e+03
|
||||
1332496919958333 2.569150e+05 2.274650e+05 2.785070e+03 3.751930e+03 7.440320e+03 3.964860e+03 -3.227860e+02 2.460890e+03
|
||||
1332496919966667 2.509510e+05 2.262000e+05 3.772710e+03 3.131950e+03 8.159860e+03 4.539860e+03 7.375190e+02 2.126750e+03
|
||||
1332496919975000 2.556710e+05 2.223720e+05 5.826200e+03 8.715560e+02 9.120240e+03 4.545110e+03 2.804310e+03 2.721000e+03
|
||||
1332496919983333 2.649730e+05 2.214860e+05 5.839130e+03 4.659180e+02 8.628300e+03 3.934870e+03 2.972490e+03 3.773730e+03
|
||||
1332496919991667 2.652170e+05 2.233920e+05 3.718770e+03 2.834970e+03 7.209900e+03 3.460260e+03 1.324930e+03 4.075960e+03
|
||||
# interval-end 1332496919991668
|
||||
# interval-start 1332496920000000
|
||||
1332496920000000 2.564370e+05 2.244300e+05 4.011610e+03 3.475340e+03 7.495890e+03 3.388940e+03 2.613970e+02 3.731260e+03
|
||||
1332496920008333 2.539630e+05 2.241670e+05 5.621070e+03 1.548010e+03 9.165170e+03 3.522930e+03 1.058930e+03 2.996960e+03
|
||||
1332496920016667 2.585080e+05 2.249300e+05 6.011400e+03 8.188660e+02 9.039950e+03 4.482440e+03 2.490390e+03 2.679340e+03
|
||||
1332496920025000 2.596270e+05 2.260220e+05 4.474500e+03 2.423020e+03 7.414190e+03 5.071970e+03 2.439380e+03 2.962960e+03
|
||||
1332496920033333 2.551870e+05 2.246320e+05 4.738570e+03 3.398040e+03 7.395120e+03 4.726450e+03 1.839030e+03 3.393530e+03
|
||||
1332496920041667 2.571020e+05 2.216230e+05 6.144130e+03 1.441090e+03 8.756480e+03 3.495320e+03 1.869940e+03 3.752530e+03
|
||||
1332496920050000 2.636530e+05 2.217700e+05 6.221770e+03 7.389620e+02 9.547600e+03 2.666820e+03 1.462660e+03 3.332570e+03
|
||||
1332496920058333 2.636130e+05 2.252560e+05 4.477120e+03 2.437450e+03 8.510210e+03 3.855630e+03 9.594420e+02 2.387180e+03
|
||||
1332496920066667 2.553500e+05 2.262640e+05 4.283720e+03 3.923940e+03 7.912470e+03 5.466520e+03 1.284990e+03 2.093720e+03
|
||||
1332496920075000 2.527270e+05 2.246090e+05 5.851930e+03 2.491980e+03 8.540630e+03 5.623050e+03 2.339780e+03 3.007140e+03
|
||||
1332496920083333 2.584750e+05 2.235780e+05 5.924870e+03 1.394480e+03 8.779620e+03 4.544180e+03 2.132030e+03 3.849760e+03
|
||||
1332496920091667 2.615630e+05 2.246090e+05 4.336140e+03 2.455750e+03 8.055380e+03 3.469110e+03 6.278730e+02 3.664200e+03
|
||||
# interval-end 1332496920100000
|
@@ -1,11 +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
|
||||
1332497040000000 2.56439e+05 2.24775e+05 2.92897e+03 4.66646e+03 7.58491e+03 3.57351e+03 -4.34171e+02 2.98819e+03
|
||||
1332497040010000 2.51903e+05 2.23202e+05 4.23696e+03 3.49363e+03 8.53493e+03 4.29416e+03 8.49573e+02 2.38189e+03
|
||||
1332497040020000 2.57625e+05 2.20247e+05 5.47017e+03 1.35872e+03 9.18903e+03 4.56136e+03 2.65599e+03 2.60912e+03
|
||||
1332497040030000 2.63375e+05 2.20706e+05 4.51842e+03 1.80758e+03 8.17208e+03 4.17463e+03 2.57884e+03 3.32848e+03
|
||||
1332497040040000 2.59221e+05 2.22346e+05 2.98879e+03 3.66264e+03 6.87274e+03 3.94223e+03 1.25928e+03 3.51786e+03
|
||||
1332497040050000 2.51918e+05 2.22281e+05 4.22677e+03 2.84764e+03 7.78323e+03 3.81659e+03 8.04944e+02 3.46314e+03
|
||||
1332497040050000 2.54478e+05 2.21701e+05 5.61366e+03 1.02262e+03 9.26581e+03 3.50152e+03 1.29331e+03 3.07271e+03
|
||||
1332497040060000 2.59568e+05 2.22945e+05 4.97190e+03 1.28250e+03 8.62081e+03 4.06316e+03 1.85717e+03 2.61990e+03
|
||||
1332497040070000 2.57269e+05 2.23697e+05 3.60527e+03 3.05749e+03 7.22363e+03 4.90330e+03 1.93736e+03 2.35357e+03
|
||||
1332497040080000 2.52274e+05 2.21438e+05 5.01228e+03 2.86309e+03 7.87115e+03 4.80448e+03 2.18291e+03 2.93397e+03
|
||||
1332497040090000 2.56468e+05 2.19205e+05 6.29804e+03 8.09467e+02 9.12895e+03 3.52055e+03 2.16980e+03 3.88739e+03
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ class JimOrderPlugin(nose.plugins.Plugin):
|
||||
name, workingDir=loader.workingDir)
|
||||
try:
|
||||
order = os.path.join(addr.filename, "test.order")
|
||||
except:
|
||||
except Exception:
|
||||
order = None
|
||||
if order and os.path.exists(order):
|
||||
files = []
|
||||
|
@@ -4,16 +4,15 @@ test_lrucache.py
|
||||
test_mustclose.py
|
||||
|
||||
test_serializer.py
|
||||
test_iteratorizer.py
|
||||
|
||||
test_timestamper.py
|
||||
test_layout.py
|
||||
test_rbtree.py
|
||||
test_interval.py
|
||||
|
||||
test_bulkdata.py
|
||||
test_nilmdb.py
|
||||
test_client.py
|
||||
test_numpyclient.py
|
||||
test_cmdline.py
|
||||
|
||||
test_*.py
|
||||
|
@@ -30,6 +30,11 @@ class TestBulkData(object):
|
||||
else:
|
||||
data = BulkData(db, file_size = size, files_per_dir = files)
|
||||
|
||||
# Try opening it again (should result in locking error)
|
||||
with assert_raises(IOError) as e:
|
||||
data2 = BulkData(db)
|
||||
in_("already locked by another process", str(e.exception))
|
||||
|
||||
# create empty
|
||||
with assert_raises(ValueError):
|
||||
data.create("/foo", "uint16_8")
|
||||
@@ -49,31 +54,40 @@ class TestBulkData(object):
|
||||
nodes.append(data.getnode("/foo/baz/quux"))
|
||||
del nodes
|
||||
|
||||
def get_node_slice(key):
|
||||
if isinstance(key, slice):
|
||||
return [ node.get_data(x, x+1) for x in
|
||||
xrange(*key.indices(node.nrows)) ]
|
||||
return node.get_data(key, key+1)
|
||||
|
||||
# Test node
|
||||
node = data.getnode("/foo/bar")
|
||||
with assert_raises(IndexError):
|
||||
x = node[0]
|
||||
x = get_node_slice(0)
|
||||
with assert_raises(IndexError):
|
||||
x = node[0] # timestamp
|
||||
raw = []
|
||||
for i in range(1000):
|
||||
raw.append([10000+i, 1, 2, 3, 4, 5, 6, 7, 8 ])
|
||||
node.append(raw[0:1])
|
||||
node.append(raw[1:100])
|
||||
node.append(raw[100:])
|
||||
raw.append("%d 1 2 3 4 5 6 7 8\n" % (10000 + i))
|
||||
node.append_data("".join(raw[0:1]), 0, 50000)
|
||||
node.append_data("".join(raw[1:100]), 0, 50000)
|
||||
node.append_data("".join(raw[100:]), 0, 50000)
|
||||
|
||||
misc_slices = [ 0, 100, slice(None), slice(0), slice(10),
|
||||
slice(5,10), slice(3,None), slice(3,-3),
|
||||
slice(20,10), slice(200,100,-1), slice(None,0,-1),
|
||||
slice(100,500,5) ]
|
||||
|
||||
# Extract slices
|
||||
for s in misc_slices:
|
||||
eq_(node[s], raw[s])
|
||||
eq_(get_node_slice(s), raw[s])
|
||||
|
||||
# Extract misc slices while appending, to make sure the
|
||||
# data isn't being added in the middle of the file
|
||||
for s in [2, slice(1,5), 2, slice(1,5)]:
|
||||
node.append([[0,0,0,0,0,0,0,0,0]])
|
||||
raw.append([0,0,0,0,0,0,0,0,0])
|
||||
eq_(node[s], raw[s])
|
||||
node.append_data("0 0 0 0 0 0 0 0 0\n", 0, 50000)
|
||||
raw.append("0 0 0 0 0 0 0 0 0\n")
|
||||
eq_(get_node_slice(s), raw[s])
|
||||
|
||||
# Get some coverage of remove; remove is more fully tested
|
||||
# in cmdline
|
||||
@@ -91,7 +105,7 @@ class TestBulkData(object):
|
||||
|
||||
# Extract slices
|
||||
for s in misc_slices:
|
||||
eq_(node[s], raw[s])
|
||||
eq_(get_node_slice(s), raw[s])
|
||||
|
||||
# destroy
|
||||
with assert_raises(ValueError):
|
||||
|
@@ -23,6 +23,7 @@ import warnings
|
||||
import resource
|
||||
import time
|
||||
import re
|
||||
import struct
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
@@ -186,8 +187,7 @@ class TestClient(object):
|
||||
datetime_tz.localtz_set("America/New_York")
|
||||
|
||||
testfile = "tests/data/prep-20120323T1000"
|
||||
start = datetime_tz.datetime_tz.smartparse("20120323T1000")
|
||||
start = start.totimestamp()
|
||||
start = nilmdb.utils.time.parse_time("20120323T1000")
|
||||
rate = 120
|
||||
|
||||
# First try a nonexistent path
|
||||
@@ -239,36 +239,52 @@ class TestClient(object):
|
||||
in_("400 Bad Request", str(e.exception))
|
||||
in_("start must precede end", str(e.exception))
|
||||
|
||||
# Good content type
|
||||
with assert_raises(ClientError) as e:
|
||||
client.http.put("stream/insert", "",
|
||||
{ "path": "xxxx", "start": 0, "end": 1,
|
||||
"binary": 1 },
|
||||
binary = True)
|
||||
in_("No such stream", str(e.exception))
|
||||
|
||||
# Bad content type
|
||||
with assert_raises(ClientError) as e:
|
||||
client.http.put("stream/insert", "",
|
||||
{ "path": "xxxx", "start": 0, "end": 1,
|
||||
"binary": 1 },
|
||||
binary = False)
|
||||
in_("Content type must be application/octet-stream", str(e.exception))
|
||||
|
||||
# Specify start/end (starts too late)
|
||||
data = timestamper.TimestamperRate(testfile, start, 120)
|
||||
with assert_raises(ClientError) as e:
|
||||
result = client.stream_insert("/newton/prep", data,
|
||||
start + 5, start + 120)
|
||||
start + 5000000, start + 120000000)
|
||||
in_("400 Bad Request", str(e.exception))
|
||||
in_("Data timestamp 1332511200.000000 < start time 1332511205.000000",
|
||||
in_("Data timestamp 1332511200000000 < start time 1332511205000000",
|
||||
str(e.exception))
|
||||
|
||||
# Specify start/end (ends too early)
|
||||
data = timestamper.TimestamperRate(testfile, start, 120)
|
||||
with assert_raises(ClientError) as e:
|
||||
result = client.stream_insert("/newton/prep", data,
|
||||
start, start + 1)
|
||||
start, start + 1000000)
|
||||
in_("400 Bad Request", str(e.exception))
|
||||
# Client chunks the input, so the exact timestamp here might change
|
||||
# if the chunk positions change.
|
||||
assert(re.search("Data timestamp 13325[0-9]+\.[0-9]+ "
|
||||
">= end time 1332511201.000000", str(e.exception))
|
||||
assert(re.search("Data timestamp 13325[0-9]+ "
|
||||
">= end time 1332511201000000", str(e.exception))
|
||||
is not None)
|
||||
|
||||
# Now do the real load
|
||||
data = timestamper.TimestamperRate(testfile, start, 120)
|
||||
result = client.stream_insert("/newton/prep", data,
|
||||
start, start + 119.999777)
|
||||
start, start + 119999777)
|
||||
|
||||
# Verify the intervals. Should be just one, even if the data
|
||||
# was inserted in chunks, due to nilmdb interval concatenation.
|
||||
intervals = list(client.stream_intervals("/newton/prep"))
|
||||
eq_(intervals, [[start, start + 119.999777]])
|
||||
eq_(intervals, [[start, start + 119999777]])
|
||||
|
||||
# Try some overlapping data -- just insert it again
|
||||
data = timestamper.TimestamperRate(testfile, start, 120)
|
||||
@@ -284,15 +300,33 @@ class TestClient(object):
|
||||
# Misc tests for extract and remove. Most of them are in test_cmdline.
|
||||
client = nilmdb.client.Client(url = testurl)
|
||||
|
||||
for x in client.stream_extract("/newton/prep", 999123, 999124):
|
||||
for x in client.stream_extract("/newton/prep",
|
||||
999123000000, 999124000000):
|
||||
raise AssertionError("shouldn't be any data for this request")
|
||||
|
||||
with assert_raises(ClientError) as e:
|
||||
client.stream_remove("/newton/prep", 123, 120)
|
||||
client.stream_remove("/newton/prep", 123000000, 120000000)
|
||||
|
||||
# Test count
|
||||
eq_(client.stream_count("/newton/prep"), 14400)
|
||||
|
||||
# Test binary output
|
||||
with assert_raises(ClientError) as e:
|
||||
list(client.stream_extract("/newton/prep",
|
||||
markup = True, binary = True))
|
||||
with assert_raises(ClientError) as e:
|
||||
list(client.stream_extract("/newton/prep",
|
||||
count = True, binary = True))
|
||||
data = "".join(client.stream_extract("/newton/prep", binary = True))
|
||||
# Quick check using struct
|
||||
unpacker = struct.Struct("<qffffffff")
|
||||
out = []
|
||||
for i in range(14400):
|
||||
out.append(unpacker.unpack_from(data, i * unpacker.size))
|
||||
eq_(out[0], (1332511200000000, 266568.0, 224029.0, 5161.39990234375,
|
||||
2525.169921875, 8350.83984375, 3724.699951171875,
|
||||
1355.3399658203125, 2039.0))
|
||||
|
||||
client.close()
|
||||
|
||||
def test_client_06_generators(self):
|
||||
@@ -301,23 +335,21 @@ class TestClient(object):
|
||||
client = nilmdb.client.Client(url = testurl)
|
||||
|
||||
# Trigger a client error in generator
|
||||
start = datetime_tz.datetime_tz.smartparse("20120323T2000")
|
||||
end = datetime_tz.datetime_tz.smartparse("20120323T1000")
|
||||
start = nilmdb.utils.time.parse_time("20120323T2000")
|
||||
end = nilmdb.utils.time.parse_time("20120323T1000")
|
||||
for function in [ client.stream_intervals, client.stream_extract ]:
|
||||
with assert_raises(ClientError) as e:
|
||||
function("/newton/prep",
|
||||
start.totimestamp(),
|
||||
end.totimestamp()).next()
|
||||
function("/newton/prep", start, end).next()
|
||||
in_("400 Bad Request", str(e.exception))
|
||||
in_("start must precede end", str(e.exception))
|
||||
|
||||
# Trigger a curl error in generator
|
||||
with assert_raises(ServerError) as e:
|
||||
client.http.get_gen("http://nosuchurl/").next()
|
||||
client.http.get_gen("http://nosuchurl.example.com./").next()
|
||||
|
||||
# Trigger a curl error in generator
|
||||
with assert_raises(ServerError) as e:
|
||||
client.http.get_gen("http://nosuchurl/").next()
|
||||
client.http.get_gen("http://nosuchurl.example.com./").next()
|
||||
|
||||
# Check 404 for missing streams
|
||||
for function in [ client.stream_intervals, client.stream_extract ]:
|
||||
@@ -367,6 +399,17 @@ class TestClient(object):
|
||||
raise AssertionError("/stream/extract is not text/plain:\n" +
|
||||
headers())
|
||||
|
||||
x = http.get("stream/extract",
|
||||
{ "path": "/newton/prep",
|
||||
"start": "123",
|
||||
"end": "124",
|
||||
"binary": "1" })
|
||||
if "transfer-encoding: chunked" not in headers():
|
||||
warnings.warn("Non-chunked HTTP response for /stream/extract")
|
||||
if "content-type: application/octet-stream" not in headers():
|
||||
raise AssertionError("/stream/extract is not binary:\n" +
|
||||
headers())
|
||||
|
||||
client.close()
|
||||
|
||||
def test_client_08_unicode(self):
|
||||
@@ -377,6 +420,7 @@ class TestClient(object):
|
||||
|
||||
# Delete streams that exist
|
||||
for stream in client.stream_list():
|
||||
client.stream_remove(stream[0])
|
||||
client.stream_destroy(stream[0])
|
||||
|
||||
# Database is empty
|
||||
@@ -442,72 +486,80 @@ class TestClient(object):
|
||||
# override _max_data to trigger frequent server updates
|
||||
ctx._max_data = 15
|
||||
|
||||
ctx.insert("100 1\n")
|
||||
ctx.insert("1000 1\n")
|
||||
|
||||
ctx.insert("101 ")
|
||||
ctx.insert("1\n102 1")
|
||||
ctx.insert("1010 ")
|
||||
ctx.insert("1\n1020 1")
|
||||
ctx.insert("")
|
||||
ctx.insert("\n103 1\n")
|
||||
ctx.insert("\n1030 1\n")
|
||||
|
||||
ctx.insert("104 1\n")
|
||||
ctx.insert("1040 1\n")
|
||||
ctx.insert("# hello\n")
|
||||
ctx.insert(" # hello\n")
|
||||
ctx.insert(" 105 1\n")
|
||||
ctx.insert(" 1050 1\n")
|
||||
ctx.finalize()
|
||||
|
||||
ctx.insert("106 1\n")
|
||||
ctx.update_end(106.5)
|
||||
ctx.insert("1070 1\n")
|
||||
ctx.update_end(1080)
|
||||
ctx.finalize()
|
||||
ctx.update_start(106.8)
|
||||
ctx.insert("107 1\n")
|
||||
ctx.insert("108 1\n")
|
||||
ctx.insert("109 1\n")
|
||||
ctx.insert("110 1\n")
|
||||
ctx.insert("111 1\n")
|
||||
ctx.update_end(113)
|
||||
ctx.insert("112 1\n")
|
||||
ctx.update_end(114)
|
||||
ctx.insert("113 1\n")
|
||||
ctx.update_end(115)
|
||||
ctx.insert("114 1" +
|
||||
ctx.update_start(1090)
|
||||
ctx.insert("1100 1\n")
|
||||
ctx.insert("1110 1\n")
|
||||
ctx.send()
|
||||
ctx.insert("1120 1\n")
|
||||
ctx.insert("1130 1\n")
|
||||
ctx.insert("1140 1\n")
|
||||
ctx.update_end(1160)
|
||||
ctx.insert("1150 1\n")
|
||||
ctx.update_end(1170)
|
||||
ctx.insert("1160 1\n")
|
||||
ctx.update_end(1180)
|
||||
ctx.insert("1170 1" +
|
||||
" # this is super long" * 100 +
|
||||
"\n")
|
||||
ctx.finalize()
|
||||
ctx.insert("# this is super long" * 100)
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_context("/context/test", 100, 200) as ctx:
|
||||
ctx.insert("115 1\n")
|
||||
with client.stream_insert_context("/context/test",
|
||||
1000, 2000) as ctx:
|
||||
ctx.insert("1180 1\n")
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_context("/context/test", 200, 300) as ctx:
|
||||
ctx.insert("115 1\n")
|
||||
with client.stream_insert_context("/context/test",
|
||||
2000, 3000) as ctx:
|
||||
ctx.insert("1180 1\n")
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_context("/context/test") as ctx:
|
||||
ctx.insert("bogus data\n")
|
||||
|
||||
with client.stream_insert_context("/context/test", 200, 300) as ctx:
|
||||
with client.stream_insert_context("/context/test", 2000, 3000) as ctx:
|
||||
# make sure our override wasn't permanent
|
||||
ne_(ctx._max_data, 15)
|
||||
ctx.insert("225 1\n")
|
||||
ctx.insert("2250 1\n")
|
||||
ctx.finalize()
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_context("/context/test", 300, 400) as ctx:
|
||||
ctx.insert("301 1\n")
|
||||
ctx.insert("302 2\n")
|
||||
ctx.insert("303 3\n")
|
||||
ctx.insert("304 4\n")
|
||||
ctx.insert("304 4\n") # non-monotonic after a few lines
|
||||
with client.stream_insert_context("/context/test",
|
||||
3000, 4000) as ctx:
|
||||
ctx.insert("3010 1\n")
|
||||
ctx.insert("3020 2\n")
|
||||
ctx.insert("3030 3\n")
|
||||
ctx.insert("3040 4\n")
|
||||
ctx.insert("3040 4\n") # non-monotonic after a few lines
|
||||
ctx.finalize()
|
||||
|
||||
eq_(list(client.stream_intervals("/context/test")),
|
||||
[ [ 100, 105.000001 ],
|
||||
[ 106, 106.5 ],
|
||||
[ 106.8, 115 ],
|
||||
[ 200, 300 ] ])
|
||||
[ [ 1000, 1051 ],
|
||||
[ 1070, 1080 ],
|
||||
[ 1090, 1180 ],
|
||||
[ 2000, 3000 ] ])
|
||||
|
||||
# destroy stream (try without removing data first)
|
||||
with assert_raises(ClientError):
|
||||
client.stream_destroy("/context/test")
|
||||
client.stream_remove("/context/test")
|
||||
client.stream_destroy("/context/test")
|
||||
client.close()
|
||||
|
||||
@@ -567,7 +619,7 @@ class TestClient(object):
|
||||
ctx.finalize() # nothing
|
||||
ctx.finalize() # nothing
|
||||
ctx.insert("1100 1\n")
|
||||
ctx.finalize() # inserts [1100, 1100.000001]
|
||||
ctx.finalize() # inserts [1100, 1101]
|
||||
ctx.update_start(1199)
|
||||
ctx.insert("1200 1\n")
|
||||
ctx.update_end(1250)
|
||||
@@ -595,13 +647,14 @@ class TestClient(object):
|
||||
(0, [400, 450]),
|
||||
(0, [500, 550]),
|
||||
(0, [1000, 1050]),
|
||||
(1, [1100, 1100.000001]),
|
||||
(1, [1100, 1101]),
|
||||
(1, [1199, 1250]),
|
||||
(0, [1400, 1450]),
|
||||
(0, [1500, 1550]),
|
||||
])
|
||||
|
||||
# Clean up
|
||||
client.stream_remove("/empty/test")
|
||||
client.stream_destroy("/empty/test")
|
||||
client.close()
|
||||
|
||||
@@ -615,7 +668,7 @@ class TestClient(object):
|
||||
poolmanager = c.http._last_response.connection.poolmanager
|
||||
pool = poolmanager.pools[('http','localhost',32180)]
|
||||
return (pool.num_connections, pool.num_requests)
|
||||
except:
|
||||
except Exception:
|
||||
raise SkipTest("can't get connection info")
|
||||
|
||||
# First request makes a connection
|
||||
@@ -637,5 +690,33 @@ class TestClient(object):
|
||||
eq_(connections(), (1, 5))
|
||||
|
||||
# Clean up
|
||||
c.stream_remove("/persist/test")
|
||||
c.stream_destroy("/persist/test")
|
||||
eq_(connections(), (1, 6))
|
||||
eq_(connections(), (1, 7))
|
||||
|
||||
def test_client_13_timestamp_rounding(self):
|
||||
# Test potentially bad timestamps (due to floating point
|
||||
# roundoff etc). The server will round floating point values
|
||||
# to the nearest int.
|
||||
client = nilmdb.client.Client(testurl)
|
||||
|
||||
client.stream_create("/rounding/test", "uint16_1")
|
||||
with client.stream_insert_context("/rounding/test",
|
||||
100000000, 200000000.1) as ctx:
|
||||
ctx.insert("100000000.1 1\n")
|
||||
ctx.insert("150000000.00003 1\n")
|
||||
ctx.insert("199999999.4 1\n")
|
||||
eq_(list(client.stream_intervals("/rounding/test")),
|
||||
[ [ 100000000, 200000000 ] ])
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_context("/rounding/test",
|
||||
200000000, 300000000) as ctx:
|
||||
ctx.insert("200000000 1\n")
|
||||
ctx.insert("250000000 1\n")
|
||||
# Server will round this and give an error on finalize()
|
||||
ctx.insert("299999999.99 1\n")
|
||||
|
||||
client.stream_remove("/rounding/test")
|
||||
client.stream_destroy("/rounding/test")
|
||||
client.close()
|
||||
|
@@ -15,17 +15,19 @@ import re
|
||||
import sys
|
||||
import StringIO
|
||||
import shlex
|
||||
import warnings
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
testdb = "tests/cmdline-testdb"
|
||||
|
||||
def server_start(max_results = None, bulkdata_args = {}):
|
||||
def server_start(max_results = None, max_removals = None, bulkdata_args = {}):
|
||||
global test_server, test_db
|
||||
# Start web app on a custom port
|
||||
test_db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(
|
||||
testdb,
|
||||
max_results = max_results,
|
||||
max_removals = max_removals,
|
||||
bulkdata_args = bulkdata_args)
|
||||
test_server = nilmdb.server.Server(test_db, host = "127.0.0.1",
|
||||
port = 32180, stoppable = False,
|
||||
@@ -215,9 +217,11 @@ class TestCmdline(object):
|
||||
def test_02_parsetime(self):
|
||||
os.environ['TZ'] = "America/New_York"
|
||||
test = datetime_tz.datetime_tz.now()
|
||||
u2ts = nilmdb.utils.time.unix_to_timestamp
|
||||
parse_time = nilmdb.utils.time.parse_time
|
||||
eq_(parse_time(str(test)), test)
|
||||
test = datetime_tz.datetime_tz.smartparse("20120405 1400-0400")
|
||||
eq_(parse_time(str(test)), u2ts(test.totimestamp()))
|
||||
test = u2ts(datetime_tz.datetime_tz.smartparse("20120405 1400-0400").
|
||||
totimestamp())
|
||||
eq_(parse_time("hi there 20120405 1400-0400 testing! 123"), test)
|
||||
eq_(parse_time("20120405 1800 UTC"), test)
|
||||
eq_(parse_time("20120405 1400-0400 UTC"), test)
|
||||
@@ -227,6 +231,13 @@ class TestCmdline(object):
|
||||
x = parse_time("now")
|
||||
eq_(parse_time("snapshot-20120405-140000.raw.gz"), test)
|
||||
eq_(parse_time("prep-20120405T1400"), test)
|
||||
eq_(parse_time("1333648800.0"), test)
|
||||
eq_(parse_time("1333648800000000"), test)
|
||||
eq_(parse_time("@1333648800000000"), test)
|
||||
eq_(parse_time("min"), nilmdb.utils.time.min_timestamp)
|
||||
eq_(parse_time("max"), nilmdb.utils.time.max_timestamp)
|
||||
with assert_raises(ValueError):
|
||||
parse_time("@hashtag12345")
|
||||
|
||||
def test_03_info(self):
|
||||
self.ok("info")
|
||||
@@ -250,6 +261,15 @@ class TestCmdline(object):
|
||||
|
||||
self.fail("create /foo float32_8")
|
||||
self.contain("invalid path")
|
||||
self.fail("create /newton/prep/ float32_8")
|
||||
self.contain("invalid path")
|
||||
|
||||
self.fail("create /newton/_format/prep float32_8")
|
||||
self.contain("path name is invalid")
|
||||
self.fail("create /_format/newton/prep float32_8")
|
||||
self.contain("path name is invalid")
|
||||
self.fail("create /newton/prep/_format float32_8")
|
||||
self.contain("path name is invalid")
|
||||
|
||||
# Bad layout type
|
||||
self.fail("create /newton/prep NoSuchLayout")
|
||||
@@ -264,6 +284,10 @@ class TestCmdline(object):
|
||||
self.ok("create /newton/prep float32_8")
|
||||
self.ok("create /newton/raw uint16_6")
|
||||
|
||||
# Create a stream that already exists
|
||||
self.fail("create /newton/raw uint16_6")
|
||||
self.contain("stream already exists at this path")
|
||||
|
||||
# Should not be able to create a stream with another stream as
|
||||
# its parent
|
||||
self.fail("create /newton/prep/blah float32_8")
|
||||
@@ -276,38 +300,19 @@ class TestCmdline(object):
|
||||
|
||||
# Verify we got those 3 streams and they're returned in
|
||||
# alphabetical order.
|
||||
self.ok("list")
|
||||
self.ok("list -l")
|
||||
self.match("/newton/prep float32_8\n"
|
||||
"/newton/raw uint16_6\n"
|
||||
"/newton/zzz/rawnotch uint16_9\n")
|
||||
|
||||
# Match just one type or one path. Also check
|
||||
# that --path is optional
|
||||
self.ok("list --path /newton/raw")
|
||||
self.match("/newton/raw uint16_6\n")
|
||||
|
||||
self.ok("list /newton/raw")
|
||||
self.match("/newton/raw uint16_6\n")
|
||||
|
||||
self.fail("list -p /newton/raw /newton/raw")
|
||||
self.contain("too many paths")
|
||||
|
||||
self.ok("list --layout uint16_6")
|
||||
self.ok("list --layout /newton/raw")
|
||||
self.match("/newton/raw uint16_6\n")
|
||||
|
||||
# Wildcard matches
|
||||
self.ok("list --layout uint16*")
|
||||
self.match("/newton/raw uint16_6\n"
|
||||
"/newton/zzz/rawnotch uint16_9\n")
|
||||
|
||||
self.ok("list --path *zzz* --layout uint16*")
|
||||
self.match("/newton/zzz/rawnotch uint16_9\n")
|
||||
|
||||
self.ok("list *zzz* --layout uint16*")
|
||||
self.match("/newton/zzz/rawnotch uint16_9\n")
|
||||
|
||||
self.ok("list --path *zzz* --layout float32*")
|
||||
self.match("")
|
||||
self.ok("list *zzz*")
|
||||
self.match("/newton/zzz/rawnotch\n")
|
||||
|
||||
# reversed range
|
||||
self.fail("list /newton/prep --start 2020-01-01 --end 2000-01-01")
|
||||
@@ -345,6 +350,8 @@ class TestCmdline(object):
|
||||
self.contain("No stream at path")
|
||||
self.fail("metadata /newton/nosuchstream --set foo=bar")
|
||||
self.contain("No stream at path")
|
||||
self.fail("metadata /newton/nosuchstream --delete")
|
||||
self.contain("No stream at path")
|
||||
|
||||
self.ok("metadata /newton/prep")
|
||||
self.match("description=The Data\nv_scale=1.234\n")
|
||||
@@ -370,6 +377,19 @@ class TestCmdline(object):
|
||||
self.fail("metadata /newton/nosuchpath")
|
||||
self.contain("No stream at path /newton/nosuchpath")
|
||||
|
||||
self.ok("metadata /newton/prep --delete")
|
||||
self.ok("metadata /newton/prep --get")
|
||||
self.match("")
|
||||
self.ok("metadata /newton/prep --set "
|
||||
"'description=The Data' "
|
||||
"v_scale=1.234")
|
||||
self.ok("metadata /newton/prep --delete v_scale")
|
||||
self.ok("metadata /newton/prep --get")
|
||||
self.match("description=The Data\n")
|
||||
self.ok("metadata /newton/prep --set description=")
|
||||
self.ok("metadata /newton/prep --get")
|
||||
self.match("")
|
||||
|
||||
def test_06_insert(self):
|
||||
self.ok("insert --help")
|
||||
|
||||
@@ -394,7 +414,7 @@ class TestCmdline(object):
|
||||
self.fail("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
|
||||
input)
|
||||
self.contain("error parsing input data")
|
||||
self.contain("line 7:")
|
||||
self.contain("line 7")
|
||||
self.contain("timestamp is not monotonically increasing")
|
||||
|
||||
# insert pre-timestamped data, from stdin
|
||||
@@ -436,6 +456,15 @@ class TestCmdline(object):
|
||||
self.fail("insert -t -r 120 -f /newton/raw "
|
||||
"tests/data/prep-20120323T1004")
|
||||
self.contain("error parsing input data")
|
||||
self.contain("can't parse value")
|
||||
|
||||
# too few rows per line
|
||||
self.ok("create /insert/test float32_20")
|
||||
self.fail("insert -t -r 120 -f /insert/test "
|
||||
"tests/data/prep-20120323T1004")
|
||||
self.contain("error parsing input data")
|
||||
self.contain("wrong number of values")
|
||||
self.ok("destroy /insert/test")
|
||||
|
||||
# empty data does nothing
|
||||
self.ok("insert -t -r 120 --start '03/23/2012 06:05:00' /newton/prep "
|
||||
@@ -449,28 +478,28 @@ class TestCmdline(object):
|
||||
self.ok("list --detail")
|
||||
lines_(self.captured, 8)
|
||||
|
||||
self.ok("list --detail --path *prep")
|
||||
self.ok("list --detail *prep")
|
||||
lines_(self.captured, 4)
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 10:02'")
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 10:02'")
|
||||
lines_(self.captured, 3)
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05'")
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 10:05'")
|
||||
lines_(self.captured, 2)
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15'")
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 10:05:15'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("10:05:15.000")
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'")
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 10:05:15.50'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("10:05:15.500")
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 19:05:15.50'")
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 19:05:15.50'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("no intervals")
|
||||
|
||||
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'"
|
||||
self.ok("list --detail *prep --start='23 Mar 2012 10:05:15.50'"
|
||||
+ " --end='23 Mar 2012 10:05:15.51'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("10:05:15.500")
|
||||
@@ -479,17 +508,17 @@ class TestCmdline(object):
|
||||
lines_(self.captured, 8)
|
||||
|
||||
# Verify the "raw timestamp" output
|
||||
self.ok("list --detail --path *prep --timestamp-raw "
|
||||
self.ok("list --detail *prep --timestamp-raw "
|
||||
"--start='23 Mar 2012 10:05:15.50'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("[ 1332497115.500000 -> 1332497160.000000 ]")
|
||||
self.contain("[ 1332497115500000 -> 1332497160000000 ]")
|
||||
|
||||
# bad time
|
||||
self.fail("list --detail --path *prep -T --start='9332497115.612'")
|
||||
self.fail("list --detail *prep -T --start='9332497115.612'")
|
||||
# good time
|
||||
self.ok("list --detail --path *prep -T --start='1332497115.612'")
|
||||
self.ok("list --detail *prep -T --start='1332497115.612'")
|
||||
lines_(self.captured, 2)
|
||||
self.contain("[ 1332497115.612000 -> 1332497160.000000 ]")
|
||||
self.contain("[ 1332497115612000 -> 1332497160000000 ]")
|
||||
|
||||
# Check --ext output
|
||||
self.ok("list --ext")
|
||||
@@ -497,7 +526,7 @@ class TestCmdline(object):
|
||||
|
||||
self.ok("list -E -T")
|
||||
c = self.contain
|
||||
c("\n interval extents: 1332496800.000000 -> 1332497160.000000\n")
|
||||
c("\n interval extents: 1332496800000000 -> 1332497160000000\n")
|
||||
c("\n total data: 43200 rows, 359.983336 seconds\n")
|
||||
c("\n interval extents: (no data)\n")
|
||||
c("\n total data: 0 rows, 0.000000 seconds\n")
|
||||
@@ -563,13 +592,20 @@ class TestCmdline(object):
|
||||
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")
|
||||
test(8, "10:01:59.9", "10:02:00.1", extra="--markup")
|
||||
test(8, "10:01:59.9", "10:02:00.1", extra="-m")
|
||||
|
||||
# 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 min --end max")
|
||||
lines_(self.captured, 43204)
|
||||
self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
|
||||
self.match("43200\n")
|
||||
|
||||
# markup for 3 intervals, plus extra markup lines whenever we had
|
||||
# a "restart" from the nilmdb.stream_extract function
|
||||
self.ok("extract -m /newton/prep --start 2000-01-01 --end 2020-01-01")
|
||||
lines_(self.captured, 43210)
|
||||
|
||||
def test_09_truncated(self):
|
||||
# Test truncated responses by overriding the nilmdb max_results
|
||||
server_stop()
|
||||
@@ -584,7 +620,7 @@ class TestCmdline(object):
|
||||
|
||||
# Try nonexistent stream
|
||||
self.fail("remove /no/such/foo --start 2000-01-01 --end 2020-01-01")
|
||||
self.contain("No stream at path")
|
||||
self.contain("no stream matched path")
|
||||
|
||||
# empty or backward ranges return errors
|
||||
self.fail("remove /newton/prep --start 2020-01-01 --end 2000-01-01")
|
||||
@@ -612,9 +648,14 @@ class TestCmdline(object):
|
||||
"--start '23 Mar 2022 20:00:30' " +
|
||||
"--end '23 Mar 2022 20:00:31'")
|
||||
self.match("0\n")
|
||||
self.ok("remove -c /newton/prep /newton/pre* " +
|
||||
"--start '23 Mar 2022 20:00:30' " +
|
||||
"--end '23 Mar 2022 20:00:31'")
|
||||
self.match("Removing from /newton/prep\n0\n" +
|
||||
"Removing from /newton/prep\n0\n")
|
||||
|
||||
# Make sure we have the data we expect
|
||||
self.ok("list --detail /newton/prep")
|
||||
self.ok("list -l --detail /newton/prep")
|
||||
self.match("/newton/prep float32_8\n" +
|
||||
" [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
|
||||
" -> Fri, 23 Mar 2012 10:01:59.991668 +0000 ]\n"
|
||||
@@ -649,7 +690,7 @@ class TestCmdline(object):
|
||||
self.match("24000\n")
|
||||
|
||||
# See the missing chunks in list output
|
||||
self.ok("list --detail /newton/prep")
|
||||
self.ok("list --layout --detail /newton/prep")
|
||||
self.match("/newton/prep float32_8\n" +
|
||||
" [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
|
||||
" -> Fri, 23 Mar 2012 10:00:05.000000 +0000 ]\n"
|
||||
@@ -663,17 +704,15 @@ class TestCmdline(object):
|
||||
# Remove all data, verify it's missing
|
||||
self.ok("remove /newton/prep --start 2000-01-01 --end 2020-01-01")
|
||||
self.match("") # no count requested this time
|
||||
self.ok("list --detail /newton/prep")
|
||||
self.ok("list -l --detail /newton/prep")
|
||||
self.match("/newton/prep float32_8\n" +
|
||||
" (no intervals)\n")
|
||||
|
||||
# Reinsert some data, to verify that no overlaps with deleted
|
||||
# data are reported
|
||||
os.environ['TZ'] = "UTC"
|
||||
self.ok("insert --timestamp -f --rate 120 /newton/prep "
|
||||
"tests/data/prep-20120323T1000")
|
||||
self.ok("insert -t --filename --rate 120 /newton/prep "
|
||||
"tests/data/prep-20120323T1002")
|
||||
for minute in ["0", "2"]:
|
||||
self.ok("insert --timestamp -f --rate 120 /newton/prep"
|
||||
" tests/data/prep-20120323T100" + minute)
|
||||
|
||||
def test_11_destroy(self):
|
||||
# Delete records
|
||||
@@ -683,13 +722,16 @@ class TestCmdline(object):
|
||||
self.contain("too few arguments")
|
||||
|
||||
self.fail("destroy /no/such/stream")
|
||||
self.contain("No stream at path")
|
||||
self.contain("no stream matched path")
|
||||
|
||||
self.fail("destroy -R /no/such/stream")
|
||||
self.contain("no stream matched path")
|
||||
|
||||
self.fail("destroy asdfasdf")
|
||||
self.contain("No stream at path")
|
||||
self.contain("no stream matched path")
|
||||
|
||||
# From previous tests, we have:
|
||||
self.ok("list")
|
||||
self.ok("list -l")
|
||||
self.match("/newton/prep float32_8\n"
|
||||
"/newton/raw uint16_6\n"
|
||||
"/newton/zzz/rawnotch uint16_9\n")
|
||||
@@ -698,19 +740,26 @@ class TestCmdline(object):
|
||||
self.ok("list --detail")
|
||||
lines_(self.captured, 7)
|
||||
|
||||
# Delete some
|
||||
self.ok("destroy /newton/prep")
|
||||
self.ok("list")
|
||||
# Fail to destroy because intervals still present
|
||||
self.fail("destroy /newton/prep")
|
||||
self.contain("all intervals must be removed")
|
||||
self.ok("list --detail")
|
||||
lines_(self.captured, 7)
|
||||
|
||||
# Destroy for real
|
||||
self.ok("destroy -R /n*/prep")
|
||||
self.ok("list -l")
|
||||
self.match("/newton/raw uint16_6\n"
|
||||
"/newton/zzz/rawnotch uint16_9\n")
|
||||
|
||||
self.ok("destroy /newton/zzz/rawnotch")
|
||||
self.ok("list")
|
||||
self.ok("list -l")
|
||||
self.match("/newton/raw uint16_6\n")
|
||||
|
||||
self.ok("destroy /newton/raw")
|
||||
self.ok("create /newton/raw uint16_6")
|
||||
self.ok("destroy /newton/raw")
|
||||
# Specify --remove with no data
|
||||
self.ok("destroy --remove /newton/raw")
|
||||
self.ok("list")
|
||||
self.match("")
|
||||
|
||||
@@ -723,18 +772,17 @@ class TestCmdline(object):
|
||||
self.ok("list")
|
||||
self.contain(path)
|
||||
# Make sure it was created empty
|
||||
self.ok("list --detail --path " + path)
|
||||
self.ok("list --detail " + path)
|
||||
self.contain("(no intervals)")
|
||||
|
||||
def test_12_unicode(self):
|
||||
# Unicode paths.
|
||||
self.ok("destroy /newton/asdf/qwer")
|
||||
self.ok("destroy /newton/prep")
|
||||
self.ok("destroy /newton/raw")
|
||||
self.ok("destroy /newton/prep /newton/raw")
|
||||
self.ok("destroy /newton/zzz")
|
||||
|
||||
self.ok(u"create /düsseldorf/raw uint16_6")
|
||||
self.ok("list --detail")
|
||||
self.ok("list -l --detail")
|
||||
self.contain(u"/düsseldorf/raw uint16_6")
|
||||
self.contain("(no intervals)")
|
||||
|
||||
@@ -785,7 +833,7 @@ class TestCmdline(object):
|
||||
|
||||
# Now recreate the data one more time and make sure there are
|
||||
# fewer files.
|
||||
self.ok("destroy /newton/prep")
|
||||
self.ok("destroy --remove /newton/prep")
|
||||
self.fail("destroy /newton/prep") # already destroyed
|
||||
self.ok("create /newton/prep float32_8")
|
||||
os.environ['TZ'] = "UTC"
|
||||
@@ -796,14 +844,16 @@ class TestCmdline(object):
|
||||
for (dirpath, dirnames, filenames) in os.walk(testdb):
|
||||
nfiles += len(filenames)
|
||||
lt_(nfiles, 50)
|
||||
self.ok("destroy /newton/prep") # destroy again
|
||||
self.ok("destroy -R /newton/prep") # destroy again
|
||||
|
||||
def test_14_remove_files(self):
|
||||
# Test BulkData's ability to remove when data is split into
|
||||
# multiple files. Should be a fairly comprehensive test of
|
||||
# remove functionality.
|
||||
# Also limit max_removals, to cover more functionality.
|
||||
server_stop()
|
||||
server_start(bulkdata_args = { "file_size" : 920, # 23 rows per file
|
||||
server_start(max_removals = 4321,
|
||||
bulkdata_args = { "file_size" : 920, # 23 rows per file
|
||||
"files_per_dir" : 3 })
|
||||
|
||||
# Insert data. Just for fun, insert out of order
|
||||
@@ -818,7 +868,7 @@ class TestCmdline(object):
|
||||
du_before = nilmdb.utils.diskusage.du(testdb)
|
||||
|
||||
# Make sure we have the data we expect
|
||||
self.ok("list --detail")
|
||||
self.ok("list -l --detail")
|
||||
self.match("/newton/prep float32_8\n" +
|
||||
" [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
|
||||
" -> Fri, 23 Mar 2012 10:01:59.991668 +0000 ]\n"
|
||||
@@ -854,7 +904,7 @@ class TestCmdline(object):
|
||||
self.match("3600\n")
|
||||
|
||||
# See the missing chunks in list output
|
||||
self.ok("list --detail")
|
||||
self.ok("list -l --detail")
|
||||
self.match("/newton/prep float32_8\n" +
|
||||
" [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
|
||||
" -> Fri, 23 Mar 2012 10:00:05.000000 +0000 ]\n"
|
||||
@@ -893,3 +943,140 @@ class TestCmdline(object):
|
||||
# See if we can extract it all
|
||||
self.ok("extract /newton/prep --start 2000-01-01 --end 2020-01-01")
|
||||
lines_(self.captured, 15600)
|
||||
|
||||
def test_15_intervals_diff(self):
|
||||
# Test "intervals" and "intervals --diff" command.
|
||||
os.environ['TZ'] = "UTC"
|
||||
|
||||
self.ok("create /diff/1 uint8_1")
|
||||
self.match("")
|
||||
self.ok("intervals /diff/1")
|
||||
self.match("")
|
||||
self.ok("intervals /diff/1 --diff /diff/1")
|
||||
self.match("")
|
||||
self.ok("intervals --diff /diff/1 /diff/1")
|
||||
self.match("")
|
||||
self.fail("intervals /diff/2")
|
||||
self.fail("intervals /diff/1 -d /diff/2")
|
||||
|
||||
self.ok("create /diff/2 uint8_1")
|
||||
self.ok("intervals -T /diff/1 -d /diff/2")
|
||||
self.match("")
|
||||
self.ok("insert -s 01-01-2000 -e 01-01-2001 /diff/1 /dev/null")
|
||||
|
||||
self.ok("intervals /diff/1")
|
||||
self.match("[ Sat, 01 Jan 2000 00:00:00.000000 +0000 -"
|
||||
"> Mon, 01 Jan 2001 00:00:00.000000 +0000 ]\n")
|
||||
|
||||
self.ok("intervals /diff/1 -d /diff/2")
|
||||
self.match("[ Sat, 01 Jan 2000 00:00:00.000000 +0000 -"
|
||||
"> Mon, 01 Jan 2001 00:00:00.000000 +0000 ]\n")
|
||||
|
||||
self.ok("insert -s 01-01-2000 -e 01-01-2001 /diff/2 /dev/null")
|
||||
self.ok("intervals /diff/1 -d /diff/2")
|
||||
self.match("")
|
||||
|
||||
self.ok("insert -s 01-01-2001 -e 01-01-2002 /diff/1 /dev/null")
|
||||
self.ok("insert -s 01-01-2002 -e 01-01-2003 /diff/2 /dev/null")
|
||||
self.ok("intervals /diff/1 -d /diff/2")
|
||||
self.match("[ Mon, 01 Jan 2001 00:00:00.000000 +0000 -"
|
||||
"> Tue, 01 Jan 2002 00:00:00.000000 +0000 ]\n")
|
||||
|
||||
self.ok("insert -s 01-01-2004 -e 01-01-2005 /diff/1 /dev/null")
|
||||
self.ok("intervals /diff/1 -d /diff/2")
|
||||
self.match("[ Mon, 01 Jan 2001 00:00:00.000000 +0000 -"
|
||||
"> Tue, 01 Jan 2002 00:00:00.000000 +0000 ]\n"
|
||||
"[ Thu, 01 Jan 2004 00:00:00.000000 +0000 -"
|
||||
"> Sat, 01 Jan 2005 00:00:00.000000 +0000 ]\n")
|
||||
|
||||
self.fail("intervals -s 01-01-2003 -e 01-01-2000 /diff/1 -d /diff/2")
|
||||
self.ok("intervals -s 01-01-2003 -e 01-01-2008 /diff/1 -d /diff/2")
|
||||
self.match("[ Thu, 01 Jan 2004 00:00:00.000000 +0000 -"
|
||||
"> Sat, 01 Jan 2005 00:00:00.000000 +0000 ]\n")
|
||||
|
||||
self.ok("destroy -R /diff/1")
|
||||
self.ok("destroy -R /diff/2")
|
||||
|
||||
def test_16_rename(self):
|
||||
# Test renaming. Force file size smaller so we get more files
|
||||
server_stop()
|
||||
recursive_unlink(testdb)
|
||||
server_start(bulkdata_args = { "file_size" : 920, # 23 rows per file
|
||||
"files_per_dir" : 3 })
|
||||
|
||||
|
||||
# Fill data
|
||||
self.ok("create /newton/prep float32_8")
|
||||
os.environ['TZ'] = "UTC"
|
||||
with open("tests/data/prep-20120323T1004-timestamped") as input:
|
||||
self.ok("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
|
||||
input)
|
||||
|
||||
# Extract it
|
||||
self.ok("extract /newton/prep --start '2000-01-01' " +
|
||||
"--end '2012-03-23 10:04:01'")
|
||||
extract_before = self.captured
|
||||
|
||||
def check_path(*components):
|
||||
# Verify the paths look right on disk
|
||||
seek = os.path.join(testdb, "data", *components)
|
||||
for (dirpath, dirnames, filenames) in os.walk(testdb):
|
||||
if "_format" in filenames:
|
||||
if dirpath == seek:
|
||||
break
|
||||
raise AssertionError("data also found at " + dirpath)
|
||||
else:
|
||||
raise AssertionError("data not found at " + seek)
|
||||
# Verify "list" output
|
||||
self.ok("list -l")
|
||||
self.match("/" + "/".join(components) + " float32_8\n")
|
||||
|
||||
# Lots of renames
|
||||
check_path("newton", "prep")
|
||||
|
||||
self.fail("rename /newton/prep /newton/prep")
|
||||
self.contain("old and new paths are the same")
|
||||
check_path("newton", "prep")
|
||||
self.fail("rename /newton/prep /newton")
|
||||
self.contain("path must contain at least one folder")
|
||||
self.fail("rename /newton/prep /newton/prep/")
|
||||
self.contain("invalid path")
|
||||
self.ok("rename /newton/prep /newton/foo/1")
|
||||
check_path("newton", "foo", "1")
|
||||
self.ok("rename /newton/foo/1 /newton/foo")
|
||||
check_path("newton", "foo")
|
||||
self.ok("rename /newton/foo /totally/different/thing")
|
||||
check_path("totally", "different", "thing")
|
||||
self.ok("rename /totally/different/thing /totally/something")
|
||||
check_path("totally", "something")
|
||||
self.ok("rename /totally/something /totally/something/cool")
|
||||
check_path("totally", "something", "cool")
|
||||
self.ok("rename /totally/something/cool /foo/bar")
|
||||
check_path("foo", "bar")
|
||||
self.ok("create /xxx/yyy/zzz float32_8")
|
||||
self.fail("rename /foo/bar /xxx/yyy")
|
||||
self.contain("subdirs of this path already exist")
|
||||
self.fail("rename /foo/bar /xxx/yyy/zzz")
|
||||
self.contain("stream already exists at this path")
|
||||
self.fail("rename /foo/bar /xxx/yyy/zzz/www")
|
||||
self.contain("path is subdir of existing node")
|
||||
self.ok("rename /foo/bar /xxx/yyy/mmm")
|
||||
self.ok("destroy -R /xxx/yyy/zzz")
|
||||
check_path("xxx", "yyy", "mmm")
|
||||
|
||||
# Extract it at the final path
|
||||
self.ok("extract /xxx/yyy/mmm --start '2000-01-01' " +
|
||||
"--end '2012-03-23 10:04:01'")
|
||||
eq_(self.captured, extract_before)
|
||||
|
||||
self.ok("destroy -R /xxx/yyy/mmm")
|
||||
|
||||
# Make sure temporary rename dirs weren't left around
|
||||
for (dirpath, dirnames, filenames) in os.walk(testdb):
|
||||
if "rename-" in dirpath:
|
||||
raise AssertionError("temporary directories not cleaned up")
|
||||
if "totally" in dirpath or "newton" in dirpath:
|
||||
raise AssertionError("old directories not cleaned up")
|
||||
|
||||
server_stop()
|
||||
server_start()
|
||||
|
@@ -8,8 +8,11 @@ from nose.tools import *
|
||||
from nose.tools import assert_raises
|
||||
import itertools
|
||||
|
||||
from nilmdb.server.interval import (Interval, DBInterval,
|
||||
IntervalSet, IntervalError)
|
||||
from nilmdb.utils.interval import IntervalError
|
||||
from nilmdb.server.interval import Interval, DBInterval, IntervalSet
|
||||
|
||||
# so we can test them separately
|
||||
from nilmdb.utils.interval import Interval as UtilsInterval
|
||||
|
||||
from testutil.helpers import *
|
||||
import unittest
|
||||
@@ -47,11 +50,20 @@ def makeset(string):
|
||||
return iset
|
||||
|
||||
class TestInterval:
|
||||
def test_client_interval(self):
|
||||
# Run interval tests against the Python version of Interval.
|
||||
global Interval
|
||||
NilmdbInterval = Interval
|
||||
Interval = UtilsInterval
|
||||
self.test_interval()
|
||||
self.test_interval_intersect()
|
||||
Interval = NilmdbInterval
|
||||
|
||||
def test_interval(self):
|
||||
# Test Interval class
|
||||
os.environ['TZ'] = "America/New_York"
|
||||
datetime_tz._localtz = None
|
||||
(d1, d2, d3) = [ datetime_tz.datetime_tz.smartparse(x).totimestamp()
|
||||
(d1, d2, d3) = [ nilmdb.utils.time.parse_time(x)
|
||||
for x in [ "03/24/2012", "03/25/2012", "03/26/2012" ] ]
|
||||
|
||||
# basic construction
|
||||
@@ -77,8 +89,8 @@ class TestInterval:
|
||||
assert(Interval(d1, d3) > Interval(d1, d2))
|
||||
assert(Interval(d1, d2) < Interval(d2, d3))
|
||||
assert(Interval(d1, d3) < Interval(d2, d3))
|
||||
assert(Interval(d2, d2+0.01) > Interval(d1, d3))
|
||||
assert(Interval(d3, d3+0.01) == Interval(d3, d3+0.01))
|
||||
assert(Interval(d2, d2+1) > Interval(d1, d3))
|
||||
assert(Interval(d3, d3+1) == Interval(d3, d3+1))
|
||||
#with assert_raises(TypeError): # was AttributeError, that's wrong
|
||||
# x = (i == 123)
|
||||
|
||||
@@ -87,16 +99,16 @@ class TestInterval:
|
||||
with assert_raises(IntervalError):
|
||||
x = Interval(d2, d3).subset(d1, d2)
|
||||
|
||||
# big integers and floats
|
||||
x = Interval(5000111222, 6000111222)
|
||||
eq_(str(x), "[5000111222.000000 -> 6000111222.000000)")
|
||||
x = Interval(123.45, 234.56)
|
||||
eq_(str(x), "[123.450000 -> 234.560000)")
|
||||
# big integers, negative integers
|
||||
x = Interval(5000111222000000, 6000111222000000)
|
||||
eq_(str(x), "[5000111222000000 -> 6000111222000000)")
|
||||
x = Interval(-5000111222000000, -4000111222000000)
|
||||
eq_(str(x), "[-5000111222000000 -> -4000111222000000)")
|
||||
|
||||
# misc
|
||||
i = Interval(d1, d2)
|
||||
eq_(repr(i), repr(eval(repr(i))))
|
||||
eq_(str(i), "[1332561600.000000 -> 1332648000.000000)")
|
||||
eq_(str(i), "[1332561600000000 -> 1332648000000000)")
|
||||
|
||||
def test_interval_intersect(self):
|
||||
# Test Interval intersections
|
||||
@@ -193,7 +205,7 @@ class TestInterval:
|
||||
# misc
|
||||
eq_(repr(iset), repr(eval(repr(iset))))
|
||||
eq_(str(iset),
|
||||
"[[100.000000 -> 200.000000), [200.000000 -> 300.000000)]")
|
||||
"[[100 -> 200), [200 -> 300)]")
|
||||
|
||||
def test_intervalset_geniset(self):
|
||||
# Test basic iset construction
|
||||
@@ -208,64 +220,90 @@ class TestInterval:
|
||||
makeset(" [-|-----|"))
|
||||
|
||||
|
||||
def test_intervalset_intersect(self):
|
||||
def test_intervalset_intersect_difference(self):
|
||||
# Test intersection (&)
|
||||
with assert_raises(TypeError): # was AttributeError
|
||||
x = makeset("[--)") & 1234
|
||||
|
||||
# Intersection with interval
|
||||
eq_(makeset("[---|---)[)") &
|
||||
list(makeset(" [------) "))[0],
|
||||
makeset(" [-----) "))
|
||||
def do_test(a, b, c, d):
|
||||
# a & b == c
|
||||
ab = IntervalSet()
|
||||
for x in b:
|
||||
for i in (a & x):
|
||||
ab += i
|
||||
eq_(ab,c)
|
||||
|
||||
# Intersection with sets
|
||||
eq_(makeset("[---------)") &
|
||||
makeset(" [---) "),
|
||||
makeset(" [---) "))
|
||||
# a \ b == d
|
||||
eq_(IntervalSet(nilmdb.utils.interval.set_difference(a,b)), d)
|
||||
|
||||
eq_(makeset(" [---) ") &
|
||||
makeset("[---------)"),
|
||||
makeset(" [---) "))
|
||||
# Intersection with intervals
|
||||
do_test(makeset("[---|---)[)"),
|
||||
makeset(" [------) "),
|
||||
makeset(" [-----) "), # intersection
|
||||
makeset("[-) [)")) # difference
|
||||
|
||||
eq_(makeset(" [-----)") &
|
||||
makeset(" [-----) "),
|
||||
makeset(" [--) "))
|
||||
do_test(makeset("[---------)"),
|
||||
makeset(" [---) "),
|
||||
makeset(" [---) "), # intersection
|
||||
makeset("[) [----)")) # difference
|
||||
|
||||
eq_(makeset(" [--) [--)") &
|
||||
makeset(" [------) "),
|
||||
makeset(" [-) [-) "))
|
||||
do_test(makeset(" [---) "),
|
||||
makeset("[---------)"),
|
||||
makeset(" [---) "), # intersection
|
||||
makeset(" ")) # difference
|
||||
|
||||
eq_(makeset(" [---)") &
|
||||
makeset(" [--) "),
|
||||
makeset(" "))
|
||||
do_test(makeset(" [-----)"),
|
||||
makeset(" [-----) "),
|
||||
makeset(" [--) "), # intersection
|
||||
makeset(" [--)")) # difference
|
||||
|
||||
eq_(makeset(" [-|---)") &
|
||||
makeset(" [-----|-) "),
|
||||
makeset(" [----) "))
|
||||
do_test(makeset(" [--) [--)"),
|
||||
makeset(" [------) "),
|
||||
makeset(" [-) [-) "), # intersection
|
||||
makeset(" [) [)")) # difference
|
||||
|
||||
eq_(makeset(" [-|-) ") &
|
||||
makeset(" [-|--|--) "),
|
||||
makeset(" [---) "))
|
||||
do_test(makeset(" [---)"),
|
||||
makeset(" [--) "),
|
||||
makeset(" "), # intersection
|
||||
makeset(" [---)")) # difference
|
||||
|
||||
do_test(makeset(" [-|---)"),
|
||||
makeset(" [-----|-) "),
|
||||
makeset(" [----) "), # intersection
|
||||
makeset(" [)")) # difference
|
||||
|
||||
do_test(makeset(" [-|-) "),
|
||||
makeset(" [-|--|--) "),
|
||||
makeset(" [---) "), # intersection
|
||||
makeset(" ")) # difference
|
||||
|
||||
do_test(makeset("[-)[-)[-)[)"),
|
||||
makeset(" [) [|)[) "),
|
||||
makeset(" [) [) "), # intersection
|
||||
makeset("[) [-) [)[)")) # difference
|
||||
|
||||
# Border cases -- will give different results if intervals are
|
||||
# half open or fully closed. Right now, they are half open,
|
||||
# although that's a little messy since the database intervals
|
||||
# often contain a data point at the endpoint.
|
||||
half_open = True
|
||||
if half_open:
|
||||
eq_(makeset(" [---)") &
|
||||
# half open or fully closed. In nilmdb, they are half open.
|
||||
do_test(makeset(" [---)"),
|
||||
makeset(" [----) "),
|
||||
makeset(" "))
|
||||
eq_(makeset(" [----)[--)") &
|
||||
makeset(" "), # intersection
|
||||
makeset(" [---)")) # difference
|
||||
|
||||
do_test(makeset(" [----)[--)"),
|
||||
makeset("[-) [--) [)"),
|
||||
makeset(" [) [-) [)"))
|
||||
else:
|
||||
eq_(makeset(" [---)") &
|
||||
makeset(" [----) "),
|
||||
makeset(" . "))
|
||||
eq_(makeset(" [----)[--)") &
|
||||
makeset("[-) [--) [)"),
|
||||
makeset(" [) [-). [)"))
|
||||
makeset(" [) [-) [)"), # intersection
|
||||
makeset(" [-) [-) ")) # difference
|
||||
|
||||
# Set difference with bounds
|
||||
a = makeset(" [----)[--)")
|
||||
b = makeset("[-) [--) [)")
|
||||
c = makeset("[----) ")
|
||||
d = makeset(" [-) ")
|
||||
eq_(nilmdb.utils.interval.set_difference(
|
||||
a.intersection(list(c)[0]), b.intersection(list(c)[0])), d)
|
||||
|
||||
# Empty second set
|
||||
eq_(nilmdb.utils.interval.set_difference(a, IntervalSet()), a)
|
||||
|
||||
class TestIntervalDB:
|
||||
def test_dbinterval(self):
|
||||
@@ -347,14 +385,13 @@ class TestIntervalSpeed:
|
||||
def test_interval_speed(self):
|
||||
import yappi
|
||||
import time
|
||||
import testutil.aplotter as aplotter
|
||||
import random
|
||||
import math
|
||||
|
||||
print
|
||||
yappi.start()
|
||||
speeds = {}
|
||||
limit = 10 # was 20
|
||||
limit = 22 # was 20
|
||||
for j in [ 2**x for x in range(5,limit) ]:
|
||||
start = time.time()
|
||||
iset = IntervalSet()
|
||||
@@ -368,7 +405,5 @@ class TestIntervalSpeed:
|
||||
speed/j,
|
||||
speed / (j*math.log(j))) # should be constant
|
||||
speeds[j] = speed
|
||||
aplotter.plot(speeds.keys(), speeds.values(), plot_slope=True)
|
||||
yappi.stop()
|
||||
yappi.print_stats(sort_type=yappi.SORTTYPE_TTOT, limit=10)
|
||||
|
||||
|
@@ -1,61 +0,0 @@
|
||||
import nilmdb
|
||||
from nilmdb.utils.printf import *
|
||||
|
||||
import nose
|
||||
from nose.tools import *
|
||||
from nose.tools import assert_raises
|
||||
import threading
|
||||
import time
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
def func_with_callback(a, b, callback):
|
||||
callback(a)
|
||||
callback(b)
|
||||
callback(a+b)
|
||||
return "return value"
|
||||
|
||||
class TestIteratorizer(object):
|
||||
def test(self):
|
||||
|
||||
# First try it with a normal callback
|
||||
self.result = ""
|
||||
def cb(x):
|
||||
self.result += str(x)
|
||||
func_with_callback(1, 2, cb)
|
||||
eq_(self.result, "123")
|
||||
|
||||
# Now make it an iterator
|
||||
result = ""
|
||||
f = lambda x: func_with_callback(1, 2, x)
|
||||
with nilmdb.utils.Iteratorizer(f) as it:
|
||||
for i in it:
|
||||
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")
|
||||
|
||||
# Now try to trigger the case where we stop iterating
|
||||
# mid-generator, and expect the iteratorizer to clean up after
|
||||
# itself. This doesn't have a particular result in the test,
|
||||
# but gains coverage.
|
||||
def foo():
|
||||
with nilmdb.utils.Iteratorizer(f) as it:
|
||||
it.next()
|
||||
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)
|
@@ -1,266 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import nilmdb
|
||||
|
||||
from nilmdb.utils.printf import *
|
||||
|
||||
from nose.tools import *
|
||||
from nose.tools import assert_raises
|
||||
import distutils.version
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
import unittest
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
from nilmdb.server.layout import *
|
||||
|
||||
class TestLayouts(object):
|
||||
# Some nilmdb.layout tests. Not complete, just fills in missing
|
||||
# coverage.
|
||||
def test_layouts(self):
|
||||
x = nilmdb.server.layout.get_named("float32_8")
|
||||
y = nilmdb.server.layout.get_named("float32_8")
|
||||
eq_(x.count, y.count)
|
||||
eq_(x.datatype, y.datatype)
|
||||
y = nilmdb.server.layout.get_named("float32_7")
|
||||
ne_(x.count, y.count)
|
||||
eq_(x.datatype, y.datatype)
|
||||
|
||||
def test_parsing(self):
|
||||
self.real_t_parsing("float32_8", "uint16_6", "uint16_9")
|
||||
self.real_t_parsing("float32_8", "uint16_6", "uint16_9")
|
||||
def real_t_parsing(self, name_prep, name_raw, name_rawnotch):
|
||||
# invalid layouts
|
||||
with assert_raises(TypeError) as e:
|
||||
parser = Parser("NoSuchLayout")
|
||||
with assert_raises(TypeError) as e:
|
||||
parser = Parser("float32")
|
||||
|
||||
# too little data
|
||||
parser = Parser(name_prep)
|
||||
data = ( "1234567890.000000 1.1 2.2 3.3 4.4 5.5\n" +
|
||||
"1234567890.100000 1.1 2.2 3.3 4.4 5.5\n")
|
||||
with assert_raises(ParserError) as e:
|
||||
parser.parse(data)
|
||||
in_("error", str(e.exception))
|
||||
|
||||
# too much data
|
||||
parser = Parser(name_prep)
|
||||
data = ( "1234567890.000000 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9\n" +
|
||||
"1234567890.100000 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9\n")
|
||||
with assert_raises(ParserError) as e:
|
||||
parser.parse(data)
|
||||
in_("error", str(e.exception))
|
||||
|
||||
# just right
|
||||
parser = Parser(name_prep)
|
||||
data = ( "1234567890.000000 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8\n" +
|
||||
"1234567890.100000 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8\n")
|
||||
parser.parse(data)
|
||||
eq_(parser.min_timestamp, 1234567890.0)
|
||||
eq_(parser.max_timestamp, 1234567890.1)
|
||||
eq_(parser.data, [[1234567890.0,1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8],
|
||||
[1234567890.1,1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8]])
|
||||
|
||||
# try uint16_6 too, with clamping
|
||||
parser = Parser(name_raw)
|
||||
data = ( "1234567890.000000 1 2 3 4 5 6\n" +
|
||||
"1234567890.100000 1 2 3 4 5 6\n" )
|
||||
parser.parse(data)
|
||||
eq_(parser.data, [[1234567890.0,1,2,3,4,5,6],
|
||||
[1234567890.1,1,2,3,4,5,6]])
|
||||
|
||||
# pass an instantiated class
|
||||
parser = Parser(get_named(name_rawnotch))
|
||||
data = ( "1234567890.000000 1 2 3 4 5 6 7 8 9\n" +
|
||||
"1234567890.100000 1 2 3 4 5 6 7 8 9\n" )
|
||||
parser.parse(data)
|
||||
|
||||
# non-monotonic
|
||||
parser = Parser(name_raw)
|
||||
data = ( "1234567890.100000 1 2 3 4 5 6\n" +
|
||||
"1234567890.099999 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.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)
|
||||
|
||||
# uint16_6 with values out of bounds
|
||||
parser = Parser(name_raw)
|
||||
data = ( "1234567890.000000 1 2 3 4 500000 6\n" +
|
||||
"1234567890.100000 1 2 3 4 5 6\n" )
|
||||
with assert_raises(ParserError) as e:
|
||||
parser.parse(data)
|
||||
in_("value out of range", str(e.exception))
|
||||
|
||||
# Empty data should work but is useless
|
||||
parser = Parser(name_raw)
|
||||
data = ""
|
||||
parser.parse(data)
|
||||
assert(parser.min_timestamp is None)
|
||||
assert(parser.max_timestamp is None)
|
||||
|
||||
def test_formatting(self):
|
||||
self.real_t_formatting("float32_8", "uint16_6", "uint16_9")
|
||||
self.real_t_formatting("float32_8", "uint16_6", "uint16_9")
|
||||
def real_t_formatting(self, name_prep, name_raw, name_rawnotch):
|
||||
# invalid layout
|
||||
with assert_raises(TypeError) as e:
|
||||
formatter = Formatter("NoSuchLayout")
|
||||
|
||||
# too little data
|
||||
formatter = Formatter(name_prep)
|
||||
data = [ [ 1234567890.000000, 1.1, 2.2, 3.3, 4.4, 5.5 ],
|
||||
[ 1234567890.100000, 1.1, 2.2, 3.3, 4.4, 5.5 ] ]
|
||||
with assert_raises(FormatterError) as e:
|
||||
formatter.format(data)
|
||||
in_("error", str(e.exception))
|
||||
|
||||
# too much data
|
||||
formatter = Formatter(name_prep)
|
||||
data = [ [ 1234567890.000000, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
|
||||
[ 1234567890.100000, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ]
|
||||
with assert_raises(FormatterError) as e:
|
||||
formatter.format(data)
|
||||
in_("error", str(e.exception))
|
||||
|
||||
# just right
|
||||
formatter = Formatter(name_prep)
|
||||
data = [ [ 1234567890.000000, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 ],
|
||||
[ 1234567890.100000, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 ] ]
|
||||
text = formatter.format(data)
|
||||
eq_(text,
|
||||
"1234567890.000000 1.100000e+00 2.200000e+00 3.300000e+00 "
|
||||
"4.400000e+00 5.500000e+00 6.600000e+00 7.700000e+00 "
|
||||
"8.800000e+00\n" +
|
||||
"1234567890.100000 1.100000e+00 2.200000e+00 3.300000e+00 "
|
||||
"4.400000e+00 5.500000e+00 6.600000e+00 7.700000e+00 "
|
||||
"8.800000e+00\n")
|
||||
|
||||
# try uint16_6 too
|
||||
formatter = Formatter(name_raw)
|
||||
data = [ [ 1234567890.000000, 1, 2, 3, 4, 5, 6 ],
|
||||
[ 1234567890.100000, 1, 2, 3, 4, 5, 6 ] ]
|
||||
text = formatter.format(data)
|
||||
eq_(text,
|
||||
"1234567890.000000 1 2 3 4 5 6\n" +
|
||||
"1234567890.100000 1 2 3 4 5 6\n")
|
||||
|
||||
# pass an instantiated class
|
||||
formatter = Formatter(get_named(name_rawnotch))
|
||||
data = [ [ 1234567890.000000, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
|
||||
[ 1234567890.100000, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ]
|
||||
text = formatter.format(data)
|
||||
eq_(text,
|
||||
"1234567890.000000 1 2 3 4 5 6 7 8 9\n" +
|
||||
"1234567890.100000 1 2 3 4 5 6 7 8 9\n")
|
||||
|
||||
# Empty data should work but is useless
|
||||
formatter = Formatter(name_raw)
|
||||
data = []
|
||||
text = formatter.format(data)
|
||||
eq_(text, "")
|
||||
|
||||
def test_roundtrip(self):
|
||||
self.real_t_roundtrip("float32_8", "uint16_6", "uint16_9")
|
||||
self.real_t_roundtrip("float32_8", "uint16_6", "uint16_9")
|
||||
def real_t_roundtrip(self, name_prep, name_raw, name_rawnotch):
|
||||
# Verify that textual data passed into the Parser, and then
|
||||
# back through the Formatter, then back into the Parser,
|
||||
# gives identical parsed representations
|
||||
random.seed(12345)
|
||||
|
||||
def do_roundtrip(layout, datagen):
|
||||
for i in range(100):
|
||||
rows = random.randint(1,100)
|
||||
data = ""
|
||||
ts = 1234567890
|
||||
for r in range(rows):
|
||||
ts += random.uniform(0,1)
|
||||
row = sprintf("%f", ts) + " "
|
||||
row += " ".join(datagen())
|
||||
row += "\n"
|
||||
data += row
|
||||
parser1 = Parser(layout)
|
||||
formatter = Formatter(layout)
|
||||
parser2 = Parser(layout)
|
||||
parser1.parse(data)
|
||||
parser2.parse(formatter.format(parser1.data))
|
||||
eq_(parser1.data, parser2.data)
|
||||
|
||||
def datagen():
|
||||
return [ sprintf("%.6e", random.uniform(-1000,1000))
|
||||
for x in range(8) ]
|
||||
do_roundtrip(name_prep, datagen)
|
||||
|
||||
def datagen():
|
||||
return [ sprintf("%d", random.randint(0,65535))
|
||||
for x in range(6) ]
|
||||
do_roundtrip(name_raw, datagen)
|
||||
|
||||
def datagen():
|
||||
return [ sprintf("%d", random.randint(0,65535))
|
||||
for x in range(9) ]
|
||||
do_roundtrip(name_rawnotch, datagen)
|
||||
|
||||
class TestLayoutSpeed:
|
||||
@unittest.skip("this is slow")
|
||||
def test_layout_speed(self):
|
||||
import time
|
||||
|
||||
random.seed(54321)
|
||||
|
||||
def do_speedtest(layout, datagen, rows = 5000, times = 100):
|
||||
# Build data once
|
||||
data = ""
|
||||
ts = 1234567890
|
||||
for r in range(rows):
|
||||
ts += random.uniform(0,1)
|
||||
row = sprintf("%f", ts) + " "
|
||||
row += " ".join(datagen())
|
||||
row += "\n"
|
||||
data += row
|
||||
|
||||
# Do lots of roundtrips
|
||||
start = time.time()
|
||||
for i in range(times):
|
||||
parser = Parser(layout)
|
||||
formatter = Formatter(layout)
|
||||
parser.parse(data)
|
||||
formatter.format(parser.data)
|
||||
elapsed = time.time() - start
|
||||
printf("roundtrip %s: %d ms, %.1f μs/row, %d rows/sec\n",
|
||||
layout,
|
||||
elapsed * 1e3,
|
||||
(elapsed * 1e6) / (rows * times),
|
||||
(rows * times) / elapsed)
|
||||
|
||||
print ""
|
||||
def datagen():
|
||||
return [ sprintf("%.6e", random.uniform(-1000,1000))
|
||||
for x in range(10) ]
|
||||
do_speedtest("float32_10", datagen)
|
||||
|
||||
def datagen():
|
||||
return [ sprintf("%d", random.randint(0,65535))
|
||||
for x in range(10) ]
|
||||
do_speedtest("uint16_10", datagen)
|
||||
|
||||
def datagen():
|
||||
return [ sprintf("%d", random.randint(0,65535))
|
||||
for x in range(6) ]
|
||||
do_speedtest("uint16_6", datagen)
|
@@ -28,9 +28,6 @@ class Test00Nilmdb(object): # named 00 so it runs first
|
||||
def test_NilmDB(self):
|
||||
recursive_unlink(testdb)
|
||||
|
||||
with assert_raises(IOError):
|
||||
nilmdb.server.NilmDB("/nonexistant-db/foo")
|
||||
|
||||
db = nilmdb.server.NilmDB(testdb)
|
||||
db.close()
|
||||
db = nilmdb.server.NilmDB(testdb)
|
||||
@@ -93,13 +90,16 @@ class Test00Nilmdb(object): # named 00 so it runs first
|
||||
eq_(db.stream_get_metadata("/newton/prep"), meta1)
|
||||
eq_(db.stream_get_metadata("/newton/raw"), meta1)
|
||||
|
||||
# fill in some test coverage for start >= end
|
||||
# fill in some misc. test coverage
|
||||
with assert_raises(nilmdb.server.NilmDBError):
|
||||
db.stream_remove("/newton/prep", 0, 0)
|
||||
with assert_raises(nilmdb.server.NilmDBError):
|
||||
db.stream_remove("/newton/prep", 1, 0)
|
||||
db.stream_remove("/newton/prep", 0, 1)
|
||||
|
||||
with assert_raises(nilmdb.server.NilmDBError):
|
||||
db.stream_extract("/newton/prep", count = True, binary = True)
|
||||
|
||||
db.close()
|
||||
|
||||
class TestBlockingServer(object):
|
||||
|
342
tests/test_numpyclient.py
Normal file
342
tests/test_numpyclient.py
Normal file
@@ -0,0 +1,342 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import nilmdb.server
|
||||
import nilmdb.client
|
||||
import nilmdb.client.numpyclient
|
||||
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.utils import timestamper
|
||||
from nilmdb.client import ClientError, ServerError
|
||||
from nilmdb.utils import datetime_tz
|
||||
|
||||
from nose.plugins.skip import SkipTest
|
||||
from nose.tools import *
|
||||
from nose.tools import assert_raises
|
||||
import itertools
|
||||
import distutils.version
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
import numpy as np
|
||||
|
||||
testdb = "tests/numpyclient-testdb"
|
||||
testurl = "http://localhost:32180/"
|
||||
|
||||
def setup_module():
|
||||
global test_server, test_db
|
||||
# Clear out DB
|
||||
recursive_unlink(testdb)
|
||||
|
||||
# Start web app on a custom port
|
||||
test_db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(testdb)
|
||||
test_server = nilmdb.server.Server(test_db, host = "127.0.0.1",
|
||||
port = 32180, stoppable = False,
|
||||
fast_shutdown = True,
|
||||
force_traceback = True)
|
||||
test_server.start(blocking = False)
|
||||
|
||||
def teardown_module():
|
||||
global test_server, test_db
|
||||
# Close web app
|
||||
test_server.stop()
|
||||
test_db.close()
|
||||
|
||||
class TestNumpyClient(object):
|
||||
|
||||
def test_numpyclient_01_basic(self):
|
||||
# Test basic connection
|
||||
client = nilmdb.client.numpyclient.NumpyClient(url = testurl)
|
||||
version = client.version()
|
||||
eq_(distutils.version.LooseVersion(version),
|
||||
distutils.version.LooseVersion(test_server.version))
|
||||
|
||||
# Verify subclassing
|
||||
assert(isinstance(client, nilmdb.client.Client))
|
||||
|
||||
# Layouts
|
||||
for layout in "int8_t", "something_8", "integer_1":
|
||||
with assert_raises(ValueError):
|
||||
for x in client.stream_extract_numpy("/foo", layout=layout):
|
||||
pass
|
||||
for layout in "int8_1", "uint8_30", "int16_20", "float64_100":
|
||||
with assert_raises(ClientError) as e:
|
||||
for x in client.stream_extract_numpy("/foo", layout=layout):
|
||||
pass
|
||||
in_("No such stream", str(e.exception))
|
||||
|
||||
with assert_raises(ClientError) as e:
|
||||
for x in client.stream_extract_numpy("/foo"):
|
||||
pass
|
||||
in_("can't get layout for path", str(e.exception))
|
||||
|
||||
client.close()
|
||||
|
||||
def test_numpyclient_02_extract(self):
|
||||
client = nilmdb.client.numpyclient.NumpyClient(url = testurl)
|
||||
|
||||
# Insert some data as text
|
||||
client.stream_create("/newton/prep", "float32_8")
|
||||
testfile = "tests/data/prep-20120323T1000"
|
||||
start = nilmdb.utils.time.parse_time("20120323T1000")
|
||||
rate = 120
|
||||
data = timestamper.TimestamperRate(testfile, start, rate)
|
||||
result = client.stream_insert("/newton/prep", data,
|
||||
start, start + 119999777)
|
||||
|
||||
# Extract Numpy arrays
|
||||
array = None
|
||||
pieces = 0
|
||||
for chunk in client.stream_extract_numpy("/newton/prep", maxrows=1000):
|
||||
pieces += 1
|
||||
if array is not None:
|
||||
array = np.vstack((array, chunk))
|
||||
else:
|
||||
array = chunk
|
||||
eq_(array.shape, (14400, 9))
|
||||
eq_(pieces, 15)
|
||||
|
||||
# Try structured
|
||||
s = list(client.stream_extract_numpy("/newton/prep", structured = True))
|
||||
assert(np.array_equal(np.c_[s[0]['timestamp'], s[0]['data']], array))
|
||||
|
||||
# Compare. Will be close but not exact because the conversion
|
||||
# to and from ASCII was lossy.
|
||||
data = timestamper.TimestamperRate(testfile, start, rate)
|
||||
actual = np.fromstring(" ".join(data), sep=' ').reshape(14400, 9)
|
||||
assert(np.allclose(array, actual))
|
||||
|
||||
client.close()
|
||||
|
||||
def test_numpyclient_03_insert(self):
|
||||
client = nilmdb.client.numpyclient.NumpyClient(url = testurl)
|
||||
|
||||
# Limit _max_data just to get better coverage
|
||||
old_max_data = nilmdb.client.numpyclient.StreamInserterNumpy._max_data
|
||||
nilmdb.client.numpyclient.StreamInserterNumpy._max_data = 100000
|
||||
|
||||
client.stream_create("/test/1", "uint16_1")
|
||||
client.stream_insert_numpy("/test/1",
|
||||
np.array([[0, 1],
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[3, 4]]))
|
||||
|
||||
# Wrong number of dimensions
|
||||
with assert_raises(ValueError) as e:
|
||||
client.stream_insert_numpy("/test/1",
|
||||
np.array([[[0, 1],
|
||||
[1, 2]],
|
||||
[[3, 4],
|
||||
[4, 5]]]))
|
||||
in_("wrong number of dimensions", str(e.exception))
|
||||
|
||||
# Wrong number of fields
|
||||
with assert_raises(ValueError) as e:
|
||||
client.stream_insert_numpy("/test/1",
|
||||
np.array([[0, 1, 2],
|
||||
[1, 2, 3],
|
||||
[3, 4, 5],
|
||||
[4, 5, 6]]))
|
||||
in_("wrong number of fields", str(e.exception))
|
||||
|
||||
# Unstructured
|
||||
client.stream_create("/test/2", "float32_8")
|
||||
client.stream_insert_numpy(
|
||||
"/test/2",
|
||||
client.stream_extract_numpy(
|
||||
"/newton/prep", structured = False, maxrows = 1000))
|
||||
|
||||
# Structured, and specifying layout
|
||||
client.stream_create("/test/3", "float32_8")
|
||||
client.stream_insert_numpy(
|
||||
path = "/test/3", layout = "float32_8",
|
||||
data = client.stream_extract_numpy(
|
||||
"/newton/prep", structured = True, maxrows = 1000))
|
||||
|
||||
# Structured, specifying wrong layout
|
||||
client.stream_create("/test/4", "float32_8")
|
||||
with assert_raises(ValueError) as e:
|
||||
client.stream_insert_numpy(
|
||||
"/test/4", layout = "uint16_1",
|
||||
data = client.stream_extract_numpy(
|
||||
"/newton/prep", structured = True, maxrows = 1000))
|
||||
in_("wrong dtype", str(e.exception))
|
||||
|
||||
# Unstructured, and specifying wrong layout
|
||||
client.stream_create("/test/5", "float32_8")
|
||||
with assert_raises(ClientError) as e:
|
||||
client.stream_insert_numpy(
|
||||
"/test/5", layout = "uint16_8",
|
||||
data = client.stream_extract_numpy(
|
||||
"/newton/prep", structured = False, maxrows = 1000))
|
||||
# timestamps will be screwy here, because data will be parsed wrong
|
||||
in_("error parsing input data", str(e.exception))
|
||||
|
||||
# Make sure the /newton/prep copies are identical
|
||||
a = np.vstack(client.stream_extract_numpy("/newton/prep"))
|
||||
b = np.vstack(client.stream_extract_numpy("/test/2"))
|
||||
c = np.vstack(client.stream_extract_numpy("/test/3"))
|
||||
assert(np.array_equal(a,b))
|
||||
assert(np.array_equal(a,c))
|
||||
|
||||
nilmdb.client.numpyclient.StreamInserterNumpy._max_data = old_max_data
|
||||
client.close()
|
||||
|
||||
def test_numpyclient_04_context(self):
|
||||
# Like test_client_context, but with Numpy data
|
||||
client = nilmdb.client.numpyclient.NumpyClient(testurl)
|
||||
|
||||
client.stream_create("/context/test", "uint16_1")
|
||||
with client.stream_insert_numpy_context("/context/test") as ctx:
|
||||
# override _max_rows to trigger frequent server updates
|
||||
ctx._max_rows = 2
|
||||
ctx.insert([[1000, 1]])
|
||||
ctx.insert([[1010, 1], [1020, 1], [1030, 1]])
|
||||
ctx.insert([[1040, 1], [1050, 1]])
|
||||
ctx.finalize()
|
||||
ctx.insert([[1070, 1]])
|
||||
ctx.update_end(1080)
|
||||
ctx.finalize()
|
||||
ctx.update_start(1090)
|
||||
ctx.insert([[1100, 1]])
|
||||
ctx.insert([[1110, 1]])
|
||||
ctx.send()
|
||||
ctx.insert([[1120, 1], [1130, 1], [1140, 1]])
|
||||
ctx.update_end(1160)
|
||||
ctx.insert([[1150, 1]])
|
||||
ctx.update_end(1170)
|
||||
ctx.insert([[1160, 1]])
|
||||
ctx.update_end(1180)
|
||||
ctx.insert([[1170, 123456789.0]])
|
||||
ctx.finalize()
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_numpy_context("/context/test",
|
||||
1000, 2000) as ctx:
|
||||
ctx.insert([[1180, 1]])
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_numpy_context("/context/test",
|
||||
2000, 3000) as ctx:
|
||||
ctx._max_rows = 2
|
||||
ctx.insert([[3180, 1]])
|
||||
ctx.insert([[3181, 1]])
|
||||
|
||||
with client.stream_insert_numpy_context("/context/test",
|
||||
2000, 3000) as ctx:
|
||||
# make sure our override wasn't permanent
|
||||
ne_(ctx._max_rows, 2)
|
||||
ctx.insert([[2250, 1]])
|
||||
ctx.finalize()
|
||||
|
||||
with assert_raises(ClientError):
|
||||
with client.stream_insert_numpy_context("/context/test",
|
||||
3000, 4000) as ctx:
|
||||
ctx.insert([[3010, 1]])
|
||||
ctx.insert([[3020, 2]])
|
||||
ctx.insert([[3030, 3]])
|
||||
ctx.insert([[3040, 4]])
|
||||
ctx.insert([[3040, 4]]) # non-monotonic after a few lines
|
||||
ctx.finalize()
|
||||
|
||||
eq_(list(client.stream_intervals("/context/test")),
|
||||
[ [ 1000, 1051 ],
|
||||
[ 1070, 1080 ],
|
||||
[ 1090, 1180 ],
|
||||
[ 2000, 3000 ] ])
|
||||
|
||||
client.stream_remove("/context/test")
|
||||
client.stream_destroy("/context/test")
|
||||
client.close()
|
||||
|
||||
def test_numpyclient_05_emptyintervals(self):
|
||||
# Like test_client_emptyintervals, with insert_numpy_context
|
||||
client = nilmdb.client.numpyclient.NumpyClient(testurl)
|
||||
client.stream_create("/empty/test", "uint16_1")
|
||||
def info():
|
||||
result = []
|
||||
for interval in list(client.stream_intervals("/empty/test")):
|
||||
result.append((client.stream_count("/empty/test", *interval),
|
||||
interval))
|
||||
return result
|
||||
eq_(info(), [])
|
||||
|
||||
# Insert a region with just a few points
|
||||
with client.stream_insert_numpy_context("/empty/test") as ctx:
|
||||
ctx.update_start(100)
|
||||
ctx.insert([[140, 1]])
|
||||
ctx.insert([[150, 1]])
|
||||
ctx.insert([[160, 1]])
|
||||
ctx.update_end(200)
|
||||
ctx.finalize()
|
||||
eq_(info(), [(3, [100, 200])])
|
||||
|
||||
# Delete chunk, which will leave one data point and two intervals
|
||||
client.stream_remove("/empty/test", 145, 175)
|
||||
eq_(info(), [(1, [100, 145]),
|
||||
(0, [175, 200])])
|
||||
|
||||
# Try also creating a completely empty interval from scratch,
|
||||
# in a few different ways.
|
||||
client.stream_insert("/empty/test", "", 300, 350)
|
||||
client.stream_insert("/empty/test", [], 400, 450)
|
||||
with client.stream_insert_numpy_context("/empty/test", 500, 550):
|
||||
pass
|
||||
|
||||
# If enough timestamps aren't provided, empty streams won't be created.
|
||||
client.stream_insert("/empty/test", [])
|
||||
with client.stream_insert_numpy_context("/empty/test"):
|
||||
pass
|
||||
client.stream_insert("/empty/test", [], start = 600)
|
||||
with client.stream_insert_numpy_context("/empty/test", start = 700):
|
||||
pass
|
||||
client.stream_insert("/empty/test", [], end = 850)
|
||||
with client.stream_insert_numpy_context("/empty/test", end = 950):
|
||||
pass
|
||||
|
||||
# Try various things that might cause problems
|
||||
with client.stream_insert_numpy_context("/empty/test", 1000, 1050):
|
||||
ctx.finalize() # inserts [1000, 1050]
|
||||
ctx.finalize() # nothing
|
||||
ctx.finalize() # nothing
|
||||
ctx.insert([[1100, 1]])
|
||||
ctx.finalize() # inserts [1100, 1101]
|
||||
ctx.update_start(1199)
|
||||
ctx.insert([[1200, 1]])
|
||||
ctx.update_end(1250)
|
||||
ctx.finalize() # inserts [1199, 1250]
|
||||
ctx.update_start(1299)
|
||||
ctx.finalize() # nothing
|
||||
ctx.update_end(1350)
|
||||
ctx.finalize() # nothing
|
||||
ctx.update_start(1400)
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
ctx.update_end(1450)
|
||||
ctx.finalize()
|
||||
ctx.update_start(1500)
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
ctx.update_end(1550)
|
||||
ctx.finalize()
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
ctx.insert(np.zeros((0,2)))
|
||||
ctx.finalize()
|
||||
|
||||
# Check everything
|
||||
eq_(info(), [(1, [100, 145]),
|
||||
(0, [175, 200]),
|
||||
(0, [300, 350]),
|
||||
(0, [400, 450]),
|
||||
(0, [500, 550]),
|
||||
(0, [1000, 1050]),
|
||||
(1, [1100, 1101]),
|
||||
(1, [1199, 1250]),
|
||||
(0, [1400, 1450]),
|
||||
(0, [1500, 1550]),
|
||||
])
|
||||
|
||||
# Clean up
|
||||
client.stream_remove("/empty/test")
|
||||
client.stream_destroy("/empty/test")
|
||||
client.close()
|
@@ -18,7 +18,7 @@ class TestPrintf(object):
|
||||
printf("hello, world: %d", 123)
|
||||
fprintf(test2, "hello too: %d", 123)
|
||||
test3 = sprintf("hello three: %d", 123)
|
||||
except:
|
||||
except Exception:
|
||||
sys.stdout = old_stdout
|
||||
raise
|
||||
sys.stdout = old_stdout
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import nilmdb
|
||||
from nilmdb.utils.printf import *
|
||||
from nilmdb.utils import datetime_tz
|
||||
|
||||
from nose.tools import *
|
||||
from nose.tools import assert_raises
|
||||
@@ -20,11 +19,11 @@ class TestTimestamper(object):
|
||||
def join(list):
|
||||
return "\n".join(list) + "\n"
|
||||
|
||||
start = datetime_tz.datetime_tz.smartparse("03/24/2012").totimestamp()
|
||||
start = nilmdb.utils.time.parse_time("03/24/2012")
|
||||
lines_in = [ "hello", "world", "hello world", "# commented out" ]
|
||||
lines_out = [ "1332561600.000000 hello",
|
||||
"1332561600.000125 world",
|
||||
"1332561600.000250 hello world" ]
|
||||
lines_out = [ "1332561600000000 hello",
|
||||
"1332561600000125 world",
|
||||
"1332561600000250 hello world" ]
|
||||
|
||||
# full
|
||||
input = cStringIO.StringIO(join(lines_in))
|
||||
@@ -42,7 +41,7 @@ class TestTimestamper(object):
|
||||
# stop iteration early
|
||||
input = cStringIO.StringIO(join(lines_in))
|
||||
ts = timestamper.TimestamperRate(input, start, 8000,
|
||||
1332561600.000200)
|
||||
1332561600000200)
|
||||
foo = ""
|
||||
for line in ts:
|
||||
foo += line
|
||||
@@ -51,14 +50,14 @@ class TestTimestamper(object):
|
||||
# stop iteration early (readlines)
|
||||
input = cStringIO.StringIO(join(lines_in))
|
||||
ts = timestamper.TimestamperRate(input, start, 8000,
|
||||
1332561600.000200)
|
||||
1332561600000200)
|
||||
foo = ts.readlines()
|
||||
eq_(foo, join(lines_out[0:2]))
|
||||
|
||||
# stop iteration really early
|
||||
input = cStringIO.StringIO(join(lines_in))
|
||||
ts = timestamper.TimestamperRate(input, start, 8000,
|
||||
1332561600.000000)
|
||||
1332561600000000)
|
||||
foo = ts.readlines()
|
||||
eq_(foo, "")
|
||||
|
||||
|
@@ -1,419 +0,0 @@
|
||||
|
||||
#-----------------------------------------------
|
||||
#aplotter.py - ascii art function plotter
|
||||
#Copyright (c) 2006, Imri Goldberg
|
||||
#All rights reserved.
|
||||
#
|
||||
#Redistribution and use in source and binary forms,
|
||||
#with or without modification, are permitted provided
|
||||
#that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the
|
||||
# above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the
|
||||
# above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of the <ORGANIZATION> nor the names of
|
||||
# its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
#ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
#LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#-----------------------------------------------
|
||||
|
||||
import math
|
||||
|
||||
|
||||
EPSILON = 0.000001
|
||||
|
||||
def transposed(mat):
|
||||
result = []
|
||||
for i in xrange(len(mat[0])):
|
||||
result.append([x[i] for x in mat])
|
||||
return result
|
||||
|
||||
def y_reversed(mat):
|
||||
result = []
|
||||
for i in range(len(mat)):
|
||||
result.append(list(reversed(mat[i])))
|
||||
return result
|
||||
|
||||
def sign(x):
|
||||
if 0<x:
|
||||
return 1
|
||||
if 0 == x:
|
||||
return 0
|
||||
return -1
|
||||
|
||||
class Plotter(object):
|
||||
|
||||
class PlotData(object):
|
||||
def __init__(self, x_size, y_size, min_x, max_x, min_y, max_y, x_mod, y_mod):
|
||||
self.x_size = x_size
|
||||
self.y_size = y_size
|
||||
self.min_x = min_x
|
||||
self.max_x = max_x
|
||||
self.min_y = min_y
|
||||
self.max_y = max_y
|
||||
self.x_mod = x_mod
|
||||
self.y_mod = y_mod
|
||||
|
||||
self.x_step = float(max_x - min_x)/float(self.x_size)
|
||||
self.y_step = float(max_y - min_y)/float(self.y_size)
|
||||
self.inv_x_step = 1/self.x_step
|
||||
self.inv_y_step = 1/self.y_step
|
||||
|
||||
self.ratio = self.y_step / self.x_step
|
||||
def __repr__(self):
|
||||
s = "size: %s, bl: %s, tr: %s, step: %s" % ((self.x_size, self.y_size), (self.min_x, self.min_y), (self.max_x, self.max_y),
|
||||
(self.x_step, self.y_step))
|
||||
return s
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.x_size = kwargs.get("x_size", 80)
|
||||
self.y_size = kwargs.get("y_size", 20)
|
||||
|
||||
self.will_draw_axes = kwargs.get("draw_axes", True)
|
||||
|
||||
self.new_line = kwargs.get("newline", "\n")
|
||||
|
||||
self.dot = kwargs.get("dot", "*")
|
||||
|
||||
self.plot_slope = kwargs.get("plot_slope", True)
|
||||
|
||||
self.x_margin = kwargs.get("x_margin", 0.05)
|
||||
self.y_margin = kwargs.get("y_margin", 0.1)
|
||||
|
||||
self.will_plot_labels = kwargs.get("plot_labels", True)
|
||||
|
||||
@staticmethod
|
||||
def get_symbol_by_slope(slope, default_symbol):
|
||||
draw_symbol = default_symbol
|
||||
if slope > math.tan(3*math.pi/8):
|
||||
draw_symbol = "|"
|
||||
elif slope > math.tan(math.pi/8) and slope < math.tan(3*math.pi/8):
|
||||
draw_symbol = "/"
|
||||
elif abs(slope) < math.tan(math.pi/8):
|
||||
draw_symbol = "-"
|
||||
elif slope < math.tan(-math.pi/8) and slope > math.tan(-3*math.pi/8):
|
||||
draw_symbol = "\\"
|
||||
elif slope < math.tan(-3*math.pi/8):
|
||||
draw_symbol = "|"
|
||||
return draw_symbol
|
||||
|
||||
|
||||
def plot_labels(self, output_buffer, plot_data):
|
||||
if plot_data.y_size < 2:
|
||||
return
|
||||
|
||||
margin_factor = 1
|
||||
|
||||
do_plot_x_label = True
|
||||
do_plot_y_label = True
|
||||
|
||||
x_str = "%+g"
|
||||
if plot_data.x_size < 16:
|
||||
do_plot_x_label = False
|
||||
elif plot_data.x_size < 23:
|
||||
x_str = "%+.2g"
|
||||
|
||||
y_str = "%+g"
|
||||
if plot_data.x_size < 8:
|
||||
do_plot_y_label = False
|
||||
elif plot_data.x_size < 11:
|
||||
y_str = "%+.2g"
|
||||
|
||||
act_min_x = (plot_data.min_x + plot_data.x_mod*margin_factor)
|
||||
act_max_x = (plot_data.max_x - plot_data.x_mod*margin_factor)
|
||||
act_min_y = (plot_data.min_y + plot_data.y_mod*margin_factor)
|
||||
act_max_y = (plot_data.max_y - plot_data.y_mod*margin_factor)
|
||||
|
||||
if abs(act_min_x) < 1:
|
||||
min_x_str = "%+.2g" % act_min_x
|
||||
else:
|
||||
min_x_str = x_str % act_min_x
|
||||
|
||||
if abs(act_max_x) < 1:
|
||||
max_x_str = "%+.2g" % act_max_x
|
||||
else:
|
||||
max_x_str = x_str % act_max_x
|
||||
|
||||
if abs(act_min_y) < 1:
|
||||
min_y_str = "%+.2g" % act_min_y
|
||||
else:
|
||||
min_y_str = y_str % act_min_y
|
||||
|
||||
if abs(act_max_y) < 1:
|
||||
max_y_str = "%+.2g" % act_max_y
|
||||
else:
|
||||
max_y_str = y_str % act_max_y
|
||||
|
||||
min_x_coord = self.get_coord(act_min_x,plot_data.min_x,plot_data.x_step)
|
||||
max_x_coord = self.get_coord(act_max_x,plot_data.min_x,plot_data.x_step)
|
||||
min_y_coord = self.get_coord(act_min_y,plot_data.min_y,plot_data.y_step)
|
||||
max_y_coord = self.get_coord(act_max_y,plot_data.min_y,plot_data.y_step)
|
||||
|
||||
|
||||
#print plot_data
|
||||
|
||||
y_zero_coord = self.get_coord(0, plot_data.min_y, plot_data.y_step)
|
||||
|
||||
#if plot_data.min_x < 0 and plot_data.max_x > 0:
|
||||
x_zero_coord = self.get_coord(0, plot_data.min_x, plot_data.x_step)
|
||||
#else:
|
||||
|
||||
#pass
|
||||
|
||||
output_buffer[x_zero_coord][min_y_coord] = "+"
|
||||
output_buffer[x_zero_coord][max_y_coord] = "+"
|
||||
output_buffer[min_x_coord][y_zero_coord] = "+"
|
||||
output_buffer[max_x_coord][y_zero_coord] = "+"
|
||||
|
||||
if do_plot_x_label:
|
||||
|
||||
for i,c in enumerate(min_x_str):
|
||||
output_buffer[min_x_coord+i][y_zero_coord-1] = c
|
||||
for i,c in enumerate(max_x_str):
|
||||
output_buffer[max_x_coord+i-len(max_x_str)][y_zero_coord-1] = c
|
||||
|
||||
if do_plot_y_label:
|
||||
|
||||
for i,c in enumerate(max_y_str):
|
||||
output_buffer[x_zero_coord+i][max_y_coord] = c
|
||||
for i,c in enumerate(min_y_str):
|
||||
output_buffer[x_zero_coord+i][min_y_coord] = c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def plot_data(self, xy_seq, output_buffer, plot_data):
|
||||
if self.plot_slope:
|
||||
xy_seq = list(xy_seq)
|
||||
#sort according to the x coord
|
||||
xy_seq.sort(key = lambda c: c[0])
|
||||
prev_p = xy_seq[0]
|
||||
e_xy_seq = enumerate(xy_seq)
|
||||
e_xy_seq.next()
|
||||
for i,(x,y) in e_xy_seq:
|
||||
draw_symbol = self.dot
|
||||
line_drawn = self.plot_line(prev_p, (x,y), output_buffer, plot_data)
|
||||
prev_p = (x,y)
|
||||
if not line_drawn:
|
||||
if i > 0 and i < len(xy_seq)-1:
|
||||
px,py = xy_seq[i-1]
|
||||
nx,ny = xy_seq[i+1]
|
||||
|
||||
if abs(nx-px) > EPSILON:
|
||||
slope = (1.0/plot_data.ratio)*(ny-py)/(nx-px)
|
||||
draw_symbol = self.get_symbol_by_slope(slope, draw_symbol)
|
||||
if x < plot_data.min_x or x >= plot_data.max_x or y < plot_data.min_y or y >= plot_data.max_y:
|
||||
continue
|
||||
|
||||
x_coord = self.get_coord(x, plot_data.min_x, plot_data.x_step)
|
||||
y_coord = self.get_coord(y, plot_data.min_y, plot_data.y_step)
|
||||
if x_coord >= 0 and x_coord < len(output_buffer) and y_coord >= 0 and y_coord < len(output_buffer[0]):
|
||||
if self.draw_axes:
|
||||
if y_coord == self.get_coord(0, plot_data.min_y, plot_data.y_step) and draw_symbol == "-":
|
||||
draw_symbol = "="
|
||||
output_buffer[x_coord][y_coord] = draw_symbol
|
||||
else:
|
||||
for x,y in xy_seq:
|
||||
if x < plot_data.min_x or x >= plot_data.max_x or y < plot_data.min_y or y >= plot_data.max_y:
|
||||
continue
|
||||
x_coord = self.get_coord(x, plot_data.min_x, plot_data.x_step)
|
||||
y_coord = self.get_coord(y, plot_data.min_y, plot_data.y_step)
|
||||
if x_coord >= 0 and x_coord < len(output_buffer) and y_coord > 0 and y_coord < len(output_buffer[0]):
|
||||
output_buffer[x_coord][y_coord] = self.dot
|
||||
|
||||
|
||||
def plot_line(self, start, end, output_buffer, plot_data):
|
||||
|
||||
start_coord = self.get_coord(start[0], plot_data.min_x, plot_data.x_step), self.get_coord(start[1], plot_data.min_y, plot_data.y_step)
|
||||
end_coord = self.get_coord(end[0], plot_data.min_x, plot_data.x_step), self.get_coord(end[1], plot_data.min_y, plot_data.y_step)
|
||||
|
||||
x0,y0 = start_coord
|
||||
x1,y1 = end_coord
|
||||
if (x0,y0) == (x1,y1):
|
||||
return True
|
||||
|
||||
clipped_line = clip_line(start, end, (plot_data.min_x, plot_data.min_y), (plot_data.max_x, plot_data.max_y))
|
||||
if clipped_line != None:
|
||||
start,end = clipped_line
|
||||
else:
|
||||
return False
|
||||
start_coord = self.get_coord(start[0], plot_data.min_x, plot_data.x_step), self.get_coord(start[1], plot_data.min_y, plot_data.y_step)
|
||||
end_coord = self.get_coord(end[0], plot_data.min_x, plot_data.x_step), self.get_coord(end[1], plot_data.min_y, plot_data.y_step)
|
||||
|
||||
x0,y0 = start_coord
|
||||
x1,y1 = end_coord
|
||||
if (x0,y0) == (x1,y1):
|
||||
return True
|
||||
x_zero_coord = self.get_coord(0, plot_data.min_x, plot_data.x_step)
|
||||
y_zero_coord = self.get_coord(0, plot_data.min_y, plot_data.y_step)
|
||||
|
||||
if start[0]-end[0] == 0:
|
||||
draw_symbol = "|"
|
||||
else:
|
||||
slope = (1.0/plot_data.ratio)*(end[1]-start[1])/(end[0]-start[0])
|
||||
draw_symbol = self.get_symbol_by_slope(slope, self.dot)
|
||||
try:
|
||||
|
||||
delta = x1-x0, y1-y0
|
||||
if abs(delta[0])>abs(delta[1]):
|
||||
s = sign(delta[0])
|
||||
slope = float(delta[1])/delta[0]
|
||||
for i in range(0,abs(int(delta[0]))):
|
||||
cur_draw_symbol = draw_symbol
|
||||
x = i*s
|
||||
cur_y = int(y0+slope*x)
|
||||
if self.draw_axes and cur_y == y_zero_coord and draw_symbol == "-":
|
||||
cur_draw_symbol = "="
|
||||
output_buffer[x0+x][cur_y] = cur_draw_symbol
|
||||
|
||||
|
||||
else:
|
||||
s = sign(delta[1])
|
||||
slope = float(delta[0])/delta[1]
|
||||
for i in range(0,abs(int(delta[1]))):
|
||||
y = i*s
|
||||
cur_draw_symbol = draw_symbol
|
||||
cur_y = y0+y
|
||||
if self.draw_axes and cur_y == y_zero_coord and draw_symbol == "-":
|
||||
cur_draw_symbol = "="
|
||||
output_buffer[int(x0+slope*y)][cur_y] = cur_draw_symbol
|
||||
except:
|
||||
print start, end
|
||||
print start_coord, end_coord
|
||||
print plot_data
|
||||
raise
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def plot_single(self, seq, min_x = None, max_x = None, min_y = None, max_y = None):
|
||||
return self.plot_double(range(len(seq)),seq, min_x, max_x, min_y, max_y)
|
||||
|
||||
|
||||
|
||||
|
||||
def plot_double(self, x_seq, y_seq, min_x = None, max_x = None, min_y = None, max_y = None):
|
||||
if min_x == None:
|
||||
min_x = min(x_seq)
|
||||
if max_x == None:
|
||||
max_x = max(x_seq)
|
||||
if min_y == None:
|
||||
min_y = min(y_seq)
|
||||
if max_y == None:
|
||||
max_y = max(y_seq)
|
||||
|
||||
if max_y == min_y:
|
||||
max_y += 1
|
||||
|
||||
x_mod = (max_x-min_x)*self.x_margin
|
||||
y_mod = (max_y-min_y)*self.y_margin
|
||||
min_x-=x_mod
|
||||
max_x+=x_mod
|
||||
min_y-=y_mod
|
||||
max_y+=y_mod
|
||||
|
||||
|
||||
plot_data = self.PlotData(self.x_size, self.y_size, min_x, max_x, min_y, max_y, x_mod, y_mod)
|
||||
|
||||
output_buffer = [[" "]*self.y_size for i in range(self.x_size)]
|
||||
|
||||
if self.will_draw_axes:
|
||||
self.draw_axes(output_buffer, plot_data)
|
||||
|
||||
self.plot_data(zip(x_seq, y_seq), output_buffer, plot_data)
|
||||
|
||||
if self.will_plot_labels:
|
||||
self.plot_labels(output_buffer, plot_data)
|
||||
|
||||
trans_result = transposed(y_reversed(output_buffer))
|
||||
|
||||
result = self.new_line.join(["".join(row) for row in trans_result])
|
||||
return result
|
||||
|
||||
def draw_axes(self, output_buffer, plot_data):
|
||||
|
||||
|
||||
draw_x = False
|
||||
draw_y = False
|
||||
|
||||
if plot_data.min_x <= 0 and plot_data.max_x > 0:
|
||||
draw_y = True
|
||||
zero_x = self.get_coord(0, plot_data.min_x, plot_data.x_step)
|
||||
for y in xrange(plot_data.y_size):
|
||||
output_buffer[zero_x][y] = "|"
|
||||
|
||||
if plot_data.min_y <= 0 and plot_data.max_y > 0:
|
||||
draw_x = True
|
||||
zero_y = self.get_coord(0, plot_data.min_y, plot_data.y_step)
|
||||
for x in xrange(plot_data.x_size):
|
||||
output_buffer[x][zero_y] = "-"
|
||||
|
||||
if draw_x and draw_y:
|
||||
output_buffer[zero_x][zero_y] = "+"
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_coord(val, min, step):
|
||||
result = int((val - min)/step)
|
||||
return result
|
||||
|
||||
def clip_line(line_pt_1, line_pt_2, rect_bottom_left, rect_top_right):
|
||||
ts = [0.0,1.0]
|
||||
if line_pt_1[0] == line_pt_2[0]:
|
||||
return ((line_pt_1[0], max(min(line_pt_1[1], line_pt_2[1]), rect_bottom_left[1])),
|
||||
(line_pt_1[0], min(max(line_pt_1[1], line_pt_2[1]), rect_top_right[1])))
|
||||
if line_pt_1[1] == line_pt_2[1]:
|
||||
return ((max(min(line_pt_1[0], line_pt_2[0]), rect_bottom_left[0]), line_pt_1[1]),
|
||||
(min(max(line_pt_1[0], line_pt_2[0]), rect_top_right[0]), line_pt_1[1]))
|
||||
|
||||
if ((rect_bottom_left[0] <= line_pt_1[0] and line_pt_1[0] < rect_top_right[0]) and
|
||||
(rect_bottom_left[1] <= line_pt_1[1] and line_pt_1[1] < rect_top_right[1]) and
|
||||
(rect_bottom_left[0] <= line_pt_2[0] and line_pt_2[0] < rect_top_right[0]) and
|
||||
(rect_bottom_left[1] <= line_pt_2[1] and line_pt_2[1] < rect_top_right[1])):
|
||||
return line_pt_1, line_pt_2
|
||||
|
||||
ts.append( float(rect_bottom_left[0]-line_pt_1[0])/(line_pt_2[0]-line_pt_1[0]) )
|
||||
ts.append( float(rect_top_right[0]-line_pt_1[0])/(line_pt_2[0]-line_pt_1[0]) )
|
||||
ts.append( float(rect_bottom_left[1]-line_pt_1[1])/(line_pt_2[1]-line_pt_1[1]) )
|
||||
ts.append( float(rect_top_right[1]-line_pt_1[1])/(line_pt_2[1]-line_pt_1[1]) )
|
||||
|
||||
ts.sort()
|
||||
if ts[2] < 0 or ts[2] >= 1 or ts[3] < 0 or ts[2]>= 1:
|
||||
return None
|
||||
result = [(pt_1 + t*(pt_2-pt_1)) for t in (ts[2],ts[3]) for (pt_1, pt_2) in zip(line_pt_1, line_pt_2)]
|
||||
return (result[0],result[1]), (result[2], result[3])
|
||||
|
||||
|
||||
|
||||
def plot(*args,**flags):
|
||||
limit_flags_names = set(["min_x","min_y","max_x","max_y"])
|
||||
limit_flags = dict([(n,flags[n]) for n in limit_flags_names & set(flags)])
|
||||
settting_flags = dict([(n,flags[n]) for n in set(flags) - limit_flags_names])
|
||||
|
||||
if len(args) == 1:
|
||||
p = Plotter(**settting_flags)
|
||||
print p.plot_single(args[0],**limit_flags)
|
||||
elif len(args) == 2:
|
||||
p = Plotter(**settting_flags)
|
||||
print p.plot_double(args[0],args[1],**limit_flags)
|
||||
else:
|
||||
raise NotImplementedError("can't draw multiple graphs yet")
|
||||
|
||||
__all__ = ["Plotter","plot"]
|
||||
|
Reference in New Issue
Block a user