alex's blog

Mocking Static Method Calls With PHPUnit

Update Nov 1, 2011

I updated the code samples in 2 places. User::sendRegistration needed to return a value, and MockTurtle was renamed to MockProxy since it just didn't seem funny or clever anymore. Late night naming gone awry.

Overview

PHPUnit 3.5 comes with some ability to mock static method calls. You create a new test class which can expect a given static call, and then use a staticExpects() call to set up your expectations just like with the normal instance-based expects().

http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Me...

This is all fine if you can call the static method directly, or if all the static method calls are in the same class. But say you have an instance method in one class which calls a static in another class, and you want to test that the static is called correctly? You're sunk. Can't be done.

This was driving me crazy tonight, so I decided to try to hack a way around the problem. I want to share what I came up with, get feedback, and see if there are better ways to do it.

Setup

Forgive this painfully contrived example. We have a User, and the User calls a RegistrationService.

//User.class.php
class User {
  public function __construct($id) {
    $this->id = $id;
  }
  public function sendRegistration() {
    return RegistrationService::processRegistration( $this->id );
  }
}

//RegistrationService.class.php
class RegistrationService {
  public static function processRegistration($user_id) {
    // call some external service
    return '{'.$user_id.':"success"}';
  }
  public static function getServiceName() {
    return 'registration';
  }
}

In my test, I don't want to call the real RegistrationService::processRegistration, but I do want to verify that $user->sendRegistration() is making that call correctly.

The core of my approach is a mock RegistrationService that looks like this:

//RegistrationService.mock.php

class MockProxy {
// This thing could use a better name.

  private static $mock;

  public static function setStaticExpectations($mock) {
    self::$mock = $mock;
  }
  // Any static calls we get are passed along to self::$mock.
  public static function __callStatic($name, $args) {
    return call_user_func_array(
      array(self::$mock,$name),
      $args
    );
  }
}

class RegistrationService extends MockProxy {}

And after all that setup, this is what the test looks like

require_once 'User.class.php';

class TestCase extends PHPUnit_Framework_TestCase {

  /**
  * @runInSeparateProcess
  * @preserveGlobalState disabled
  */

  public function test_sendRegistration_calls_RegistrationService() {
    require_once 'RegistrationService.mock.php';

    $mock = $this->getMock( 'RegistrationService', array('processRegistration') );
    $mock->expects( $this->once() )
      ->method( 'processRegistration' )
      ->with( 25 )
      ->will( $this->returnValue('{mock:true}') );

    RegistrationService::setStaticExpectations($mock);

    $subject = new User( 25 );
    $this->assertEquals('{mock:true}', $subject->sendRegistration());
  }

  public function test_RegistrationService_reports_its_service_name() {
    require_once 'RegistrationService.class.php';
    $this->assertEquals('registration', RegistrationService::getServiceName());
  }
}

In the first test, any static methods calls made to the mock RegistrationService get passed along to the mock we supplied with setStaticExpectations. Note that these are normal instance-based expectations, not static ones. That's a little counter-intuitive, but if you follow the code it makes sense.

The second test has nothing to do with User, and really doesn't belong in this suite at all. I include it to show that you can use a mock in one test, and invoke the real un-mocked class in another test. You can find an explanation of @runInSeparateProcess and @preserveGlobalState in http://matthewturland.com/2010/08/19/process-isolation-in-phpunit/. As the first one implies, it means that this particular test will be run in its own process. This is necessary since we're dealing with 2 different classes both named RegistrationService.

OUTPUT

alex@turnip:~/Code$ phpunit UserTest.php
PHPUnit 3.5.14 by Sebastian Bergmann.

..

Time: 1 second, Memory: 5.50Mb

OK (2 tests, 2 assertions)

and if I break the first test on purpose, the error message is clear and easy to follow. That's one metric I was worried this approach wasn't going to do well on, but it seems just fine in this case at least.

alex@turnip:~/Code$ phpunit UserTest.php
PHPUnit 3.5.14 by Sebastian Bergmann.

F.

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) TestCase::test_sendRegistration_calls_RegistrationService
Failed asserting that <integer:2525> matches expected <integer:25>.

/Users/alex/Code/RegistrationService.mock.php:15
/Users/alex/Code/User.class.php:9
/Users/alex/Code/User.class.php:9
/Users/alex/Code/UserTest.php:24

FAILURES!
Tests: 2, Assertions: 1, Failures: 1.

THOUGHTS?

So... feedback? Are there better ways to do this, and my Google-fu was just too weak tonight? How could I improve this approach?

Let me say right off the bat that I'm really really really not interested in "statics are bad, you should refactor your code to not use them" kinds of responses.

I actually tend to agree, and if I'm creating a project from scratch I tend to follow that advice. But how many projects do you work on which are entirely your own design and your own code? In my world, the count is 0. I expect to use 3rd-party libraries. I can't control their APIs, but I don't feel like that fact should prevent me from doing comprehensive testing of how I use those APIs.

Actually, I think we'd have the same problem even if User were using an instance of RegistrationService instead of calling a static, and it could be solved in essentially the same way.

But again that could just be me looking at the problem wrong. It seems like so much of testing is about habits. I really learned my habits doing Rails, and the kinds of stuff you can do with Mocha just aren't possible in PHP. That doesn't mean good testing isn't possible - it just means that the habits & intuition I've built up over the years aren't serving me well when I try testing PHP. I'm keen to learn, so fill me in!

FINALLY, SOME LOVE

Thanks to Sebastian Bergmann and everyone else who have contributed to PHPUnit. You make the best testing tools for PHP, and if I grumble about limitations here & there it should not be interpreted as "PHPUnit sucks, look what it can't do". I hate that kind of attitude, and I appreciate all the work you've done for all of us!

Have a good night! I think I did. :)

Ganglia References

This is a collection of ganglia-related links I put together for an 'Introduction to Ganglia' presentation I'm doing for the Ruby Users of Minnesota.

General Info

Live Demo

RRD

Custom Metrics

Metrics via log parsing

Custom Graphs

Ganglia/Nagios Integration

PHP Comparison Surprises

UPDATE

@tcollen reminded me that any non-empty string evaluates to true. Ok, my mistake there. I guess my PHP is rustier than I thought...


I'd expect that if $a == $b, and $b == $c, then $a == $c. Alas, I just tripped over a case where this isn't true.

$ php -r "echo 1 == true ? 'true' : 'false';"
true

$ php -r "echo true == 'enabled' ? 'true' : 'false';"
true

$ php -r "echo 1 == 'enabled' ? 'true' : 'false';"
false

First: This is just nuts. true == 'enabled'?!?!

Second: Where did 'enabled' get it's special status? I wasn't expecting it, and I can't find it documented anywhere. http://php.net/manual/en/types.comparisons.php

Third: ARGH!!!

Creating HTML elements with CSS classes in jQuery : IE surprise.

Intro

Let's say you've got an HTML snippet like this, which you want to add a new element to.

  <div id="test"></div>

You can easily add new elements.

jQuery( '<span>' ).appendTo( '#test' );

Danger

You might also want to add a CSS class also.

jQuery( '<span class="foo">' ).appendTo( '#test' );

But be warned! This fails silently in IE7 and IE8! There's no error raised, but no element is created or added to the test div.

A Workable Alternative

jQuery( '<span>' ).addClass( 'foo' ).appendTo( '#test' );

seems to work just fine, though.

Some Hacker at UCAR Likes Poe

Just now I was building NetCDF, and noticed this output:

got NC_CHAR val = A (0x41)
got NC_CHAR val = B (0x42)
got NC_CHAR val = "The red death had long devastated the country."
got val = A (0x41)
got val = B (0x42)
got val = "The red death had long devastated the country."
got vals = 0.000000 ... 447.000000
re nc_close ret = 0
PASS: t_nc

Reminded me of http://www.sysop.ca/?p=90, which I tweeted about a few weeks ago. Wonder how many other little nuggets like this I'll start noticing now. Though most of the time I think (hope?) I've got better things to do than watch ./configure; make; make install output scroll by.

Technical ToDo List

I feel like I have a million little projects floating around in my head. In this post, I'm going to mention some of the ones I think of most often. Should be a small way to hold myself more accountable for actually getting something done. Ask me how I'm doing on these in 6 months! :)

  • Ganglia contributions: I've been helping out with a refresh of the ganglia PHP app, and I need to get more done on that. https://github.com/alexdean/ganglia-misc
  • Regions Project v2: I created www.regionsproject.org long before I knew anything about real GIS. Now that GeoServer and OpenLayers exist, I would like to do a new version of that project using open-source GIS.
  • Invent a better CAPTCHA: I get all kinds of junk user registrations on this site. Somebody somewhere is solving reCaptchas for spammers. I keep having this sense that adding some kind of interaction would make this harder to pull off, but I haven't thought if what it should be.
  • Marc Finder: Blacklight (http://projectblacklight.org/) is a great system for cataloging books. I want a local Blacklight installation for my books, but I need to get ahold of MARC records for all my books first. I have a rough Rails app which can query Z3950 servers based on ISBN numbers, and I'd like to vamp this up into a system people could use. Picture a site where you could submit a batch of ISBN numbers, and get back a tarball full of MARC records to dump into Blacklight. Couple a cheap barcode scanner with MarcFinder and Blacklight, and you've got a nice system.
  • RGB: Really Good Backups. This is just a collection of Python and bash scripts I use for managing my rdiff-backup repositories. I have 1 repo for each of our workstations at home, as well as other repos for database data backups and website content. Again, I think this is probably releasable. It could use some polish and some unit tests.
  • Shot Tracker: Web application for target shooters, mainly for tracking your accuracy with various ammunition. I think it'd be really cool to have a mobile app which would let you photograph a target, find the holes, do some image recognition, and measure your group size for you. Upload that to the site, and build up a set of accuracy data. Inspired by http://bulletin.accurateshooter.com/2010/04/22-lr-ammunition-accuracy-55...

All these projects have been in various states of half-finished-ness (or exist as pure vapor-ware) for quite a while. Let's see how I can do in pushing them forward in the next six months!

Somehow I suspect I'll spend more time doing dishes and playing hide-and-seek with the kids. As a father of young kids, I think that's as it should be, but it does grate on me how little time I seem able to make for other projects. Ah well, every day is a day for change, right?!

Delegation in Rails

I work for IPS Meteostar. We make software for meteorologists to use in the production of forecasts. In the world of aviation meteorology, metwatching is the practice of comparing a current weather observation to a forecast, and making adjustments to the forecast when they differ too much.

The definition of what 'too much' is varies from parameter to parameter (visibility, wind speed, temperature, etc). Our software allows users to define various rules like "if visibility varies by more than 0.5 miles, color-code the airport red on my map". Each interval has a lower & upper boundary. A metwatching interval ties these lower & upper bounds to a color which should be displayed when the current value falls between those lower & upper boundaries.

Up to now, we've stored those rules in a configuration file. Now we need to move them to the database, and that has presented a challenge.

My code used to look like this:

class Interval
  attr_reader :lower, :upper
end

class MetwatchInterval < Interval
end

Note: None of my example code is complete. I'm only including enough to make my point about how the classes relate to each other. So, if it looks like there's a lot missing... there is!

Now, MetwatchInterval needs to become a subclass of ActiveRecord::Base. So how to access all those Interval methods? Without multiple inheritance, something has to give.

This was my first approach:

class MetwatchInterval < ActiveRecord::Base

  def initialize
    @interval = Interval.new
  end

  def method_missing( meth, *args )
    if @interval.respond_to?( meth )
      @interval.send( meth, *args )
    else
      raise NoMethodError, "#{meth} is missing."
    end
  end

end

This works well enough, but it just feels unclear. method_missing can be a life-saver, but I feel like it's worth avoiding when there are simpler solutions available. There's that extra method call to process, and the stack traces are frequently less clear when method_missing is used.

Enter Rails' delegate.

class MetwatchInterval < ActiveRecord::Base

  delegate :upper, :lower, :to=>:@interval

  def initialize
    @interval = Interval.new
  end

end

The external API exposed by MetwatchInterval is the same as the method_missing version, plus I think it's easier to read & understand. Under the hood, delegate just defines a few new methods on MetwatchInterval for me, so (internally), it looks the same as if I'd written

class MetwatchInterval < ActiveRecord::Base
 
  def initialize
    @interval = Interval.new
  end
 
  def upper( *args, &block )
    @interval.send( :upper, *args, &block )
  end

  def lower( *args, &block )
    @interval.send( :lower, *args, &block )
  end

end

Nice and simple. Not too much magic. There are plenty of times I'll be glad to have discovered this. It makes favoring composition over inheritance easy, and that generally feels like good design practice to me.

The Disemvoweler : Shorten those tweets!

And I give you... The Disemvoweler! Shorten your text and easily tweet the results.

I think it's fairly common knowledge that you can remove all the vowels from most sentences, and still be left with an intelligible statement. This is often called disemvoweling, which I think is just awesome. http://en.wikipedia.org/wiki/Disemvoweling After a http://ruby.mn meeting a few months ago, this topic came up over beers and I finally decided to do something about implementing it as a simple tool.

I'll probably continue goofing around with the algorithm here & there. For example, I think I probably shouldn't mangle words less that 3 characters long, but I haven't done that yet.

Here's what it does currently.

  • Remove all vowels, except those that begin words. These seem to be more important when it comes to legibility.
  • Don't disemvowel any words which have an interior '.'. This prevents me from mangling URLs.

If you have any other suggestions, please leave a comment and let me know.

Motivation, and predicting the future.

A few weeks ago I wrote about getting stuck, where motivation disappears and making progress seems impossible. I've continued to ponder what kinds of things contribute to this problem, since it's unpleasant and it seems so strange to me that I can continue to fall into a trap I know I don't like. So... here goes.

The issue

Satisfaction comes from solving some new problem. Frustration comes from investing lots of energy into something and ending up with nothing to show for the effort, and frequent frustration kills motivation.

It's the big problems which offer both the biggest payoffs (the satisfaction of solving something tricky) and the biggest dangers. The more often a given project throws up roadblocks and unexpected surprises, the harder it is to continue to motivate yourself to try again, to continue to work at it. And... you're stuck!

The point I want to add in this post is that, when your starting out on a genuinely new project, it's usually impossible to tell if you're going to make steady progress or if you're going to hit unexpected roadblocks. You can't. I think this explains some of my own reluctance to dive back into my big projects which have gotten bogged down. It gets easier and easier to see these big problems as unsolvable, and that's how I get stuck.

The solution?

I come to the same conclusion as I did a few weeks ago: it's best to approach a big task as a series of smaller tasks. It's absolutely essential to be able to see what progress is being made. I think this is already well-known in terms of keeping product owners happy, but I also see how important it is for me as a developer. If I feel like I'm getting nowhere, it gets incrementally harder to summon the motivation to continue. So, set things up in ways that you can see progress, and prove to yourself that you're not just going round and round the same carousel.

There's a constant tension between enough planning and too much planning. You need some high-level picture to know you're making progress, and to know you're actually solving the right problems. But you very quickly reach a point where the value of additional planning drops sharply. More investigation beyond this point is paralysis by analysis. At some point you just have to get going and see how things work out. But I do believe there's definitely such a thing as not enough planning, and that can lead to the kinds of pitfalls I'm writing about.

As a final note... I think it can be a welcome relief sometimes to attack a problem which is entirely within your comfort zone. I think of my brain like a muscle. Muscles don't grow if they aren't stretched, even to the point of pain, but constant pain is counter-productive. It's not a sign of weakness to knock off a few easy tasks in the midst of the big stuff. There's no constant relationship between the difficulty of a project and the importance of a project. Some easy stuff is really really important. Fixing little bugs can make a big difference. And the little stuff can be a great way to recharge between (or in the midst of) the bigger stuff.

PHP array creation on first assignment

$test['key'] = 'value';
echo $test['key'];

I thought I had an error here, since the code is making an assignment to an array which hasn't yet been initialized. There was nothing in my error log, so I assumed that I hadn't set my error_reporting value high enough. But even after using E_ALL | E_STRICT, the highest level possible, there's still not so much as a peep.

As I've discovered, this is a perfectly legitimate way to initialize an array.

An existing array can be modified by explicitly setting values in it. This is done by assigning values to the array, specifying the key in brackets. The key can also be omitted, resulting in an empty pair of brackets ([]). If $arr doesn't exist yet, it will be created, so this is also an alternative way to create an array.

http://us2.php.net/manual/en/language.types.array.php

Well, heck, that's convenient.

Define Ganglia Custom Graphs Using JSON

Vladimir Vuksan's blog has a nice writeup on how to define new custom Ganglia graphs using JSON. I helped implement this, so it's nice to see how the project has continued to improve.

http://vuksan.com/blog/2011/02/20/json-representation-for-graphs-in-gang...

How to not get stuck.

The software equivalent of writers block is what I call 'getting stuck'. You have a problem, and for whatever reason you're blocking on how to move forward. Maybe it's especially hard, maybe it's boring, but for whatever reason you end up doing other things instead of what you need to be doing.

This all is a little bit embarrassing to write, since it's a post about how I sometimes end up wasting time and not doing what I know I need to be doing. Lately I've been actively trying to understand and fix this problem, and I've concluded that becoming more aware of the problem is key to solving it. I write now because I expect I'm far from unique in this respect. I hope that others who have similar experiences may find some value in how I'm trying to describe things here.

Recognizing your own tendencies when you get stuck can be really helpful.

It's good to be able to step back and recognize habits, and think intentionally about how you can modify them to help get you back on track as soon as possible.

  • I'm in danger of getting stuck when the problem is big. When it's too much to think about, it's easy to drift onto other things and put off the big decisions.
  • I get stuck when I have a big task and I get interrupted a lot. It's hard to get in the groove of a big project when something else is coming up every few minutes.
  • I get stuck when issues outside of work pull my attention away. This is similar to 'getting interrupted', but is more about other stuff which is on my mind, rather than other people who need my attention.
  • The real kicker is that I get stuck when I've already been stuck for a while. Stuck-ness has momentum, and the longer it goes on, the more intractable the problem seems. Believing in the intractability of a problem is a huge motivation-killer.

Recognizing that you have a problem is the sine qua non. You won't get very far until you at least recognize that there's an issue to be dealt with. Just understanding you have a problem is no guarantee you'll be able to easily overcome it, but it's rare to overcome a problem you're not aware you have.

What does it look like?

  • I check email and Twitter too often.
  • I get a snack I'm not really hungry for.
  • I do dumb stuff like brush my teeth in the middle of the day.
  • I read Wikipedia pages about completely random events.

I expect this list is familiar to most people, and could easily contain hundreds of other items. Really, the point of this list isn't so much what's on it, but rather what's not on it... the task you should actually be working on.

How do you get unstuck?

Here's the real question. Generally, drifting into stuck-ness happens unconsiously and unintentionally. I never say to myself "hey, this is a tough project, I think I'll blow some time doing nothing." But the truth is, that's exactly what happens when you're stuck, and productivity suffers. This leads to self-inflicted frustration that you're still faced with this big problem which hasn't been dealt with.

  • Just do it. Dive in and force yourself to stay focused. Sometimes this works, but often it doesn't.
  • Carve it up. The best way to solve a big problem is to make it a series of little problems.
    • Details help, but beware that planning itself can become procrastination. (aka "paralysis by analysis". See 'Just do it' above.) The point is to get moving again, not to plan it to death.
    • I like to ask: "What do I need to do and in what order?" and "What do I need to get done in the next 2 hours to feel like I'm getting somewhere?"
    • The easiest first answer is "I don't know.", and the correct response is "What do I need to do to start figuring that out.?"

    The point is to arrange things in such a way that you can solve a series of smaller problems instead of solving some enormous problem all at once.

  • Explain the problem to someone. This is really powerful. Just having to explain the issue and it's background to someone not already familiar with it forces you to think about the problem differently. It gets you to back up, and often helps you re-examine assumptions you didn't realize you were making. I have often started long emails to co-workers or mailing lists, only to eventually delete them because the act of explaining the problem in detail got me rolling again.
  • Ask for help. If the problem really is too big, or you really don't know what to do and you've been in that situation for hours, it's better to admit this than to continue to be unproductive.
  • Take a break. This is really different than checking email or any of the other avoidance strategies I mentioned. Taking a break (by taking a walk around the block, maybe) to clear your mind is not at all the same as simple mindless avoidance. The difference is that you're intentionally trying to change direction, rather than giving in to some mental Brownian motion. Nobody but you will be able to tell which one you're doing, but you should be able to. I strongly think this should be done outside of your normal work environment, otherwise it's too easy to drift back into the unproductive time-wasting behaviors which get you nowhere.
  • Change your environment in some small but noticeable way. Is the light on? Turn it off. Is the window closed? Open it. Are you sitting down? Walk around or stand on your chair. Is nobody else there? Talk about the problem out loud anyway. Don't mutter, speak confidently. Explain it like you're giving a keynote address. The key is that if you're in a rut, don't stay there. Little tweaks to your surroundings can help jolt you into motion.

I find that since I spend so much of my day doing things purely in my head and on my keyboard, shifting to doing something which is more physical and less mental is a really good way to loosen the blockages. I've had lots of "AH-HA!" moments walking my dog, taking a shower, or doing other things which have no bearing on the actual problem at hand. Don't underestimate the power of these moments, but also keep in mind that "just do it" is often the right decision.

You have to work on knowing yourself, and figuring out which strategy is appropriate to the situation. I tend to take some quick break in my office, then try to "just do it", and then try something more disruptive (like asking for help or taking a walk) if I'm still not getting anywhere.

Over time, it should happen less and less.

If it doesn't, you need to find the patterns in what gets you stuck. Is it always hard to come up with the ideal database schema, or to feel like your test coverage is good enough? Are you uncomfortable with filesystems? Does Oracle seem like a total mystery you'd rather avoid?

If you're constantly facing things you hate, figure out how to get onto different projects. Getting stuck is, I think, both a strong indicator of and a cause of unhappiness. "Just do it" is a great short-term strategy, and a terrible long-term strategy. You should strive for a work environment (and a mental environment) where stuck-ness is the exception not the rule.

Which one is that, again?

I'm really enjoying reading "High Performance JavaScript" by Nicholas C. Zakas and others. There's great advice on how to write better JavaScript, backed up by clear explanations about how the language works and why their recommendations are what they are.

I'm much less impressed by many of the charts they include. Try this example:

Scan10002

Answer these questions for me:

  • Which one is Firefox 3, and which one is IE 8?
  • Which one is Firefox 3.5, which one is IE 7, and which one is Opera 10 Beta?
  • Which browser is represented by a dashed line with circles?

My best guess is that this was originally a color chart which wasn't re-thought when it went to the black & white press. In my mind, that's poor quality control, and really detracts from the overall quality of the book. I'm still in the early chapters of "High Performance JavaScript", and overall I still have to give it a thumbs-up, but little annoyances like this definitely detract from the experience.


Creating a bonded network interface in Red Hat linux

Introduction

Network interfaces (NICs) are crucial to any server machine. If you can't talk to the network, you can't do much of anything. In any high-availability system, you want to remove as many single-points-of-failure as possible, and NICs are no exception. The linux kernel provides several ways to aggregate (bond) many physical NICs into a single virtual interface. This interface looks like a single thing to any applications which use it.

There are 2 primary use cases for using bonding:

  • Load balancing for higher throughput : If you have more network traffic than can flow through a single interface, you can build a single bonded interface which uses all the bandwidth of its physical interfaces. Keep in mind that this doesn't necessarily provide high-availability. (If you NEED 2 Gb/sec of throughput, losing 1 of your 2 1 Gb/sec interfaces will kill you.)
  • Failover for high availability : Sets of physical interfaces can be configured in primary/secondary arrangements. Traffic flows through only 1 interface at a time, but if that interface fails for some reason, the bonding driver instantly starts routing traffic through the secondary instead.

There are many bonding modes supported by the linux kernel. Some provide load balancing, some provide high-availability, some may provide both. Some require cooperation/configuration on the switch, and some don't. Modes are described in detail : http://www.linuxfoundation.org/collaborate/workgroups/networking/bonding

Configure Network Interfaces

First we create a configuration script for the virtual interface. This is the thing which can have IP addresses configured on it.

/etc/sysconfig/network-scripts/ifcfg-bond0

DEVICE=bond0
IPADDR=192.168.0.1
NETMASK=255.255.255.0
USERCTL=no
BOOTPROTO=none
ONBOOT=yes
BONDING_OPTS="mode=0 miimon=100 primary=eth2"

Selecting a different bonding mode is simply a matter of changing the mode=0 option listed above.

Now we need to create configuration scripts for the physical interfaces which will be part of bond0. Since they are configured as components of bond0 instead of independent interfaces in their own right, they cannot have IP addresses attached to them.

/etc/sysconfig/network-scripts/ifcfg-eth2

DEVICE=eth2
USERCTL=no
BOOTPROTO=none
ONBOOT=yes
MASTER=bond0
SLAVE=yes

/etc/sysconfig/network-scripts/ifcfg-eth3

DEVICE=eth3
USERCTL=no
BOOTPROTO=none
ONBOOT=yes
MASTER=bond0
SLAVE=yes

Configure modprobe

Add the following to /etc/modprobe.conf

alias bond0 bonding
options bond0 mode=balance-rr miimon=100

The options specified here get overridden by those in ifcfg-bond0, but it may be helpful in some circumstances to know that you can set options here as well.

Then load the module

modprobe bonding

Start It Up

Now restart the nework to bring up the bonded device.

service network restart

You can get information on the bonding configuration and status:

# cat /proc/net/bonding/bond0
Ethernet Channel Bonding Driver: v3.4.0 (October 7, 2008)

Bonding Mode: load balancing (round-robin)
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

Slave Interface: eth2
MII Status: up
Link Failure Count: 1
Permanent HW addr: 00:26:55:32:26:2c

Slave Interface: eth3
MII Status: up
Link Failure Count: 1
Permanent HW addr: 00:26:55:32:26:2e

Tests

  • Try pinging over the bonded interface, and make sure you aren't dropping packets.
  • Use tcpdump on both interfaces to see which one is being used. In round-robin mode, both should be generating traffic. In failover mode, only 1 should be. More info in the last paragraph of this post: http://lists.linbit.com/pipermail/drbd-user/2007-September/007440.html

References

Simple handling of 1 or many method arguments

It's pretty common to have a method which accepts either a single value, or an array of values. In the method body, it's not hard to detect whether you've been passed an array or not, but you can actually do this in Ruby without any conditionals.

def test( args )
  Array( args )
end

>> test( 1 )
=> [1]
>> test( [ 1, 2 ] )
=> [1, 2]

Taking this one step further, it's pretty simple to handle multiple arguments in the same manner.

def test( *args )
  Array( args ).flatten
end

>> test( 1 )
=> [1]
>> test( [ 1, 2 ] )
=> [1, 2]
>> test( 1, 2 )
=> [1, 2]

I think you can make a good case that this isn't necessarily great API design, but it's nice to have something as simple as Array() when it's appropriate.

Inferring Motives

I saw this at http://www.infoq.com/presentations/Hacking-Your-Organization, around time 38:00.

When we observe the action of another...

  • we impute a motivation for that action
  • and react emotionally to that imputed motivation

This imputation process is the core of most conflict.

This whole presentation is a really useful examination of what 'company culture' is, and how interpersonal communication matters in organizations. Really good stuff.

Secure file deletion in OSX

Want to remove a file from your Mac, and have some confidence that it'll be very hard or impossible to recover that file later on? OSX includes the srm utility, which is quite useful for this purpose.

srm somefile.txt

The default is to overwrite your file with 35 passes of random data, as prescribed by the Gutmann method. This can take quite a while, so the slightly less paranoid among you can use the 7-pass --medium option.

srm --medium somefile.txt

Keep in mind that this isn't a fast process. I sold an external hard drive a while ago, and used srm to wipe the contents. It took about 2 days to completely wipe the 120GB of contents using --medium.

This StackOverflow question on secure disk erasure methods has a few other interesting comments and references.

Fun with binary searches

I ran across a blog post challenging people to write a binary search without actually running the code. The idea is to see what you can come up with without iteratively finding & fixing problems.

Given that I really like iteratively finding & fixing problems, this seemed a little bizarre at first. But the more I ponder it, the more I like the idea (at least as an exercise/toy/diversion). It reminds me of a programming class where we got a Java program on a printed sheet of paper, and had to write out what the outputs would be. It was really annoying, and I hated it, but I did definitely learn to think more clearly about code by doing these exercises. I hardly ever do anything like that anymore, and this little binary-search challenge was a nice refresher.

The post is the first in a series, and the whole set is really great reading. The "testing is not a substitute for thinking" part was especially good. I remember lots of times arguing that "the absence of errors is not evidence of correct behavior", which is one of his main points. Though he stated it more clearly, I think, as "Tests can only show the presence of bugs, not their absence".

So, here's the binary search I came up with. I've never run this code. It might have syntax errors. It might not work (though I think it probably does). I'll try some tests and see how it goes, but posting before actually trying it seemed most in line with the spirit of the challenge. I usually do Ruby these days, so I tried it in PHP just for fun.

/**
 * Returns either the array index of $needle in $haystack, or null.
 * Assume: $haystack is a non-sparse, sorted, numerically indexed array.
 */

function binary_search( $needle, $haystack ) {
  $left_idx = 0;
  $right_idx = count( $haystack ) - 1;
  if( $needle < $haystack[ $left_idx ] || $needle > $haystack[ $right_idx ] ) {
    return null;
  }
  return recursive_binary_search( $needle, $haystack, $left_idx, $right_idx );
}

function recursive_binary_search( $needle, $haystack, $left_idx, $right_idx ) {
  $midpoint_idx = floor( ($right_idx - $left_idx) / 2 ) + $left_idx;
  $midpoint_val = $haystack[ $midpoint_idx ];
 
  if( $midpoint_val == $needle ) {
    return $midpoint_idx;
  } else if ( $needle > $midpoint_val ) {
    $next_left_idx = $midpoint_idx+1;
    $next_right_idx = $right_idx;
  } else {
    $next_left_idx = $left_idx;
    $next_right_idx = $midpoint_idx-1;
  }
  if( $next_left_idx > $next_right_idx ) {
    return null;
  }
  return recursive_binary_search( $needle, $haystack, $next_left_idx, $next_right_idx );
}

I'm pretty sure I could make it shorter, more dense, etc. I think it's pretty readable this way, though.

Errors compiling ganglia gmond gmetad on Mac OSX?

If you run into errors like the following, it's because kvm.h is no longer part of OSX as of release 10.5 (Leopard). Have a look at my instructions on building ganglia for OSX. There's an easy workaround.

Ganglia has this problem with OSX released >= 10.5 (Leopard). A bug report for the kvm.h problem has been created in Ganglia's Bugzilla, and will hopefully be addressed soon. I've put a patch for the problem on my GitHub account in the meantime.

mkdir .libs
 gcc -std=gnu99 -DHAVE_CONFIG_H -I. -I.. -I/opt/local/include -I/usr/X11R6/include -I.. -I../../lib -I../../include -g -O2 -Wall -MT metrics.lo -MD -MP -MF .deps/metrics.Tpo -c metrics.c  -fno-common -DPIC -o .libs/metrics.o
metrics.c:14:17: error: kvm.h: No such file or directory
metrics.c: In function ‘proc_run_func’:
metrics.c:658: warning: implicit declaration of function ‘host_processor_sets’
metrics.c:667: warning: implicit declaration of function ‘host_processor_set_priv’
metrics.c:693: warning: implicit declaration of function ‘thread_info’
metrics.c:718: warning: implicit declaration of function ‘vm_deallocate’
metrics.c:631: warning: unused variable ‘ti’
metrics.c:630: warning: unused variable ‘a_task’
metrics.c:626: warning: unused variable ‘port’
metrics.c: In function ‘mem_free_func’:
metrics.c:789: warning: pointer targets in passing argument 4 of ‘host_statistics’ differ in signedness
metrics.c:783: warning: unused variable ‘host_port’
metrics.c: In function ‘mtu_func’:
metrics.c:847: warning: unused variable ‘min’
metrics.c: In function ‘get_netbw’:
metrics.c:966: warning: implicit declaration of function ‘errx’
metrics.c: In function ‘makenetvfslist’:
metrics.c:1263: warning: implicit declaration of function ‘warnx’
metrics.c:1302: warning: assignment makes integer from pointer without a cast
make[4]: *** [metrics.lo] Error 1
make[3]: *** [all-recursive] Error 1
make[2]: *** [all] Error 2
make[1]: *** [all-recursive] Error 1
make: *** [all] Error 2

ArgumentError: Anonymous modules have no name to be referenced by

ArgumentError: Anonymous modules have no name to be referenced by

Huh?

This less-than-clear error messages means you're trying to use a class or module which doesn't exist. The 'anonymous' module is the one you're trying to use.

Maybe you've got a typo in the class name, or maybe you're missing a require somewhere.

Syndicate content