Archive for the ‘Cube Life’ Category

Tough Afternoon – Glad I Pushed Through it

Monday, December 13th, 2010

Dorey.jpg

This afternoon I wound it exceptionally hard to get motivated. I mean really, really hard. I just wanted to do something a little interesting, but that was not in the cards. I didn't have anything pressing I needed to get done, and I was still waiting for management to make some announcements they had talked about, so I was really just in the "Just keep swimming" mode.

I had things that needed to be done - there's always things that need to be done. So I got a few of them out of the way. I need to make a parser for the ticker plant responder to change values, and I really couldn't get geared up for that this afternoon. It's going to be tough, and I need to be fresh when I start on that.

So I did the grunt work that needed to be done. Script updating... made a few shell apps for some things I'll need in the coming weeks... standard, but uninspired, stuff.

But if there's one thing I've learned over and over again... it's that the individual that pushes through these tough parts and continues to create, has an incredible advantage over the one that requires motivation. Pushing through the tough parts is a skill. It needs practice, and this afternoon my "muscles" are "sore" from the "workout".

Solid New IRC Features Deployed

Monday, December 13th, 2010

chat.jpg

This morning I finished up a lot of work I had started Friday afternoon about getting some useful features into the IRC interface for my ticker plants. I wanted to be able to get people something more than a proof-of-concept, but it was taking more time than I'd expected. I wanted to make it possible for any number of users to get the sampled stats I log to the Log4Cpp file. That meant I needed to modify the ticker plant itself to have a list of "interested parties" and then run through that sending the stats.

None of it was all that hard, but getting it all together in the responder and the ticker plant was just more time than I had to give on Friday evening. After I got that all working, I wanted to add a few more things: querying an instrument for it's messages... querying a family of instruments for their messages... restricting these to a certain message type... all things that I know the support guys are going to want to do.

I have to say that I'm pretty pleased with how all this is turning out. It's very easy to add these features into the responder and into the ticker plant. But then again, I've done this a few times, and I know the problems I faced in those previous incarnations, and what really worked well. This time I only had to do the "good" stuff, and it worked wonderfully.

I'm not nearly done, but I wanted to get these all in and pushed up to the development environment where people can play with them. That will get them thinking about what I can deliver for them, and since almost all this is for the support folks, I wanted to get them thinking about this as soon as possible.

Good morning.

More Issues with IRC Servers

Friday, December 10th, 2010

chat.jpg

This morning I've done a bit of coding as well as waiting on the IRC server. The admins have decided to switch to InspIRCd as it has user-logging which is going to be critical for compliance purposes - and The Shop always has to be aware of that. So that's a significant change.

The biggest issue I've seen so far is that the response from InspIRCd is a lot more like the RFC for IRC. It's got the numeric codes in the responses that can be used to determine what is the nature of the message. I was missing that in the previous server, and I'm glad it's back.

Still... I need to get a stable server up and then I can start to test my changes.

Messing with IRC Servers and Stuff

Thursday, December 9th, 2010

chat.jpg

Well... it's been an interesting day. Earlier in the day I got word that there is now a nice, new, IRC server based on ircd-ratbox 3.0.6 running in The Shop. I was free to test my IRC client work and then integrate it into my ticker plants. The testing was pretty fun - I had a logic flaw because I wasn't starting the Protocol thread for doing the async reading from the socket. That was an easy fix - add a simple check and start() to the addToResponders() method. If I don't have any responders, there's no need to read anything.

The next one was trimming the whitespace off the incoming messages. Again, pretty easy, but it was annoying to see it. After that, it was tested and ready to go. Sweet.

I then uncommented the code in the ticker plant and then started firing up the ticker plants.

Massive problem! The IRC server was only allowing two of my ticker plants, in addition to my one xchat client on my box. Clearly, this is a configuration issue, and the rest of the day has been spent working with the admins trying to figure out what the right parameter needs to be in order to get things to work.

No joke... it's been a long while. But to be fair, it's not trivial. I remember spending the better part of a day getting the configuration right back when I set one up for something just like this. It's a mess of config values in the files - all because they are designed to protect against bad clients and bad people on good clients.

The problem I'm seeing in my code is that something happens and it causes my ticker plant to consume enormous amounts of memory (15GB) and race a CPU - not good. So I need to figure out how to handle the boost asio better to make sure that no matter what the IRC server does, I'm not getting into that situation.

That's tomorrow.

An Attempt at a More Useful UUID

Thursday, December 9th, 2010

Ringmaster

Late yesterday I was talking with my manager about the use of the endpoint-based UUID in the communication with the Broker. In the original implementation, we used a random 128-bit UUID with the system call:

  #include <uuid/uuid.h>
 
  void UUID::fill()
  {
    uuid_generate(uint8_t *)mBlocks);
  }

to populate the ivar data that was very simply:

  private:
    uint64_t      mBlocks[2];

and it worked fine, but the point was raised: Can't we make this more useful? and so we thought about packing the TCP socket endpoint data (address and port) as well as a sequence number into the same 128-bits. The data would look something like this where the MSB byte is byte 0 and the LSB is byte 15:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Local Addr Port Remote Addr Port Counter

The code for generating this is a little tricky, as it needs to be populated most naturally in network byte order, but the machine needs it eventually in host byte order. So I came up with the idea of creating hton() and ntoh() for the uint128_t values. All was working as we had planned. The endpoints were being properly encoded into the UUID and the counter attempted to keep them unique.

But there was a nasty truth about the ephemeral ports used - they would be pooled and re-used by the application if it restarted. Likewise, the "sequence numbers" would too. Unfortunately, this made it possible to run the application several times in succession and get the exact same UUIDs based on the endpoints. Not good. Not horrible, but the big point was to make it easy in the logs to see who was connecting to whom and all. For that, it was a failure.

Add to that, the fact that with the random UUID we could create the UUID before the socket connection was established. With the endpoint-based solution, we had to wait until after. This made the code a lot more complex, and I'm not a big fan of complexity.

So we decided that it was probably better to forego the endpoint-based UUIDs and just stick with the random ones. It's not ideal, but it's actually better than the possible disinformation that the endpoint-based UUIDs might have brought.

Finally Got the ZeroMQ Patch in for Recovery Interval in Milliseconds

Thursday, December 9th, 2010

ZeroMQ

Yesterday, Martin has looking at the patch I submitted for the Recovery Interval in Milliseconds feature, and he was talking with the OpenPGM guru of the project - Steven, about the correct default setting on the ZMQ_RECOVERY_IVL_MSEC value. Should it be 0 (as I had it), or should it be -1?

Honestly, I don't care, and when Martin came back with the answer of "make it -1", I went into the code... again... and got the master branch and applied the changes I had, and then updated the options to allow for an int32_t as opposed to an uint32_t. Then I changed the default from 0 to -1 in the options class constructor. Done.

I then built it, verified it. Then repackaged the changes into a new tarball for building the RPMs, and built them. Installed them, and then build the jzmq Java client libraries and deployed them on my boxes.

It's not hard, but there's a lot of little things to do. Someday I may actually automate this.

Polishing Ticker Plant

Wednesday, December 8th, 2010

Today I spent most of my time polishing the Ticker Plant - specifically, adding support scripts for the operations folks, and putting in the initial cut of the IRC client in the ticker plant so that I'd have a framework of how to extend the capabilities of the IRC interface as I needed a more capable interface to the application. Currently, you can set and inquire about the logging level, get help, and that's about it. But as I start to talk to the support folks, and they express what it is they need to be able to see and do, I'll be expanding this to accommodate their needs.

Also, I talked to the guy re-writing the Broker and he's decided that the channel ID being composed of socket-level details is a nice idea, but we can really loose uniqueness of channel ID in a lot of cases. That's not good, so I need to look at reverting that in the morning.

Lots to do and keep busy.

Tracking Down Nasty Memory Issue – Patience is a Virtue (cont.)

Friday, December 3rd, 2010

Detective.jpg

This morning has been very enlightening on ZeroMQ. Very exciting stuff. As I was leaving yesterday I had made a test app for the ZeroMQ guys to check and then posted the following test results as I varied the value of ZMQ_RATE:

bps ZMQ_RATE Initial Final
10 Mbps 10000 7 MB 18 MB
50 Mbps 50000 7 MB 73 MB
200 Mbps 200000 7 MB 280 MB

The data was pretty compelling. The effect ZMQ_RATE had on the memory footprint of the same data source was staggering. Thankfully, I put it all together in a nice email to the mailing list and I got a great hit from Martin S.:

Isn't it just the TX buffer? The size of PGM's TX buffer can be be computed as ZMQ_RATE * ZMQ_RECOVERY_IVL. The messages are held in memory even after they are sent to allow retransmission (repair) for the period of ZMQ_RECOVERY_IVL seconds.

So I added the following to the ZMQ transmitter's code:

  static int64_t     __rate = 50000;
  static int64_t     __recovery = 1;
  static int64_t     __loopback = 0;
 
  // we need to set this guy up properly
  top->socket->setsockopt(ZMQ_RATE, &__rate, sizeof(__rate));
  top->socket->setsockopt(ZMQ_RECOVERY_IVL, &__recovery, sizeof(__recovery));
  top->socket->setsockopt(ZMQ_MCAST_LOOP, &__loopback, sizeof(__loopback));

And then started running the tests again.

The results were amazing:

bps ZMQ_RATE Initial Final
50 Mbps 50000 7 MB 11 MB
200 Mbps 200000 7 MB 32 MB

This was exactly what I was looking for! The ZMQ_RECOVER_IVL can't go below 1 sec, but for me even that's too much. If you're not here and ready to get ticks, then waiting a second is likely to be several hundred if not several thousand messages. It'd be fine with me to make it 0.5 sec - but Martin says that's the underlying resolution of OpenPGM.

Not bad. I'll take it. What a great morning!

[12/7] UPDATE: the option:

  static int64_t     __loopback = 0;
 
  top->socket->setsockopt(ZMQ_MCAST_LOOP, &__loopback, sizeof(__loopback));

is a massive red herring. It's not about the loopback interface, as my reliable multicast URLs are all targeted to specific NICs, it's more about being able to receive on the same box as the sender. I was trying to figure out why things "broke", and it's when I took this out that things worked again. Dangerously worded docs on this one... leave it out.

Tracking Down Nasty Memory Issue – Patience is a Virtue

Thursday, December 2nd, 2010

Detective.jpg

I've been trying to track down what I believed to be a nasty memory leak in my code today. The short-cut to the answer is that it wasn't a leak, and it wasn't in my code. But I'm getting ahead of myself.

The problem was manifesting itself as steadily growing memory on some of my ticker plants. In truth, it was probably all of them, but it wasn't effecting all of them equally. I have spent a lot of time on this over the past weeks, and today I was going to get to the bottom of this for sure.

So I started digging into the problem by shutting things off. What I found was that if I was listening to anything on the UDP socket and doing anything with it I was getting about an 8-byte increase every two seconds. Very odd. I had turned off ZeroMQ at the time, so the messages were just getting dropped in the trash, but they were being processed completely up to that point.

I was trying everything, and then I had to run to a meeting. I left the test running because I needed to hurry. It wasn't going to consume the box in half an hour, anyway.

When I came back I noticed that the memory had stabilized!

Now it was getting interesting. Very interesting. I started tracking things down and it turns out that the ZMQ_RATE parameter was a major factor in the terminal memory value. I then wrote up a simple test - something that I knew the ZeroMQ guys would appreciate, and started running it.

Again - major dependency on the value of ZMQ_RATE. I'll have to do more work on this tomorrow.

Fantastic Speed Boost on My uint128_t

Wednesday, December 1st, 2010

Professor.jpg

Late yesterday I realized that I had some lingering code that was using the uint128_t I had created because I needed to uniquely map the instrument names into some kind of number space for including in the likes of std::map. The code I had originally written worked, but it wasn't nearly fast enough, and so I stopped using it (so I thought), and switched to the trie.

But it wasn't really gone. I had a lingering use for it in my client code, and I decided this morning to fix up the implementation so that I had something that was a lot faster - hopefully in the same ballpark as a uint64_t for map usage.

The first thing I did was to add a timed test section to my testing code for the conflation key - that's what I called the 128-bit value generated from the name of the instrument. It was pretty simple:

  int         cnt = 100000;
  log.info("starting the uint64_t tests...");
  boost::unordered_map<uint64_t, int> little;
  uint64_t    startTime = msg::TransferStats::usecSinceEpoch();
  // ...first the puts
  for (int i = 0; i < cnt; ++i) {
    little[i] = i;
  }
  // ...now the gets
  for (int i = 0; i < cnt; ++i) {
    if (little[i] != i) {
      error = true;
      log.error("uint64_t test failed for i=%ld", i);
    }
  }
  uint64_t    totalTime = msg::TransferStats::usecSinceEpoch() - startTime;
  log.info("%d uint64_t tests completed in %ld usec", cnt, totalTime);
 
  log.info("starting the uint128_t tests...");
  boost::unordered_map<uint128_t, int> big;
  startTime = msg::TransferStats::usecSinceEpoch();
  // ...first the puts
  for (int i = 0; i < cnt; ++i) {
    big[i] = i;
  }
  // ...now the gets
  for (int i = 0; i < cnt; ++i) {
    if (big[i] != i) {
      error = true;
      log.error("uint128_t test failed for i=%ld", i);
    }
  }
  totalTime = msg::TransferStats::usecSinceEpoch() - startTime;
  log.info("%d uint128_t tests completed in %ld usec", cnt, totalTime);  

What I saw in my initial tests was horrible. It wasn't even close. I had more than a factor of 300x difference between the two. When I looked at the way I'd implemented the uint128_t it made a lot of sense:

  private:
    uint8_t   mBytes[16];

I had 16 individual bytes as the data ivar for the object. Makes a lot of sense as it never suffers from the host/network byte ordering issues, and things looked fast in the code - but there were loops and a lot of calls to memcpy(). So I needed to take a new approach, and I decided to go to the other extreme - two uint64_t values as opposed to sixteen uint8_t values.

This changed a lot of the code. For one, it made a lot of sense to write my own hton() and ntoh() functions for the classes so they'd look like the system calls ntohl() and the like. It really wasn't all that hard, either:

  uint128_t hton( const uint128_t & aValue )
  {
    uint128_t     retval;
 
    // get the byte pointers to the source and destination
    uint8_t *dest = (uint8_t *)retval;
    uint8_t *src = (uint8_t *)aValue;
    // now map the bytes one-by-one from source to destination
    dest[0]  = src[7];
    dest[1]  = src[6];
    dest[2]  = src[5];
    dest[3]  = src[4];
    dest[4]  = src[3];
    dest[5]  = src[2];
    dest[6]  = src[1];
    dest[7]  = src[0];
    dest[8]  = src[15];
    dest[9]  = src[14];
    dest[10] = src[13];
    dest[11] = src[12];
    dest[12] = src[11];
    dest[13] = src[10];
    dest[14] = src[9];
    dest[15] = src[8];
 
    return retval;
  }
 
 
  uint128_t ntoh( const uint128_t & aValue )
  {
    uint128_t     retval;
 
    // get the byte pointers to the source and destination
    uint8_t *dest = (uint8_t *)retval;
    uint8_t *src = (uint8_t *)aValue;
    // now map the bytes one-by-one from source to destination
    dest[7]  = src[0];
    dest[6]  = src[1];
    dest[5]  = src[2];
    dest[4]  = src[3];
    dest[3]  = src[4];
    dest[2]  = src[5];
    dest[1]  = src[6];
    dest[0]  = src[7];
    dest[15] = src[8];
    dest[14] = src[9];
    dest[13] = src[10];
    dest[12] = src[11];
    dest[11] = src[12];
    dest[10] = src[13];
    dest[9]  = src[14];
    dest[8]  = src[15];
 
    return retval;
  }

The old scheme allowed me to use memcpy() to put the data into a data stream - and to take it out. But now with a real "host byte order", I needed to add methods on the uuid_t class to pack and unpack it's data from the data streams. Not bad, and it made the code look a lot cleaner, but I had that crud scattered in the code in a ton of places.

Bad form on my part - really.

I even had to create the prefix/postfix increment and decrement operators to make sure it could function in the loops I might have. I really wanted this to be complete. Thankfully, the code to do this didn't turn out to be that hard. In fact, I was able to do it all in a lot fewer lines of code because I could use the compiler to do a lot of the up-casting work that I had to do with memcpy() before. Nice benefit.

The upshot of all these changes is that the new uint128_t was only 30% slower than the uint64_t! That's amazing compared to where it started. It's not going to set any speed records, but given that it's not a built-in CPU data type, it's pretty good. Certainly good enough for all the things I need it to do.

Fantastic work!