Tuesday, February 7, 2012

The INs and OUTs of IO.

So, as per my previous post, I've started to play with the dart:io library. While there is a sample of its usage to create a chat server available in the bleeding edge repository, but in all honesty that is far more complete and thorough than anything I plan on just yet. So essentially I'm just going off of the API documentation. Which, while good, is not exactly bursting with usage examples.

As I mentioned in my previous post, currently the goal of the script is to just run an external command and output the results. The command I chose to work with in this case is snmpget for conducting SNMP queries on equipment. This is a precursor to some further scripting I would do with the VM and Dart in general on both administrative fronts and potentially a full web-app as well.

So I focus my attention on the Process API. Now before I go any further I want to call to attention that I am far from an expert in Dart, or even Javascript for that matter. Most of my programming experiences lay in other area such as perl, python, and then Ruby. The particular use of callbacks in the manner they are used is something not entirely familiar with me and I am quite possibly breaking a few rules with them. If you have any notes or suggestions on improving/securing my simple code by all means feel free to leave some feedback in the comments!

So looking at the API I see I obviously need to create a new Process object with Process.start. I will then also need to have a startHandler and we'll throw in a simple errorHandler just incase I mess up something. Looking at the information for close I should also put in an exitHandler. In fact the close information gives me my first hint as to where the process logic should be contained. According to the documents for close, Usually, close should be called in the exitHandler. That tells me most of my other logic dealing with input/output streams should probably be inside of the startHandler. I also see I'll need to get the stdout of the process to print it. So I start with this simple code and see where I can get from there (note for security reasons I've changed the community code, and IP of the device).
#library('snmp_poll');

#import("dart:io");

void main() {
  var proc = new Process.start('/usr/bin/snmpget', ['-v 2c', '-c public', '127.0.0.1', '.1.3.6.1.2.1.1.1.0']);
  proc.startHandler = beginHandle() {
    var res = proc.stdout;
    print(res.read());
  };

  proc.errorHandler = errorHandle(errMsg) {
    print(errMsg);
  };

  proc.exitHandler = exitHandle(exitCode) {
    print(exitCode);
    proc.close();
  };
}
null
1

Initially I had some issues with assigning the handlers but that's because I tried to use the startHandler, errorHandler, and exitHandler as methods as opposed to properties with get/set. That was simple enough to straighten up so didn't bother showing that mistake here.
So after reading the API for InputStream, I see I should retrieve the data with the read method. But all I'm getting is null. Also, my return code is 1 which tells me it didn't execute properly. The process itself did execute which is why it's not calling the errorHandler. But still there's something missing. No output and why am I getting a 0 for the return code. Time to add some more code and a few debugging prints.
#import("dart:io");

void main() {
  var proc = new Process.start('/usr/bin/snmpget', ['-v 2c', '-c public', '127.0.0.1', '.1.3.6.1.2.1.1.1.0']);
  proc.startHandler = beginHandle() {
    var res = proc.stdout;
    var err = proc.stderr;
    
    print("Inside beginHandle");
    print(res.read());
    print(err.read());
  };

  proc.errorHandler = errorHandle(errMsg) {
    print("Inside beginHandle");
    print(errMsg);
  };

  proc.exitHandler = exitHandle(exitCode) {
    print("Inside beginHandle");
    print("Exit code is: $exitCode");
    proc.close();
  };
}
Inside beginHandle
null
null
Inside beginHandle
Exit code is: 1

Okay so now both stderr and stdout are both showing null. Gah what's going on. I've done something wrong obviously. Reading the API for InputStream tells me that if no data is available null is returned. But why is there no data available. Standard out or Standard error should be reporting something here.. All I can think is that we need to use the dataHandlers because these are streams rather than dumped to a string as I'm used to with other scripting languages (particularly when using the back-tick notation). Alrighty then, lets add the dataHandlers.
#import("dart:io");

void main() {
  var proc = new Process.start('/usr/bin/snmpget', ['-v 2c', '-c public', '127.0.0.1', '.1.3.6.1.2.1.1.1.0']);
  proc.startHandler = beginHandle() {
    print("Inside beginHandle");
    var res = proc.stdout;
    var err = proc.stderr;
    
    res.dataHandler = nowRead() {
      print("Inside stdout dataHandler");
      print(res.read());
    };
    
    err.dataHandler = errRead() {
      print("Inside stderr dataHandler");
      print(err.read());
    };
  };

  proc.errorHandler = errorHandle(errMsg) {
    print("Inside beginHandle");
    print(errMsg);
  };

  proc.exitHandler = exitHandle(exitCode) {
    print("Inside beginHandle");
    print("Exit code is: $exitCode");
    proc.close();
  };
}
Inside beginHandle
Inside stderr dataHandler
_InternalByteArray
Inside stderr dataHandler
_InternalByteArray
Inside stderr dataHandler
_InternalByteArray
Inside stderr dataHandler
_InternalByteArray
Inside stderr dataHandler
_InternalByteArray
Inside beginHandle
Exit code is: 1

Alright now we're getting somewhere... except apparently print doesn't like the output from the read method. Though looking at the API I do see that it does return a List. So lets iterate over that list to see what's in them. Since I see its only the stderr reporting the results I'll just play with that one now. And to save some screen space I'm just going to paste the snippet below:
...
err.dataHandler = errRead() {
  print("Inside stderr dataHandler");
  for(var x in err.read()) {
    print(x);
  }
};
...
Inside beginHandle
Inside stderr dataHandler
73
110
118
< ... snipped for brevity ... >
50
99
10
Inside beginHandle
Exit code is: 1

Ack! Okay that's so not what I wanted.. I'm getting integer character codes instead of the strings or characters returned. I need to convert these character codes to a new string. Fortunately a little search found just the constructor I needed in the String class. new String.fromCharCodes. So a quick little adjustment to my script and I can actually see what error I'm receiving from the snmpget command itself. And I no longer require that silly for loop.
#import("dart:io");

void main() {
  var proc = new Process.start('/usr/bin/snmpget', ['-v 2c', '-c public', '127.0.0.1', '.1.3.6.1.2.1.1.1.0']);
  proc.startHandler = beginHandle() {
    print("Inside beginHandle");
    var res = proc.stdout;
    var err = proc.stderr;
    
    res.dataHandler = nowRead() {
      print("Inside stdout dataHandler");
      print(new String.fromCharCodes(res.read()));
    };
    
    err.dataHandler = errRead() {
      print("Inside stderr dataHandler");
      print(new String.fromCharCodes(err.read()));
    };
  };

  proc.errorHandler = errorHandle(errMsg) {
    print("Inside beginHandle");
    print(errMsg);
  };

  proc.exitHandler = exitHandle(exitCode) {
    print("Inside beginHandle");
    print("Exit code is: $exitCode");
    proc.close();
  };
}
Inside beginHandle
Inside stderr dataHandler
Invalid version specified after -v flag:  2c

Inside stderr dataHandler
USAGE: snmpget [OPTIONS] AGENT OID [OID]...
<... snipped for brevity ...>

Alright so I'm getting the full snmpget error message but at the top I can see it's not happy about my flags. After playing around a little I discovered that this has something to do with how exactly the Process.start passes the argument list. The two options I came up with were to remove the spaces (which can sometimes cause issues with the receiving program) or after each flag place its argument as its own member of the list. So making that change I end up with the following code which works perfectly and calls the snmpget command and prints its return.
#import("dart:io");

void main() {
  var proc = new Process.start('/usr/bin/snmpget', ['-v', '2c', '-c', 'public', '127.0.0.1', '.1.3.6.1.2.1.1.1.0']);
  proc.startHandler = beginHandle() {
    print("Inside of start Handler");
    var res = proc.stdout;
    var err = proc.stderr;
    res.dataHandler = nowRead() {
      print("Inside stdout dataHandler");
      print(new String.fromCharCodes(res.read()));
    };
    err.dataHandler = errRead() {
      print("Inside stderr dataHandler");
      print(new String.fromCharCodes(err.read()));
    };
  };
  proc.exitHandler = exitHandle(exitCode) {
    print("Inside of exit Handler");
    print("Exit code is: ${exitCode}");
    proc.close();
  };
  
  proc.errorHandler = errorHandle(errorMsg) {
    print("Inside of Error Handler");
    print(errorMsg);
  };
}
Inside of start Handler
Inside stdout dataHandler
iso.3.6.1.2.1.1.1.0 = STRING: "Super Top Secret Equipment Name"

Inside of exit Handler
Exit code is: 0

Finally! Success! This is just the start of the script which I can now use to handle, parse and interpret the results of the snmp queries I make and pass them along as needed, perhaps providing them via json as an ajax service. In any case I certainly learned a lot more than expected about how Dart handles I/O and how I, in turn, need to handle the requests. While this small sample in itself seems rather verbose I can see how a few small wrappers could make dealing with a large number of processes fairly clean and robust.

2 comments:

  1. Instead of creating a string containing the full output (new String.fromCharCodes) you could also use the StringInputStream class. For an example look at lines 260+ and 224+ of the test_runner.dart (our test script):
    http://code.google.com/p/dart/source/browse/branches/bleeding_edge/dart/tools/testing/dart/test_runner.dart?spec=svn4007&r=3891

    ReplyDelete
    Replies
    1. Thanks Florian. I just did up a quick example of that a moment ago and see what you mean. I'll play with it a little further and write a little update to my script above to share later.

      Delete