Thursday, August 9, 2012

Back to School: XML Parsed with C++

Reading an XML file into a hierarchy of data structures is a pretty common problem.  If those data structures have been defined already and do not follow the recursive structure of the XML, some validation is needed to ensure the XML input conforms to the data structure declarations.

School Data Example


Consider the following data structures (from Target.h):


#ifndef TARGET_H
#define TARGET_H

#include <vector>

// forward declarations
struct Person;
struct Student;
struct Teacher;
struct Class;
struct School;
struct Result;

struct Person {
 Person() { name[0] = '\0'; }
 char name[256];
};

struct Student : public Person {
 typedef Class BelongsTo;
 Student() {}
};

struct Teacher : public Person {
 typedef Class BelongsTo;
 Teacher() {}
 char subject[256];
};

struct Class {
 typedef School BelongsTo;
 Class() : teacher(0) {}

 char day[64];
 int hour;
 Teacher * teacher;
 std::vector<student> students;
};

struct School {
 typedef Result BelongsTo;
 School() {}

 std::vector<class> classes;
};

struct Result {
 typedef void BelongsTo;
 Result() : school(0) {}

 School * school;
};

#endif // TARGET_H

And an example of XML input (fom TastyPS.xml):


<school>
 <student name="foo" />
 <class day="Monday" hour="10">
  <teacher name="Mr Gauss" subject="math" />
  <student name="Jake" />
  <student name="Mark" />
 </class>
 <class day="Tuesday" hour="11">
  <teacher name="Mr Shakespeare" subject="english" />
  <student name="Christine" />
  <student name="Thom" />
 </class>
</school>

The goal is to have a function which reads the content of a file and returns a Result * which can be used by other parts of the code.  If the structure of the the XML does not match the data structures defined above we want to have the reading code to raise a C++ exception.

Top-level interface


The interface for our main program to use for reading the XML file is very simple (in readschoolxml.h):


#ifndef XML_READ_H
#define XML_READ_H

struct Result;

namespace xml {

Result *
readfile(const char * filename);

} // namespace xml

#endif // XML_READ_H


The minimalism of just having a C-like functional interface is something worth appreciating now. The symbols exposed to the following main program are very limited (no need to over expose the implementation to the user code main.cpp):


#include "Target.h"
#include "readschoolxml.h"

int
main()
{
 Result * __attribute__((unused)) res = 
  xml::readfile("TastyPS.xml");
 return 0;
}



Parsing with Expat


Expat provide a C library interface for reading XML and handling every node with its attributes using callbacks (one for the start of the node and one for the end).  Interfacing Expat with a push-down state::Reader state can be done without referencing any detail of the data structures we are targetting:


#include "readschoolxml.h"
#include "Target.h"
#include "Attribute.h"
#include <expat.h>
#include <string.h>
#include <map>
#include <fstream>

namespace xml {
namespace state {
// implementation stuff linked from elsewhere

#define XML_READER_MAX_LINE 2048

struct Reader; // opaque here

extern Reader * initial();
extern Result * get_initial_obj_and_delete(Reader *);
extern void push(Reader **parent, const char * el,
    const char ** attr);
extern void pop(Reader **child);

} // namespace state


The four functions initial, get_initial_obj_delete, push and pop in the xml::state namespace will implement the XML reading state which the following Expat callbacks will use:


void
startHandler(void *data, const char *el, const char **attr)
{
 state::Reader ** reader = 
  reinterpret_cast<state::reader>(data);
 state::push(reader,el,attr);
}

void 
endHandler(void * data, const char * )
{
 state::Reader ** reader = 
  reinterpret_cast<state::reader>(data);
 state::pop(reader);
}

// data and comments are not interesting for us here
void dataHandler(void *, const char *, int) {}
void commentHandler(void *, const char *) {}



Finally, the readfile() function can be implemented with I/O calls and Expat parsing:

Result *
readfile(const char * filename)
{
 std::ifstream in(filename);

 if ( !in ) {
  // throw file not opened
 }

 XML_Parser p = XML_ParserCreate(NULL);
 if ( !p ) {
  // throw parser creation failed 
  // (odd problem)
 }
 state::Reader * reader = state::initial();
 XML_SetUserData(p, &reader);
 XML_SetElementHandler(p, startHandler, endHandler);
 XML_SetCharacterDataHandler(p, dataHandler);
 XML_SetCommentHandler(p, commentHandler);

 int done,len;
 char buff[XML_READER_MAX_LINE +1];

 while ( in.getline(buff, XML_READER_MAX_LINE) ) {
  len = strlen(buff);
  done = in.eof();
  if ( XML_Parse(p, buff, len, done) 
    == XML_STATUS_ERROR ) {
   // throw syntax error 
   // parsing a line of XML
  }
 }
 in.close();
 XML_ParserFree(p);
 Result * res = get_initial_obj_and_delete(reader);
 return res;
}

} // namespace xml


All of this code for interfacing with the Expat library is fairly generic. It just sets up a stack-based interface with an opaque type called state::Reader.

Dealing with Attributes



A class called Attribute is used to hold the attribute values and provide type conversions if necessary (since XML attributes are basically strings) in Attribute.h:


#ifndef XML_ATTRIBUTE_H
#define XML_ATTRIBUTE_H

#include <string>
#include <map>
#include <cstdlib>

namespace xml {

class Attribute {
public:
 Attribute() : _v(NULL) {}
 Attribute(const char *v) : _v(v) {}
 Attribute(const Attribute & a) : _v(a._v) {}

 Attribute & operator=(const Attribute & a)
 {
  _v = a._v;
  return *this;
 }
 int intVal() const { return atoi(_v); }
 const char * c_str() const { return _v; }
 bool boolVal() const
 {
  static const std::string t = "true";
  return t == _v;
 }
private:
 const char * _v;
};

Another class called AttributeMap is used to hold a map from constant strings to Attribute values:


class AttributeMap : public std::map<const char *, Attribute> {
public:
 Attribute & getOrThrow(const char * k)
 {
  std::map<const char *, Attribute>::iterator 
   itr = find(k);
  if(itr == end()) {
   // exception thrown here
  }
  return (*itr).second;
 }
 Attribute getOrDefault(const char * k, 
  const char * str_default)
 {
  // ...
 }
};

} // namespace xml

#endif // XML_ATRRIBUTE_H

Push-down State Details



In schoolstate.cpp the XML reader state is described in more detail. The add<>() template function describes how objects are added to other objects (partial template specialization is used here to help cover the variety of ways that one object is added to another):


#include "Target.h"
#include "Attribute.h"
#include <string.h>

namespace xml {
namespace state {

struct Reader {
 Reader(Reader * n) : next(n) {}
 virtual ~Reader() {}

 Reader * next;
};

void 
pop(Reader **r)
{
 Reader * d = *r;
 *r = d->next;
 delete d;
}

template< typename P
 , typename C
 >
inline void add(P * parent, C * child)
{}

template<> inline void 
add<Result,School>(Result * r, School *s)
{
 // throw if r->school != 0
 r->school = s;
}

template<> inline void 
add<School,Class>(School *s,Class * c)
{
 s->classes.push_back(c);
}
template<> inline void 
add<Class,Student>(Class * c,Student * s)
{
 c->students.push_back(s);
}
template<> inline void 
add<Class,Teacher>(Class * c,Teacher * t)
{
 // throw if c->teacher != 0
 c->teacher = t;
}



The ReaderImpl<> template creates the new Target.h object and adds it to the containing object when the Reader is destructed:

template< typename T
 >
struct ReaderImpl : public Reader {
 ReaderImpl(Reader * n) : Reader(n) , obj(new T()) {}
 virtual ~ReaderImpl()
 {
  ReaderImpl<typename T::BelongsTo> * parent =
   dynamic_cast<ReaderImpl< T::BelongsTo> * >(next);
  // throw on !parent (cast failed)
  add<typename T::BelongsTo,T>(parent->obj,obj);
 }

 T * obj;
};

template<> // top-level over-ride
struct ReaderImpl<Result> : public Reader {
 ReaderImpl<Result>(Reader * n) 
            : Reader(n) , obj(new Result()) {}
 virtual ~ReaderImpl<result>() {}

 Result *obj;
};

Reader *
initial()
{
 return new ReaderImpl<Result>(0);
}



Some constant strings are useful for understanding the XML content:

namespace vocab {

// attribute values
static const char * monday = "Monday";
static const char * tuesday = "Tuesday";
static const char * wednesday = "Wednesday";
static const char * thursday = "Thursday";
static const char * friday = "Friday";
static const char * saturday = "Saturday";
static const char * sunday = "Sunday";

static const char * math = "math";
static const char * science = "science";
static const char * english = "english";

// attributes:
static const char * day = "day";
static const char * hour = "hour";
static const char * name = "name";
static const char * subject = "subject";

// nodes:
static const char * school="school";
static const char * clazz="class";
static const char * teacher="teacher";
static const char * student="student";

} // namespace vocab



Filling the AttributeMap using the Expat provided array of attribute/values is fairly simple (and validation of the values happens when they are from a finite set):

void
fillAttributeMap(AttributeMap & map, const char ** attr)
{
 using namespace vocab;
 static const char * days[] =
  { monday, tuesday, wednesday, thursday, 
                  friday, saturday, sunday, 0 };
 static const char * subjects[] =
  { math , english , science , 0 };
 static const struct {
  const char * attr_name;
  const char ** restrict_values;
 } attr_vocab[] = 
  { { day, days }
  , { name, 0 }
  , { hour, 0 }
  , { subject, subjects }
  , { 0, 0 }
 };
 for(size_t i = 0; attr[i]; i += 2) {
  Attribute attrib(attr[i+1]);
  int fd = -1;
  for(size_t j = 0; attr_vocab[j].attr_name; ++j) {
   if(strcmp(attr_vocab[j].attr_name,attr[i]) == 0) {
    fd = j;
    break;
   }
  }
  if(fd < 0) {
   // throw unrecognized attribute
  }
  bool val_fd = (attr_vocab[fd].restrict_values == 0);
  for(size_t j = 0; !val_fd 
   && attr_vocab[fd].restrict_values[j]; ++j) {
   if(strcmp(attr_vocab[fd].restrict_values[j],attrib.c_str
()) == 0) {
    val_fd = true;
    break;
   }
  }
  if(!val_fd) {
   // throw invalid value for attribute
  }
  map[attr_vocab[fd].attr_name] = attrib;
 }
}


Pushing the XML reader state is now possible with the factory<> template function (using C++ template function partial specialization in order to get the attribute values into the data structures):

template< typename T > Reader * 
factory(Reader *n, AttributeMap &)
{
 return new ReaderImpl<T>(n);
}

template<> Reader * 
factory<Student>(Reader *n, AttributeMap & map)
{
 ReaderImpl<Student> * res = new ReaderImpl<Student>(n);
 strcpy(res->obj->name,map.getOrThrow(vocab::name).c_str());
 return res;
}

template<> Reader * 
factory<Teacher>(Reader *n, AttributeMap & map)
{
 ReaderImpl<Teacher> * res = new ReaderImpl<Teacher>(n);
 strcpy(res->obj->subject,
  map.getOrThrow(vocab::subject).c_str());
 strcpy(res->obj->name, map.getOrThrow(vocab::name).c_str());
 return res;
}

template<> Reader * 
factory<Class>(Reader *n, AttributeMap & map)
{
 ReaderImpl<Class> * res = new ReaderImpl<Class>(n);
 strcpy(res->obj->day, map.getOrThrow(vocab::day).c_str());
 res->obj->hour = map.getOrThrow(vocab::hour).intVal();
 return res;
}


void
push(Reader **parent, const char * el, const char ** attr)
{
 static const struct {
  const char * el_name;
  Reader * (*factory_fun)(Reader *, AttributeMap &);
 } lookup[] =
  { { vocab::school,  factory<School> }
  , { vocab::clazz,   factory<Class> }
  , { vocab::teacher, factory<Teacher> }
  , { vocab::student, factory<Student> }
  , { 0, 0 } };
  
        AttributeMap map;
        fillAttributeMap(map,attr);
 for(size_t i = 0; lookup[i].el_name; ++i) {
  if(strcmp(lookup[i].el_name,el)==0) {
   *parent = (*(lookup[i].factory_fun))(*parent,map);
   return;
  }
 }
 // throw not found
}

Result *
get_initial_obj_and_delete(Reader *r)
{
 ReaderImpl<Result> * ri =
  dynamic_cast<ReaderImpl<Result> > *>(r);
 // throw on !ri (cast failed)
 Result * res = ri->obj;
 delete r;
 return res;
}

} // namespace state
} // namespace xml


I have left the exception throwing parts of the code as comments, they really need to be there for this software to properly handle errors.

Summary


The first revision of the code that this sample is based on had a huge switch statement in it and maintained lots of state variables in order to get the job done.  These variable kept track of the XML state that had been read.  The code was fragile and hard to maintain (other coders easily broke it).  The sample above does more validation, encapsulates the knowledge of the target data structures (and how they are constructed) in one compilation unit, and the functions involved are much smaller.  The type checking that happens within the C++ template code also helps catch syntax errors for input that does not match what is expected.  I hope to compare this implementation with a comparable one in OCaml at some point in the future.  Static typing, recursive data structures, pattern matching and type inference will likely make for much simpler implementation of this code.

Tuesday, August 7, 2012

OFlux Plugin Away

Plugin architectures allow for optional functionality to be added to a simple core. The core or kernel of the program provides the bones on which the rest of the program is built. It is not necessary for the kernel to rely on the parts added ontop (the plugins) -- in fact it would be bad if that happened. Plugins can depend on more primitive plugins to accomplish their jobs.

Unlike modules which are re-usable as multiple instances within the program, plugins are intended to only either be there (once) or not there at all.  Most web servers accept plugins (e.g. for dynamic scripting language execution or CGIs) which extend the functionality of the core web server program (which parses HTTP headers etc).  This can be very a very powerful way of organizing server software generally.

When shipping software to customers with differing needs, plugins allow the the end user to customize the code that they are running in a controlled way.  A plugin which is not running is one which does not adversely affect performance, and cannot cause the program to crash.  More critically, turning off functionality which is only suitable in non-production environments (so it never runs on a live system with customers using it) is great safety feature.


My First Plugin



In order to prepare the way for a plugin Plug, the kernel.flux program you write first needs to have made available some abstract nodes for the plugin to hook into:

 
 node S () => (int a);
 node A (int a) => ...;
 node N (int a) => ();

 source S -> A;
 A = N;


By default all of the outputs of node S are consumed by concrete node (meaning it has a C++ implementation function) N via abstract node A (its only purpose is to provide a place for the new plugin to hook-in). Now we can write a new plugin which routes away some cases from the kernel flow to handle them in using their own special code:

 
 include kernel.flux

 plugin Plug
  begin
  external node A (int a) => ...;

  condition isZero (int a) => bool;
  node NForZero (int a) => ();

  A : [isZero] = NForZero;
  end


On the C++ side there is a Plug namespace which holds all of the symbols for the plugin, and it is compiled into a libPlug.so dynamic shared object (loaded dynamically at run time).  The decision to load a particular plugin is based on configuration (by default symbolic links to XML files such as Plug.xml in a particular directory), so it is easy to turn them on and off.  The content of a plugin XML file describes the list of required plugins that need to be loaded first, and how it is that the program flow is patched/modified by the new plugin code.

The effect of the Plug plugin is to divert the flow to node NForZero when the output a of S has isZero(a) evaluate to true. This is really a conditional augmentation of the existing kernel flow:


The dot output from compiling the plugin (using the -p compiler option), shows what the plugin added to the flow it is built on top of.  Had Plug depended on other plugins, those would also be highlighted in red colored boxes (and each .flux file would need to have include statements at the top of Plug.flux).

Had the conditional isZero been replaced by a *, the new route NForZero would become the new default (N being unreachable after that).  This is a way for the plugin to over-ride existing functionality in the kernel program.

Another Possibility is to add a concurrent successor node to the flow using the special &= operator which causes a second node (in addition to N) to run on every output from S:


 Node M (int a) => ();
 A &= M;

Summary


Plugins provide a method of extending a program with optional functionality.  In the case of OFlux plugins the functionality can be new parts of the flow which augment a pre-existing flow.  Plugins can use (and therefore depend on) the functionality of other plugins.  This way of coding away from the core with ever more specialized code with finer grain concerns is very useful.  It has many benefits such as reducing compile time (of the plugin component), enforcing dependencies, reducing exposure to bugs and increasing performance (by not running code you do not require).

Friday, August 3, 2012

OFlux Multipling Successor Events

Dynamically increasing the number of output successor events from a given node event execution is a powerful concept. By submitting many events to the OFlux run-time event queue, we can distribute the follow on work to other run-time worker threads. In a previous post, I described how to (staticly) have a node event's output processed concurrently by two separate nodes. Although similar, the functionality of processing the same input with two C++ node functions in the flow is orthogonal to the idea that a node event might produce several outputs.

Producing No Output


If a node function wants to cancel the flow to its successors it can do that by returning a non-zero result. This -- in effect -- means that the execution of that node function encountered an error. If there is an error handler for that node, it will be called -- but passed the input to the node that threw the error. The error handler node has a chance to re-inspect the input to the failed node function, and take remedial action:


Node Foo (const char * type) => (int type_id);
Node Oops (const char *) => ();

handle error Foo -> Oops;

The C++ code for the Foo node might check a static look-up table for a matching entry and return an error when no entry is found (causing no successor events to run, but rather having an Oops node event process the input instead):

int
Foo(const Foo_in *in, Foo_out *out, Foo_atoms *)
{
  static struct { const char * t, int tid } lookup[] =
    { { "apple", 1 }
    , ...
    , { 0, 0 } };
  int res = -1; // indicates not found - its an error
  for(size_t i = 0; lookup[i].t; ++i) {
    if(0 == strcmp(lookup[i].t,in->type)) {
      res = 0;
      out->type_id = lookup[i].tid;
      break;
    }
  }
  return res;
}


If no error handler is declared for a node, then no error node event is scheduled to run (meaning the error is ignored).

Producing More Output


In order to have a node produce more than a one output structure (leading to many successor events), there is a C++ help er gadget called oflux::PushTool<> which gives the node function access to this capability. Here is an instance of its use:



Modifying the flow above to have the Foo node "splay" all matching outputs in the lookup table (rather than just the first one) by adding a (non-compulsory) comment:

node Foo(const char * type) => /*splay*/ (int type_id); 

And changing our C++ implementation of Foo as follows:

Foo(const Foo_in *in, Foo_out *out, Foo_atoms *)
{
  static struct { const char * t, int tid } lookup[] =
    { { "apple", 1 }
    , ...
    , { 0, 0 } };
  size_t out_count = 0;
  oflux::PushTool<Foo_out> ptool(out);
  for(size_t i = 0; lookup[i].t; ++i) {
    if(0 == strcmp(lookup[i].t,in->type)) {
      ++out_count;
      ptool->type_id = lookup[i].tid;
      ptool.next();
    }
  }
  return out_count ? 0 : -1;
}

Now Foo could produce several outputs (type_ids) on one input, and each of those will get processed using the flow that follows node Foo. If no matches are found in the lookup, then no flow will operate (no successor node events).

Summary


In some cases where it is advantageous to release multiple outputs from a node (causing more successor events to be created), the run-time can be leveraged to dispatch the processing of those events on multiple threads. More threads doing work means more concurrency. The trade-off is that the successor event work must be more (in single thread terms) than the over head incurred (in the run-time) by doing this. It may be that successor processing happens so quickly in a single thread, that having Foo just output a container (e.g. vector) containing all results is better.

Thursday, August 2, 2012

Comparing 10Gbe Cards

10Gbe network interfaces deliver lower latency as well as higher bandwidth.  Even if you are not close to saturating the 1Gbe cards in your setup, it might be worth considering the next generation of networking technology on the basis of latency alone.  In this post, I will compare a few 10Gbe cards which I have had the privilege to try out.  I do have a favorite card at this point, which I will reveal at the end (hint: it has more to do with the software stack than the hardware specifically.

Wired magazine online had a series of articles earlier in the year where it was revealed that over 60% of the network ports in the data centers managed by internet giants were 10Gbe.  The potential latency for Infiniband (a similar but slightly older technology) is known to be under 5 microseconds.  It is fairly typical to see latencies in 1Gbe hover in the 150 to 30 microseconds (latency is dependent on the size of the packet payload).

In order to push a network interface to handle this much data, a modern computer with a PCIe slot with enough lanes (typically 10Gbe cards use an x8 slot -- so the x16 slot available for a graphics card is sufficient) is required.  Achieving the lower latencies this new hardware is capable of is challenging for the operating system (in my case Linux) since the TCP/IP stack and Berkeley sockets API begins to become a bottle neck.  Almost every manufacturer  has attempted to solve this problem in  their own way, providing a software work-around which achieves higher performance than what is dirrectly available via the kernel and standard API.


Method


To test a pair of cards, I plugged them into the x16 slots on two cluster nodes and cabled them directly to each other (so no switch in between).  I then configured them for ethernet, assigned IP addresses, and  ran some benchmarks:
 
 # modprobe 
 # service openib start 
 # ifconfig eth1 192.168.3.10
 # iperf 
 # NPtcp

And on the other node:
 
 # modprobe 
 # service openib start
 # ifconfig eth1 192.168.3.11
 # iperf -c 192.168.3.10
 # NPtcp -h 192.168.3.10

The iperf test mostly checks bandwidth and for me is just a basic sanity test. The more interesting test is netpipe (NPtcp) which does a latency test at various packet sizes.

Testing RDMA latency on a card that provides it is a simple matter of running a bundled utility (-s indicates payload bytes and -t is the number of iterations):


# rdma_lat -c -s32 -t500 192.168.3.10
 2935: Local address:  LID 0000, QPN 000000, PSN 0xb1c951 RKey 0x70001901 VAddr 0x00000001834020
 2935: Remote address: LID 0000, QPN 000000, PSN 0xcbb0d3, RKey 0x001901 VAddr 0x0000000165b020
 
 Latency typical: 0.984693 usec
 Latency best   : 0.929267 usec
 Latency worst  : 15.8892 usec


Mellanox 



This card is widely used and very popular.  Mellannox has considerable experience with Infiniband products, and have been able to produce cards which are capable of transporting TCP/IP traffic ontop of infiniband technology.  For them this is mostly accomplished using kernel drivers which are part of the OFED software stack.  I found that it is best to get a snapshot of this suite of packages from Mellanox directly for one of the particular distributions (all of them, ultimately, a variation on Red Hat Linux).  Although Debian wheezy had OFED packages in its repository, they were not recent enough for one of the newer cards I was trying.  For these reasons, I ended up dual booting my cluster to Oracle Enterprise Linux (OEL 6.1 specifically).  Debian Wheezy was able to run this card as an ethernet interface (using the kernel TCP/IP stack), it's just that fancy things like Infiniband and RDMA were not accessible.

I also managed to test a Mellannox ConnectX3 card, but I found that its performance was not (statistically) discernable from the ConnectX2.  If you told me to figure out which card was in a box from its benchmarks I would not be able to separate the ConnectX2 and ConnectX3 -- although presumably the new revision does have some advantage which I did not find.


Solar Flare 


Solar Flare makes several models of 10Gbe cards.  The value added by solar flare is mostly in their open onload driver technology which makes use of their alternative network stack which runs mostly in user space.  This software accesses a so-called virtual NIC interface on the card to speedup network interaction bypassing the standard kernel TCP/IP stack.  Just like the Mellanox cards, I found that Debian Wheezy could recognize the cards and run them with the Linux kernel TCP stack, but the special drivers (open onload) needed to run on OEL (I hope to attempt to build the sfc kernel driver on Wheezy soon).


Measurements and Summary



Below is a summary of the measurements that I did on these cards using various TCP stacks (vanilla indicates the Linux 3.2.0 Kernel is being used) and RDMA


Communication Type Card Distro K Mod Mesg bytes Latency
vanilla TCP solar flare OEL sfc 32 17 usec
vanilla TCP solar flare OEL sfc 1024 18 usec
vanilla TCP Mellanox connectX3 OEL mlx4_en 32 12 usec
vanilla TCP Mellanox connectX3 OEL mlx4_en 1024 16 usec
vanilla TCP Mellanox connectX2 OEL mlx4_en 32 13 usec
vanilla TCP Mellanox connectX2 OEL mlx4_en 1024 9 usec
onload userspaceTCP solar flare OEL sfc 32 2.4 usec
onload userspaceTCP solar flare OEL sfc 1024 3.6 usec
RDMA Mellanox connectX3 OEL mlx4_ib 32 1.0 usec
RDMA Mellanox connectX3 OEL mlx4_ib 1024 3.0 usec
RDMA Mellanox connectX2 OEL mlx4_ib 32 1.0 usec
RDMA Mellanox connectX2 OEL mlx4_ib 1024 3.0 usec


The big surprise in this investigation is open onload (more info can be had from this presentation).  This driver is activated selectively using user space system call interposition (so you can choose which applications run on it).  It does not require the application to be rewritten, recompiled or rebuilt in any way.  This means, in particular that closed source third party software can use it.  It's this extra flexibility which really has my attention.  Without coding to a fancy/complicated API, a developer can make use of familiar programming tools to create systems with low networking latency.

Tuesday, July 31, 2012

OFlux Modules

Code re-use is a good thing.  Developers want to have encapsulated functionality which they can easily turn into libraries so that those parts can be re-used for other programming projects.  OFlux does this using its modules system which takes its inspiration from Standard ML (or OCaml for that matter).  Standard ML extends modules a bit with an idea called functors for making modules (and their signatures -- like interfaces) generic.  OFlux does not yet support functors, but I have toyed with the idea from time to time.

Writing an OFlux module Bar involves creating the following within C++ namespace Bar:

  • a Bar::ModuleConfig structure to hold its "self" object state (each instance has its own object).  Often this object encapsulates the state of the module.  Static modules do not have a ModuleConfig structure -- so they are stateless in a way.
  • in a file called Bar.flux (and after including needed OFlux source) the module is declared using the syntax module Bar begin ... end.  All of the usual things go inside of this block (nodes, guards, flow, instances of other modules).  A module is not a recursive concept, so you can't instantiate it within itself.
  • to instantiate the module in another OFlux source location the syntax is instance Bar barinst;
  • Unless a node N (within the module) is declared with special keyword mutable, it will implicitly acquire the module instance's readwrite self guard for read access.  This provides to the C++ node function Bar::N a way to access the Bar::ModuleConfig object for that instance.  Mutable nodes acquire the self guard for write instead of read, and therefore they can change the internal instance state.
The semantics of instantiating a module is that the content of the module is notionally copied into the current scope of the program and its content (nodes, guards, etc) are just prefixed with the instance name and a "." character.  In order to allow the guards inside of a module to unify (conflating them to one guard rather than two), the syntax where guard1 = instguard1, guard2 = instguard2 is appended to the guard instantiation.  

There is no notion of inheritance between modules, since composition is done by inclusion (via instantiation).  So a developer can build modules that use other modules (e.g. we include Bar.flux in a new module which will contain an instance (or more) of Bar within itself).  Inheritance is a problematic abstraction, and the rule that inclusion should be used (indicating "has a" relationships) guided the design of this language feature.

To see an example of how an OFlux module is written, please have a look at this example in the Github repo and the top-level for that example.

Monday, July 30, 2012

xplot.org on Time Series Events

Visualizing events in a distributed system is tough.  There are good solutions for experimentation on a single machine, but once the software lives on multiple machines issues crop up.  The number one issue is timing synchronization.  Out of the box NTP based time synchronization is not accurate enough (I have found) to deliver proper time synchronization.


Precise Time Synchronization



Success (on Linux) involved installing and running ptpd2 (PTP stands for Precision Time Protocol) available on sourceforge.

On the master (fileserver machine) I keep ptpd2 running with:

 ./ptpd2 -W -b eth0

And on the slave compute nodes I just keep them running in sync with the master using:

 ./ptpd2 -c -g -B

Together, these processes keep the cluster time synchronization locked to around 1 microsecond - which is sufficient for my immediate needs.


Enter xplot.org



When looking into a TCP/IP issue, I discovered tcptrace which is a program for analyzing the output of tcpdump or snoop (on Solaris).  Several visualizations of a TCP/IP session captured by those sniffing tools are available, and (most importantly) they are viualized within a tool called xplot.org.  This tool is an X windows program which can efficiently display large time series data sets in a 2D graph.  Unfortunately, its text-based data format is only really documented by reviewing its C source code.  I intend to share what I have learned about the format so that (hopefully) others can skip reading the source.

Most of the commands are issued on a single line, with the exception of commands that take a text argument.  Commands that display text in the graph require the subsequent line for the text content.  Most drawing commands looks like:


 commandname x-coordinate1 y-coordinate2 [ x-coordinate2 y-coordinate2 ] [ color ]


Configuration of a time series graph that uses UTC timestamps (second resolution with microseconds expressed after the decimal part) is done using:


timeval double

Using three text commands we can label (using xlabel and ylabel commands) the axes and title the graph itself:

title
Results from Tue Apr 24 11:45:55 2012 - run Tue Apr 24 11:48:00 2012
xlabel
Wall clock time
ylabel
Event id

Adding some lines is easy with the line command:


line 1335282355.550356 1 1335282355.550356 2 yellow
line 1335282355.550356 2 1335282355.620854 2 yellow
line 1335282355.620854 2 1335282355.620854 3 yellow
line 1335282355.620854 3 1335282355.691357 3 yellow
line 1335282355.691357 3 1335282355.691357 4 yellow

Adding some more random things:


dtick 1335282355.623986 2.004608 blue
line 1335282355.623986 2.004608 1335282355.624049 2.004608 blue
diamond 1335282355.624049 2.004608 green
diamond 1335282355.624101 2.005530 green
line 1335282355.624049 2.004608 1335282355.624101 2.005530 green
line 1335282355.624049 2.004608 1335282355.624514 2.002304 gray20
box 1335282355.624514 2.002304 gray20

Finally a look at how xplot.org paints all of this data in its viewer:


 Zooming in on the green diamond (left button on my mouse):


Zooming in once more:


Finally we can see the event tree that I am depicting here (the Y-axis is mostly used for layout of the tree), and the X-axis shows accurate timing information:


The middle button (or wheel button) on my mouse helps to pan in one direction or another, and a single left-click is used to pop back through the zoom-ins you have done recently.


Summary



If you have lots of data to process and are comfortable generating an xplot file using your favourite tool (programs like awk and Perl are useful for transformations like this), then you may get some mileage out of xplot.org for visualizing moderate amounts of time series data (100s of megabytes) using a modest Linux computer (e.g. a netbook).  More serious hardware is definitely capable of more.  I have not found an in-browser viewer which is close to being as fast as the native xplot.org program.

Friday, July 27, 2012

Enumeration on the stack

Allocating objects in C++ on the heap is not free. Although there are many implementations of allocators that are very fast (and particularly good for multi-threaded applications), it is still preferable to avoid allocating too many things on the heap. In Java programs there is not much choice in the matter, since the use of the heap is kind of an endemic habit. Fortunately garbage collection in Java has gotten a whole lot better. In this post I re-visit the enumeration interface I described earlier and try to provide two implementations of it (one uses the program stack only, and the other relies on the heap).


Enumerators Again



In Enumerator.h I describe the same generic enumerator interface, and then I offer some convenience C macros which allow a function to be called which creates the enumerator, and enables a traversal of it:


template< typename AnyT >
struct Enumerator {
  virtual bool next(const AnyT * &) = 0;
  virtual ~Enumerator() {}
};

The stack macros within Enumerator.h are as follows (note the use of "placement new" and the explicit call to the virtual destructor):

#define stack_for_each_start(EFUN,Tp,X) \
 { /*new scope*/ \
  char _enum_buff[EFUN##__storage_size]; \
  typedef Tp _enum_Tp; \
  Enumerator<Tp> * _enum##X = EFUN(_enum_buff); \
  Enumerator<Tp> * _enum = _enum##X; /*for cleanup*/ \
  const Tp * X = 0; \
  while((_enum##X) && (_enum##X)->next(X)) {

#define stack_for_each_end \
 } if(_enum) { (_enum)->~Enumerator<_enum_Tp>(); } }  

So that is reasonably ugly, and most C macros are, but the beauty on the application side is coming -- be patient! Here are the heap allocated macros:

#define heap_for_each_start(EFUN,Tp,X) \
 { /*new scope*/ \
  Enumerator<Tp> * _enum##X = EFUN(); \
  Enumerator<Tp> * _enum = _enum##X; /*for cleanup*/ \
  const Tp * X = 0; \
  while((_enum##X) &&(_enum##X)->next(X)) {

#define heap_for_each_end \
 } delete (_enum); }


Our Library



In order to write our application library header file app.h, we want to minimize the amount of junk we expose the user application code. Here we declare a single function app::get_all_ts() which will return the enumerator and use existing storage if provided as an argument (which is our way to allocating on the stack):

#include "enumerator.h"
#include <cstdlib>

namespace app {
  struct T { int id; char name[40]; }; // T defined here
  Enumerator<T> * get_all_ts(void * storage = 0);
  extern const size_t get_all_ts__storage_size;
} // namespace app

The app::get_all_ts__storage_size symbol is provided so that we know how much storage the underlying enumerator implementation needs.  The implementation of the library code behind app::get_all_ts hides almost all the detail of the collection being enumerated, and how the enumerator implementation works:


#include "app.h"
#include <new>

namespace app {

T t1 = { 1, "sam" };
T t2 = { 2, "bill" };
T * tarray[] = { &t1, &t2, 0 };

struct AllEnumeratorImpl : public Enumerator<T> {
 AllEnumeratorImpl() : at(&(tarray[0])) {}
 virtual bool next(const T * & tp) {
  if((tp = *at)) {
   ++at;
   return true;
  }
  return false;
 }
private:
 T ** at;
};

Enumerator<T> * 
get_all_ts(void * storage)
{
 return storage
  ? new(storage) AllEnumeratorImpl()
  : new AllEnumeratorImpl();
}

const size_t get_all_ts__storage_size = 
        sizeof(AllEnumeratorImpl);

} // namespace app

My simple C array tarray of objects is just an example. A real library would have a more interesting collection with less static contents.

User Code



Finally we can use these mechanics to do a traversal in the ex.cpp user code:


#include "app.h"
#include <cstdio>

int
main()
{
  stack_for_each_start(app::get_all_ts, app::T, tptr)
    printf("%d %s\n", tptr->id, tptr->name);
  stack_for_each_end
  return 0;
}

Building and running the ex.cpp code we get:

 % g++ -g app.cpp ex.cpp -o ex
 % ./ex
 1 sam
 2 bill


Checking for Leaks



Using the heap_ version of the macros available in Enumerator.h, the same result is had. The difference in Valgrind output on the two implementations is a bit interesting (first for the stack macros):

% valgrind --tool=memcheck --leak-check=full ./ex
==29306== Memcheck, a memory error detector.
==29306== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==29306== Using LibVEX rev 1884, a library for dynamic binary translation.
==29306== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==29306== Using valgrind-3.4.1-Debian, a dynamic binary instrumentation framework.
==29306== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==29306== For more details, rerun with: -v
==29306== 
1 sam
2 bill
==29306== 
==29306== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 17 from 1)
==29306== malloc/free: in use at exit: 0 bytes in 0 blocks.
==29306== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==29306== For counts of detected errors, rerun with: -v
==29306== All heap blocks were freed -- no leaks are possible. 

Comparing this with the heap implementation which does see one alloc happen:

% valgrind --tool=memcheck --leak-check=full ./ex
==29288== Memcheck, a memory error detector.
==29288== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==29288== Using LibVEX rev 1884, a library for dynamic binary translation.
==29288== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==29288== Using valgrind-3.4.1-Debian, a dynamic binary instrumentation framework.
==29288== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==29288== For more details, rerun with: -v
==29288== 
1 sam
2 bill
==29288== 
==29288== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 17 from 1)
==29288== malloc/free: in use at exit: 0 bytes in 0 blocks.
==29288== malloc/free: 1 allocs, 1 frees, 8 bytes allocated.
==29288== For counts of detected errors, rerun with: -v
==29288== All heap blocks were freed -- no leaks are possible.

At this point we also know that neither method leaks memory, or segmentation faults. So they are pretty equivalent to the user program.


Performance



To compare performance I made a new sample ex_timing.cpp with no I/O but iterated 10 million times:

#include "app.h"
#include <cstdio>

int
main()
{
  for(long i = 0; i < 10000000; ++i) {
    heap_for_each_start(app::get_all_ts, app::T, tptr)
    heap_for_each_end
  }
  return 0;
}

I ran each version (stack and heap) 3 times and picked the middle user timing to get this table (these used compiler optimization -O3):

macro prefixuser time for 10 million iterations
stack292 ms
heap960 ms


The stack version is over three times faster!

Summary


For local things like enumerators the method of encapsulating the functionality had us returning a heap allocated object which the user code had to delete (via out macro) the object when it was done.  This was quite functional, but it has a performance penalty relative to what can be done with the stack.  The trouble with the stack is that the memory for the enumerator object has to be be "allocated" prior to the call into the library call which returns the enumerator -- and this forced us to expose the size of the implementation enumerator somehow.  I think it is an acceptable cost (some minor symbol pollution) in order to get some benefit for performance.  Of course, I wish for a way to do this which does not involve using C macros.

Follow Mark on GitHub