summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
m---------grextras0
-rw-r--r--include/gras/CMakeLists.txt1
-rw-r--r--include/gras/chrono.hpp74
-rw-r--r--include/gras/top_block.hpp5
-rw-r--r--lib/CMakeLists.txt1
-rw-r--r--lib/block.cpp6
-rw-r--r--lib/block_actor.cpp7
-rw-r--r--lib/block_handlers.cpp12
-rw-r--r--lib/block_task.cpp24
-rw-r--r--lib/element.cpp3
-rw-r--r--lib/element_impl.hpp1
-rw-r--r--lib/gras_impl/block_actor.hpp11
-rw-r--r--lib/gras_impl/debug.hpp32
-rw-r--r--lib/gras_impl/messages.hpp9
-rw-r--r--lib/gras_impl/stats.hpp30
-rw-r--r--lib/register_messages.cpp1
-rw-r--r--lib/tag_handlers.hpp3
-rw-r--r--lib/top_block_stats.cpp63
-rw-r--r--lib/topology_handler.cpp6
-rw-r--r--python/gras/CMakeLists.txt16
-rw-r--r--python/gras/stats/__init__.py36
-rw-r--r--python/gras/stats/main.css1
-rw-r--r--python/gras/stats/main.html22
-rw-r--r--python/gras/stats/main.js109
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/stats_test.py28
26 files changed, 442 insertions, 60 deletions
diff --git a/grextras b/grextras
-Subproject c721d3eeb3b678e1f657e25b8397857b3200c6a
+Subproject f274e53f3a74f7fbac6f241504be3eafee30e58
diff --git a/include/gras/CMakeLists.txt b/include/gras/CMakeLists.txt
index 15651c4..301f96a 100644
--- a/include/gras/CMakeLists.txt
+++ b/include/gras/CMakeLists.txt
@@ -3,6 +3,7 @@
########################################################################
install(FILES
+ chrono.hpp
block.hpp
block.i
element.hpp
diff --git a/include/gras/chrono.hpp b/include/gras/chrono.hpp
new file mode 100644
index 0000000..c976675
--- /dev/null
+++ b/include/gras/chrono.hpp
@@ -0,0 +1,74 @@
+// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information.
+
+//Boost chrono has awesome high res timer!
+//But its only in very recent boosts,
+//so we have this little wrapper...
+//now/tps inspired by libnumanuma.
+
+#ifndef INCLUDED_GRAS_CHRONO_HPP
+#define INCLUDED_GRAS_CHRONO_HPP
+
+#include <gras/gras.hpp>
+
+namespace gras
+{
+ typedef long long time_ticks_t;
+
+ //! Get the time now in tick counts
+ time_ticks_t time_now(void);
+
+ //! Get the number of ticks per second
+ time_ticks_t time_tps(void);
+}
+
+//--------------------------------------------------------------------//
+//------------------ implementation details below --------------------//
+//--------------------------------------------------------------------//
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
+
+#include <windows.h>
+
+namespace gras
+{
+
+ GRAS_FORCE_INLINE time_ticks_t time_now(void)
+ {
+ LARGE_INTEGER counts;
+ QueryPerformanceCounter(&counts);
+ return counts.QuadPart;
+ }
+
+ GRAS_FORCE_INLINE time_ticks_t time_tps(void)
+ {
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency(&freq);
+ return freq.QuadPart;
+ }
+
+} //namespace gras
+
+#else
+
+#include <ctime>
+
+namespace gras
+{
+
+ GRAS_FORCE_INLINE time_ticks_t time_now(void)
+ {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec*1000000000UL + ts.tv_nsec;
+ }
+
+ GRAS_FORCE_INLINE time_ticks_t time_tps(void)
+ {
+ return 1000000000UL;
+ }
+
+} //namespace gras
+
+#endif
+
+#endif /*INCLUDED_GRAS_CHRONO_HPP*/
diff --git a/include/gras/top_block.hpp b/include/gras/top_block.hpp
index fadcefa..497f54f 100644
--- a/include/gras/top_block.hpp
+++ b/include/gras/top_block.hpp
@@ -77,6 +77,11 @@ struct GRAS_API TopBlock : HierBlock
*/
virtual bool wait(const double timeout);
+ /*!
+ * Get block usage statistics in XML format.
+ * An external app will visualize the data.
+ */
+ std::string get_stats_xml(void);
};
} //namespace gras
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 715f844..76e4f2d 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -57,6 +57,7 @@ list(APPEND GRAS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/output_handlers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/hier_block.cpp
${CMAKE_CURRENT_SOURCE_DIR}/top_block.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/top_block_stats.cpp
${CMAKE_CURRENT_SOURCE_DIR}/register_messages.cpp
)
diff --git a/lib/block.cpp b/lib/block.cpp
index 330e1a6..424df83 100644
--- a/lib/block.cpp
+++ b/lib/block.cpp
@@ -164,16 +164,17 @@ void Block::produce(const size_t num_items)
item_index_t Block::get_consumed(const size_t which_input)
{
- return (*this)->block->items_consumed[which_input];
+ return (*this)->block->stats.items_consumed[which_input];
}
item_index_t Block::get_produced(const size_t which_output)
{
- return (*this)->block->items_produced[which_output];
+ return (*this)->block->stats.items_produced[which_output];
}
void Block::post_output_tag(const size_t which_output, const Tag &tag)
{
+ (*this)->block->stats.items_produced[which_output]++;
(*this)->block->post_downstream(which_output, InputTagMessage(tag));
}
@@ -194,6 +195,7 @@ PMCC Block::pop_input_msg(const size_t which_input)
if (input_tags.empty()) return PMCC();
PMCC p = input_tags.front().object;
input_tags.erase(input_tags.begin());
+ (*this)->block->stats.items_consumed[which_input]++;
return p;
}
diff --git a/lib/block_actor.cpp b/lib/block_actor.cpp
index 19f8f97..5b1c5db 100644
--- a/lib/block_actor.cpp
+++ b/lib/block_actor.cpp
@@ -88,16 +88,9 @@ BlockActor::BlockActor(void):
active_thread_pool.reset(); //actors hold this, now its safe to reset, weak_framework only
}
this->register_handlers();
- this->handle_task_count = 0;
- this->work_count = 0;
}
BlockActor::~BlockActor(void)
{
this->mark_done();
- #ifdef WORK_COUNTS
- if (work_count == 0) std::cerr << "\n WORK FAIL!!!" << std::endl;
- std::cerr << name << " handle_task_count " << handle_task_count << std::endl;
- std::cerr << name << " work_count " << work_count << std::endl;
- #endif
}
diff --git a/lib/block_handlers.cpp b/lib/block_handlers.cpp
index 1290a67..73a22da 100644
--- a/lib/block_handlers.cpp
+++ b/lib/block_handlers.cpp
@@ -16,6 +16,7 @@ void BlockActor::handle_top_active(
if (this->block_state != BLOCK_STATE_LIVE)
{
this->block_ptr->notify_active();
+ this->stats.start_time = time_now();
}
this->block_state = BLOCK_STATE_LIVE;
this->active_token = message.token;
@@ -128,3 +129,14 @@ void BlockActor::handle_self_kick(
MESSAGE_TRACER();
this->handle_task();
}
+
+void BlockActor::handle_get_stats(
+ const GetStatsMessage &,
+ const Theron::Address from
+){
+ GetStatsMessage message;
+ message.block_id = this->block_ptr->to_string();
+ message.stats = this->stats;
+ message.stats_time = time_now();
+ this->Send(message, from); //ACK
+}
diff --git a/lib/block_task.cpp b/lib/block_task.cpp
index b97dabf..52cf505 100644
--- a/lib/block_task.cpp
+++ b/lib/block_task.cpp
@@ -9,6 +9,7 @@ void BlockActor::mark_done(void)
{
if (this->block_state == BLOCK_STATE_DONE) return; //can re-enter checking done first
+ this->stats.stop_time = time_now();
this->block_ptr->notify_inactive();
//flush partial output buffers to the downstream
@@ -98,18 +99,13 @@ void BlockActor::output_fail(const size_t i)
void BlockActor::handle_task(void)
{
+ const time_ticks_t task_start = time_now();
//------------------------------------------------------------------
//-- Decide if its possible to continue any processing:
//-- Handle task may get called for incoming buffers,
//-- however, not all ports may have available buffers.
//------------------------------------------------------------------
- this->handle_task_count++;
if GRAS_UNLIKELY(not this->is_work_allowed()) return;
- this->work_count++;
-
- #ifdef WORK_DEBUG
- WorkDebugPrinter WDP(block_ptr->to_string());
- #endif
//------------------------------------------------------------------
//-- Asynchronous notification through atomic variable
@@ -185,6 +181,7 @@ void BlockActor::handle_task(void)
//------------------------------------------------------------------
//-- the work
//------------------------------------------------------------------
+ const time_ticks_t work_start = time_now();
if GRAS_UNLIKELY(this->interruptible_thread)
{
this->interruptible_thread->call();
@@ -193,6 +190,7 @@ void BlockActor::handle_task(void)
{
this->task_work();
}
+ const time_ticks_t work_stop = time_now();
//------------------------------------------------------------------
//-- Flush output buffers downstream
@@ -216,6 +214,14 @@ void BlockActor::handle_task(void)
//still have IO ready? kick off another task
this->task_kicker();
+
+ //save stats
+ const time_ticks_t task_time = time_now() - task_start;
+ const time_ticks_t work_time = work_stop - work_start;
+ this->stats.work_count++;
+ this->stats.total_time_work += work_time;
+ this->stats.total_time_work_other += task_time - work_time;
+ this->stats.time_last_work = work_stop;
}
void BlockActor::consume(const size_t i, const size_t items)
@@ -223,7 +229,7 @@ void BlockActor::consume(const size_t i, const size_t items)
#ifdef ITEM_CONSPROD
std::cerr << name << " consume " << items << std::endl;
#endif
- this->items_consumed[i] += items;
+ this->stats.items_consumed[i] += items;
const size_t bytes = items*this->input_items_sizes[i];
this->input_queues.consume(i, bytes);
this->trim_tags(i);
@@ -235,7 +241,7 @@ void BlockActor::produce(const size_t i, const size_t items)
std::cerr << name << " produce " << items << std::endl;
#endif
SBuffer &buff = this->output_queues.front(i);
- this->items_produced[i] += items;
+ this->stats.items_produced[i] += items;
const size_t bytes = items*this->output_items_sizes[i];
buff.length += bytes;
}
@@ -244,7 +250,7 @@ void BlockActor::produce_buffer(const size_t i, const SBuffer &buffer)
{
this->flush_output(i);
const size_t items = buffer.length/output_items_sizes[i];
- this->items_produced[i] += items;
+ this->stats.items_produced[i] += items;
InputBufferMessage buff_msg;
buff_msg.buffer = buffer;
this->post_downstream(i, buff_msg);
diff --git a/lib/element.cpp b/lib/element.cpp
index 71e4e69..368dbea 100644
--- a/lib/element.cpp
+++ b/lib/element.cpp
@@ -19,6 +19,7 @@ Element::Element(const std::string &name)
this->reset(new ElementImpl());
(*this)->name = name;
(*this)->unique_id = ++unique_id_pool;
+ (*this)->id = str(boost::format("%s(%d)") % this->name() % this->unique_id());
if (GENESIS) std::cerr << "New element: " << to_string() << std::endl;
}
@@ -42,5 +43,5 @@ std::string Element::name(void) const
std::string Element::to_string(void) const
{
- return str(boost::format("%s(%d)") % this->name() % this->unique_id());
+ return (*this)->id;
}
diff --git a/lib/element_impl.hpp b/lib/element_impl.hpp
index 9e3c9b3..c707268 100644
--- a/lib/element_impl.hpp
+++ b/lib/element_impl.hpp
@@ -26,6 +26,7 @@ struct ElementImpl
//common element properties
std::string name;
long unique_id;
+ std::string id;
//top block stuff
SharedThreadGroup thread_group;
diff --git a/lib/gras_impl/block_actor.hpp b/lib/gras_impl/block_actor.hpp
index dca21e6..ba69567 100644
--- a/lib/gras_impl/block_actor.hpp
+++ b/lib/gras_impl/block_actor.hpp
@@ -11,6 +11,7 @@
#include <gras/thread_pool.hpp>
#include <Apology/Worker.hpp>
#include <gras_impl/token.hpp>
+#include <gras_impl/stats.hpp>
#include <gras_impl/messages.hpp>
#include <gras_impl/output_buffer_queues.hpp>
#include <gras_impl/input_buffer_queues.hpp>
@@ -56,6 +57,7 @@ struct BlockActor : Apology::Worker
this->RegisterHandler(this, &BlockActor::handle_output_update);
this->RegisterHandler(this, &BlockActor::handle_self_kick);
+ this->RegisterHandler(this, &BlockActor::handle_get_stats);
}
//handlers
@@ -83,6 +85,7 @@ struct BlockActor : Apology::Worker
void handle_output_update(const OutputUpdateMessage &, const Theron::Address);
void handle_self_kick(const SelfKickMessage &, const Theron::Address);
+ void handle_get_stats(const GetStatsMessage &, const Theron::Address);
//helpers
void buffer_returner(const size_t index, SBuffer &buffer);
@@ -124,10 +127,6 @@ struct BlockActor : Apology::Worker
std::vector<InputPortConfig> input_configs;
std::vector<OutputPortConfig> output_configs;
- //keeps track of production
- std::vector<item_index_t> items_consumed;
- std::vector<item_index_t> items_produced;
-
//work buffers for the new work interface
Block::InputItems input_items;
Block::OutputItems output_items;
@@ -171,9 +170,7 @@ struct BlockActor : Apology::Worker
std::vector<std::vector<OutputHintMessage> > output_allocation_hints;
- //status keepers
- size_t handle_task_count;
- size_t work_count;
+ BlockStats stats;
};
} //namespace gras
diff --git a/lib/gras_impl/debug.hpp b/lib/gras_impl/debug.hpp
index 61376d5..c01e7d4 100644
--- a/lib/gras_impl/debug.hpp
+++ b/lib/gras_impl/debug.hpp
@@ -24,11 +24,9 @@ extern void *operator new(std::size_t n) throw (std::bad_alloc);
//----------------------------------------------------------------------
//-- define to enable these debugs:
//----------------------------------------------------------------------
-//#define WORK_DEBUG
-//#define ASSERTING
+#define ASSERTING
//#define MESSAGE_TRACING
//#define ITEM_CONSPROD
-//#define WORK_COUNTS
//----------------------------------------------------------------------
//-- time accumulation printer
@@ -68,32 +66,4 @@ extern void *operator new(std::size_t n) throw (std::bad_alloc);
#define ASSERT(x)
#endif
-//----------------------------------------------------------------------
-//-- implementation for work debug
-//----------------------------------------------------------------------
-#ifdef WORK_DEBUG
-
-#include <boost/thread/mutex.hpp>
-
-static boost::mutex work_debug_mutex;
-
-struct WorkDebugPrinter
-{
- WorkDebugPrinter(const std::string &name):
- lock(work_debug_mutex), name(name)
- {
- std::cerr << "-----> begin work on " << name << std::endl;
- }
-
- ~WorkDebugPrinter(void)
- {
- std::cerr << "<----- end work on " << name << std::endl;
- }
-
- boost::mutex::scoped_lock lock;
- std::string name;
-};
-
-#endif
-
#endif /*INCLUDED_LIBGRAS_IMPL_DEBUG_HPP*/
diff --git a/lib/gras_impl/messages.hpp b/lib/gras_impl/messages.hpp
index 57f308b..386cd0a 100644
--- a/lib/gras_impl/messages.hpp
+++ b/lib/gras_impl/messages.hpp
@@ -8,6 +8,7 @@
#include <gras/tags.hpp>
#include <gras/sbuffer.hpp>
#include <gras_impl/token.hpp>
+#include <gras_impl/stats.hpp>
namespace gras
{
@@ -128,6 +129,13 @@ struct SelfKickMessage
//empty
};
+struct GetStatsMessage
+{
+ std::string block_id;
+ BlockStats stats;
+ time_ticks_t stats_time;
+};
+
} //namespace gras
#include <Theron/Register.h>
@@ -157,5 +165,6 @@ THERON_DECLARE_REGISTERED_MESSAGE(gras::OutputAllocMessage);
THERON_DECLARE_REGISTERED_MESSAGE(gras::OutputUpdateMessage);
THERON_DECLARE_REGISTERED_MESSAGE(gras::SelfKickMessage);
+THERON_DECLARE_REGISTERED_MESSAGE(gras::GetStatsMessage);
#endif /*INCLUDED_LIBGRAS_IMPL_MESSAGES_HPP*/
diff --git a/lib/gras_impl/stats.hpp b/lib/gras_impl/stats.hpp
new file mode 100644
index 0000000..9c9c8e1
--- /dev/null
+++ b/lib/gras_impl/stats.hpp
@@ -0,0 +1,30 @@
+// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information.
+
+#ifndef INCLUDED_LIBGRAS_IMPL_STATS_HPP
+#define INCLUDED_LIBGRAS_IMPL_STATS_HPP
+
+#include <gras/chrono.hpp>
+#include <vector>
+
+namespace gras
+{
+
+struct BlockStats
+{
+ time_ticks_t start_time;
+ time_ticks_t stop_time;
+
+ std::vector<item_index_t> items_consumed;
+ std::vector<item_index_t> tags_consumed;
+ std::vector<item_index_t> items_produced;
+ std::vector<item_index_t> tags_produced;
+
+ item_index_t work_count;
+ time_ticks_t time_last_work;
+ time_ticks_t total_time_work;
+ time_ticks_t total_time_work_other;
+};
+
+} //namespace gras
+
+#endif /*INCLUDED_LIBGRAS_IMPL_STATS_HPP*/
diff --git a/lib/register_messages.cpp b/lib/register_messages.cpp
index caa8ed8..f68bc14 100644
--- a/lib/register_messages.cpp
+++ b/lib/register_messages.cpp
@@ -26,3 +26,4 @@ THERON_DEFINE_REGISTERED_MESSAGE(gras::OutputAllocMessage);
THERON_DEFINE_REGISTERED_MESSAGE(gras::OutputUpdateMessage);
THERON_DEFINE_REGISTERED_MESSAGE(gras::SelfKickMessage);
+THERON_DEFINE_REGISTERED_MESSAGE(gras::GetStatsMessage);
diff --git a/lib/tag_handlers.hpp b/lib/tag_handlers.hpp
index e2eb3b9..d42ce02 100644
--- a/lib/tag_handlers.hpp
+++ b/lib/tag_handlers.hpp
@@ -25,7 +25,7 @@ GRAS_FORCE_INLINE void BlockActor::trim_tags(const size_t i)
//------------------------------------------------------------------
std::vector<Tag> &tags_i = this->input_tags[i];
- const size_t items_consumed_i = this->items_consumed[i];
+ const size_t items_consumed_i = this->stats.items_consumed[i];
size_t last = 0;
while (last < tags_i.size() and tags_i[last].offset < items_consumed_i)
{
@@ -39,6 +39,7 @@ GRAS_FORCE_INLINE void BlockActor::trim_tags(const size_t i)
//now its safe to perform the erasure
tags_i.erase(tags_i.begin(), tags_i.begin()+last);
+ this->stats.items_consumed[i] += last;
}
} //namespace gras
diff --git a/lib/top_block_stats.cpp b/lib/top_block_stats.cpp
new file mode 100644
index 0000000..5d206e2
--- /dev/null
+++ b/lib/top_block_stats.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) by Josh Blum. See LICENSE.txt for licensing information.
+
+#include "element_impl.hpp"
+#include <gras/top_block.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+
+using namespace gras;
+
+struct GetStatsReceiver : Theron::Receiver
+{
+ GetStatsReceiver(void)
+ {
+ this->RegisterHandler(this, &GetStatsReceiver::handle_get_stats);
+ }
+
+ void handle_get_stats(const GetStatsMessage &message, const Theron::Address)
+ {
+ this->messages.push_back(message);
+ }
+
+ std::vector<GetStatsMessage> messages;
+};
+
+std::string TopBlock::get_stats_xml(void)
+{
+ GetStatsReceiver receiver;
+ (*this)->executor->post_all(GetStatsMessage(), receiver);
+ std::string xml;
+ xml += str(boost::format(" <now>%llu</now>\n") % time_now());
+ xml += str(boost::format(" <tps>%llu</tps>\n") % time_tps());
+ BOOST_FOREACH(const GetStatsMessage &message, receiver.messages)
+ {
+ const BlockStats &stats = message.stats;
+ std::string block_xml;
+ block_xml += str(boost::format(" <tps>%llu</tps>\n") % time_tps());
+ block_xml += str(boost::format(" <stats_time>%llu</stats_time>\n") % message.stats_time);
+ block_xml += str(boost::format(" <start_time>%llu</start_time>\n") % stats.start_time);
+ block_xml += str(boost::format(" <stop_time>%llu</stop_time>\n") % stats.stop_time);
+ block_xml += str(boost::format(" <work_count>%llu</work_count>\n") % stats.work_count);
+ block_xml += str(boost::format(" <time_last_work>%llu</time_last_work>\n") % stats.time_last_work);
+ block_xml += str(boost::format(" <total_time_work>%llu</total_time_work>\n") % stats.total_time_work);
+ block_xml += str(boost::format(" <total_time_work_other>%llu</total_time_work_other>\n") % stats.total_time_work_other);
+ for (size_t i = 0; i < stats.items_consumed.size(); i++)
+ {
+ block_xml += str(boost::format(" <items_consumed>%llu</items_consumed>\n") % stats.items_consumed[i]);
+ }
+ for (size_t i = 0; i < stats.tags_consumed.size(); i++)
+ {
+ block_xml += str(boost::format(" <tags_consumed>%llu</tags_consumed>\n") % stats.tags_consumed[i]);
+ }
+ for (size_t i = 0; i < stats.items_produced.size(); i++)
+ {
+ block_xml += str(boost::format(" <items_produced>%llu</items_produced>\n") % stats.items_produced[i]);
+ }
+ for (size_t i = 0; i < stats.tags_produced.size(); i++)
+ {
+ block_xml += str(boost::format(" <tags_produced>%llu</tags_produced>\n") % stats.tags_produced[i]);
+ }
+ xml += str(boost::format(" <block id=\"%s\">\n%s</block>\n") % message.block_id % block_xml);
+ }
+ return str(boost::format("<gras_stats>\n%s</gras_stats>") % xml);
+}
diff --git a/lib/topology_handler.cpp b/lib/topology_handler.cpp
index af4dead..4f12619 100644
--- a/lib/topology_handler.cpp
+++ b/lib/topology_handler.cpp
@@ -39,8 +39,10 @@ void BlockActor::handle_topology(
resize_fill_back(this->output_configs, num_outputs);
//resize the bytes consumed/produced
- resize_fill_grow(this->items_consumed, num_inputs, 0);
- resize_fill_grow(this->items_produced, num_outputs, 0);
+ resize_fill_grow(this->stats.items_consumed, num_inputs, 0);
+ resize_fill_grow(this->stats.tags_consumed, num_inputs, 0);
+ resize_fill_grow(this->stats.items_produced, num_outputs, 0);
+ resize_fill_grow(this->stats.tags_produced, num_outputs, 0);
//resize all work buffers to match current connections
this->input_items.resize(num_inputs);
diff --git a/python/gras/CMakeLists.txt b/python/gras/CMakeLists.txt
index 8171e7f..4c72e9b 100644
--- a/python/gras/CMakeLists.txt
+++ b/python/gras/CMakeLists.txt
@@ -42,3 +42,19 @@ GR_PYTHON_INSTALL(
DESTINATION ${GR_PYTHON_DIR}/gras
COMPONENT ${GRAS_COMP_PYTHON}
)
+
+GR_PYTHON_INSTALL(
+ FILES
+ stats/__init__.py
+ DESTINATION ${GR_PYTHON_DIR}/gras/stats
+ COMPONENT ${GRAS_COMP_PYTHON}
+)
+
+INSTALL(
+ FILES
+ stats/main.html
+ stats/main.js
+ stats/main.css
+ DESTINATION ${GR_PYTHON_DIR}/gras/stats
+ COMPONENT ${GRAS_COMP_PYTHON}
+)
diff --git a/python/gras/stats/__init__.py b/python/gras/stats/__init__.py
new file mode 100644
index 0000000..2ed52ea
--- /dev/null
+++ b/python/gras/stats/__init__.py
@@ -0,0 +1,36 @@
+import time
+import BaseHTTPServer
+
+import os
+__path__ = os.path.abspath(os.path.dirname(__file__))
+
+get_stats_registry = [lambda: ""]
+
+class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+ def do_HEAD(s):
+ s.send_response(200)
+ s.send_header("Content-type", "text/html")
+ s.end_headers()
+
+ def do_GET(s):
+ """Respond to a GET request."""
+ if s.path.endswith('stats.xml'):
+ s.send_response(200)
+ s.send_header("Content-type", "text/xml")
+ s.end_headers()
+ s.wfile.write(get_stats_registry[0]())
+ return
+ s.send_response(200)
+ s.send_header("Content-type", "text/html")
+ s.end_headers()
+ path = s.path
+ if path.startswith('/'): path = path[1:]
+ if not path: path = 'main.html'
+ s.wfile.write(open(os.path.join(__path__, path)).read())
+
+def http_server(args, get_stats_xml):
+ get_stats_registry[0] = get_stats_xml
+ server_class = BaseHTTPServer.HTTPServer
+ httpd = server_class(args, MyHandler)
+ return httpd
diff --git a/python/gras/stats/main.css b/python/gras/stats/main.css
new file mode 100644
index 0000000..4950e84
--- /dev/null
+++ b/python/gras/stats/main.css
@@ -0,0 +1 @@
+/*TODO*/
diff --git a/python/gras/stats/main.html b/python/gras/stats/main.html
new file mode 100644
index 0000000..6bfc6b6
--- /dev/null
+++ b/python/gras/stats/main.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
+ <title>GRAS status monitor</title>
+ <link rel="stylesheet" type="text/css" href="/main.css" />
+ <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
+ <script type="text/javascript" src="http://www.google.com/jsapi"></script>
+ <script type="text/javascript" src="/main.js"></script>
+ <script type="text/javascript">
+ google.load('visualization', '1.0', {'packages':['corechart']});
+ google.setOnLoadCallback(gras_stats_main);
+ </script>
+</head>
+
+<body>
+ <p id="tps">Default</p>
+ <div id="overall_charts" style="width:1000px;height:700px;"></div>
+ <div id="individual_charts"></div>
+</body>
+
+</html>
diff --git a/python/gras/stats/main.js b/python/gras/stats/main.js
new file mode 100644
index 0000000..64ea289
--- /dev/null
+++ b/python/gras/stats/main.js
@@ -0,0 +1,109 @@
+/***********************************************************************
+ * Utility functions for stats
+ **********************************************************************/
+var gras_extract_block_ids = function(point)
+{
+ var ids = new Array();
+ $('block', point).each(function()
+ {
+ ids.push($(this).attr('id'));
+ });
+ return ids;
+}
+
+var gras_extract_total_items = function(point, id)
+{
+ var block_data = $('block[id="' + id + '"]', point);
+ var total_items = 0;
+ $('items_consumed,items_produced', block_data).each(function()
+ {
+ total_items += parseInt($(this).text());
+ });
+ return total_items;
+}
+
+var gras_extract_throughput_delta = function(p0, p1, id)
+{
+ var d0 = $('block[id="' + id + '"]', p0);
+ var d1 = $('block[id="' + id + '"]', p1);
+ var t0 = parseInt($('stats_time', d0).text());
+ var t1 = parseInt($('stats_time', d1).text());
+ var tps = parseInt($('tps', d0).text());
+ var items0 = gras_extract_total_items(p0, id);
+ var items1 = gras_extract_total_items(p1, id);
+ return ((items1-items0)*tps)/(t1-t0);
+}
+
+var gras_extract_throughput = function(point, id)
+{
+ var block_data = $('block[id="' + id + '"]', point);
+ var start_time = parseInt($('start_time', block_data).text());
+ var stats_time = parseInt($('stats_time', block_data).text());
+ var tps = parseInt($('tps', block_data).text());
+ var total_items = gras_extract_total_items(point, id);
+ return (total_items*tps)/(stats_time-start_time);
+}
+
+var gras_update_throughput_chart = function(history)
+{
+ if (history.length < 2) return;
+
+ var ids = gras_extract_block_ids(history[0]);
+ var data_set = [['Throughput'].concat(ids)];
+ for (var i = Math.max(history.length-10, 1); i < history.length; i++)
+ {
+ var row = new Array();
+ row.push(i.toString());
+ for (var j = 0; j < ids.length; j++)
+ {
+ row.push(gras_extract_throughput_delta(history[i-1], history[i], ids[j])/1e6);
+ }
+ data_set.push(row);
+ }
+
+
+ var data = google.visualization.arrayToDataTable(data_set);
+
+ var options = {
+ title: 'Throughput per block over time',
+ vAxis: {title: "rate (MIps)"},
+ hAxis: {title: "time (seconds)"}
+ };
+
+ var chart = new google.visualization.LineChart($('#overall_charts').get(0));
+ chart.draw(data, options);
+
+}
+
+/***********************************************************************
+ * Query stats
+ **********************************************************************/
+var gras_query_stats = function(history)
+{
+ $.ajax({
+ type: "GET",
+ async: true,
+ url: "/stats.xml",
+ dataType: "xml",
+ success: function(xml)
+ {
+ if ($(xml, "gras_stats") !== undefined)
+ {
+ history.push(xml);
+ gras_update_throughput_chart(history);
+ }
+ var onceHandle = window.setTimeout(function() {
+ gras_query_stats(history);
+ }, 500);
+ }
+ });
+}
+
+/***********************************************************************
+ * Init
+ **********************************************************************/
+var gras_stats_main = function()
+{
+ var history = new Array();
+ gras_query_stats(history);
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c1d219f..27a2965 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -11,3 +11,4 @@ GR_ADD_TEST(block_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/block_te
GR_ADD_TEST(hier_block_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/hier_block_test.py)
GR_ADD_TEST(thread_pool_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/thread_pool_test.py)
GR_ADD_TEST(sbuffer_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/sbuffer_test.py)
+GR_ADD_TEST(stats_test ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/stats_test.py)
diff --git a/tests/stats_test.py b/tests/stats_test.py
new file mode 100644
index 0000000..9d937e9
--- /dev/null
+++ b/tests/stats_test.py
@@ -0,0 +1,28 @@
+# Copyright (C) by Josh Blum. See LICENSE.txt for licensing information.
+
+import unittest
+import gras
+import numpy
+from demo_blocks import *
+
+class StatsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.tb = gras.TopBlock()
+
+ def tearDown(self):
+ self.tb = None
+
+ def test_simple(self):
+ vec_source = VectorSource(numpy.uint32, [0, 9, 8, 7, 6])
+ vec_sink = VectorSink(numpy.uint32)
+
+ self.tb.connect(vec_source, vec_sink)
+ self.tb.run()
+
+ self.assertEqual(vec_sink.get_vector(), (0, 9, 8, 7, 6))
+
+ print self.tb.get_stats_xml()
+
+if __name__ == '__main__':
+ unittest.main()