Node Monitor v0.6.5

Show:

File: lib/Probe.js

// Probe.js (c) 2010-2014 Loren West and other contributors
// May be freely distributed under the MIT license.
// For further details and documentation:
// http://lorenwest.github.com/node-monitor
(function(root){

  // Module loading
  var Monitor = root.Monitor || require('./Monitor'),
      log = Monitor.getLogger('Probe'),
      stat = Monitor.getStatLogger('Probe'),
      Cron = Monitor.Cron, _ = Monitor._, Backbone = Monitor.Backbone;

  /**
  * A software device used to expose real time data to monitors
  *
  * This is the base class from which all probe implementations extend.
  *
  * In order to send probe data to monitors, probe implementations simply set
  * their model data using ```set()```.  Those changes are detected and propagated
  * to all monitors of this probe, firing their change events.
  *
  * In order to allow remote probe control, probes need only provide a method
  * called ```{name}_control()```.  See the ```ping_control()``` method as an example,
  * and the ```Probe.onControl()``` method for more information.
  *
  * @class Probe
  * @extends Backbone.Model
  * @constructor
  * @param model - Initial data model.  Can be a JS object or another Model.
  *     @param model.id {String} The probe id.
  *       Assigned by the <a href="Router.html">Router</a> on probe instantiation.
  */
  var Probe = Monitor.Probe = Backbone.Model.extend({

    defaults: {
      id:  null
    },

    /**
    * Initialize the probe
    *
    * This is called on the probe during construction.  It contains
    * the probe initialization attributes and an option to make probe
    * construction asynchronous.
    *
    * Probe implementations can defer the initial response to the monitor until
    * the initial state is loaded.  This allows the callback on
    * <a href="Monitor.html#method_connect">```Monitor.connect()```</a>
    * to have the complete initial state of the probe when called.
    *
    * If the initial probe state cannot be determined in ```initialize```, it should
    * set the ```options.asyncInit``` option to ```true```, and call the
    * ```options.callback(error)``` once the initial state is determined.
    *
    *     // Asynchronous initialization
    *     options.asyncInit = true;
    *     var callback = options.callback
    *
    * If ```asyncInit``` is set to true, the ```callback``` must be called once
    * the initial state of the probe is known (or in an error condition).
    *
    *     // Set the initial state, and call the callback
    *     this.set(...);
    *     callback(null);
    *
    * See the <a href="../files/lib_probes_FileProbe.js.html#l47">```initialize```</a>
    * method of the <a href="FileProbe.html">FileProbe</a> probe for an example.  It defers
    * returning the probe to the monitor until the initial file contents are loaded.
    *
    * @method initialize
    * @param attributes {Object} Initial probe attributes sent in from the Monitor
    * @param options {Object} Initialization options
    *     @param options.asyncInit {boolean} Set this to TRUE if the initial probe
    *         state can't be known immediately.
    *     @param options.callback {function(error)} The callback to call
    *         if asyncInit is set to true.  If an error is passed, the probe
    *         will not be used.
    */
    initialize: function(attributes, options) {
      var t = this;
      log.info('init', t.toJSON(), options);
    },

    /**
    * Release any resources consumed by this probe.
    *
    * This can be implemented by derived classes that need to be informed when
    * they are to be shut down.
    *
    * Probes that listen to events should use this method to remove their
    * event listeners.
    *
    * @method release
    */
    release: function(){
      var t = this;
      log.info('release', t.toJSON());
    },

    /**
    * Dispatch a control message to the appropriate control function.
    *
    * This is called when the
    * <a href="Monitor.html#method_control">```control()```</a>
    * method of a monitor is called.
    * The name determines the method name called on the probe.
    *
    * The probe must implement a method with the name ```{name}_control()```,
    * and that method must accept two parameters - an input params and a callback.
    * The callback must be called, passing an optional error and response object.
    *
    * For example, if the probe supports a control with the name ```go```, then
    * all it needs to do is implement the ```go_control()``` method with the
    * proper signature.  See ```ping_control()``` for an example.
    *
    * @method onControl
    * @param name {String} Name of the control message.
    * @param [params] {Any} Input parameters specific to the control message.
    * @param [callback] {Function(error, response)} Called to send the message (or error) response.
    * <ul>
    *   <li>error (Any) An object describing an error (null if no errors)</li>
    *   <li>response (Any) Response parameters specific to the control message.
    * </ul>
    */
    onControl: function(name, params, callback) {
      var t = this,
          controlFn = t[name + '_control'],
          startTime = Date.now(),
          errMsg,
          logId = 'onControl.' + t.probeClass + '.' + name;

      params = params || {};
      callback = callback || function(){};
      log.info(logId, t.get('id'), params);

      if (!controlFn) {
        errMsg = 'No control function: ' + name;
        log.error(logId, errMsg);
        return callback({msg: errMsg});
      }

      var whenDone = function(error) {
        if (error) {
          log.error(logId, error);
          return callback(error);
        }
        var duration = Date.now() - startTime;
        log.info(logId, params);
        stat.time(t.logId, duration);
        callback.apply(null, arguments);
      };

      try {
        controlFn.call(t, params, whenDone);
      } catch (e) {
        errMsg = 'Error calling control: ' + t.probeClass + ':' + name;
        whenDone({msg:errMsg, err: e.toString()});
      }
    },

    /**
    * Respond to a ping control sent from a monitor
    *
    * @method ping_control
    * @param params {Object} Input parameters (not used)
    * @param callback {Function(error, response)} Called to send the message (or error) response.
    * <ul>
    *   <li>error (Any) An object describing an error</li>
    *   <li>response (String) The string 'pong' is returned as the response</li>
    * </ul>
    */
    ping_control: function(params, callback) {
      return callback(null, 'pong');
    }

  });

  // Register probe classes when loaded
  Probe.classes = {}; // key = name, data = class definition
  Probe.extend = function(params) {
    var t = this, probeClass = Backbone.Model.extend.apply(t, arguments);
    if (params.probeClass) {Probe.classes[params.probeClass] = probeClass;}
    return probeClass;
  };

  /**
  * Constructor for a list of Probe objects
  *
  *     var myList = new Probe.List(initialElements);
  *
  * @static
  * @method List
  * @param [items] {Array} Initial list items.  These can be raw JS objects or Probe data model objects.
  * @return {Backbone.Collection} Collection of Probe data model objects
  */
  Probe.List = Backbone.Collection.extend({model: Probe});

}(this));