From d08b195de44d2e85467de1764884c14973f92115 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 21 Jun 2013 00:08:01 -0700 Subject: gras: first cut at time tag class --- include/gras/CMakeLists.txt | 1 + include/gras/time_tag.hpp | 58 +++++++++++++++++++++++++++++ lib/CMakeLists.txt | 1 + lib/time_tag.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 include/gras/time_tag.hpp create mode 100644 lib/time_tag.cpp diff --git a/include/gras/CMakeLists.txt b/include/gras/CMakeLists.txt index d0e3cfa..becd6cb 100644 --- a/include/gras/CMakeLists.txt +++ b/include/gras/CMakeLists.txt @@ -16,6 +16,7 @@ install(FILES sbuffer.hpp sbuffer.i tags.hpp + time_tag.hpp tags.i tag_iter.hpp tag_iter.i diff --git a/include/gras/time_tag.hpp b/include/gras/time_tag.hpp new file mode 100644 index 0000000..d350fa2 --- /dev/null +++ b/include/gras/time_tag.hpp @@ -0,0 +1,58 @@ +// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information. + +#ifndef INCLUDED_GRAS_TIME_TAG_HPP +#define INCLUDED_GRAS_TIME_TAG_HPP + +#include +#include +#include +#include + +namespace gras +{ + +struct GRAS_API TimeTag : + boost::less_than_comparable, + boost::additive +{ + //! Default contructor - hold time 0 + TimeTag(void); + + //! Create a time tag from ticks w/ default ticks per second + static TimeTag from_ticks(const time_ticks_t ticks); + + //! Create a time tag from ticks w/ specified ticks per second + static TimeTag from_ticks(const time_ticks_t ticks, const double rate); + + //! Create a time tag from a PMC containing a PMCTuple<2>(uint64, double) + static TimeTag from_pmc(const PMCC &p); + + //! Convert this time tag to ticks w/ default ticks per second + time_ticks_t to_ticks(void); + + //! Convert this time tag to ticks w/ specified ticks per second + time_ticks_t to_ticks(const double rate); + + //! Convert this time tag to a PMC containing a PMCTuple<2>(uint64, double) + PMCC to_pmc(void); + + //! Addition for additive interface + TimeTag &operator+=(const TimeTag &); + + //! Subtraction for additive interface + TimeTag &operator-=(const TimeTag &); + + //! full seconds + time_ticks_t _fsecs; + + //! fractional ticks + time_ticks_t _ticks; +}; + +GRAS_API bool operator<(const TimeTag &lhs, const TimeTag &rhs); + +GRAS_API bool operator==(const TimeTag &lhs, const TimeTag &rhs); + +} //namespace gras + +#endif /*INCLUDED_GRAS_TIME_TAG_HPP*/ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 44b3012..ab5d886 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -48,6 +48,7 @@ list(APPEND GRAS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/buffer_queue_circ.cpp ${CMAKE_CURRENT_SOURCE_DIR}/buffer_queue_pool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tags.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/time_tag.cpp ${CMAKE_CURRENT_SOURCE_DIR}/block.cpp ${CMAKE_CURRENT_SOURCE_DIR}/block_config.cpp ${CMAKE_CURRENT_SOURCE_DIR}/block_message.cpp diff --git a/lib/time_tag.cpp b/lib/time_tag.cpp new file mode 100644 index 0000000..d438fc3 --- /dev/null +++ b/lib/time_tag.cpp @@ -0,0 +1,89 @@ +// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information. + +#include +#include +#include //uint64 +#include + +using namespace gras; + +static TimeTag &normalize(TimeTag &t) +{ + int num = int(t._ticks/time_tps()); + if (num < 0) num--; //stops negative ticks + t._fsecs += num; + t._ticks -= num*time_tps(); + return t; +} + +TimeTag::TimeTag(void): + _fsecs(0), _ticks(0) +{/*NOP*/} + +TimeTag TimeTag::from_ticks(const time_ticks_t ticks) +{ + TimeTag t; + t._ticks = ticks; + return normalize(t); +} + +TimeTag TimeTag::from_ticks(const time_ticks_t ticks, const double rate) +{ + TimeTag t; + t._fsecs = time_ticks_t(ticks/rate); + const double error = ticks - (t._fsecs*rate); + t._ticks = boost::math::llround((error*time_tps())/rate); + return normalize(t); +} + +TimeTag TimeTag::from_pmc(const PMCC &p) +{ + TimeTag t; + const PMCTuple<2> &tuple = p.as >(); + t._fsecs = tuple[0].as(); + t._ticks = boost::math::llround(tuple[1].as()*time_tps()); + return normalize(t); +} + +time_ticks_t TimeTag::to_ticks(void) +{ + return _fsecs*time_tps() + _ticks; +} + +time_ticks_t TimeTag::to_ticks(const double rate) +{ + return _fsecs*time_tps() + boost::math::llround((_ticks*rate)/time_tps()); +} + +PMCC TimeTag::to_pmc(void) +{ + PMCTuple<2> tuple; + tuple[0] = PMC_M(_fsecs); + tuple[1] = PMC_M(_ticks/double(time_tps())); + return PMC_M(tuple); +} + +TimeTag &TimeTag::operator+=(const TimeTag &rhs) +{ + _fsecs += rhs._fsecs; + _ticks += rhs._ticks; + return normalize(*this); +} + +TimeTag &TimeTag::operator-=(const TimeTag &rhs) +{ + _fsecs -= rhs._fsecs; + _ticks -= rhs._ticks; + return normalize(*this); +} + +bool gras::operator<(const TimeTag &lhs, const TimeTag &rhs) +{ + if (lhs._fsecs == rhs._fsecs) return lhs._ticks < rhs._ticks; + return lhs._fsecs < rhs._fsecs; +} + +bool gras::operator==(const TimeTag &lhs, const TimeTag &rhs) +{ + return (lhs._fsecs == rhs._fsecs) and (lhs._ticks == rhs._ticks); +} -- cgit From d47f192f9edfc2332fa73e6ed6c4c1cdedefb96c Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 21 Jun 2013 00:33:31 -0700 Subject: gras: adding pythonic interface to time tag --- include/gras/CMakeLists.txt | 3 ++- include/gras/time_tag.hpp | 7 +++++++ include/gras/time_tag.i | 39 +++++++++++++++++++++++++++++++++++++++ lib/serialize_types.cpp | 15 +++++++++++++++ python/gras/CMakeLists.txt | 2 ++ python/gras/GRAS_TimeTag.i | 14 ++++++++++++++ python/gras/__init__.py | 1 + 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 include/gras/time_tag.i create mode 100644 python/gras/GRAS_TimeTag.i diff --git a/include/gras/CMakeLists.txt b/include/gras/CMakeLists.txt index becd6cb..176d5e5 100644 --- a/include/gras/CMakeLists.txt +++ b/include/gras/CMakeLists.txt @@ -16,8 +16,9 @@ install(FILES sbuffer.hpp sbuffer.i tags.hpp - time_tag.hpp tags.i + time_tag.hpp + time_tag.i tag_iter.hpp tag_iter.i thread_pool.hpp diff --git a/include/gras/time_tag.hpp b/include/gras/time_tag.hpp index d350fa2..9459f9f 100644 --- a/include/gras/time_tag.hpp +++ b/include/gras/time_tag.hpp @@ -11,6 +11,13 @@ namespace gras { +/*! + * TimeTag represents an absolute time or a time delta. + * A TimeTag can be converted to and from a tick count. + * Conversion support is provided for the pseudo-standard + * PMCTuple format often used inside a StreamTag value. + * And TimeTag supports overloaded arithmetic operations. + */ struct GRAS_API TimeTag : boost::less_than_comparable, boost::additive diff --git a/include/gras/time_tag.i b/include/gras/time_tag.i new file mode 100644 index 0000000..fb3d721 --- /dev/null +++ b/include/gras/time_tag.i @@ -0,0 +1,39 @@ +// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information. + +#ifndef INCLUDED_GRAS_TIME_TAG_I +#define INCLUDED_GRAS_TIME_TAG_I + +%{ +#include +%} + +//////////////////////////////////////////////////////////////////////// +// remove base class warning -- boost::less_than_comparable +// remove base class warning -- boost::additive +//////////////////////////////////////////////////////////////////////// +#pragma SWIG nowarn=401 + +%include +%include +%include +%import + +//////////////////////////////////////////////////////////////////////// +// Make it pythonic +//////////////////////////////////////////////////////////////////////// +%extend gras::TimeTag +{ + bool __nonzero__(void) + { + return ($self)->to_ticks() != 0; + } + + int __cmp__(const TimeTag &other) + { + if ((*($self)) < other) return -1; + if ((*($self)) > other) return +1; + return 0; + } +} + +#endif /*INCLUDED_GRAS_TIME_TAG_I*/ diff --git a/lib/serialize_types.cpp b/lib/serialize_types.cpp index afa9481..fde7277 100644 --- a/lib/serialize_types.cpp +++ b/lib/serialize_types.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -82,3 +83,17 @@ void serialize(Archive &ar, gras::StreamTag &t, const unsigned int) }} PMC_SERIALIZE_EXPORT(gras::StreamTag, "PMC") + +/*********************************************************************** + * support for time tag type + **********************************************************************/ +namespace boost { namespace serialization { +template +void serialize(Archive &ar, gras::TimeTag &t, const unsigned int) +{ + ar & t._fsecs; + ar & t._ticks; +} +}} + +PMC_SERIALIZE_EXPORT(gras::TimeTag, "PMC") diff --git a/python/gras/CMakeLists.txt b/python/gras/CMakeLists.txt index 8171e7f..a7fc8f3 100644 --- a/python/gras/CMakeLists.txt +++ b/python/gras/CMakeLists.txt @@ -19,6 +19,7 @@ set(GR_SWIG_LIBRARIES gras) file(GLOB GR_SWIG_SOURCE_DEPS "${GRAS_SOURCE_DIR}/include/gras/*.i") GR_SWIG_MAKE(GRAS_Tags GRAS_Tags.i) +GR_SWIG_MAKE(GRAS_TimeTag GRAS_TimeTag.i) GR_SWIG_MAKE(GRAS_Block GRAS_Block.i) GR_SWIG_MAKE(GRAS_HierBlock GRAS_HierBlock.i) GR_SWIG_MAKE(GRAS_ThreadPool GRAS_ThreadPool.i) @@ -26,6 +27,7 @@ GR_SWIG_MAKE(GRAS_SBuffer GRAS_SBuffer.i) GR_SWIG_INSTALL( TARGETS GRAS_Tags + GRAS_TimeTag GRAS_Block GRAS_HierBlock GRAS_ThreadPool diff --git a/python/gras/GRAS_TimeTag.i b/python/gras/GRAS_TimeTag.i new file mode 100644 index 0000000..3b77dfb --- /dev/null +++ b/python/gras/GRAS_TimeTag.i @@ -0,0 +1,14 @@ +// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information. + + +%include + +%include + +DECL_PMC_SWIG_TYPE(gras::TimeTag, swig_time_tag) + +%pythoncode %{ +from GRAS_TimeTag import TimeTag +%} + +REG_PMC_SWIG_TYPE(swig_time_tag, TimeTag) diff --git a/python/gras/__init__.py b/python/gras/__init__.py index 33d2b00..cf2c7f5 100644 --- a/python/gras/__init__.py +++ b/python/gras/__init__.py @@ -3,6 +3,7 @@ from PMC import * from GRAS_SBuffer import SBufferConfig, SBuffer from GRAS_Tags import Tag, StreamTag, PacketMsg +from GRAS_TimeTag import TimeTag from GRAS_Block import Block from GRAS_HierBlock import HierBlock, TopBlock from GRAS_ThreadPool import ThreadPoolConfig, ThreadPool -- cgit From 90579c89748704dc1d7e9d80120c42988481197c Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 21 Jun 2013 18:45:45 -0700 Subject: gras: added unit tests for time tags --- include/gras/time_tag.i | 10 ++++++ tests/CMakeLists.txt | 1 + tests/serialize_tags_test.cpp | 9 ++++++ tests/time_tags_test.py | 74 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 tests/time_tags_test.py diff --git a/include/gras/time_tag.i b/include/gras/time_tag.i index fb3d721..42deed7 100644 --- a/include/gras/time_tag.i +++ b/include/gras/time_tag.i @@ -34,6 +34,16 @@ if ((*($self)) > other) return +1; return 0; } + + TimeTag __add__(const TimeTag &other) + { + return (*($self)) + other; + } + + TimeTag __sub__(const TimeTag &other) + { + return (*($self)) - other; + } } #endif /*INCLUDED_GRAS_TIME_TAG_I*/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 761239e..bc3815e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -47,3 +47,4 @@ GR_ADD_TEST(thread_pool_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/th GR_ADD_TEST(sbuffer_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/sbuffer_test.py) GR_ADD_TEST(query_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/query_test.py) GR_ADD_TEST(block_props_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/block_props_test.py) +GR_ADD_TEST(time_tags_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/time_tags_test.py) diff --git a/tests/serialize_tags_test.cpp b/tests/serialize_tags_test.cpp index 58d9b1e..b4f9ec2 100644 --- a/tests/serialize_tags_test.cpp +++ b/tests/serialize_tags_test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -65,3 +66,11 @@ BOOST_AUTO_TEST_CASE(test_pkt_msg_type) BOOST_CHECK(pkt_msg.info.eq(result_msg.info)); BOOST_CHECK(std::memcmp(pkt_msg.buff.get(), result_msg.buff.get(), pkt_msg.buff.length) == 0); } + +BOOST_AUTO_TEST_CASE(test_time_tag_type) +{ + const gras::TimeTag t0 = gras::TimeTag::from_ticks(42); + PMCC result = loopback_test(PMC_M(t0)); + const gras::TimeTag &t1 = result.as(); + BOOST_CHECK(t0 == t1); +} diff --git a/tests/time_tags_test.py b/tests/time_tags_test.py new file mode 100644 index 0000000..7d0221f --- /dev/null +++ b/tests/time_tags_test.py @@ -0,0 +1,74 @@ +# Copyright (C) by Josh Blum. See LICENSE.txt for licensing information. + +import unittest +import gras +import numpy +import time + +class TimeTagsTest(unittest.TestCase): + + def test_make_time_tag(self): + t0 = gras.TimeTag() + + def test_make_time_tag_from_ticks(self): + t0 = gras.TimeTag.from_ticks(42) + self.assertEqual(t0.to_ticks(), 42) + + t1 = gras.TimeTag.from_ticks(1000, 1e9) + self.assertEqual(t1.to_ticks(1e9), 1000) + + t2 = gras.TimeTag.from_ticks(-1000, 1e9) + self.assertEqual(t2.to_ticks(1e9), -1000) + + time_ns_now = long(time.time()*1e9) + t3 = gras.TimeTag.from_ticks(time_ns_now, 1e9) + self.assertEqual(t3.to_ticks(1e9), time_ns_now) + + def test_time_tag_compare(self): + t0 = gras.TimeTag.from_ticks(42) + t1 = gras.TimeTag.from_ticks(-1000) + self.assertEqual(t0, t0) + self.assertEqual(t1, t1) + self.assertGreater(t0, t1) + self.assertLess(t1, t0) + + def test_time_tag_add(self): + t0 = gras.TimeTag.from_ticks(42) + t1 = gras.TimeTag.from_ticks(-1000) + t2 = t0 + t1 + self.assertEqual(t2.to_ticks(), (42 + -1000)) + + t3 = gras.TimeTag.from_ticks(99) + t4 = gras.TimeTag.from_ticks(-98) + t3 += t4 + self.assertEqual(t3.to_ticks(), (99 + -98)) + + def test_time_tag_sub(self): + t0 = gras.TimeTag.from_ticks(42) + t1 = gras.TimeTag.from_ticks(-1000) + t2 = t0 - t1 + self.assertEqual(t2.to_ticks(), (42 + 1000)) + + t3 = gras.TimeTag.from_ticks(99) + t4 = gras.TimeTag.from_ticks(-98) + t3 -= t4 + self.assertEqual(t3.to_ticks(), (99 + 98)) + + def test_time_tag_pmc_foo(self): + time_us_now = long(time.time()*1e6) + t0 = gras.TimeTag.from_ticks(time_us_now, 1e6) + + #test the validity of the time tuple + p0 = t0.to_pmc() + tp = p0() + full = long(time_us_now/1e6) + self.assertEqual(tp[0], full) + delta = time_us_now - full*long(1e6) + self.assertEqual(tp[1], (delta/1e6)) + + #and check if it loops back + t1 = gras.TimeTag.from_pmc(p0) + self.assertEqual(t0, t1) + +if __name__ == '__main__': + unittest.main() -- cgit