├── .gitignore
├── .npmignore
├── .travis.yml
├── History.md
├── LICENSE
├── README.md
├── config
├── default.js
├── doc
│ ├── README
│ ├── assets
│ │ ├── css
│ │ │ ├── combo-min.css
│ │ │ ├── cssgrids-min.css
│ │ │ ├── main.css
│ │ │ └── sprite.png
│ │ └── js
│ │ │ ├── combo-min.js
│ │ │ └── yui-min.js
│ ├── layouts
│ │ └── main.handlebars
│ └── partials
│ │ ├── events.handlebars
│ │ ├── files.handlebars
│ │ ├── index.handlebars
│ │ ├── method.handlebars
│ │ └── props.handlebars
├── external.js
├── runtime.json
└── test.js
├── dist
├── monitor-all.js
├── monitor-all.min.js
├── monitor.js
└── monitor.min.js
├── grunt.js
├── lib
├── Connection.js
├── Log.js
├── Monitor.js
├── Probe.js
├── Router.js
├── Server.js
├── Stat.js
├── Sync.js
├── index.js
└── probes
│ ├── DataModelProbe.js
│ ├── FileProbe.js
│ ├── FileSyncProbe.js
│ ├── InspectProbe.js
│ ├── LogProbe.js
│ ├── PollingProbe.js
│ ├── ProcessProbe.js
│ ├── RecipeProbe.js
│ ├── ReplProbe.js
│ ├── StatProbe.js
│ ├── StreamProbe.js
│ └── SyncProbe.js
├── monitor.js
├── package.json
├── test
├── ConnectionTest.js
├── DataModelProbeTest.js
├── FileProbeTest.js
├── InspectTest.js
├── LogTest.js
├── MonitorTest.js
├── ProbeTest.js
├── RecipeProbeTest.js
├── RouterTest.js
├── ServerTest.js
├── StatTest.js
└── SyncProbeTest.js
└── yuidoc.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /config/doc
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | - "0.8"
5 | # - "0.6" // problems w/travis downloading dependencies
6 | before_script:
7 | - npm install -g grunt@0.3.17
8 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 | 0.6.10 / 2014-05-16
2 | ===================
3 |
4 | * Added cron/interval polling to RecipeProbe
5 |
6 | 0.6.9 / 2014-03-26
7 | ===================
8 |
9 | * Introduced writable probe attributes via monitor.set()
10 | * Introduced the Recipe probe for monitor automation
11 | * Introduced the DataModel probe for remote state management
12 | * Better Inspect probe evaluation error logging
13 | * Tests for the above
14 |
15 | 0.6.8 / 2014-02-22
16 | ===================
17 |
18 | * Better auto-start sequencing
19 |
20 | 0.6.7 / 2014-02-22
21 | ===================
22 |
23 | * Disable monitor connection until all auto-start monitors are connected
24 |
25 | 0.6.6 / 2014-02-21
26 | ===================
27 |
28 | * Named monitor instances
29 | * Auto-start monitors
30 | * Added tests for server start/stop
31 | * Made the example a little less confusing
32 |
33 | 0.6.5 / 2014-01-06
34 | ===================
35 |
36 | * Updated copyright year
37 |
38 | 0.6.4 / 2013-12-23
39 | ===================
40 |
41 | * Better README
42 |
43 | 0.6.3 / 2013-11-14
44 | ===================
45 |
46 | * Silence error logging that were common-case vs. errors
47 |
48 | 0.6.2 / 2013-11-12
49 | ===================
50 |
51 | * Better console log formatting
52 | * Exported global.Monitor for multi-module loading
53 |
54 | 0.6.1 / 2013-11-07
55 | ===================
56 |
57 | * Bumped up node-config version
58 |
59 | 0.6.0 / 2013-11-06
60 | ===================
61 |
62 | * Project name change from monitor-min
63 |
64 | 0.5.12 / 2013-10-23
65 | ===================
66 |
67 | * Updated config dependency to not freak out if the config directory is absent
68 |
69 | 0.5.11 / 2013-10-22
70 | ===================
71 |
72 | * Added TRACE to default console logging output
73 | * Better server listen error handling and debug output
74 | * Better error message on probe failure
75 |
76 | 0.5.10 / 2013-10-15
77 | ===================
78 |
79 | * Better console logging
80 | * Can chain require().start()
81 | * Travis test
82 |
83 | 0.5.9 / 2013-10-04
84 | ==================
85 |
86 | * Better probe instantiation error message
87 |
88 | 0.5.8 / 2013-09-15
89 | ==================
90 |
91 | * Use a gateway if available
92 | * More efficient localhost processing
93 | * Don't restart if already started
94 | * Fixed lint issues
95 |
96 | 0.5.7 / 2013-08-28
97 | ==================
98 |
99 | * Allow socket.io to connect any way it can
100 | * Fixed process.uptime
101 | * Detect and prevent stat & log recursion
102 | * Improved log usage
103 |
104 | 0.5.6 / 2013-07-22
105 | ==================
106 |
107 | * Changed log/stat probe timestamp from integer to ISO
108 |
109 | 0.5.6 / 2013-07-19
110 | ==================
111 |
112 | * Added a timestamp to logProbe/statProbe bundles
113 |
114 | 0.5.5 / 2013-07-18
115 | ==================
116 |
117 | * Added Log & Stat classes
118 | * Tests for Stat & Log
119 | * Added LogProbe & StatProbe
120 | * Added Log.console for console logging
121 | * Added log statements & stats gathering
122 |
123 | 0.5.4 / 2013-05-23
124 | ==================
125 |
126 | * Exported documentation to a public site
127 | * Updated links in README.txt
128 |
129 | 0.5.3 / 2013-05-16
130 | ==================
131 |
132 | * Fixed a dependency issue in the monitor.js bootstrap
133 |
134 | 0.5.2 / 2013-04-17
135 | ==================
136 |
137 | * Revved the major version to match node-monitor major version
138 | * Added Stats and Logs
139 |
140 | 0.1.1 / 2013-03-21
141 | ==================
142 |
143 | * Initial checkin after montior re-org
144 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2014 Loren West and other contributors
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Monitor your Node.js application
2 | ================================
3 |
4 | [](https://travis-ci.org/lorenwest/node-monitor)
5 |
6 | Introduction
7 | ------------
8 |
9 | Node-monitor is a library for remote monitoring and control of your Node.js app servers.
10 |
11 | Like JMX in the Java world, node-monitor comes with a handful of general monitors, and allows you to create custom monitors for your application.
12 |
13 | These monitors can be scripted using JavaScript, or placed onto a dashboard.
14 |
15 | Project Guidelines
16 | ------------------
17 |
18 | * *Simple* - Get started quickly
19 | * *Powerful* - For multi-node enterprise deployment
20 | * *Lightweight* - Inactive until used, small footprint during use
21 | * *Flexible* - Easy to write custom monitors for your app
22 | * *Stable* - Well tested foundation for module developers
23 |
24 |
25 | Getting Started
26 | ---------------
27 |
28 | Run the following from your app server directory
29 |
30 | $ npm install monitor
31 |
32 | Then place the following line in your application bootstrap, and restart your server
33 |
34 | require('monitor').start();
35 |
36 | Monitoring your app with a REPL console
37 | ---------------------------------------
38 |
39 | Ad-hoc monitoring can be done from a REPL console.
40 |
41 | Start up the REPL, and get the Monitor class. Feel free to copy/paste these lines into your console:
42 |
43 | $ node
44 | > var Monitor = require('monitor');
45 | undefined
46 |
47 | Now connect a monitor to a probe on your app server. There are a handful of built-in probes, and you can build custom probes for your application or npm module.
48 |
49 | For this example, we'll monitor the *Process* probe:
50 |
51 | > var processMonitor = new Monitor({probeClass:'Process'});
52 | > processMonitor.connect();
53 |
54 | The monitor is a [Backbone.js](http://backbonejs.org/) data model so it updates in real time, and you can get all fields with toJSON():
55 |
56 | > processMonitor.get('freemem');
57 | 86368256
58 | > processMonitor.get('freemem');
59 | 80044032
60 | > processMonitor.toJSON();
61 | ...
62 |
63 | As the monitor changes, it emits change events:
64 |
65 | > processMonitor.on('change', function() {
66 | ... console.log(processMonitor.get('freemem'));
67 | ... });
68 |
69 | Monitoring your app with a custom script
70 | ----------------------------------------
71 |
72 | Using Node.js as a scripting language, you can write custom monitors that do anything Node.js can do. Here's an example that prints to the console when free memory falls below a threshold.
73 |
74 | Save this file to low-memory-warn.js, and run **node low-memory-warn**
75 |
76 | // Low memory warning monitor
77 | var Monitor = require('monitor');
78 | var LOW_MEMORY_THRESHOLD = 100000000;
79 |
80 | // Set the probe to push changes every 10 seconds
81 | var options = {
82 | hostName: 'localhost',
83 | probeClass: 'Process',
84 | initParams: {
85 | pollInterval: 10000
86 | }
87 | }
88 | var processMonitor = new Monitor(options);
89 |
90 | // Attach the change listener
91 | processMonitor.on('change', function() {
92 | var freemem = processMonitor.get('freemem');
93 | if (freemem < LOW_MEMORY_THRESHOLD) {
94 | console.log('Low memory warning: ' + freemem);
95 | }
96 | });
97 |
98 | // Now connect the monitor
99 | processMonitor.connect(function(error) {
100 | if (error) {
101 | console.error('Error connecting with the process probe: ', error);
102 | process.exit(1);
103 | }
104 | });
105 |
106 | Monitoring your app in a browser
107 | --------------------------------
108 |
109 | The above script runs just as well within an html ```
112 |
113 | The browser distribution included in node-monitor exports a single variable ```Monitor``` to the global namespace, and it can be used just like the ```Monitor``` variable in ```var Monitor = require('monitor')```.
114 |
115 | Your browser will probably have to be pointing to localhost or behind your firewall in order to connect with the app server on the configured monitor port. See *Security Concerns* below.
116 |
117 | Monitoring your app in a dashboard
118 | ---------------------------
119 | 
120 |
121 | The [monitor-dashboard](https://github.com/lorenwest/monitor-dashboard) application lets you visualize your monitors in a dashboard.
122 |
123 | $ npm install monitor-dashboard
124 | $ npm start monitor-dashboard
125 |
126 | Security Concerns
127 | -----------------
128 |
129 | Exposing the internals of your app server is a high security risk. By default, the server listens on port 42000 and will connect with localhost clients only.
130 |
131 | In order to monitor across machines, the default configuration must be changed to listen beyond localhost. Before doing this, it is recommended to understand the risks and have external measures in place to prevent unauthorized access.
132 |
133 | See notes in the ```config/external.js``` file for more information.
134 |
135 | Links
136 | -------
137 |
138 | * [API Docs](http://lorenwest.github.io/node-monitor/doc/index.html) - Node monitor JavaScript documentation.
139 | * [Monitor Dashboard](https://github.com/lorenwest/monitor-dashboard) - Dashboards for the node monitor project.
140 | * [Dashboard Components](https://github.com/lorenwest/core-monitor) - Core components for the Dashboard project.
141 |
142 | License
143 | -------
144 |
145 | May be freely distributed under the MIT license
146 | See the [LICENSE](https://github.com/lorenwest/node-monitor/blob/master/LICENSE) file.
147 | Copyright (c) 2010-2014 Loren West
148 |
149 |
--------------------------------------------------------------------------------
/config/default.js:
--------------------------------------------------------------------------------
1 | // Default configurations.
2 | module.exports = {
3 | Monitor: {
4 |
5 | // This is the running applicaiton name. It should be overridden
6 | // in applications that embed the monitor package.
7 | appName: 'Monitor',
8 |
9 | // The base port to use for monitor connections. If this is changed,
10 | // it must be changed on all processes in the monitor network as it
11 | // is used by both client and server processes. Clients use this as
12 | // the starting port to scan. Servers attempt to listen on this port,
13 | // and will continue with higher ports if other processes are listening
14 | // on the port.
15 | serviceBasePort: 42000,
16 |
17 | // When attempting to connect to a remote server, scan this number of
18 | // ports on the remote machine (starting at the serviceBasePort) to
19 | // discover monitor processes.
20 | portsToScan: 20,
21 |
22 | // Only allow connections from this machine by default. This reduces
23 | // accidental security breaches by requiring you to consider your network
24 | // security policies before allowing external connections.
25 | // See the external.js file in this directory for more information.
26 | allowExternalConnections: false,
27 |
28 | // Monitors to start on load.
29 | // This is a map of objects, each passed as the first parameter
30 | // to a new Monitor instance. The autoStart monitors are named so
31 | // additional autoStart monitors can be added in subsequent config files.
32 | autoStart: {
33 | // monitorName: {probeName: '...', probeClass: '...', initParams:{...}}
34 | },
35 |
36 | // Named list of recipe definitions to load when Monitor loads.
37 | // See Recipe.js for the structure and behavior of a recipe.
38 | recipes: {
39 | // recipeName: {recipeDefinition}
40 | },
41 |
42 | // Configure the built-in console log output
43 | consoleLogListener: {
44 | pattern: "{trace,warn,error,fatal}.*"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/config/doc/README:
--------------------------------------------------------------------------------
1 | These override from the default yuidoc templates installed globally:
2 |
3 | /usr/local/lib/node_modules/yuidocjs/themes/default
4 |
5 | In order to load locally vs. requiring a network connection (required for developing on the train):
6 |
7 | * Download yui-min.js into assets/js/yui-min.js
8 | * Remove the reference to http://yui.yahooapis.com/
9 | * Download all combo* .js files that it grabs from yahoo -> into assets/js/combo-min.js
10 | * Download all combo* .css files -> into assets/css/combo-min.css
11 | * Download sprite.png -> into assets/css/sprite.png
12 | * Change all sprite.png references from url(http...sprite.png) to url(sprite.png) in combo-min.css
13 | * Edit the main.handlebars to include assets/js/combo-min.js and assets/css/combo-min.css
14 |
15 |
--------------------------------------------------------------------------------
/config/doc/assets/css/combo-min.css:
--------------------------------------------------------------------------------
1 | /*
2 | YUI 3.4.0 (build 3928)
3 | Copyright 2011 Yahoo! Inc. All rights reserved.
4 | Licensed under the BSD License.
5 | http://yuilibrary.com/license/
6 | */
7 | .yui3-widget-hidden{display:none}.yui3-widget-content{overflow:hidden}.yui3-widget-content-expanded{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;height:100%}.yui3-widget-tmp-forcesize{overflow:hidden!important}
8 | /*
9 | YUI 3.4.0 (build 3928)
10 | Copyright 2011 Yahoo! Inc. All rights reserved.
11 | Licensed under the BSD License.
12 | http://yuilibrary.com/license/
13 | */
14 | .yui3-tab-panel{display:none}.yui3-tab-panel-selected{display:block}.yui3-tabview-list,.yui3-tab{margin:0;padding:0;list-style:none}.yui3-tabview{position:relative}.yui3-tabview,.yui3-tabview-list,.yui3-tabview-panel,.yui3-tab,.yui3-tab-panel{zoom:1}.yui3-tab{display:inline-block;*display:inline;vertical-align:bottom;cursor:pointer}.yui3-tab-label{display:block;display:inline-block;padding:6px 10px;position:relative;text-decoration:none;vertical-align:bottom}.yui3-skin-sam .yui3-tabview-list{border:solid #2647a0;border-width:0 0 5px;zoom:1}.yui3-skin-sam .yui3-tab{margin:0 .2em 0 0;padding:1px 0 0;zoom:1}.yui3-skin-sam .yui3-tab-selected{margin-bottom:-1px}.yui3-skin-sam .yui3-tab-label{background:#d8d8d8 url(sprite.png) repeat-x;border:solid #a3a3a3;border-width:1px 1px 0 1px;color:#000;cursor:hand;font-size:85%;padding:.3em .75em;text-decoration:none}.yui3-skin-sam .yui3-tab-label:hover,.yui3-skin-sam .yui3-tab-label:focus{background:#bfdaff url(sprite.png) repeat-x left -1300px;outline:0}.yui3-skin-sam .yui3-tab-selected .yui3-tab-label,.yui3-skin-sam .yui3-tab-selected .yui3-tab-label:focus,.yui3-skin-sam .yui3-tab-selected .yui3-tab-label:hover{background:#2647a0 url(sprite.png) repeat-x left -1400px;color:#fff}.yui3-skin-sam .yui3-tab-selected .yui3-tab-label{padding:.4em .75em}.yui3-skin-sam .yui3-tab-selected .yui3-tab-label{border-color:#243356}.yui3-skin-sam .yui3-tabview-panel{background:#edf5ff}.yui3-skin-sam .yui3-tabview-panel{border:1px solid #808080;border-top-color:#243356;padding:.25em .5em}
15 |
--------------------------------------------------------------------------------
/config/doc/assets/css/cssgrids-min.css:
--------------------------------------------------------------------------------
1 | /*
2 | YUI 3.4.0 (build 3928)
3 | Copyright 2011 Yahoo! Inc. All rights reserved.
4 | Licensed under the BSD License.
5 | http://yuilibrary.com/license/
6 | */
7 | .yui3-g{letter-spacing:-0.31em;*letter-spacing:normal;word-spacing:-0.43em}.yui3-u,.yui3-u-1,.yui3-u-1-2,.yui3-u-1-3,.yui3-u-2-3,.yui3-u-1-4,.yui3-u-3-4,.yui3-u-1-5,.yui3-u-2-5,.yui3-u-3-5,.yui3-u-4-5,.yui3-u-1-6,.yui3-u-5-6,.yui3-u-1-8,.yui3-u-3-8,.yui3-u-5-8,.yui3-u-7-8,.yui3-u-1-12,.yui3-u-5-12,.yui3-u-7-12,.yui3-u-11-12,.yui3-u-1-24,.yui3-u-5-24,.yui3-u-7-24,.yui3-u-11-24,.yui3-u-13-24,.yui3-u-17-24,.yui3-u-19-24,.yui3-u-23-24{display:inline-block;zoom:1;*display:inline;letter-spacing:normal;word-spacing:normal;vertical-align:top}.yui3-u-1{display:block}.yui3-u-1-2{width:50%}.yui3-u-1-3{width:33.33333%}.yui3-u-2-3{width:66.66666%}.yui3-u-1-4{width:25%}.yui3-u-3-4{width:75%}.yui3-u-1-5{width:20%}.yui3-u-2-5{width:40%}.yui3-u-3-5{width:60%}.yui3-u-4-5{width:80%}.yui3-u-1-6{width:16.656%}.yui3-u-5-6{width:83.33%}.yui3-u-1-8{width:12.5%}.yui3-u-3-8{width:37.5%}.yui3-u-5-8{width:62.5%}.yui3-u-7-8{width:87.5%}.yui3-u-1-12{width:8.3333%}.yui3-u-5-12{width:41.6666%}.yui3-u-7-12{width:58.3333%}.yui3-u-11-12{width:91.6666%}.yui3-u-1-24{width:4.1666%}.yui3-u-5-24{width:20.8333%}.yui3-u-7-24{width:29.1666%}.yui3-u-11-24{width:45.8333%}.yui3-u-13-24{width:54.1666%}.yui3-u-17-24{width:70.8333%}.yui3-u-19-24{width:79.1666%}.yui3-u-23-24{width:95.8333%}
--------------------------------------------------------------------------------
/config/doc/assets/css/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lorenwest/node-monitor/7afd5d22c1d44b93c7cb2250ea9e05ef7b8451f8/config/doc/assets/css/sprite.png
--------------------------------------------------------------------------------
/config/doc/layouts/main.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{htmlTitle}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
27 |
28 |
29 | {{>options}}
30 |
31 |
32 |
33 | {{>layout_content}}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/config/doc/partials/events.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
5 |
{{#crossLink type}}{{/crossLink}}
6 |
7 | {{#if deprecated}}
8 |
deprecated
9 | {{/if}}
10 |
11 | {{#if access}}
12 |
{{access}}
13 | {{/if}}
14 |
15 | {{#if final}}
16 |
final
17 | {{/if}}
18 |
19 | {{#if static}}
20 |
static
21 | {{/if}}
22 |
23 |
53 |
54 |
55 | {{{eventDescription}}}
56 |
57 |
58 | {{#if params}}
59 |
60 |
Event Payload:
61 |
62 |
114 |
115 | {{/if}}
116 |
117 |
118 | {{#example}}
119 |
120 |
Example:
121 |
122 |
123 | {{{.}}}
124 |
125 |
126 | {{/example}}
127 |
128 |
--------------------------------------------------------------------------------
/config/doc/partials/files.handlebars:
--------------------------------------------------------------------------------
1 | File: {{fileName}}
2 |
3 |
4 |
5 | {{fileData}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/config/doc/partials/index.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Node.js Application Monitoring
5 |
Remote monitoring and control for your Node.js app
6 |
7 |
9 | Classes in this module represent baseline monitor functionality. They can
10 | be loaded and run in a node.js container as well as within a browser.
11 |
12 |
13 |
15 | The probes in this module offer baseline functionality, and provide examples
16 | for building custom probes.
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/config/doc/partials/method.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | {{#if params}}
7 |
8 |
(
9 | {{#params}}
10 |
11 | {{#if optional}}
12 | [{{name}}{{#if optdefault}}={{optdefault}}{{/if}}]
13 | {{else}}
14 | {{name}}
15 | {{/if}}
16 |
17 | {{/params}}
18 | )
19 |
20 | {{else}}
21 |
()
22 | {{/if}}
23 |
24 | {{#if return}}
25 |
26 | {{#crossLink returnType}}{{/crossLink}}
27 |
28 | {{/if}}
29 |
30 | {{#if deprecated}}
31 |
deprecated
32 | {{/if}}
33 |
34 | {{#if access}}
35 |
{{access}}
36 | {{/if}}
37 |
38 | {{#if final}}
39 |
final
40 | {{/if}}
41 |
42 | {{#if static}}
43 |
static
44 | {{/if}}
45 |
46 | {{#if chainable}}
47 |
chainable
48 | {{/if}}
49 |
50 |
81 |
82 |
83 | {{{methodDescription}}}
84 |
85 |
86 | {{#if params}}
87 |
88 |
Parameters:
89 |
90 |
154 |
155 | {{/if}}
156 |
157 | {{#return}}
158 |
159 |
Returns:
160 |
161 |
162 | {{#if description}}
163 | {{#if type}}
164 | {{#crossLink type}}{{/crossLink}} :
165 | {{/if}}
166 | {{{description}}}
167 | {{else}}
168 | {{#if type}}
169 | {{#crossLink type}}{{/crossLink}} :
170 | {{/if}}
171 | {{/if}}
172 |
173 |
174 | {{/return}}
175 |
176 | {{#example}}
177 |
178 |
Example:
179 |
180 |
181 | {{{.}}}
182 |
183 |
184 | {{/example}}
185 |
186 |
--------------------------------------------------------------------------------
/config/doc/partials/props.handlebars:
--------------------------------------------------------------------------------
1 |
2 |
5 |
{{#crossLink type}}{{/crossLink}}
6 |
7 | {{#if deprecated}}
8 |
deprecated
9 | {{/if}}
10 |
11 | {{#if access}}
12 |
{{access}}
13 | {{/if}}
14 |
15 | {{#if final}}
16 |
final
17 | {{/if}}
18 |
19 | {{#if static}}
20 |
static
21 | {{/if}}
22 |
23 |
53 |
54 |
55 | {{{propertyDescription}}}
56 |
57 |
58 | {{#if default}}
59 |
Default: {{default}}
60 | {{/if}}
61 |
62 | {{#example}}
63 |
64 |
Example:
65 |
66 |
67 | {{{.}}}
68 |
69 |
70 | {{/example}}
71 |
72 | {{#if subprops}}
73 |
Sub-properties:
74 |
75 |
114 | {{/if}}
115 |
116 |
--------------------------------------------------------------------------------
/config/external.js:
--------------------------------------------------------------------------------
1 | // Configuration overrides when NODE_ENV=external
2 |
3 | // This configuration allows incoming connections from remote systems.
4 | // It should be used only after assuring the network firewall prevents
5 | // untrusted connections on the service port range (usually 42000+).
6 | module.exports = {
7 | Monitor: {
8 | allowExternalConnections: true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/config/runtime.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/config/test.js:
--------------------------------------------------------------------------------
1 | // Configurations for running tests
2 | var NL = '\n';
3 | module.exports = {
4 | Monitor: {
5 | autoStart: {
6 | testProbe: {
7 | probeName: 'ProcessTest', probeClass: 'Process', initParams:{pollInterval: 1234}
8 | },
9 | testModel: {
10 | probeName: 'DataModelTest', probeClass: 'DataModel', initParams:{
11 | testParam1:'testValue1',
12 | attr1:'attrValue1',
13 | derivedAttr1: 'derivedAttrValue1'
14 | }
15 | },
16 | testRecipe: {
17 | probeName: 'RecipeTest', probeClass: 'Recipe', initParams:{
18 | autoStart:true,
19 | monitors: {
20 | process: {probeName: 'ProcessTest'},
21 | dataModel: {probeName: 'DataModelTest'}
22 | },
23 | script:
24 | "// Forward attr1 to attr2, showing the script was run" + NL +
25 | " dataModel.set('attr2', dataModel.get('attr1'));"
26 | }
27 | }
28 | },
29 |
30 | // Squelch log output so error tests aren't chatty
31 | consoleLogListener: {
32 | pattern_save1: "{debug,warn,error,fatal}.*",
33 | pattern_save2: "*",
34 | pattern: ""
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/grunt.js:
--------------------------------------------------------------------------------
1 | // grunt.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For all details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 |
6 | var exec = require('child_process').exec;
7 |
8 | // This is used in the build automation tasks, and on the server
9 | // when running in dev mode to serve individual files for debugging.
10 | var MODULE_DEF = {
11 | lib: [
12 | "lib/Monitor.js",
13 | "lib/Stat.js",
14 | "lib/Log.js",
15 | "lib/Probe.js",
16 | "lib/Connection.js",
17 | "lib/Server.js",
18 | "lib/Router.js",
19 | "lib/Sync.js",
20 | "lib/probes/DataModelProbe.js",
21 | "lib/probes/RecipeProbe.js",
22 | "lib/probes/PollingProbe.js",
23 | "lib/probes/StreamProbe.js",
24 | "lib/probes/InspectProbe.js",
25 | "lib/probes/StatProbe.js",
26 | "lib/probes/LogProbe.js"
27 | ],
28 | ext: [
29 | "node_modules/underscore/underscore.js",
30 | "node_modules/backbone/backbone.js",
31 | "node_modules/backbone-callbacks/backbone-callbacks.js",
32 | "node_modules/socket.io-client/dist/socket.io.js"
33 | ],
34 | probes: [
35 | "lib/probes/FileProbe.js",
36 | "lib/probes/ReplProbe.js",
37 | "lib/probes/ProcessProbe.js",
38 | "lib/probes/SyncProbe.js",
39 | "lib/probes/FileSyncProbe.js"
40 | ]
41 | };
42 |
43 | // Build automation tasks
44 | module.exports = function(grunt) {
45 |
46 | // Project configuration.
47 | grunt.initConfig({
48 | pkg: '',
49 | monitor: MODULE_DEF,
50 | meta: {
51 | banner: '/* <%= pkg.name %> - v<%= pkg.version %> - ' +
52 | '<%= grunt.template.today("yyyy-mm-dd") %> */'
53 | },
54 | lint: {
55 | files: ['grunt.js', '', '', 'test/*.js']
56 | },
57 | test: {
58 | files: ['test/*.js']
59 | },
60 | watch: {
61 | files: ['grunt.js', 'yuidoc.json', '', '', 'config/doc/**', 'test/*.js'],
62 | tasks: 'doc lint test'
63 | },
64 | concat: {
65 | lib: {
66 | src: ['', ''],
67 | dest: './dist/monitor.js'
68 | },
69 | all: {
70 | src: ['', '', ''],
71 | dest: './dist/monitor-all.js'
72 | }
73 | },
74 | min: {
75 | lib: {
76 | src: ['', './dist/monitor.js'],
77 | dest: './dist/monitor.min.js'
78 |
79 | },
80 | all: {
81 | src: ['', './dist/monitor-all.js'],
82 | dest: './dist/monitor-all.min.js'
83 | }
84 | },
85 | jshint: {
86 | options: {
87 | strict: false,
88 | curly: true,
89 | eqeqeq: true,
90 | immed: true,
91 | latedef: true,
92 | newcap: true,
93 | noarg: true,
94 | sub: true,
95 | undef: true,
96 | boss: true,
97 | eqnull: true,
98 | node: true
99 | },
100 | globals: {
101 | exports: true
102 | }
103 | }
104 | });
105 |
106 | grunt.registerTask('doc', 'Generate documentation files', function() {
107 | var t = this, done = t.async(), child, version = grunt.config.get('pkg').version;
108 | var cmd = 'yuidoc --project-version ' + version;
109 | console.log(cmd);
110 | child = exec(cmd, function (error, stdout, stderr) {
111 | console.log(stderr);
112 | console.log(stdout);
113 | cmd = 'cp -R doc/* ../node-monitor-pages/doc; rm -rf doc';
114 | console.log(cmd);
115 | child = exec(cmd, function (error, stdout, stderr) {
116 | console.log(stderr);
117 | console.log(stdout);
118 | done();
119 | });
120 | });
121 | });
122 |
123 | grunt.registerTask('rm_dist', 'Remove distribution files', function() {
124 | var t = this, done = t.async(), child;
125 | child = exec('rm -f dist/*', function (error, stdout, stderr) {
126 | console.log(stderr);
127 | console.log(stdout);
128 | done();
129 | });
130 | });
131 |
132 | // Default task.
133 | grunt.registerTask('default', 'doc lint test dist');
134 | grunt.registerTask('dist', 'rm_dist concat:lib concat:all min:lib min:all');
135 |
136 | };
137 |
138 | // Expose externally
139 | module.exports.MODULE_DEF = MODULE_DEF;
140 |
--------------------------------------------------------------------------------
/lib/Log.js:
--------------------------------------------------------------------------------
1 | /*jslint browser: true */
2 | // Log.js (c) 2010-2014 Loren West and other contributors
3 | // May be freely distributed under the MIT license.
4 | // For further details and documentation:
5 | // http://lorenwest.github.com/node-monitor
6 | (function(root){
7 |
8 | // Module loading
9 | var Monitor = root.Monitor || require('./Monitor'),
10 | // Raw events on the server (for speed), backbone events on the browser (for functionality)
11 | EventEmitter = Monitor.commonJS ? require('events').EventEmitter.prototype : Monitor.Backbone.Events,
12 | Stat = Monitor.Stat,
13 | stat = new Stat('Log'),
14 | _ = Monitor._,
15 | emittingNow = false;
16 |
17 | /**
18 | * A lightweight component for gathering and emitting application logs
19 | *
20 | * It's designed with low development and runtime cost in mind, encouraging
21 | * usage with minimum concern for overhead. Runtime monitoring can be as chatty
22 | * as desired, outputting every log statement of every type, or finely tuned
23 | * with regular expressions to monitor specific log statements.
24 | *
25 | * Log Collector
26 | * -------------
27 | *
28 | * As a collector, it's a place to send application logs.
29 | *
30 | * Example for outputting a log in your application:
31 | *
32 | * var log = require('monitor').getLogger('myModule');
33 | * ...
34 | * log.info('Credit limit accepted', limit, requestedAmount);
35 | *
36 | * The above is a request to output an ```info``` log for ```myModule``` named
37 | * ```Credit limit accepted```. The log entry includes all additional parameters,
38 | * in this case the customer credit limit and the reqeusted amount.
39 | *
40 | * The full name for this log entry is: ```"info.myModule.Credit limit accepted"```
41 | * The name is important, as monitors can be configured to output logs based
42 | * on this name.
43 | *
44 | * Best practices are to include dynamic parameters in extra arguments
45 | * vs. concatenating strings. This reduces logging overhead, especially
46 | * for log statements that aren't currently being watched.
47 | *
48 | * Log Emitter
49 | * -----------
50 | * As an emitter, the Log module is a place to capture logging output.
51 | *
52 | * When listening for log entries, wildcards can be used to register for
53 | * particular log types and entries.
54 | *
55 | * var Log = require('monitor').Log;
56 | * ...
57 | * Log.on('info.myModule.*', myFunction);
58 | *
59 | * Will call ```myFunction``` when all ```info.myModule.*``` logs are emitted.
60 | *
61 | * Listeners are invoked with the following arguments:
62 | *
63 | * - type - The log type (trace, debug, info, warn, error, or fatal)
64 | * - module - The logger module name
65 | * - name - The log entry name
66 | * - args... - Additional arguments passed into the log entry are passed on
67 | * as additional args to the event listener.
68 | *
69 | * Wildcards
70 | * ---------
71 | * A flexible and user-oriented wildcard pattern is used for monitoring
72 | * logs. The pattern is described in the Wildcard secttion of the Stats class .
73 | *
74 | * Choosing Good Names
75 | * -------------------
76 | * It's a good idea to pick a good naming scheme with each dot-delimited segment
77 | * having a consistent, well-defined purpose. Volatile segments should be as deep
78 | * into the hierarchy (furthest right) as possible. Keeping the names less
79 | * volatile makes it easier to turn statistics recording on for all logs.
80 | *
81 | * @class Log
82 | * @constructor
83 | */
84 | var Log = Monitor.Log = function(module) {
85 | var t = this;
86 | t.module = module;
87 | };
88 | var proto = Log.prototype;
89 |
90 | // This is a map of registered event names to compiled regexs, for
91 | // quickly testing if a log needs to be emitted.
92 | Log.eventRegex = {};
93 |
94 | /**
95 | * Output a ```trace``` log entry
96 | *
97 | * @method trace
98 | * @param name {String} Log entry name
99 | * @param [...] {Any} Subsequent arguments to add to the log
100 | */
101 |
102 | /**
103 | * Output a ```debug``` log entry
104 | *
105 | * @method debug
106 | * @param name {String} Log entry name
107 | * @param [...] {Any} Subsequent arguments to add to the log
108 | */
109 |
110 | /**
111 | * Output a ```info``` log entry
112 | *
113 | * @method info
114 | * @param name {String} Log entry name
115 | * @param [...] {Any} Subsequent arguments to add to the log
116 | */
117 |
118 | /**
119 | * Output a ```warn``` log entry
120 | *
121 | * @method warn
122 | * @param name {String} Log entry name
123 | * @param [...] {Any} Subsequent arguments to add to the log
124 | */
125 |
126 | /**
127 | * Output a ```error``` log entry
128 | *
129 | * @method error
130 | * @param name {String} Log entry name
131 | * @param [...] {Any} Subsequent arguments to add to the log
132 | */
133 |
134 | /**
135 | * Output a ```fatal``` log entry
136 | *
137 | * @method fatal
138 | * @param name {String} Log entry name
139 | * @param [...] {Any} Subsequent arguments to add to the log
140 | */
141 |
142 | // Add a method for each log type
143 | ['trace','debug','info','warn','error','fatal'].forEach(function(method) {
144 | proto[method] = function(name) {
145 | Log._emit(method, this.module, name, arguments);
146 | };
147 | });
148 |
149 | /**
150 | * Send the log to all registered listeners
151 | *
152 | * @private
153 | * @static
154 | * @method emit
155 | * @param type {string} The log type (trace, debug, info, etc)
156 | * @param module {String} The log module name
157 | * @param name {String} The log entry name
158 | * @param args {any[]} Arguments to the log entry
159 | */
160 | Log._emit = function(type, module, name, args) {
161 | var eventName,
162 | fullName = type + '.' + module + '.' + name;
163 |
164 | // Prevent log recursion. This has the effect of disabling all logging
165 | // for log handlers (and their downstream effect), but is necessary to
166 | // prevent infinite recursion. If it's desired to log the output of
167 | // log handlers, then delay that processing until nextTick.
168 | if (emittingNow) {
169 | return;
170 | }
171 | emittingNow = true;
172 |
173 | // Output a counter stat for this log
174 | stat.increment(fullName);
175 |
176 | // Test the name against all registered events
177 | for (eventName in Log._events) {
178 |
179 | // Get the regex associated with the name (using the Stat package)
180 | var regex = Log.eventRegex[eventName];
181 | if (!regex) {
182 | regex = Log.eventRegex[eventName] = Stat._buildRegex(eventName);
183 | }
184 |
185 | // Test the long name with the regex, and emit if it matches
186 | if (regex.test(fullName)) {
187 |
188 | // Build the arguments as event name, log type, module, name, [other args...]
189 | var allArgs = _.toArray(args),
190 | emitFn = Log.emit || Log.trigger; // NodeJS/server=emit, Backbone/browser=trigger
191 | allArgs.splice(0, 1, eventName, type, module, name);
192 | emitFn.apply(Log, allArgs);
193 | }
194 | }
195 |
196 | // Turn off recursion prevention
197 | emittingNow = false;
198 | };
199 |
200 | // Mixin event processing for the Log class
201 | _.extend(Log, EventEmitter);
202 |
203 | // Expose this class from the Monitor module
204 | Monitor.setLoggerClass(Log);
205 |
206 | /**
207 | * Output log statements to the console
208 | *
209 | * This method can be used as a listener to send logs to the console.
210 | *
211 | * It uses console.error() for error and fatal log types, and console.log()
212 | * for all other log types.
213 | *
214 | * Example:
215 | *
216 | * var Log = Monitor.Log;
217 | * Log.on('*.MyModule.*', Log.console);
218 | *
219 | * @static
220 | * @method consoleLogger
221 | * @param type {string} The log type (trace, debug, info, etc)
222 | * @param module {String} The log module name
223 | * @param name {String} The log entry name
224 | * @param args {any...} All original, starting with the short name
225 | */
226 | Log.console = function(type, module, name) {
227 |
228 | // Build the string to log, in log4js format
229 | var nowStr = (new Date()).toJSON(),
230 | args = _.toArray(arguments),
231 | logStr = '[' + nowStr + '] [' + type.toUpperCase() + '] ' + module;
232 |
233 | // Remove the type, module, name leaving the args to the log
234 | args.splice(0,3);
235 |
236 | // If no args, then they didn't provide a name
237 | if (args.length === 0) {
238 | args = [name];
239 | }
240 | else {
241 | // Add the log entry name
242 | logStr += '.' + name;
243 | }
244 |
245 | // If the output is simple, just print it. Otherwise JSON.stringify it.
246 | logStr += ' - ';
247 | if (args.length === 1 && typeof args[0] === 'string') {
248 | logStr += args[0];
249 | }
250 | else {
251 | try {
252 | logStr += JSON.stringify(args);
253 | } catch(e) {
254 | logStr += Monitor.stringify(args);
255 | }
256 | }
257 |
258 | // Send to the console - Log or error
259 | if (type === 'error' || type === 'fatal') {
260 | console.error(logStr);
261 | }
262 | else {
263 | console.log(logStr);
264 | }
265 |
266 | };
267 |
268 | // Attach the console log listener
269 | var pattern = Monitor.Config.Monitor.consoleLogListener.pattern;
270 | if (pattern) {
271 | Log.on(pattern, Log.console);
272 | }
273 |
274 | }(this));
275 |
--------------------------------------------------------------------------------
/lib/Probe.js:
--------------------------------------------------------------------------------
1 | // Probe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading
8 | var Monitor = root.Monitor || require('./Monitor'),
9 | log = Monitor.getLogger('Probe'),
10 | stat = Monitor.getStatLogger('Probe'),
11 | Cron = Monitor.Cron, _ = Monitor._, Backbone = Monitor.Backbone;
12 |
13 | /**
14 | * A software device used to expose real time data to monitors
15 | *
16 | * This is the base class from which all probe implementations extend.
17 | *
18 | * In order to send probe data to monitors, probe implementations simply set
19 | * their model data using ```set()```. Those changes are detected and propagated
20 | * to all monitors of this probe, firing their change events.
21 | *
22 | * In order to allow remote probe control, probes need only provide a method
23 | * called ```{name}_control()```. See the ```ping_control()``` method as an example,
24 | * and the ```Probe.onControl()``` method for more information.
25 | *
26 | * @class Probe
27 | * @extends Backbone.Model
28 | * @constructor
29 | * @param model - Initial data model. Can be a JS object or another Model.
30 | * @param model.id {String} The probe id.
31 | * Assigned by the Router on probe instantiation.
32 | */
33 | var Probe = Monitor.Probe = Backbone.Model.extend({
34 |
35 | defaults: {
36 | id: null
37 | },
38 |
39 | /**
40 | * Initialize the probe
41 | *
42 | * This is called on the probe during construction. It contains
43 | * the probe initialization attributes and an option to make probe
44 | * construction asynchronous.
45 | *
46 | * Probe implementations can defer the initial response to the monitor until
47 | * the initial state is loaded. This allows the callback on
48 | * ```Monitor.connect()```
49 | * to have the complete initial state of the probe when called.
50 | *
51 | * If the initial probe state cannot be determined in ```initialize```, it should
52 | * set the ```options.asyncInit``` option to ```true```, and call the
53 | * ```options.callback(error)``` once the initial state is determined.
54 | *
55 | * // Asynchronous initialization
56 | * options.asyncInit = true;
57 | * var callback = options.callback
58 | *
59 | * If ```asyncInit``` is set to true, the ```callback``` must be called once
60 | * the initial state of the probe is known (or in an error condition).
61 | *
62 | * // Set the initial state, and call the callback
63 | * this.set(...);
64 | * callback(null);
65 | *
66 | * See the ```initialize```
67 | * method of the FileProbe probe for an example. It defers
68 | * returning the probe to the monitor until the initial file contents are loaded.
69 | *
70 | * @method initialize
71 | * @param attributes {Object} Initial probe attributes sent in from the Monitor
72 | * @param options {Object} Initialization options
73 | * @param options.asyncInit {boolean} Set this to TRUE if the initial probe
74 | * state can't be known immediately.
75 | * @param options.callback {function(error)} The callback to call
76 | * if asyncInit is set to true. If an error is passed, the probe
77 | * will not be used.
78 | */
79 | initialize: function(attributes, options) {
80 | var t = this;
81 | log.info('init', t.toJSON(), options);
82 | },
83 |
84 | /**
85 | * Release any resources consumed by this probe.
86 | *
87 | * This can be implemented by derived classes that need to be informed when
88 | * they are to be shut down.
89 | *
90 | * Probes that listen to events should use this method to remove their
91 | * event listeners.
92 | *
93 | * @method release
94 | */
95 | release: function(){
96 | var t = this;
97 | log.info('release', t.toJSON());
98 | },
99 |
100 | /**
101 | * Dispatch a control message to the appropriate control function.
102 | *
103 | * This is called when the
104 | * ```control()```
105 | * method of a monitor is called.
106 | * The name determines the method name called on the probe.
107 | *
108 | * The probe must implement a method with the name ```{name}_control()```,
109 | * and that method must accept two parameters - an input params and a callback.
110 | * The callback must be called, passing an optional error and response object.
111 | *
112 | * For example, if the probe supports a control with the name ```go```, then
113 | * all it needs to do is implement the ```go_control()``` method with the
114 | * proper signature. See ```ping_control()``` for an example.
115 | *
116 | * @method onControl
117 | * @param name {String} Name of the control message.
118 | * @param [params] {Any} Input parameters specific to the control message.
119 | * @param [callback] {Function(error, response)} Called to send the message (or error) response.
120 | *
121 | * error (Any) An object describing an error (null if no errors)
122 | * response (Any) Response parameters specific to the control message.
123 | *
124 | */
125 | onControl: function(name, params, callback) {
126 | var t = this,
127 | controlFn = t[name + '_control'],
128 | startTime = Date.now(),
129 | errMsg,
130 | logId = 'onControl.' + t.probeClass + '.' + name;
131 |
132 | params = params || {};
133 | callback = callback || function(){};
134 | log.info(logId, t.get('id'), params);
135 |
136 | if (!controlFn) {
137 | errMsg = 'No control function: ' + name;
138 | log.error(logId, errMsg);
139 | return callback({msg: errMsg});
140 | }
141 |
142 | var whenDone = function(error) {
143 | if (error) {
144 | log.error(logId + '.whenDone', error);
145 | return callback(error);
146 | }
147 | var duration = Date.now() - startTime;
148 | log.info(logId, params);
149 | stat.time(t.logId, duration);
150 | callback.apply(null, arguments);
151 | };
152 |
153 | // Run the control on next tick. This provides a consistent callback
154 | // chain for local and remote probes.
155 | setTimeout(function(){
156 | try {
157 | controlFn.call(t, params, whenDone);
158 | } catch (e) {
159 | errMsg = 'Error calling control: ' + t.probeClass + ':' + name;
160 | whenDone({msg:errMsg, err: e.toString()});
161 | }
162 | }, 0);
163 | },
164 |
165 | /**
166 | * Remotely set a probe attribute.
167 | *
168 | * This allows setting probe attributes that are listed in writableAttributes.
169 | * It can be overwritten in derived Probe classes for greater control.
170 | *
171 | * @method set_control
172 | * @param attrs {Object} Name/Value attributes to set. All must be writable.
173 | * @param callback {Function(error)} Called when the attributes are set or error
174 | */
175 | set_control: function(attrs, callback) {
176 | var t = this,
177 | writableAttributes = t.get('writableAttributes') || [];
178 |
179 | // Validate the attributes are writable
180 | if (writableAttributes !== '*') {
181 | for (var attrName in attrs) {
182 | if (writableAttributes.indexOf(attrName) < 0) {
183 | return callback({code:'NOT_WRITABLE', msg: 'Attribute not writable: ' + attrName});
184 | }
185 | }
186 | }
187 |
188 | // Set the data
189 | var error = null;
190 | if (!t.set(attrs)) {
191 | error = {code:'VALIDATION_ERROR', msg:'Data set failed validation'};
192 | log.warn('set_control', error);
193 | }
194 | return callback(error);
195 | },
196 |
197 | /**
198 | * Respond to a ping control sent from a monitor
199 | *
200 | * @method ping_control
201 | * @param params {Object} Input parameters (not used)
202 | * @param callback {Function(error, response)} Called to send the message (or error) response.
203 | *
204 | * error (Any) An object describing an error
205 | * response (String) The string 'pong' is returned as the response
206 | *
207 | */
208 | ping_control: function(params, callback) {
209 | return callback(null, 'pong');
210 | }
211 |
212 | });
213 |
214 | // Register probe classes when loaded
215 | Probe.classes = {}; // key = name, data = class definition
216 | Probe.extend = function(params) {
217 | var t = this, probeClass = Backbone.Model.extend.apply(t, arguments);
218 | if (params.probeClass) {Probe.classes[params.probeClass] = probeClass;}
219 | return probeClass;
220 | };
221 |
222 | /**
223 | * Constructor for a list of Probe objects
224 | *
225 | * var myList = new Probe.List(initialElements);
226 | *
227 | * @static
228 | * @method List
229 | * @param [items] {Array} Initial list items. These can be raw JS objects or Probe data model objects.
230 | * @return {Backbone.Collection} Collection of Probe data model objects
231 | */
232 | Probe.List = Backbone.Collection.extend({model: Probe});
233 |
234 | }(this));
235 |
--------------------------------------------------------------------------------
/lib/Server.js:
--------------------------------------------------------------------------------
1 | // Server.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading
8 | var Monitor = root.Monitor || require('./Monitor'),
9 | Config = Monitor.Config, _ = Monitor._, Backbone = Monitor.Backbone,
10 | log = Monitor.getLogger('Server'),
11 | stat = Monitor.getStatLogger('Server'),
12 | Connection = Monitor.Connection,
13 | Http = Monitor.commonJS ? require('http') : null,
14 | SocketIO = root.io || require('socket.io');
15 |
16 | /**
17 | * A server for accepting inbound connections from remote monitors
18 | *
19 | * Servers are created when a process wants to expose probe data to remote
20 | * monitors. Example:
21 | *
22 | * // Accept remote monitors
23 | * var server = new Monitor.Server();
24 | * server.start();
25 | *
26 | * An instance of this class represents a listening server accepting inbound
27 | * connections. As inbound connections are detected, a new
28 | * Connection object is created to manage
29 | * traffic on that connection.
30 | *
31 | * Security: Make sure the port range specified in Monitor.Config (starting
32 | * at 42000) is not exposed outside your internal network. If you desire a
33 | * different security model, create your secure server and pass it to the
34 | * constructor.
35 | *
36 | * @class Server
37 | * @extends Backbone.Model
38 | * @constructor
39 | * @param model - Initial data model. Can be a JS object or another Model.
40 | * @param model.gateway {Boolean} - Allow incoming monitors to use me as a gateway (default false)
41 | * @param model.server {HttpServer} - The listening node.js server. Constructed by this class, or specified if a custom server is desired.
42 | * @param model.port {Integer} - The connected port. This is set upon start() if the server isn't specified on construction.
43 | */
44 | var Server = Monitor.Server = Backbone.Model.extend({
45 |
46 | initialize: function(params) {
47 | var t = this;
48 | t.isListening = false;
49 | t.connections = new Connection.List();
50 | },
51 |
52 | /**
53 | * Start accepting monitor connections
54 | *
55 | * This method starts listening for incoming monitor connections on the
56 | * server.
57 | *
58 | * If the server was specified during object creation, this binds the
59 | * socket.io service to the server.
60 | *
61 | * If the server was not specified during object creation, this will create
62 | * a server on the first available monitor port.
63 | *
64 | * @method start
65 | * @param options {Object} - Start options. OPTIONAL
66 | * @param options.port {Integer} - Port to attempt listening on if server isn't specified. Default: 42000
67 | * @param options.attempt {Integer} - Attempt number for internal recursion detection. Default: 1
68 | * @param callback {Function(error)} - Called when the server is accepting connections.
69 | */
70 | /**
71 | * The server has started
72 | *
73 | * This event is fired when the server has determined the port to accept
74 | * connections on, and has successfully configured the server to start
75 | * accepting new monitor connections.
76 | *
77 | * @event start
78 | */
79 | /**
80 | * A client error has been detected
81 | *
82 | * This event is fired if an error has been detected in the underlying
83 | * transport. It may indicate message loss, and may result in a
84 | * subsequent stop event if the connection cannot be restored.
85 | *
86 | * @event error
87 | */
88 | start: function(options, callback) {
89 | if (typeof options === 'function') {
90 | callback = options;
91 | options = null;
92 | }
93 | options = options || {};
94 | callback = callback || function(){};
95 | var t = this, server = t.get('server'), error,
96 | startTime = Date.now(),
97 | port = options.port || Config.Monitor.serviceBasePort,
98 | attempt = options.attempt || 1,
99 | allowExternalConnections = Config.Monitor.allowExternalConnections;
100 |
101 | // Recursion detection. Only scan for so many ports
102 | if (attempt > Config.Monitor.portsToScan) {
103 | error = {err:'connect:failure', msg: 'no ports available'};
104 | log.error('start', error);
105 | return callback(error);
106 | }
107 |
108 | // Bind to an existing server, or create a new server
109 | if (server) {
110 | t.bindEvents(callback);
111 | } else {
112 | server = Http.createServer();
113 |
114 | // Try next port if a server is listening on this port
115 | server.on('error', function(err) {
116 | if (err.code === 'EADDRINUSE') {
117 | // Error if the requested port is in use
118 | if (t.get('port')) {
119 | log.error('portInUse',{host:host, port:port});
120 | return callback({err:'portInUse'});
121 | }
122 | // Try the next port
123 | log.info('portInUse',{host:host, port:port});
124 | return t.start({port:port + 1, attempt:attempt + 1}, callback);
125 | }
126 | // Unknown error
127 | callback(err);
128 | });
129 |
130 | // Allow connections from INADDR_ANY or LOCALHOST only
131 | var host = allowExternalConnections ? '0.0.0.0' : '127.0.0.1';
132 |
133 | // Start listening, callback on success
134 | server.listen(port, host, function(){
135 |
136 | // Set a default NODE_APP_INSTANCE based on the available server port
137 | if (!process.env.NODE_APP_INSTANCE) {
138 | process.env.NODE_APP_INSTANCE = '' + (port - Config.Monitor.serviceBasePort + 1);
139 | }
140 |
141 | // Record the server & port, and bind incoming events
142 | t.set({server: server, port: port});
143 | t.bindEvents(callback);
144 | log.info('listening', {
145 | appName: Config.Monitor.appName,
146 | NODE_APP_INSTANCE: process.env.NODE_APP_INSTANCE,
147 | listeningOn: host,
148 | port: port
149 | });
150 | });
151 | }
152 | },
153 |
154 | /**
155 | * Bind incoming socket events to the server
156 | *
157 | * This method binds to the socket events and attaches the socket.io
158 | * server. It is called when the connection starts listening.
159 | *
160 | * @protected
161 | * @method bindEvents
162 | * @param callback {Function(error)} - Called when all events are bound
163 | */
164 | bindEvents: function(callback) {
165 |
166 | // Detect server errors
167 | var t = this, server = t.get('server');
168 | server.on('clientError', function(err){
169 | log.error('bindEvents', 'clientError detected on server', err);
170 | t.trigger('error', err);
171 | });
172 | server.on('close', function(err){
173 | server.hasEmittedClose = true;
174 | log.info('bindEvents.serverClose', 'Server has closed', err);
175 | t.stop();
176 | });
177 |
178 | // Start up the socket.io server.
179 | var socketIoParams = {
180 | log: false
181 | };
182 | t.socketServer = SocketIO.listen(server, socketIoParams);
183 | t.socketServer.sockets.on('connection', function (socket) {
184 | var connection = Monitor.getRouter().addConnection({
185 | socket: socket, gateway: t.get('gateway')
186 | });
187 | t.connections.add(connection);
188 | var onDisconnect = function(reason) {
189 | t.connections.remove(connection);
190 | Monitor.getRouter().removeConnection(connection);
191 | connection.off('disconnect', onDisconnect);
192 | log.info('client.disconnect', 'Disconnected client socket');
193 | };
194 | connection.on('disconnect', onDisconnect);
195 | log.info('client.connect', 'Connected client socket');
196 | });
197 |
198 | // Notify that we've started
199 | t.isListening = true;
200 | if (callback) {callback(null);}
201 | t.trigger('start');
202 | },
203 |
204 | /**
205 | * Stop processing inbound monitor traffic
206 | *
207 | * This method stops accepting new inbound monitor connections, and closes
208 | * all existing monitor connections associated with the server.
209 | *
210 | * @method stop
211 | * @param callback {Function(error)} - Called when the server has stopped
212 | */
213 | /**
214 | * The server has stopped
215 | *
216 | * This event is fired after the server has stopped accepting inbound
217 | * connections, and has closed all existing connections and released
218 | * associated resources.
219 | *
220 | * @event stop
221 | */
222 | stop: function(callback) {
223 | var t = this, server = t.get('server'), router = Monitor.getRouter();
224 | callback = callback || function(){};
225 |
226 | // Call the callback, but don't stop more than once.
227 | if (!t.isListening) {
228 | return callback();
229 | }
230 |
231 | // Release resources
232 | t.connections.each(router.removeConnection, router);
233 | t.connections.reset();
234 |
235 | // Shut down the server
236 | t.isListening = false;
237 | server.close();
238 |
239 | // Send notices
240 | t.trigger('stop');
241 | return callback();
242 | }
243 | });
244 |
245 | /**
246 | * Constructor for a list of Server objects
247 | *
248 | * var myList = new Server.List(initialElements);
249 | *
250 | * @static
251 | * @method List
252 | * @param [items] {Array} Initial list items. These can be raw JS objects or Server data model objects.
253 | * @return {Backbone.Collection} Collection of Server data model objects
254 | */
255 | Server.List = Backbone.Collection.extend({model: Server});
256 |
257 | }(this));
258 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | // index.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | /*
8 | * Entry point for commonJS style loading
9 | *
10 | * This file coordinates the loading of modules in a consistent order
11 | * in a commonJS environment.
12 | */
13 |
14 | var commonJS = (typeof exports !== 'undefined');
15 | if (commonJS) {
16 |
17 | // Only load once
18 | if (global.Monitor) {
19 | module.exports = global.Monitor;
20 | }
21 | else {
22 |
23 | // Export the Monitor class to module and global scope to assure
24 | // a single load, and to match the browser-side global Monitor.
25 | var Monitor = global.Monitor = module.exports = require('./Monitor');
26 |
27 | // Attach backbone callbacks
28 | require('backbone-callbacks').attach(Monitor.Backbone);
29 |
30 | // Grunt.js contains the module definition files
31 | var MODULE_DEF = require('../grunt.js').MODULE_DEF;
32 |
33 | // Load local library files, then server-only probes
34 | var allFiles = MODULE_DEF.lib.concat(MODULE_DEF.probes);
35 | allFiles.forEach(function(file) {require('../' + file);});
36 | }
37 | }
38 |
39 | }(this));
40 |
--------------------------------------------------------------------------------
/lib/probes/DataModelProbe.js:
--------------------------------------------------------------------------------
1 | // DataModelProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 |
6 | (function(root){
7 |
8 | // Module loading - this runs server-side only
9 | var Monitor = root.Monitor || require('../Monitor');
10 |
11 | /**
12 | * Probe representation of a simple data model
13 | *
14 | * This probe allows remote creation, manipulation, and change moitoring for
15 | * arbitrary data. It is useful for monitor applications needing to maintain
16 | * a small amount of state on the system being monitored.
17 | *
18 | * @class DataModelProbe
19 | * @extends Probe
20 | * @constructor
21 | * @param [initParams] - Initialization parameters. An object containing the
22 | * initial state of the data model. All properties become data model
23 | * elements, readable and writable by all monitors connected to the probe.
24 | */
25 | var DataModelProbe = Monitor.DataModelProbe = Monitor.Probe.extend({
26 |
27 | // These are required for Probes
28 | probeClass: 'DataModel',
29 | writableAttributes: '*'
30 |
31 | });
32 |
33 | }(this));
34 |
--------------------------------------------------------------------------------
/lib/probes/FileSyncProbe.js:
--------------------------------------------------------------------------------
1 | // FileSyncProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading - this runs server-side only
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | logger = Monitor.getLogger('FileSyncProbe'),
10 | Probe = Monitor.Probe,
11 | _ = Monitor._,
12 | SyncProbe = Monitor.SyncProbe,
13 | FS = require('fs'),
14 | Path = require('path'),
15 | FileProbe = Monitor.FileProbe;
16 |
17 | // This must be set using setRootPath() before the probe will operate
18 | var ROOT_PATH = null;
19 |
20 | /**
21 | * Probe for synchronizing a Backbone data model with a file on the O/S
22 | *
23 | * Probe parameters are listed under SyncProbe .
24 | *
25 | * @class FileSyncProbe
26 | * @extends Probe
27 | * @constructor
28 | */
29 | var FileSyncProbe = SyncProbe.FileSyncProbe = Probe.extend({
30 |
31 | probeClass: 'FileSyncProbe',
32 |
33 | initialize: function(attributes, options){
34 | var t = this;
35 | Probe.prototype.initialize.apply(t, arguments);
36 |
37 | // Disable the probe if the root path hasn't been set
38 | if (!ROOT_PATH) {
39 | throw new Error('FileSync has not been enabled on this server.');
40 | }
41 |
42 | // Class name must exist
43 | if (!t.has('className')) {
44 | throw new Error('FileSync - Class name not specified');
45 | }
46 |
47 | // Don't allow a path above the root path
48 | t.dirPath = Path.join(ROOT_PATH, t.get('className'));
49 | if (t.dirPath.indexOf(ROOT_PATH) !== 0) {
50 | throw new Error('Invalid file path');
51 | }
52 |
53 | // We're done if this isn't a liveSync probe
54 | if (!t.has('modelId')) {
55 | return;
56 | }
57 |
58 | // Assume callback responsibility
59 | options.asyncInit = true;
60 | var callback = options.callback;
61 |
62 | // Get the full path to the file
63 | t.getFullPath(t.get('modelId'), function(error, response){
64 | if (error) {
65 | return callback({msg: 'Failed to get the path', err:error});
66 | }
67 |
68 | // Get the file and stats
69 | var fullPath = response.path;
70 | var stats = response.stats;
71 |
72 | // Build the function to watch the file
73 | var onFileWatch = function(error, content) {
74 |
75 | var isInitializing = (callback !== null),
76 | initCallback = callback;
77 | callback = null;
78 |
79 | if (error && error.code === 'ENOENT') {
80 | // File doesn't exist. Set the model to null.
81 | t.set({model: {}}, {silent: isInitializing});
82 | // Convert the code from the sync probe spec
83 | error.code = 'NOTFOUND';
84 | }
85 | if (error) {
86 | if (isInitializing) {
87 | t.release();
88 | var err = {code: error.code, msg: 'LiveSync requires the file to exist and be readable'};
89 | initCallback(err);
90 | }
91 | return;
92 | }
93 |
94 | // Parse the JSON content into a JS object.
95 | try {
96 | content = JSON.parse(content);
97 | logger.info('fileParse', {id: t.get('modelId'), content: content});
98 | } catch (e) {
99 |
100 | // Fail the probe on first load error
101 | if (isInitializing) {
102 | t.release();
103 | initCallback({code: 'BAD_FORMAT', msg: 'Non-JSON formatted file'});
104 | }
105 |
106 | // Nothing productive to do if the file can't be parsed. Just log it.
107 | logger.error('fileParse', {error: e, id: t.get('modelId'), content: content});
108 | return;
109 | }
110 |
111 | // Set the content into the model if it's different
112 | // Have to compare raw objects because toJSON returns deep references to models
113 | var priorModel = t.get('model');
114 | if (!priorModel || !_.isEqual(content, JSON.parse(JSON.stringify(priorModel)))) {
115 | t.set({model: content}, {silent: isInitializing});
116 | }
117 |
118 | // Call the initialization callback on first load
119 | if (isInitializing) {
120 | initCallback();
121 | }
122 | };
123 |
124 | // Load and watch the file
125 | var watcherOpts = {
126 | preload: true,
127 | persistent: true
128 | };
129 | t.fileWatcher = FileProbe.watchLoad(fullPath, watcherOpts, onFileWatch);
130 |
131 | });
132 | },
133 |
134 | // Documentation for these methods in SyncProbe
135 | create_control: function(args, callback) {
136 | // Make sure the ID exists
137 | var t = this, model = args.model;
138 | if (!model || !model.id) {
139 | return callback({msg:'SyncProbe create - Data model with ID not present'});
140 | }
141 |
142 | // Make sure the file doesn't already exist
143 | t.getFullPath(model.id, function(error, response) {
144 | if (error) {
145 | return callback(error);
146 | }
147 |
148 | if (response.stats) {
149 | return callback({msg:'Document with this ID already exists'});
150 | }
151 |
152 | // Forward to the update control
153 | t.update_control(args, callback);
154 | });
155 | },
156 |
157 | read_control: function(args, callback) {
158 | // Make sure the ID exists
159 | var t = this, id = args.id;
160 | if (!id) {
161 | return callback({msg:'SyncProbe read - ID not present'});
162 | }
163 |
164 | // Read the file
165 | t.getFullPath(id, function(error, response){
166 | if (error) {
167 | return callback(error);
168 | }
169 | if (!response.stats) {
170 | return callback({code: 'NOTFOUND', msg:'Document with this ID not found'});
171 | }
172 |
173 | var fullPath = response.path;
174 | FS.readFile(fullPath, 'utf8', function(error, data) {
175 | if (error) {
176 | return callback({code: 'UNKNOWN', msg:'Error reading file', error: error.code});
177 | }
178 |
179 | // Parse the file
180 | var model;
181 | try {
182 | model = JSON.parse(data);
183 | } catch (e) {
184 | return callback({code: 'PARSE', msg: 'Error parsing file'});
185 | }
186 | callback(null, model);
187 | });
188 | });
189 | },
190 |
191 | update_control: function(args, callback) {
192 |
193 | // Make sure the ID exists
194 | var t = this, model = args.model;
195 | if (!model || !model.id) {
196 | return callback({msg:'SyncProbe create - Data model with ID not present'});
197 | }
198 |
199 | // Make sure the directory exists
200 | t.getFullPath(model.id, function(error, response) {
201 | if (error) {
202 | return callback(error);
203 | }
204 |
205 | var fullPath = response.path,
206 | parentDir = Path.dirname(fullPath);
207 | FileProbe.mkdir_r(parentDir, function(error) {
208 | if (error) {
209 | return callback(error);
210 | }
211 |
212 | // Set the contents of the model for liveSync
213 | if (t.has('modelId')) {
214 | t.set('model', model);
215 | }
216 |
217 | // Write the file
218 | FS.writeFile(fullPath, JSON.stringify(model, null, 2), 'utf8', function(error){
219 | callback(error, {});
220 | });
221 | });
222 | });
223 |
224 | },
225 |
226 | delete_control: function(args, callback) {
227 | // Make sure the ID exists
228 | var t = this, id = args.id;
229 | if (!id) {
230 | return callback({msg:'SyncProbe delete - ID not present'});
231 | }
232 |
233 | // Set the contents of the model for liveSync
234 | t.getFullPath(id, function(error, response) {
235 | if (error) {
236 | return callback({msg:'Error removing file', err:error});
237 | }
238 | var fullPath = response.path;
239 | if (t.has('modelId')) {
240 | t.set('model', null);
241 | }
242 |
243 | // Remove the file
244 | FS.unlink(fullPath, function(error, data) {
245 | if (error) {
246 | return callback({msg:'Error removing file'});
247 | }
248 | return callback(null, {});
249 | });
250 | });
251 | },
252 |
253 | release: function() {
254 | var t = this;
255 | if (t.fileWatcher) {
256 | t.fileWatcher.close();
257 | t.fileWatcher = null;
258 | }
259 | },
260 |
261 | /**
262 | * Get the full path to the file
263 | *
264 | * This builds the full pathname to the file, and performs an fs.sync()
265 | * on that pathname, providing the pathname and sync object in the callback.
266 | *
267 | * @method getFullPath
268 | * @param modelId {String} ID of the data model to sync
269 | * @param callback {Function(error, return)}
270 | * @param callback.error {Object} Error object (null if no error)
271 | * @param callback.return {Object} return object
272 | * @param callback.return.path {String} Full pathname to the file
273 | * @param callback.return.stat {fs.stats} Stats object (null if the file doesn't esixt)
274 | */
275 | getFullPath: function(modelId, callback) {
276 | var t = this,
277 | dirPath = t.dirPath;
278 |
279 | // Don't allow relative paths
280 | var fullPath = Path.join(t.dirPath, modelId);
281 | if (fullPath.indexOf(dirPath) !== 0) {
282 | return callback({msg: 'Model ID ' + modelId + ' cannot represent a relative path'});
283 | }
284 |
285 | // See if the path represents a directory
286 | FS.stat(fullPath, function(error, stats){
287 |
288 | // If this is an existing directory, return a path to dir/index.json
289 | if (!error && stats.isDirectory()) {
290 | return t.getFullPath(modelId + '/index', callback);
291 | }
292 |
293 | // Normal case - return the path & stat to the json file
294 | fullPath += '.json';
295 | FS.stat(fullPath, function(error, stats){
296 |
297 | // Not an error if error == ENOENT
298 | if (error && error.code === 'ENOENT') {
299 | error = null; stats = null;
300 | }
301 |
302 | // Process other FS errors
303 | if (error) {
304 | return callback({err: error, msg: "Error while observing file: " + fullPath});
305 | }
306 |
307 | // Forward the callback
308 | return callback(null, {path: fullPath, stats: stats});
309 | });
310 | });
311 | }
312 |
313 | });
314 |
315 | /**
316 | * Set the server root path for objects stored with this probe
317 | *
318 | * For security purposes, this must be set before the SyncFileProbe
319 | * will operate. It will not accept any changes once set.
320 | *
321 | * @static
322 | * @method setRootPath
323 | * @param rootPath {String} A path to the root directory for model object storage
324 | */
325 | FileSyncProbe.setRootPath = function(rootPath) {
326 | var normalized = Path.normalize(rootPath);
327 | if (ROOT_PATH && ROOT_PATH !== normalized) {
328 | throw new Error('Cannot change the File probe root path once set.');
329 | }
330 | ROOT_PATH = normalized;
331 | };
332 |
333 | /**
334 | * Get the current root path.
335 | *
336 | * As a static method, this is only available on the server running the probe.
337 | * For security purposes, this is not exposed in the FileSyncProbe data model.
338 | *
339 | * @static
340 | * @method getRootPath
341 | * @return {String} The path to the root directory for the FilePath probe
342 | */
343 | FileSyncProbe.getRootPath = function() {
344 | return ROOT_PATH;
345 | };
346 |
347 | }(this));
348 |
--------------------------------------------------------------------------------
/lib/probes/InspectProbe.js:
--------------------------------------------------------------------------------
1 | // InspectProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 |
6 | /* This class is evil. You probably shouldn't use it. Or drink. Or drink while using it. */
7 | /*jslint evil: true */
8 |
9 | (function(root){
10 |
11 | // Module loading - this runs server-side only
12 | var Monitor = root.Monitor || require('../Monitor'),
13 | _ = Monitor._,
14 | logger = Monitor.getLogger('InspectProbe'),
15 | Backbone = Monitor.Backbone,
16 | PollingProbe = Monitor.PollingProbe;
17 |
18 | // Constants
19 | var DEFAULT_DEPTH = 2;
20 |
21 | /**
22 | * Inspect and manipulate variables on the monitored server.
23 | *
24 | * This class monitors the variable specified by the key.
25 | *
26 | * The key is evaluated to determine the variable to monitor, so it may
27 | * be a complex key starting at global scope. If the key isn't
28 | * specified, it monitors all variables in the global scope.
29 | *
30 | * If the key points to an object of type Backbone.Model, this probe
31 | * will update the value in real time, triggered on the *change* event.
32 | * Otherwise it will update the value as it notices changes, while polling
33 | * on the specified polling interval (default: 1 second).
34 | *
35 | * @class InspectProbe
36 | * @extends PollingProbe
37 | * @constructor
38 | * @param [initParams] - Initialization parameters
39 | * @param [initParams.key=null] {String} A global variable name or expression
40 | * @param [initParams.depth=2] {Integer} If the key points to an object, this
41 | * is the depth to traverse the object for changes. Default=2, or 1 if
42 | * key='window'.
43 | * @param [initParams.pollInterval] {Integer} (from PollingProbe ) Polling interval in milliseconds. Default: null
44 | * @param [initParams.cronPattern] {String} (from PollingProbe ) Crontab syle polling pattern. Default once per second: "* * * * * *"
45 | * @param model - Monitor data model elements
46 | * @param model.value - The value of the element being inspected
47 | * @param model.isModel - Is the value a Backbone.Model?
48 | */
49 | var InspectProbe = Monitor.InspectProbe = PollingProbe.extend({
50 |
51 | // These are required for Probes
52 | probeClass: 'Inspect',
53 | writableAttributes: ['value'],
54 |
55 | initialize: function(initParams){
56 | var t = this;
57 |
58 | // Get the global object if the key isn't specified
59 | t.key = initParams.key;
60 | if (typeof initParams.key === 'undefined') {
61 | t.key = typeof window === 'undefined' ? 'global' : 'window';
62 | }
63 |
64 | // Get a good depth default. Default unless key = window.
65 | if (typeof initParams.depth === 'undefined') {
66 | if (!initParams.key && t.key === 'window') {
67 | t.depth = 1;
68 | } else {
69 | t.depth = DEFAULT_DEPTH;
70 | }
71 | } else {
72 | t.depth = initParams.depth;
73 | }
74 |
75 | // Evaluate the expression to see if it's a Backbone.Model
76 | // This will throw an exception if the key is a bad expression
77 | t.value = t._evaluate(t.key);
78 | t.isModel = t.value instanceof Backbone.Model;
79 |
80 | // Set the initial values
81 | t.set({
82 | value: Monitor.deepCopy(t.value, t.depth),
83 | isModel: t.isModel
84 | });
85 |
86 | // Watch for backbone model changes, or initialize the polling probe
87 | if (t.isModel) {
88 | t.value.on('change', t.poll, t);
89 | } else {
90 | PollingProbe.prototype.initialize.apply(t, arguments);
91 | }
92 | },
93 |
94 | /**
95 | * Remotely set the inspected variable's value
96 | *
97 | * @method set_control
98 | * @param attrs {Object} Name/Value attributes to set. All must be writable.
99 | * @param callback {Function(error)} Called when the attributes are set or error
100 | */
101 | set_control: function(attrs, callback) {
102 | var t = this;
103 |
104 | // Value is the only thing to set
105 | if (typeof attrs.value === 'undefined') {
106 | return callback({code:'NO_VALUE'});
107 | }
108 |
109 | // Set the model elements. These cause change events to fire
110 | if (t.isModel) {
111 | t.value.set(attrs.value);
112 | }
113 | else {
114 | // Set the variable directly
115 | var jsonValue = JSON.stringify(attrs.value);
116 | t._evaluate(t.key + ' = ' + jsonValue);
117 | t.set('value', attrs.value);
118 | }
119 | return callback();
120 | },
121 |
122 | // Stop watching for change events or polling
123 | release: function() {
124 | var t = this;
125 | if (t.isModel) {
126 | t.value.off('change', t.poll, t);
127 | } else {
128 | PollingProbe.prototype.release.apply(t, arguments);
129 | }
130 | },
131 |
132 | /**
133 | * Evaluate an expression, returning the depth-limited results
134 | *
135 | * @method eval_control
136 | * @param expression {String} Expression to evaluate
137 | * @param [depth=2] {Integer} Depth of the object to return
138 | * @return value {Mixed} Returns the depth-limited value
139 | */
140 | eval_control: function(expression, depth){
141 | var t = this;
142 |
143 | // Determine a default depth
144 | depth = typeof depth === 'undefined' ? DEFAULT_DEPTH : depth;
145 |
146 | // Get the raw value
147 | var value = t._evaluate(expression);
148 |
149 | // Return the depth limited results
150 | return Monitor.deepCopy(value, depth);
151 | },
152 |
153 | /**
154 | * Evaluate an expression, returning the raw results
155 | *
156 | * @protected
157 | * @method _evaluate
158 | * @param expression {String} Expression to evaluate
159 | * @return value {Mixed} Returns the expression value
160 | */
161 | _evaluate: function(expression){
162 | var t = this,
163 | value = null;
164 |
165 | // Evaluate the expression
166 | try {
167 | value = eval(expression);
168 | } catch (e) {
169 | var err = 'Unable to evaluate expression: "' + expression + '"';
170 | logger.error('evaluate', err);
171 | throw new Error(err);
172 | }
173 |
174 | // Return the value
175 | return value;
176 | },
177 |
178 | /**
179 | * Poll for changes in the evaluation
180 | *
181 | * @method poll
182 | */
183 | poll: function() {
184 | var t = this,
185 | newValue = t.eval_control(t.key, t.depth);
186 |
187 | // Set the new value if it has changed from the current value
188 | if (!_.isEqual(newValue, t.get('value'))) {
189 | t.set({value: newValue});
190 | }
191 | }
192 | });
193 |
194 | }(this));
195 |
--------------------------------------------------------------------------------
/lib/probes/LogProbe.js:
--------------------------------------------------------------------------------
1 | // LogProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root) {
6 |
7 | // Module loading - this runs server-side only
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | _ = Monitor._,
10 | StreamProbe = Monitor.StreamProbe,
11 | Log = Monitor.Log;
12 |
13 | // Constants
14 | var DEFAULT_PATTERN = '*';
15 |
16 | /**
17 | * Remote application log monitoring
18 | *
19 | * This probe forwards application logs to the monitor.
20 | *
21 | * @class LogProbe
22 | * @extends StreamProbe
23 | * @constructor
24 | * @param [initParams] {Object} Probe initialization parameters
25 | * @param [initParams.pattern=*] {String} Log name pattern to monitor (see Log )
26 | * @param [initParams.interval=1000] {Numeric} Queue interval (see StreamProbe )
27 | * @param model {Object} Monitor data model elements
28 | * @param model.bundle {Log array} Array of Log elements.
29 | * @param model.bundle.timestamp {String} Timestamp of the log statement
30 | * @param model.bundle.logType {String} Log type (error, info, etc)
31 | * @param model.bundle.module {String} Module that emitted the log
32 | * @param model.bundle.name {String} Log entry name
33 | * @param model.bundle.args {any[]} Arguments to the log statement
34 | * @param model.sequence {Integer} A numeric incrementer causing a change event
35 | */
36 | var LogProbe = Monitor.LogProbe = StreamProbe.extend({
37 |
38 | probeClass: 'Log',
39 |
40 | defaults: _.extend({}, StreamProbe.prototype.defaults, {
41 | pattern: DEFAULT_PATTERN
42 | }),
43 |
44 | initialize: function(){
45 | var t = this;
46 |
47 | // Call parent constructor
48 | StreamProbe.prototype.initialize.apply(t, arguments);
49 |
50 | // The watcher just forwards all args to queueItem as an array
51 | t.watcher = function() {
52 | // Add timestamp as the first element
53 | var logElems = _.toArray(arguments);
54 | logElems.splice(0,0,JSON.stringify(new Date()).substr(1,24));
55 | t.queueItem.call(t, logElems);
56 | };
57 | Log.on(t.get('pattern'), t.watcher);
58 | },
59 |
60 | release: function() {
61 | var t = this;
62 | Log.off(t.get('pattern'), t.watcher);
63 | }
64 |
65 | });
66 |
67 | }(this));
68 |
--------------------------------------------------------------------------------
/lib/probes/PollingProbe.js:
--------------------------------------------------------------------------------
1 | // PollingProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | _ = Monitor._,
10 | Cron = Monitor.Cron,
11 | Probe = Monitor.Probe,
12 | Backbone = Monitor.Backbone;
13 |
14 | // Constants
15 | var DEFAULT_POLL_INTERVAL = 1000;
16 | var DEFAULT_CRON_PATTERN = "* * * * * *";
17 |
18 | /**
19 | * ## Base class for probes that require polling to detect and set model changes.
20 | *
21 | * The probe wakes up every polling interval and executes the poll() method
22 | * in the derived class.
23 | *
24 | * PollingProbes are instantiated with either a polling interval (in milliseconds)
25 | * or a cron pattern. If the polling interval is set, that's what will be used.
26 | *
27 | * The cronPattern isn't available in browser-side probes.
28 | *
29 | * To disable polling, set the pollInterval to 0.
30 | *
31 | * More about cron formats, with examples
32 | *
37 | *
38 | * @class PollingProbe
39 | * @extends Probe
40 | * @constructor
41 | * @param [initParams] {Object} Probe initialization parameters
42 | * @param [initParams.pollInterval] {Integer} Polling interval in milliseconds. Default: null
43 | * @param [initParams.cronPattern] {String} Crontab syle polling pattern. Default once per second: "* * * * * *"
44 | *
45 | * The format is: [second] [minute] [hour] [day of month] [month] [day of week] .
46 | */
47 | var PollingProbe = Monitor.PollingProbe = Probe.extend({
48 | defaults: _.extend({}, Probe.prototype.defaults, {
49 | pollInterval: null,
50 | cronPattern: DEFAULT_CRON_PATTERN
51 | }),
52 | initialize: function(){
53 | var t = this,
54 | pollInterval = t.get('pollInterval'),
55 | cronPattern = t.get('cronPattern'),
56 | poll = function(){t.poll();};
57 | Probe.prototype.initialize.apply(t, arguments);
58 |
59 | // Override cron for the default 1-second interval
60 | // (this allows the default to work when Cron isn't available)
61 | if (pollInterval == null && cronPattern === DEFAULT_CRON_PATTERN) {
62 | pollInterval = DEFAULT_POLL_INTERVAL;
63 | }
64 |
65 | // Poll once, then set up the interval
66 | t.poll();
67 | if (pollInterval !== 0) {
68 | if (pollInterval) {
69 | t.timer = setInterval(poll, pollInterval);
70 | } else {
71 | if (!Cron) {
72 | throw new Error("Cron is not available in this client");
73 | }
74 | t.cronJob = new Cron.CronJob(cronPattern, poll);
75 | }
76 | }
77 | },
78 | release: function(){
79 | var t = this, timer = (t.cronJob ? t.cronJob.timer : t.timer);
80 | if (t.cronJob && !t.cronJob.initiated) {
81 | // If cron isn't initiated we've been asked to shut down within the
82 | // first second, and the timer hasn't been set (but will be soon).
83 | setTimeout(function(){clearInterval(t.cronJob.timer);}, 1000);
84 | } else if (t.timer) {
85 | clearInterval(timer);
86 | }
87 | t.timer = t.cron = null;
88 | Probe.prototype.release.apply(t, arguments);
89 | }
90 |
91 | });
92 |
93 | }(this));
94 |
--------------------------------------------------------------------------------
/lib/probes/ProcessProbe.js:
--------------------------------------------------------------------------------
1 | // ProcessProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading - this runs server-side only
8 | var util = require('util'), OS = require('os'),
9 | Monitor = root.Monitor || require('../Monitor'), _ = Monitor._,
10 | logger = Monitor.getLogger('ProcessProbe'),
11 | PollingProbe = Monitor.PollingProbe;
12 |
13 | /**
14 | * Probe for attaining process and O/S information
15 | *
16 | * @class ProcessProbe
17 | * @extends PollingProbe
18 | * @constructor
19 | * @param [initParams] {Object} Probe initialization parameters (from PollingProbe)
20 | * @param [initParams.pollInterval] {Integer} Polling interval in milliseconds. Default: null
21 | * @param [initParams.cronPattern] {String} Crontab syle polling pattern. Default once per second: "* * * * * *"
22 | * @param model {Object} Monitor data model elements
23 | * @param model.platform {String} O/S Platform
24 | * @param model.version {String} Node.js compiled-in version
25 | * @param model.installPrefix {String} Node.js installation directory
26 | * @param model.title {String} The current process title (as reported in ps)
27 | * @param model.execPath {String} The path to the current node.js executable
28 | * @param model.argv {Array(String)} Arguments passed on the command line to this process
29 | * @param model.env {Object} Current environment (inherited)
30 | * @param model.cwd {String} Current working directory
31 | * @param model.uptime {Integer} Number of seconds the process has been up (if available)
32 | * @param model.versions {String} Versions of V8 and dependent libraries (if available)
33 | * @param model.arch {String} Processor architecture (if available)
34 | * @param model.gid {Integer} Process group ID
35 | * @param model.uid {Integer} Process user ID
36 | * @param model.pid {Integer} Unique process ID
37 | * @param model.umask {Integer} The process file mode creation mask
38 | * @param model.memoryUsage {Object} An object describing memory usage of the node.js process
39 | * @param model.memoryUsage.rss {Integer} As defined by process.memoryUsage
40 | * @param model.memoryUsage.vsize {Integer} As defined by process.memoryUsage
41 | * @param model.memoryUsage.heapTotal {Integer} As defined by process.memoryUsage
42 | * @param model.memoryUsage.heapUsed {Integer} As defined by process.memoryUsage
43 | * @param model.os {Object} An object containing O/S information
44 | * @param model.os.hostname {String} Name of the host operating system
45 | * @param model.os.type {String} Operating system type
46 | * @param model.os.release {String} O/S Release version
47 | * @param model.os.uptime {String} O/S Uptime in seconds
48 | * @param model.os.loadavg {Array(Number)} An array containing the 1, 5, and 15 minute load averages
49 | * @param model.os.freemem {Integer} Free O/S memory (in bytes)
50 | * @param model.os.totalmem {Integer} Total O/S memory capacity (in bytes)
51 | * @param model.os.cpus {Array(Object)} An array of objects containing information about each CPU/core installed
52 | */
53 | var ProcessProbe = Monitor.ProcessProbe = PollingProbe.extend({
54 |
55 | // These are required for Probes
56 | probeClass: 'Process',
57 |
58 | /* not required
59 | initialize: function(){
60 | var t = this;
61 | PollingProbe.prototype.initialize.apply(t, arguments);
62 | ...
63 | },
64 | release: function() {
65 | var t = this;
66 | PollingProbe.prototype.release.apply(t, arguments);
67 | ... // release any resources held
68 | })
69 | */
70 |
71 | /**
72 | * Poll the probe for changes
73 | *
74 | * This method is called by the parent PollingProbe on the interval specified by the client Monitor .
75 | *
76 | * It polls for process information, and updates the data model with any changes.
77 | *
78 | * @method poll
79 | */
80 | poll: function() {
81 | var t = this,
82 | attrs = _.extend({
83 | platform: process.platform,
84 | version: process.version,
85 | installPrefix: process.installPrefix,
86 | title: process.title,
87 | execPath: process.execPath,
88 | argv: process.argv,
89 | env: process.env,
90 | cwd: process.cwd(),
91 | gid: process.getgid ? process.getgid() : 0,
92 | uid: process.getuid ? process.getuid() : 0,
93 | pid: process.pid,
94 | umask: process.umask(),
95 | hostname: OS.hostname(),
96 | type: OS.type(),
97 | release: OS.release(),
98 | osUptime: OS.uptime(),
99 | loadavg: OS.loadavg(),
100 | freemem: OS.freemem(),
101 | totalmem: OS.totalmem(),
102 | cpus: OS.cpus()
103 | }, process.memoryUsage());
104 | if (process.uptime) {attrs.uptime = process.uptime();}
105 | if (process.versions) {attrs.versions = process.versions;}
106 | if (process.arch) {attrs.arch = process.arch;}
107 | t.set(attrs);
108 | }
109 | });
110 |
111 | }(this));
112 |
--------------------------------------------------------------------------------
/lib/probes/RecipeProbe.js:
--------------------------------------------------------------------------------
1 | // RecipeProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 |
6 | /* This class is evil. You probably shouldn't use it. Or drink. Or drink while using it. */
7 | /*jslint evil: true */
8 |
9 | (function(root){
10 |
11 | // Module loading - this runs server-side only
12 | var Monitor = root.Monitor || require('../Monitor'),
13 | _ = Monitor._,
14 | Cron = Monitor.Cron,
15 | logger = Monitor.getLogger('RecipeProbe'),
16 | vm = Monitor.commonJS ? require('vm') : null,
17 | Probe = Monitor.Probe;
18 |
19 | /**
20 | * Monitor automation probe
21 | *
22 | * The Recipe probe monitors other probes and runs instructions when the
23 | * probes change, and controls other probes based on these instructions.
24 | *
25 | * It contains a list of monitors to instantiate, and a script to run when the
26 | * monitor ```change``` event is fired.
27 | *
28 | * When the script fires, the monitors are available to the script by name.
29 | * The script can ```get()``` monitor values, ```set()``` writable monitor
30 | * values, and control the monitor using the ```control()`` method.
31 | *
32 | * The ```this``` variable is consistent between script runs, so state can be
33 | * maintained by setting attributes in ```this```.
34 | *
35 | * @class RecipeProbe
36 | * @extends Probe
37 | * @constructor
38 | * @param monitors {Object} - Named list of monitors to instantiate
39 | * Key: monitor variable name, Value: Monitor model parameters
40 | * @param script {String} - JavaScript script to run.
41 | * The script has access to ```console```, ```logger```, and all defined
42 | * monitors by name.
43 | * @param [recipeName] {String} - Recipe name for logging
44 | * @param [autoStart=false] {boolean} - Call the start control on instantiation?
45 | * @param [triggeredBy] {Object} - Trigger the recipe by the items in the object.
46 | * Items can include: 'interval', 'cron', and/or monitorName(s)
47 | * If 'interval' is the key, the value is the interval in milliseconds
48 | * If 'cron' is the key, the value is a string representing the cron pattern
49 | * If any monitor name is the key, the value is the monitor event to trigger on.
50 | * Example:
51 | * triggeredBy: {
52 | * interval: 5000, // This triggers the recipe every 5 seconds
53 | * cron: '* * * * * *', // [second] [minute] [hour] [day of month] [month] [day of week]
54 | * myMonitor: 'change:someAttribute change:someOtherAttribute'
55 | * }
56 | * If triggeredBy isn't specified, any monitor change will trigger the recipe.
57 | * @param [started] {boolean} - Is the recipe started and currently active?
58 | */
59 | var RecipeProbe = Monitor.RecipeProbe = Probe.extend({
60 |
61 | probeClass: 'Recipe',
62 | writableAttributes: [],
63 | defaults: {
64 | recipeName: '',
65 | monitors: {},
66 | script: '',
67 | autoStart: false,
68 | started: false,
69 | triggeredBy: null
70 | },
71 |
72 | initialize: function(attributes, options){
73 | var t = this;
74 |
75 | // Periodic triggers
76 | t.interval = null;
77 | t.cronJob = null;
78 |
79 | // Precondition test
80 | if (_.size(t.get('monitors')) === 0) {
81 | logger.error('initialize', 'No monitors defined in the recipe');
82 | return;
83 | }
84 |
85 | // This is a list of monitors (vs. monitor definitions)
86 | t.monitors = {};
87 |
88 | // Auto start, calling the callback when started
89 | if (t.get('autoStart')) {
90 | options.asyncInit = true;
91 | t.start_control({}, options.callback);
92 | }
93 | },
94 |
95 | release: function() {
96 | var t = this,
97 | args = arguments;
98 | t.stop_control({}, function(){
99 | Probe.prototype.release.apply(t, args);
100 | });
101 | },
102 |
103 | /**
104 | * Start the recipe
105 | *
106 | * This connects to each monitor and sets up the recipe triggers
107 | *
108 | * @method start_control
109 | */
110 | start_control: function(params, callback) {
111 | var t = this,
112 | connectError = false,
113 | monitors = t.get('monitors');
114 |
115 | if (t.get('started')) {
116 | var err = {code:'RUNNING', msg:'Cannot start - the recipe is already running.'};
117 | logger.warn(err);
118 | return callback(err);
119 | }
120 |
121 | // Called when a monitor has connected
122 | var onConnect = function(error) {
123 | if (connectError) {return;}
124 | if (error) {
125 | var err = {code:'CONNECT_ERROR', err: error};
126 | connectError = true;
127 | logger.error('start', err);
128 | return callback(err);
129 | }
130 | for (var name1 in t.monitors) {
131 | if (!t.monitors[name1].isConnected()) {
132 | return;
133 | }
134 | }
135 | t.set({started:true});
136 | t.connectListeners(true);
137 | callback();
138 | };
139 |
140 | // Connect all monitors
141 | for (var name2 in monitors) {
142 | t.monitors[name2] = new Monitor(monitors[name2]);
143 | t.monitors[name2].connect(onConnect);
144 | }
145 |
146 | },
147 |
148 | /**
149 | * Stop the recipe
150 | *
151 | * This disconnects each monitor
152 | *
153 | * @method stop_control
154 | */
155 | stop_control: function(params, callback) {
156 | var t = this,
157 | disconnectError = false;
158 |
159 | if (!t.get('started')) {
160 | var err = {code:'NOT_RUNNING', msg:'The recipe is already stopped.'};
161 | logger.warn('precondition', err);
162 | return callback(err);
163 | }
164 |
165 | // Called when a monitor has disconnected
166 | var onDisconnect = function(error) {
167 | if (disconnectError) {return;}
168 | if (error) {
169 | var err = {code:'DISONNECT_ERROR', err: error};
170 | disconnectError = true;
171 | logger.error('onDisconnect', err);
172 | return callback(err);
173 | }
174 | for (var name1 in t.monitors) {
175 | if (t.monitors[name1].isConnected()) {
176 | return;
177 | }
178 | }
179 | t.set({started:false});
180 | t.compiledScript = null;
181 | callback();
182 | };
183 |
184 | // Disconnect all monitors
185 | t.connectListeners(false);
186 | t.context = null;
187 | for (var name2 in t.monitors) {
188 | t.monitors[name2].disconnect(onDisconnect);
189 | }
190 | },
191 |
192 | /**
193 | * Connect the change listeners
194 | *
195 | * @private
196 | * @method connectListeners
197 | */
198 | connectListeners: function(connect) {
199 | var t = this,
200 | triggeredBy = t.get('triggeredBy'),
201 | onTrigger = t.onTrigger.bind(t);
202 |
203 | // Default to listen on changes to all monitors
204 | if (!triggeredBy) {
205 | for (var monitorName in t.monitors) {
206 | t.monitors[monitorName][connect ? 'on' : 'off']('change', t.onTrigger, t);
207 | }
208 | return;
209 | }
210 |
211 | // Process the elements in triggeredBy
212 | for (var name in triggeredBy) {
213 | var value = triggeredBy[name];
214 |
215 | // Construct a new cron job
216 | if (name === 'cron') {
217 | if (connect) {
218 | t.cronJob = new Cron.CronJob(value, onTrigger);
219 | }
220 | else {
221 | if (t.cronJob.initiated) {
222 | clearInterval(t.CronJob.timer);
223 | }
224 | else {
225 | setTimeout(function(){clearInterval(t.cronJob.timer);}, 1000);
226 | }
227 | }
228 | }
229 |
230 | // Set a polling interval
231 | else if (name === 'interval') {
232 | if (connect) {
233 | t.interval = setInterval(onTrigger, value);
234 | }
235 | else {
236 | clearInterval(t.interval);
237 | t.interval = null;
238 | }
239 | }
240 |
241 | // Must be a monitor name
242 | else {
243 | t.monitors[name][connect ? 'on' : 'off'](value, onTrigger);
244 | }
245 | }
246 | },
247 |
248 | /**
249 | * Called when a trigger is fired
250 | *
251 | * @private
252 | * @method onTrigger
253 | */
254 | onTrigger: function() {
255 | var t = this;
256 | t.run_control({}, function(error){
257 | if (error) {
258 | logger.error('onTrigger', error);
259 | }
260 | });
261 | },
262 |
263 | /**
264 | * Run the recipe script
265 | *
266 | * This manually runs a started recipe. The callback is called immediately
267 | * after executing the script.
268 | *
269 | * @method run_control
270 | */
271 | run_control: function(params, callback) {
272 | var t = this,
273 | error = null;
274 | if (!t.get('started')) {
275 | error = {code:'NOT_RUNNING', msg:'Cannot run - recipe not started.'};
276 | logger.warn(error);
277 | return callback(error);
278 | }
279 |
280 | // Name the probe
281 | t.name = t.get('probeName') || t.get('id');
282 |
283 | // Build a context to pass onto the script. The context contains
284 | // a console, a logger, and each monitor by name.
285 | if (!t.context) {
286 | t.context = vm ? vm.createContext({}) : {};
287 | t.context.console = console;
288 | t.context.logger = Monitor.getLogger('Recipe.run.' + t.name);
289 | for (var monitorName in t.monitors) {
290 | t.context[monitorName] = t.monitors[monitorName];
291 | }
292 | }
293 |
294 | // Run the script
295 | try {
296 | t.run(t.context);
297 | } catch(e) {
298 | error = "Error running script: " + e.toString();
299 | logger.error('run_control', error);
300 | }
301 | callback(error);
302 | },
303 |
304 | /**
305 | * Execute the recipe. This is a private method that can be overridden
306 | * in derived recipe classes that contain the recipe.
307 | *
308 | * @private
309 | * @method run
310 | */
311 | run: function(context) {
312 | var t = this,
313 | script = t.get('script');
314 |
315 | // Run in a VM or exec (if running in a browser)
316 | if (vm) {
317 | // Compile the script on first run. This throws an exception if
318 | // the script has a problem compiling.
319 | if (!t.compiledScript) {
320 | t.compiledScript = vm.createScript(script);
321 | }
322 |
323 | // Execute the compiled script
324 | t.compiledScript.runInContext(context, t.name);
325 | }
326 | else {
327 | // Bring all context variables local, then execute the script
328 | eval(t.bringLocal(context));
329 | eval(script);
330 | }
331 | },
332 |
333 | /**
334 | * Generate a script that brings context members into local scope
335 | *
336 | * @private
337 | * @method bringLocal
338 | */
339 | bringLocal: function(context) {
340 | var varName,
341 | localVars = [];
342 | for (varName in context) {
343 | localVars.push('var ' + varName + ' = context.' + varName + ';');
344 | }
345 | return localVars.join('\n');
346 | }
347 |
348 | });
349 |
350 |
351 | }(this));
352 |
--------------------------------------------------------------------------------
/lib/probes/ReplProbe.js:
--------------------------------------------------------------------------------
1 | // ReplProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading - this runs server-side only
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | _ = Monitor._,
10 | Probe = Monitor.Probe,
11 | REPL = require('repl'),
12 | Stream = require('stream'),
13 | util = require('util'),
14 | events = require('events'),
15 | ChildProcess = require('child_process');
16 |
17 | // Statics
18 | var CONSOLE_PROMPT = '> ';
19 | var NEW_REPL = (typeof REPL.disableColors === 'undefined');
20 |
21 | /**
22 | * A probe based Read-Execute-Print-Loop console for node.js processes
23 | *
24 | * @class ReplProbe
25 | * @extends Probe
26 | * @constructor
27 | * @param initParams {Object} Probe initialization parameters
28 | * @param initParams.uniqueInstance - Usually specified to obtain a unique REPL probe instance
29 | * @param model {Object} Monitor data model elements
30 | * @param model.output {String} Last (current) REPL output line
31 | * @param model.sequence {Integer} Increasing sequence number - to enforce unique line output
32 | */
33 | var ReplProbe = Monitor.ReplProbe = Probe.extend({
34 |
35 | probeClass: 'Repl',
36 | description: 'A socket.io based Read-Execute-Print-Loop console for node.js processes.',
37 | defaults: {
38 | // This assures output events are sent, even if the
39 | // data is the same as the prior output.
40 | sequence: 0,
41 | output: ''
42 | },
43 |
44 | initialize: function(attributes, options){
45 | var t = this;
46 | Probe.prototype.initialize.apply(t, arguments);
47 |
48 | // Don't send change events before connected
49 | process.nextTick(function(){
50 | t.stream = new ReplStream(t);
51 | if (NEW_REPL) {
52 | t.repl = require('repl').start({
53 | prompt: CONSOLE_PROMPT,
54 | input: t.stream,
55 | output: t.stream
56 | });
57 | } else {
58 | t.repl = REPL.start(CONSOLE_PROMPT, t.stream);
59 | }
60 | t.htmlConsole = new HtmlConsole(t);
61 | t.shellCmd = null;
62 | t.repl.context.console = t.htmlConsole;
63 | });
64 | },
65 |
66 | /**
67 | * Send output to the terminal
68 | *
69 | * This forces the change event even if the last output is the same
70 | * as this output.
71 | *
72 | * @protected
73 | * @method output
74 | * @param str {String} String to output to the repl console
75 | */
76 | _output: function(str) {
77 | var t = this;
78 | t.set({
79 | output: str,
80 | sequence: t.get('sequence') + 1
81 | });
82 | },
83 |
84 | /**
85 | * Release any resources consumed by this probe.
86 | *
87 | * Stop the REPL console. Consoles live 1-1 with a UI counterpart, so stop
88 | * requests exit the underlying repl console. If the probe is re-started it
89 | * will get a new repl stream and console.
90 | *
91 | * @method release
92 | */
93 | release: function(){
94 | var t = this;
95 | t.stream = null;
96 | t.repl = null;
97 | },
98 |
99 | /**
100 | * Process an autocomplete request from the client
101 | *
102 | * @method autocomplete
103 | * @param {Object} params Named parameters
104 | * @param {Function(error, returnParams)} callback Callback function
105 | */
106 | autocomplete_control: function(params, callback) {
107 | var t = this;
108 | if (typeof(params) !== 'string' || params.length < 1) {
109 | callback("Autocomplete paramter must be a nonzero string");
110 | }
111 |
112 | // Forward to the completion mechanism if it can be completed
113 | if (params.substr(-1).match(/([0-9])|([a-z])|([A-Z])|([_])/)) {
114 | t.repl.complete(params, callback);
115 | } else {
116 | // Return a no-op autocomplete
117 | callback(null, [[],'']);
118 | }
119 | },
120 |
121 | /**
122 | * Handle user input from the console line
123 | *
124 | * @method input
125 | * @param {Object} params Named parameters
126 | * @param {Function(error, returnParams)} callback Callback function
127 | */
128 | input_control: function(params, callback) {
129 | var t = this;
130 | if (params === '.break' && t.shellCmd) {
131 | t.shellCmd.kill();
132 | }
133 | if (NEW_REPL) {
134 | t.stream.emit('data', params + "\n");
135 | } else {
136 | t.stream.emit('data', params);
137 | }
138 | return callback(null);
139 | },
140 |
141 | /**
142 | * Execute a shell command
143 | *
144 | * @method sh
145 | * @param {Object} params Named parameters
146 | * @param {Function(error, returnParams)} callback Callback function
147 | */
148 | sh_control: function(params, callback) {
149 | var t = this;
150 | return callback(null, t._runShellCmd(params));
151 | },
152 |
153 | /**
154 | * Run a shell command and emit the output to the browser.
155 | *
156 | * @private
157 | * @method _runShellCmd
158 | * @param {String} command - The shell command to invoke
159 | */
160 | _runShellCmd: function(command) {
161 | var t = this;
162 | t.shellCmd = ChildProcess.exec(command, function(err, stdout, stderr) {
163 | if (err) {
164 | var outstr = 'exit';
165 | if (err.code) {
166 | outstr += ' (' + err.code + ')';
167 | }
168 | if (err.signal) {
169 | outstr += ' ' + err.signal;
170 | }
171 | t._output(outstr);
172 | return null;
173 | }
174 | if (stdout.length) {
175 | t._output(stdout);
176 | }
177 | if (stderr.length) {
178 | t._output(stderr);
179 | }
180 | t.shellCmd = null;
181 | t._output(CONSOLE_PROMPT);
182 | });
183 | return null;
184 | }
185 |
186 | });
187 |
188 | // Define an internal stream class for the probe
189 | var ReplStream = function(probe){
190 | var t = this;
191 | t.probe = probe;
192 | events.EventEmitter.call(t);
193 | if (t.setEncoding) {
194 | t.setEncoding('utf8');
195 | }
196 | };
197 | util.inherits(ReplStream, events.EventEmitter);
198 | // util.inherits(ReplStream, require('stream'));
199 | ReplStream.prototype.readable = true;
200 | ReplStream.prototype.writable = true;
201 | ['pause','resume','destroySoon','pipe', 'end']
202 | .forEach(function(fnName){
203 | ReplStream.prototype[fnName] = function(){
204 | console.log("REPL Stream function unexpected: " + fnName);
205 | };
206 | });
207 | ['resume']
208 | .forEach(function(fnName){
209 | ReplStream.prototype[fnName] = function(){
210 | // Handled
211 | };
212 | });
213 | ReplStream.prototype.write = function(data) {
214 | var t = this;
215 | t.probe._output(data);
216 | };
217 | ReplStream.prototype.destroy = function(data) {
218 | var t = this;
219 | console.log("REPL stream destroy " + t.probe.get('id'));
220 | t.probe.stop();
221 | };
222 |
223 | // Define format if it's not in util.
224 | var formatRegExp = /%[sdj]/g;
225 | var format = util.format || function (f) {
226 | if (typeof f !== 'string') {
227 | var objects = [];
228 | for (var i = 0; i < arguments.length; i++) {
229 | objects.push(util.inspect(arguments[i]));
230 | }
231 | return objects.join(' ');
232 | }
233 | var j = 1;
234 | var args = arguments;
235 | var str = String(f).replace(formatRegExp, function(x) {
236 | switch (x) {
237 | case '%s': return String(args[j++]);
238 | case '%d': return Number(args[j++]);
239 | case '%j': return JSON.stringify(args[j++]);
240 | default:
241 | return x;
242 | }
243 | });
244 | for (var len = args.length, x = args[j]; j < len; x = args[++j]) {
245 | if (x === null || typeof x !== 'object') {
246 | str += ' ' + x;
247 | } else {
248 | str += ' ' + util.inspect(x);
249 | }
250 | }
251 | return str;
252 | };
253 |
254 | // Re-define the console so it goes to the HTML window
255 | var HtmlConsole = function(probe){
256 | this.probe = probe;
257 | };
258 | HtmlConsole.prototype.log = function(msg) {
259 | this.probe._output(format.apply(this, arguments));
260 | };
261 | HtmlConsole.prototype.info = HtmlConsole.prototype.log;
262 | HtmlConsole.prototype.warn = HtmlConsole.prototype.log;
263 | HtmlConsole.prototype.error = HtmlConsole.prototype.log;
264 | HtmlConsole.prototype.dir = function(object) {
265 | this.probe._output(util.inspect(object));
266 | };
267 | var times = {};
268 | HtmlConsole.prototype.time = function(label) {
269 | times[label] = Date.now();
270 | };
271 | HtmlConsole.prototype.timeEnd = function(label) {
272 | var duration = Date.now() - times[label];
273 | this.log('%s: %dms', label, duration);
274 | };
275 |
276 | }(this));
277 |
--------------------------------------------------------------------------------
/lib/probes/StatProbe.js:
--------------------------------------------------------------------------------
1 | // StatProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root) {
6 |
7 | // Module loading - this runs server-side only
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | _ = Monitor._,
10 | StreamProbe = Monitor.StreamProbe,
11 | Stat = Monitor.Stat;
12 |
13 | // Constants
14 | var DEFAULT_PATTERN = '*';
15 |
16 | /**
17 | * Remote application statistics monitoring
18 | *
19 | * This probe forwards application statistics to the monitor.
20 | *
21 | * @class StatProbe
22 | * @extends StreamProbe
23 | * @constructor
24 | * @param [initParams] {Object} Probe initialization parameters
25 | * @param [initParams.pattern=*] {String} Stat name pattern to monitor (see Stat )
26 | * @param [initParams.interval=1000] {Numeric} Queue interval (see StreamProbe )
27 | * @param model {Object} Monitor data model elements
28 | * @param model.bundle {Stat array} Array of Stat elements.
29 | * @param model.bundle.timestamp {String} Timestamp of the stat entry
30 | * @param model.bundle.module {String} Stat module
31 | * @param model.bundle.name {String} Stat name
32 | * @param model.bundle.value {Numeric} Stat value
33 | * @param model.bundle.type {String} 'c'ounter, 'g'ague, or 'ms'timer
34 | * @param model.sequence {Integer} A numeric incrementer causing a change event
35 | */
36 | var StatProbe = Monitor.StatProbe = StreamProbe.extend({
37 |
38 | probeClass: 'Stat',
39 |
40 | defaults: _.extend({}, StreamProbe.prototype.defaults, {
41 | pattern: DEFAULT_PATTERN
42 | }),
43 |
44 | initialize: function(){
45 | var t = this;
46 |
47 | // Call parent constructor
48 | StreamProbe.prototype.initialize.apply(t, arguments);
49 |
50 | // The watcher just forwards all args to queueItem as an array
51 | t.watcher = function() {
52 | // Add timestamp as the first element
53 | var logElems = _.toArray(arguments);
54 | logElems.splice(0,0,JSON.stringify(new Date()).substr(1,24));
55 | t.queueItem.call(t, logElems);
56 | };
57 | Stat.on(t.get('pattern'), t.watcher);
58 | },
59 |
60 | release: function() {
61 | var t = this;
62 | Stat.off(t.get('pattern'), t.watcher);
63 | }
64 |
65 | });
66 |
67 | }(this));
68 |
--------------------------------------------------------------------------------
/lib/probes/StreamProbe.js:
--------------------------------------------------------------------------------
1 | // StreamProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | Probe = Monitor.Probe,
10 | _ = Monitor._;
11 |
12 | // Constants
13 | var DEFAULT_BUNDLE_INTERVAL = 1000;
14 |
15 | /**
16 | * Base class for probes that stream data
17 | *
18 | * Offering real time data streaming can result in degraded performance due
19 | * to the I/O overhead of sending individual stream elements to remote monitors.
20 | *
21 | * This class eases that overhead by bundling stream elements, and sending those
22 | * bundles in scheduled intervals. The monitor gets to decide the interval based
23 | * on the stream volume, and their needs.
24 | *
25 | * Derived classes output their stream data as elements of the ```bundle```
26 | * attribute.
27 | *
28 | * A ```sequence``` attribute is incremented sequentially to assure change
29 | * events are fired, and to allow clients to insure stream ordering and
30 | * completeness.
31 | *
32 | * @class StreamProbe
33 | * @extends Probe
34 | * @constructor
35 | * @param [initParams] {Object} Probe initialization parameters
36 | * @param [initParams.interval=1000] {Numeric} Number of milliseconds
37 | * to wait between bundles.
38 | */
39 | var StreamProbe = Monitor.StreamProbe = Probe.extend({
40 |
41 |
42 | defaults: _.extend({}, Probe.prototype.defaults, {
43 | bundle: [],
44 | interval: DEFAULT_BUNDLE_INTERVAL,
45 | sequence: 0
46 | }),
47 |
48 | initialize: function(){
49 | var t = this;
50 |
51 | // Initialize parent
52 | Probe.prototype.initialize.apply(t, arguments);
53 |
54 | // Moving the interval into an instance variable for performance
55 | t.interval = t.get('interval');
56 |
57 | // Set up for the first bundle
58 | t.queue = [];
59 | t.timer = null;
60 | t.lastSendTime = 0;
61 | },
62 |
63 | /**
64 | * Queue an item in the stream
65 | *
66 | * This method places the item into the stream and outputs it to the
67 | * monitor, or queues it up for the next bundle.
68 | *
69 | * @method queueItem
70 | * @param item {Any} Item to place into the queue
71 | */
72 | queueItem: function(item) {
73 | var t = this,
74 | now = Date.now(),
75 | msSinceLastSend = now - t.lastSendTime;
76 |
77 | // Queue the item
78 | t.queue.push(item);
79 |
80 | // Send the bundle?
81 | if (msSinceLastSend > t.interval) {
82 | // It's been a while since the last send. Send it now.
83 | t._send();
84 | }
85 | else {
86 | // Start the timer if it's not already running
87 | if (!t.timer) {
88 | t.timer = setTimeout(function(){
89 | t._send();
90 | }, t.interval - msSinceLastSend);
91 | }
92 | }
93 | },
94 |
95 | /**
96 | * Send the bundle to the montitor
97 | *
98 | * @private
99 | * @method _send
100 | */
101 | _send: function() {
102 | var t = this,
103 | now = Date.now();
104 |
105 | // This kicks off the send
106 | t.lastSendTime = now;
107 | t.set({
108 | bundle: t.queue,
109 | sequence: t.get('sequence') + 1
110 | });
111 |
112 | // Reset
113 | t.queue = [];
114 | if (t.timer) {
115 | clearTimeout(t.timer);
116 | t.timer = null;
117 | }
118 | }
119 |
120 | });
121 |
122 | }(this));
123 |
--------------------------------------------------------------------------------
/lib/probes/SyncProbe.js:
--------------------------------------------------------------------------------
1 | // SyncProbe.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Module loading - this runs server-side only
8 | var Monitor = root.Monitor || require('../Monitor'),
9 | _ = Monitor._, Probe = Monitor.Probe;
10 |
11 | /**
12 | * Probe for exposing backbone data models from server-side persistence
13 | *
14 | * This probe is used by the client-side Sync class
15 | * to connect a local backbone model with server-side storage.
16 | *
17 | * It delegates to a specialized SyncProbe defined by the server for the
18 | * specific data class. For example, the server may determine that one class
19 | * type uses FileSyncProbe, and another class uses a different persistence
20 | * mechanism.
21 | *
22 | * For security purposes, the server must configure specific SyncProbes for
23 | * classes, or a default sync probe before this will operate.
24 | *
25 | * @class SyncProbe
26 | * @extends Probe
27 | * @constructor
28 | * @param className {String} Name of the class to synchronize with
29 | * @param [modelId] {String} Id of the data model for live synchronization
30 | * If not set, a non-live probe is set up for control access only.
31 | * @param [model] {Object} If this is a liveSync probe, this contains
32 | * the attributes of the current model object.
33 | */
34 | var SyncProbe = Monitor.SyncProbe = Probe.extend({
35 |
36 | probeClass: 'Sync',
37 | defaults: {
38 | className: null,
39 | modelId: null,
40 | model: null
41 | },
42 |
43 | initialize: function(attributes, options){
44 | var t = this;
45 | Probe.prototype.initialize.apply(t, arguments);
46 |
47 | // Determine the probe name based on the class, and coerce this
48 | // object into one of those by copying all prototype methods.
49 | var className = t.get('className'),
50 | config = SyncProbe.Config,
51 | probeClassName = config.classMap[className] || config.defaultProbe,
52 | probeClass = SyncProbe[probeClassName];
53 | _.each(_.functions(probeClass.prototype), function(methodName) {
54 | t[methodName] = probeClass.prototype[methodName];
55 | });
56 | t.probeClass = probeClass.prototype.probeClass;
57 |
58 | // Forward class initialization to the coerced initialize method
59 | return t.initialize.apply(t, arguments);
60 | },
61 |
62 | release: function() {
63 | var t = this;
64 | Probe.prototype.release.apply(t, arguments);
65 | },
66 |
67 | /**
68 | * Create and save a new instance of the class into storage
69 | *
70 | * This probe control requests a new instance of a data model to be
71 | * persisted onto storage. It is invoked when a data model that has
72 | * the Sync probe attached calls ```save()``` on a new object.
73 | *
74 | * @method create_control
75 | * @param model {Object} Full data model to save. This must contain
76 | * the id element.
77 | * @param callback {Function(error, result)} Callback when complete
78 | * @param callback.error {Mixed} Set if an error occurs during creation.
79 | * @param callback.result {Object} An object containing any differing
80 | * parameters from the model sent in. Normally a blank object.
81 | */
82 | create_control: function(args, callback) {
83 | callback({msg: 'not implemented'});
84 | },
85 |
86 | /**
87 | * Read an instance from storage
88 | *
89 | * This probe control reads the instance with the specified id
90 | * from storage, and returns it in the callback.
91 | *
92 | * @method read_control
93 | * @param id {String} ID of the object to read
94 | * @param callback {Function(error, result)} Callback when complete
95 | * @param callback.error {Mixed} Set if an error occurs during read.
96 | * if error.code === 'NOTFOUND' then the requested object wasn't found.
97 | * if error.code === 'PARSE' then the document was poorly formatted JSON.
98 | * @param callback.result {Object} The full object.
99 | */
100 | read_control: function(args, callback) {
101 | callback({msg: 'not implemented'});
102 | },
103 |
104 | /**
105 | * Update a data model in storage
106 | *
107 | * This acts like a REST PUT, meaning it can create a new object, or
108 | * update an existing object.
109 | *
110 | * Backbone has only a save() method. If the client sets the ID
111 | * of the object before save(), Backbone thinks the object exists and
112 | * will call update vs. create.
113 | *
114 | * @method update_control
115 | * @param model {Object} Full data model to save. This must contain
116 | * the id element.
117 | * @param callback {Function(error, result)} Callback when complete
118 | * @param callback.error {Mixed} Set if an error occurs during save.
119 | * @param callback.result {Object} An object containing any differing
120 | * parameters from the model sent in. Normally a blank object.
121 | */
122 | update_control: function(args, callback) {
123 | callback({msg: 'not implemented'});
124 | },
125 |
126 | /**
127 | * Delete an instance from storage
128 | *
129 | * This probe control deletes the instance with the specified id
130 | * from storage.
131 | *
132 | * @method delete_control
133 | * @param id {String} ID of the object to read
134 | * @param callback {Function(error)} Callback when complete
135 | * @param callback.error {Mixed} Set if an error occurs during read.
136 | */
137 | delete_control: function(args, callback) {
138 | callback({msg: 'not implemented'});
139 | }
140 |
141 | });
142 |
143 | /**
144 | * Static Configurations
145 | *
146 | * These can be set onto the Monitor.SyncProbe class after it's loaded.
147 | *
148 | * The SyncProbe will *not* work until the defaultProbe is defined.
149 | *
150 | * Example:
151 | *
152 | * var syncConfig = Monitor.SyncProbe.Config;
153 | * syncConfig.defaultProbe = 'FileSyncProbe';
154 | * syncConfig.classMap = {
155 | * Book: 'MongoDbSync',
156 | * Author: 'MongoDbSync'
157 | * }
158 | *
159 | * @static
160 | * @property Config
161 | * @type <Object>
162 | *
163 | * defaultProbe (String)
Name of the sync probe to use if the class isn't listed in the classMap
164 | * classMap (Object)
Map of className to sync probe name to use instead of the default for that class
165 | *
166 | */
167 | var defaultConfig = {
168 | defaultProbe: '',
169 | classMap: {}
170 | };
171 |
172 | // Expose default configurations to the config package
173 | SyncProbe.Config = _.extend({}, defaultConfig);
174 |
175 | }(this));
176 |
--------------------------------------------------------------------------------
/monitor.js:
--------------------------------------------------------------------------------
1 | // monitor.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // Load dependencies
8 | var Monitor = require('./lib/index'),
9 | log = Monitor.getLogger('monitor'),
10 | stat = Monitor.getStatLogger('monitor'),
11 | OS = require('os');
12 |
13 | /**
14 | * Bootstrap for a standalone monitor server
15 | *
16 | * @static
17 | * @class server
18 | */
19 |
20 | console.log("");
21 | console.log(" __________");
22 | console.log("_______ ___________________(_)_ /______________ ");
23 | console.log("__ __ `__ \\ __ \\_ __ \\_ /_ __/ __ \\_ ___/");
24 | console.log("_ / / / / / /_/ / / / / / / /_ / /_/ / /");
25 | console.log("/_/ /_/ /_/\\____//_/ /_//_/ \\__/ \\____//_/");
26 | console.log("");
27 |
28 | // Boot the monitor server.
29 | // This accepts websocket connections on the configured port.
30 | var server = new Monitor.Server();
31 | server.start(function(error) {
32 | if (error) {
33 | log.error('monitor.start', error);
34 | return;
35 | }
36 |
37 | var connectTo = Monitor.Config.Monitor.allowExternalConnections ? OS.hostname() : 'localhost';
38 | console.log('Monitor service started on host: ' + connectTo);
39 |
40 | // Output security concerns
41 | if (!Monitor.Config.Monitor.allowExternalConnections) {
42 | console.log("");
43 | console.log("External connections disabled.");
44 | console.log("See " + process.cwd() + "/config/external.js for more information.");
45 | }
46 |
47 | });
48 |
49 | // Process uncaught exceptions.
50 | process.on('uncaughtException', function(err){
51 |
52 | // On laptop sleep/startup the DNS servers aren't immediately available,
53 | // resulting in a flood of these for socket.io until DNS services are back up.
54 | if (err.message === 'ECONNREFUSED, Could not contact DNS servers') {
55 | return;
56 | }
57 |
58 | // Don't allow the process to continue in an unknown state.
59 | log.fatal('moniotor.uncaught', 'Uncaught Exception: ' + err.message);
60 | log.fatal('moniotor.uncaught', err.stack);
61 | server.stop(function(){
62 | process.exit(1);
63 | });
64 |
65 | // Don't wait around if the server is hung.
66 | setTimeout(function(){process.exit(1);}, 2000);
67 | });
68 |
69 | }(this));
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monitor",
3 | "description": "Runtime monitoring for node.js applications",
4 | "version": "0.6.10",
5 | "main": "./lib/index.js",
6 | "author": {
7 | "name": "Loren West",
8 | "email": "open_source@lorenwest.com",
9 | "url": "https://github.com/lorenwest"
10 | },
11 | "homepage": "http://lorenwest.github.com/node-monitor/",
12 | "keywords": ["monitor", "node-monitor", "remote control", "realtime", "probe", "JMX"],
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/lorenwest/node-monitor.git"
16 | },
17 | "licenses": [
18 | {
19 | "type": "MIT",
20 | "url": "https://github.com/lorenwest/node-monitor/blob/master/LICENSE"
21 | }
22 | ],
23 | "dependencies": {
24 | "config": ">=0.4.34 <0.5.0",
25 | "cron": ">=0.1.3 <0.2.0",
26 | "backbone": "0.9.9",
27 | "underscore": ">=1.4.3 <1.5.0",
28 | "backbone-callbacks": ">=0.1.4 <0.2.0",
29 | "socket.io-client": ">=0.9.11 <0.10.0",
30 | "socket.io": ">=0.9.10 <0.10.0"
31 | },
32 | "devDependencies": {
33 | "grunt": "0.3.17"
34 | },
35 | "engines": { "node": ">= 0.6.0" },
36 | "scripts": {
37 | "test": "grunt test",
38 | "start": "node monitor"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/ConnectionTest.js:
--------------------------------------------------------------------------------
1 | // ConnectionTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Connection = Monitor.Connection, Backbone = Monitor.Backbone,
14 | server, serverPort;
15 |
16 | /**
17 | * Unit tests for the Connection class.
18 | * @class ConnectionTest
19 | */
20 |
21 | /**
22 | * Test group for connection functionality
23 | * @method Connection
24 | */
25 | module.exports['Connection'] = {
26 |
27 | /**
28 | * Create a Server to test connections with
29 | * @method Connection-setUp
30 | */
31 | setUp: function(callback) {
32 | server = new Monitor.Server();
33 | server.start(callback);
34 | },
35 |
36 | /**
37 | * Tests that the Connection classes are available
38 | * @method Connection-Classes
39 | */
40 | Classes: function(test) {
41 | test.ok(Connection.prototype instanceof Backbone.Model, 'The Connection data model is in place');
42 | test.ok(Connection.List.prototype instanceof Backbone.Collection, 'The Connection.List collection is in place');
43 | test.done();
44 | },
45 |
46 | /**
47 | * Assure that a connect / disconnect to the server host/port works
48 | * @method Connection-ConnectDisconnect
49 | */
50 | ConnectDisconnect: function(test) {
51 | var port = server.get('port'), conn = new Monitor.Connection({hostName:'localhost', hostPort:port});
52 | conn.on('connect', function() {
53 | test.ok(conn.get('remoteHostName'), 'The remote host name is known');
54 | conn.on('disconnect', test.done);
55 | conn.disconnect();
56 | });
57 | },
58 |
59 | /**
60 | * Test pinging the remote connection
61 | * @method Connection-PingPong
62 | */
63 | PingPong: function(test) {
64 | var port = server.get('port'), conn = new Monitor.Connection({hostName:'localhost', hostPort:port});
65 | conn.on('connect', function() {
66 | test.ok(conn.get('remoteHostName'), 'The remote host name is known');
67 | conn.ping(function(){
68 | test.ok(true, 'Ping made its way to and from the remote server');
69 | conn.on('disconnect', test.done);
70 | conn.disconnect();
71 | });
72 | });
73 | },
74 |
75 | /**
76 | * Tear down the test Server
77 | * @method Connection-tearDown
78 | */
79 | tearDown: function(callback) {
80 | server.stop(callback);
81 | }
82 |
83 | };
84 |
85 | }(this));
86 |
--------------------------------------------------------------------------------
/test/DataModelProbeTest.js:
--------------------------------------------------------------------------------
1 | // DataModelProbeTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | DataModelProbe = Monitor.DataModelProbe,
14 | dataModelMonitor = null,
15 | dataModelMonitor1 = null,
16 | dataModelMonitor2 = null,
17 | Backbone = Monitor.Backbone, _ = Monitor._;
18 |
19 | /**
20 | * Unit tests for the DataModel probe.
21 | * @class DataModelProbeTest
22 | */
23 |
24 | /**
25 | * Test group for baseline DataModel probe functionality
26 | *
27 | * @method DataModelProbe
28 | */
29 | module.exports['DataModelProbe'] = {
30 |
31 | /**
32 | * Tests that classes are in correct
33 | * @method DataModelProbe-Classes
34 | */
35 | Classes: function(test) {
36 | test.ok(DataModelProbe.prototype instanceof Backbone.Model, 'The data model is in place');
37 | test.ok(DataModelProbe.prototype instanceof Monitor.Probe, 'It is a probe');
38 | test.done();
39 | },
40 |
41 | /**
42 | * Test that you can connect to the test data model
43 | * @method DataModelProbe-Connect
44 | */
45 | Connect: function(test) {
46 | // Get a monitor to the underlying data model the recipe manipulates
47 | setTimeout(function(){
48 | dataModelMonitor = new Monitor({probeName:'DataModelTest'});
49 | dataModelMonitor.connect(function(error) {
50 | test.ok(!error, 'No error on data model connect');
51 | test.equal(dataModelMonitor.get('testParam1'), 'testValue1', 'Connected to the correct data model');
52 | test.done();
53 | });
54 | }, 0);
55 | },
56 |
57 | /**
58 | * Tests that a single monitor can update the value
59 | * @method DataModelProbe-SingleMonitor
60 | */
61 | SingleMonitor: function(test) {
62 |
63 | // Expect only a single change (vs. a change in the monitor, and a subsequent
64 | // change from the probe).
65 | var onChange = function() {
66 | test.equal(dataModelMonitor.get('testParam1'), 'testValue2', 'The change event was triggered correctly');
67 | };
68 |
69 | // Done with the test in 10 ms (should have only gotten one onChange event triggered)
70 | setTimeout(function(){
71 | dataModelMonitor.off('change:testParam1', onChange);
72 | test.expect(1);
73 | test.done();
74 | }, 10);
75 |
76 | // Now attach the listener and change an attribute
77 | dataModelMonitor.on('change:testParam1', onChange);
78 | dataModelMonitor.set('testParam1', 'testValue2');
79 | },
80 |
81 | /**
82 | * Tests that multiple monitors can all be triggered from a single change
83 | * @method DataModelProbe-SingleMonitor
84 | */
85 | MultiMonitor: function(test) {
86 |
87 | // Expect two changes - one from each monitor
88 | var onChange1 = function() {
89 | test.equal(dataModelMonitor.get('testParam1'), 'testValue3', 'The change event was triggered correctly');
90 | dataModelMonitor1.off('change:testParam1', onChange1);
91 | };
92 | var onChange2 = function() {
93 | test.equal(dataModelMonitor.get('testParam1'), 'testValue3', 'The change event was triggered correctly');
94 | dataModelMonitor2.off('change:testParam1', onChange2);
95 | };
96 |
97 | dataModelMonitor1 = new Monitor({probeName:'DataModelTest'});
98 | dataModelMonitor2 = new Monitor({probeName:'DataModelTest'});
99 |
100 | dataModelMonitor1.connect(function(error) {
101 | test.ok(!error, 'No error on data model 1 connect');
102 | test.equal(dataModelMonitor.get('testParam1'), 'testValue2', 'Connected to the correct data model');
103 | dataModelMonitor1.on('change:testParam1', onChange1);
104 |
105 | dataModelMonitor2.connect(function(error) {
106 | test.ok(!error, 'No error on data model 2 connect');
107 | test.equal(dataModelMonitor.get('testParam1'), 'testValue2', 'Connected to the correct data model');
108 | dataModelMonitor2.on('change:testParam1', onChange2);
109 |
110 | // Now change the value from the original monitor. This change should
111 | // be propagated to the two other monitors just connected.
112 | dataModelMonitor.set('testParam1', 'testValue3');
113 |
114 | // Done with the test in 10 ms (should have gotten two onChange events triggered
115 | // from different monitors, plus the 4 connect tests)
116 | setTimeout(function(){
117 | test.expect(6);
118 | test.done();
119 | }, 10);
120 |
121 | });
122 | });
123 |
124 | }
125 |
126 | };
127 |
128 | }(this));
129 |
--------------------------------------------------------------------------------
/test/FileProbeTest.js:
--------------------------------------------------------------------------------
1 | // FileProbeTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Path = require('path'),
14 | FS = require('fs'),
15 | FileProbe = Monitor.FileProbe,
16 | Backbone = Monitor.Backbone, _ = Monitor._;
17 |
18 | // Constants
19 | var TEST_ROOT_PATH = __dirname + '/fileTestData',
20 | TEST_FILE_RELATIVE_PATH = 'testdir/testfile1.json',
21 | TEST_FILE_FULL_PATH = Path.join(TEST_ROOT_PATH, TEST_FILE_RELATIVE_PATH),
22 | TEST_FILE_DIR = Path.dirname(TEST_FILE_FULL_PATH),
23 | TEST_OBJECT = {
24 | testNumber:1,
25 | testString:"two",
26 | testObject:{some:"sub_object"},
27 | testArray:[1, "two", 3]
28 | },
29 | JSON_CONTENT = JSON.stringify(TEST_OBJECT, null, 2);
30 |
31 | // Old style watch takes *forever* to connect
32 | var OLD_WATCHER_CONNECT_MS = 1000,
33 | NEW_WATCHER_CONNECT_MS = 10,
34 | WATCH_CONNECT_TIME = FS.watch ? NEW_WATCHER_CONNECT_MS : OLD_WATCHER_CONNECT_MS;
35 |
36 | /**
37 | * Unit tests for the File probe.
38 | * @class FileProbeTest
39 | */
40 |
41 | /**
42 | * Test group for baseline FileProbe functionality
43 | *
44 | * @method FileProbe
45 | */
46 | module.exports['FileProbe'] = {
47 |
48 | /**
49 | * Tests that classes are in correct
50 | * @method FileProbe-Classes
51 | */
52 | Classes: function(test) {
53 | test.ok(FileProbe.prototype instanceof Backbone.Model, 'The data model is in place');
54 | test.ok(FileProbe.prototype instanceof Monitor.Probe, 'It is a probe');
55 | test.done();
56 | },
57 |
58 | /**
59 | * Tests that public static methods are in place
60 | * @method FileProbe-Static
61 | */
62 | Static: function(test) {
63 | test.equal(typeof FileProbe.setRootPath, 'function');
64 | test.equal(typeof FileProbe.getRootPath, 'function');
65 | test.equal(typeof FileProbe.rm_rf, 'function');
66 | test.equal(typeof FileProbe.mkdir_r, 'function');
67 | test.equal(typeof FileProbe.watch, 'function');
68 | test.equal(typeof FileProbe.watchLoad, 'function');
69 | test.equal(typeof FileProbe.tail, 'function');
70 | test.done();
71 | }
72 |
73 | };
74 |
75 | /**
76 | * Test group for static file/directory utilities
77 | *
78 | * @method Utils
79 | */
80 | module.exports['Utils'] = {
81 |
82 | /**
83 | * Test the mkdir_r utility
84 | * @method Utils-Mkdir_R
85 | */
86 | Mkdir_R: function(test) {
87 | FileProbe.mkdir_r(TEST_FILE_DIR, function(error) {
88 | test.ok(!error, 'Mkdir-r error ' + JSON.stringify(error));
89 | var status = FS.statSync(TEST_FILE_DIR);
90 | test.ok(status.isDirectory(), "Recursive mkdir created all directories");
91 | test.done();
92 | });
93 | },
94 |
95 | /**
96 | * Test the rm_rf utility
97 | * @method Utils-Rm_Rf
98 | */
99 | Rm_Rf: function(test) {
100 | // Make a test directory structure
101 | FileProbe.mkdir_r(TEST_FILE_DIR, function(error) {
102 | var status = FS.statSync(TEST_FILE_DIR);
103 | test.ok(status.isDirectory(), "Recursive mkdir created all directories");
104 |
105 | // Make a bunch of files in the lowest directory
106 | for (var i = 0; i < 10; i++) {
107 | FS.writeFileSync(TEST_FILE_FULL_PATH + i, JSON_CONTENT);
108 | }
109 |
110 | // Remove everything from the test root
111 | FileProbe.rm_rf(TEST_ROOT_PATH, function(err1) {
112 | test.ok(!err1, "rm_rf error: " + err1);
113 | FS.readdir(TEST_FILE_DIR, function(err2, files) {
114 | test.ok(err2, "Readdir correctly reported an error on no directory");
115 | test.equal(err2.code, 'ENOENT', "Directory and all sub-files removed correctly");
116 | test.done();
117 | });
118 | });
119 | });
120 | },
121 |
122 | /**
123 | * Tests the file watch functionality
124 | * @method Utils-Watch
125 | */
126 | Watch: function(test) {
127 | // Create a file
128 | FileProbe.mkdir_r(TEST_FILE_DIR, function(err) {
129 | test.ok(!err, 'Made the test directory');
130 | writeTestFile();
131 |
132 | // Get a watcher on the file
133 | var watcher = FileProbe.watch(TEST_FILE_FULL_PATH, {persistent:true}, function(){
134 | test.ok(true, "File change detected");
135 | watcher.close();
136 | FileProbe.rm_rf(TEST_ROOT_PATH, function(){
137 | test.done();
138 | });
139 | });
140 |
141 | // Wait for the O/S to start watching, then change the file
142 | writeTestFile("new", WATCH_CONNECT_TIME);
143 | });
144 | },
145 |
146 | /**
147 | * Tests the polling style file watching mechanism
148 | * @method Utils-PollingWatcher
149 | */
150 | PollingWatcher: function(test) {
151 | // Create a file
152 | FileProbe.mkdir_r(TEST_FILE_DIR, function(err) {
153 | test.ok(!err, 'Made the test directory');
154 | writeTestFile();
155 |
156 | // Test the old-style watching
157 | var watcher = FileProbe.watch(TEST_FILE_FULL_PATH, {persistent:true, pollStyle:true}, function(){
158 | test.ok(true, "File change detected");
159 | watcher.close();
160 | FileProbe.rm_rf(TEST_ROOT_PATH, function(){
161 | test.done();
162 | });
163 | });
164 |
165 | // Wait long enough for the old-style watcher to connect
166 | writeTestFile('new', OLD_WATCHER_CONNECT_MS);
167 | });
168 | },
169 |
170 | /**
171 | * Tests the watchLoad functionality
172 | * @method Utils-WatchLoad
173 | */
174 | WatchLoad: function(test) {
175 | // Create a file
176 | FileProbe.mkdir_r(TEST_FILE_DIR, function(err) {
177 | test.ok(!err, 'Made the test directory');
178 | writeTestFile();
179 |
180 | // Test with initial preload
181 | var watchCount = 0;
182 | var watcher = FileProbe.watchLoad(TEST_FILE_FULL_PATH, {persistent:true, preload:true}, function(err1, content){
183 | test.ok(!err1, "watchLoad error: " + err1);
184 | watchCount++;
185 | if (watchCount === 1) {
186 | test.equal(content, JSON_CONTENT, "Initial file contents preloaded");
187 |
188 | // Test with subsequent file write
189 | writeTestFile("extra", WATCH_CONNECT_TIME);
190 | } else if (watchCount === 2) {
191 | test.equal(content, JSON_CONTENT + "extra", "Subsequent content loaded after update");
192 | watcher.close();
193 | FileProbe.rm_rf(TEST_ROOT_PATH, function(){
194 | test.done();
195 | });
196 | }
197 | });
198 | });
199 | }
200 |
201 | };
202 |
203 | /**
204 | * Test group for File based probe functionality
205 | *
206 | * @method Probe
207 | */
208 | module.exports['Probe'] = {
209 |
210 | // Create the test directory and file
211 | setUp: function(callback) {
212 | FileProbe.mkdir_r(TEST_FILE_DIR, function(err) {
213 | writeTestFile();
214 | callback();
215 | });
216 | },
217 |
218 | // Remove the test directory
219 | tearDown: function(callback) {
220 | FileProbe.rm_rf(TEST_ROOT_PATH, function(){
221 | callback();
222 | });
223 | },
224 |
225 | /**
226 | * Tests the ROOT_PATH functionality
227 | * @method Probe-RootPath
228 | */
229 | RootPath: function(test) {
230 | // Only perform tests if root path hasn't been set
231 | if (FileProbe.getRootPath() === null) {
232 | FileProbe.setRootPath(TEST_ROOT_PATH);
233 | test.equal(FileProbe.getRootPath(), TEST_ROOT_PATH);
234 | try {
235 | FileProbe.setRootPath('/');
236 | test.ok(false, "This line shouldn't be reached");
237 | }
238 | catch (e) {
239 | test.ok(true, "Root path correctly disallowed changes");
240 | }
241 | }
242 | test.done();
243 | },
244 |
245 | /**
246 | * This tests the File probe initializes properly
247 | * @method Probe-Init
248 | */
249 | Init: function(test) {
250 |
251 | // Build and connect the file monitor
252 | var fileMonitor = new Monitor({
253 | probeClass: "File",
254 | initParams: {path: TEST_FILE_RELATIVE_PATH}
255 | });
256 | fileMonitor.connect(function(){
257 |
258 | // Make sure the initial content is correct
259 | test.equal(fileMonitor.get("text"), JSON_CONTENT, "File content is correct");
260 |
261 | // Watch for file changes after first change event
262 | process.nextTick(function(){
263 | fileMonitor.on('change', function() {
264 | test.equal(fileMonitor.get("text"), JSON_CONTENT + 'altered', "Altered file content is correct");
265 | fileMonitor.off('change');
266 | fileMonitor.disconnect();
267 | test.done();
268 | });
269 | });
270 |
271 | // Alter the file
272 | writeTestFile('altered', WATCH_CONNECT_TIME);
273 | });
274 | }
275 | };
276 |
277 | /**
278 | * Write the test file to disk, with possible appendage
279 | *
280 | * @static
281 | * @method writeTestFile
282 | * @param append {String} String to append onto the standard test file
283 | * @param wait {Integer} Number of milliseconds to wait before writing
284 | */
285 | function writeTestFile(append, wait) {
286 | var content = append ? JSON_CONTENT + append : JSON_CONTENT;
287 | if (wait) {
288 | setTimeout(function(){
289 | FS.writeFileSync(TEST_FILE_FULL_PATH, content);
290 | }, wait);
291 | } else {
292 | FS.writeFileSync(TEST_FILE_FULL_PATH, content);
293 | }
294 | }
295 |
296 | }(this));
297 |
--------------------------------------------------------------------------------
/test/InspectTest.js:
--------------------------------------------------------------------------------
1 | // InspectTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | InspectProbe = Monitor.InspectProbe,
14 | Backbone = Monitor.Backbone, _ = Monitor._;
15 |
16 | /**
17 | * Unit tests for the Inspect probe.
18 | * @class InspectTest
19 | */
20 |
21 | /**
22 | * Test group for baseline Inspect probe functionality
23 | *
24 | * @method Inspect
25 | */
26 | module.exports['Inspect'] = {
27 |
28 | /**
29 | * Tests that classes are in correct
30 | * @method Inspect-Classes
31 | */
32 | Classes: function(test) {
33 | test.ok(InspectProbe.prototype instanceof Backbone.Model, 'The data model is in place');
34 | test.ok(InspectProbe.prototype instanceof Monitor.Probe, 'It is a probe');
35 | test.ok(InspectProbe.prototype instanceof Monitor.PollingProbe, 'It is a polling probe');
36 | test.done();
37 | },
38 |
39 | /**
40 | * Tests the no-param constructor
41 | * @method Inspect-NoParams
42 | */
43 | NoParams: function(test) {
44 | var monitor = new Monitor({
45 | probeClass:'Inspect'
46 | });
47 | monitor.connect(function(error) {
48 | test.ok(!error, "Able to construct a top level inspector");
49 | var globalValue = monitor.get('value');
50 | test.ok(typeof globalValue.Monitor !== 'undefined', 'Global object returned');
51 | monitor.disconnect(function(error){
52 | test.ok(!error, 'Properly disconnected');
53 | test.done();
54 | });
55 | });
56 | },
57 |
58 | /**
59 | * Tests the key parameter as a global variable
60 | * @method Inspect-KeyVariable
61 | */
62 | KeyVariable: function(test) {
63 | var monitor = new Monitor({
64 | probeClass:'Inspect',
65 | initParams: {
66 | key: 'Monitor'
67 | }
68 | });
69 | monitor.connect(function(error) {
70 | test.ok(!error, "Able to inspect a global variable");
71 | var value = monitor.get('value');
72 | test.ok(typeof value.Probe !== 'undefined', 'The monitor object was returned');
73 | monitor.disconnect(function(error){
74 | test.ok(!error, 'Properly disconnected');
75 | test.done();
76 | });
77 | });
78 | },
79 |
80 | /**
81 | * Tests the key parameter as an expression
82 | * @method Inspect-KeyExpression
83 | */
84 | KeyExpression: function(test) {
85 | var monitor = new Monitor({
86 | probeClass:'Inspect',
87 | initParams: {
88 | key: 'Monitor.getRouter()'
89 | }
90 | });
91 | monitor.connect(function(error) {
92 | test.ok(!error, "Able to inspect an expression");
93 | var value = monitor.get('value');
94 | test.ok(typeof value.firewall !== 'undefined', 'The expression returned the correct object');
95 | monitor.disconnect(function(error){
96 | test.ok(!error, 'Properly disconnected');
97 | test.done();
98 | });
99 | });
100 | }
101 |
102 | };
103 |
104 | }(this));
105 |
--------------------------------------------------------------------------------
/test/LogTest.js:
--------------------------------------------------------------------------------
1 | // LogTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Log = Monitor.Log,
14 | log = Monitor.getLogger('log-test'),
15 | Backbone = Monitor.Backbone;
16 |
17 | /**
18 | * Unit tests for the Log class.
19 | * @class LogTest
20 | */
21 |
22 | /**
23 | * Test group for baseline Log functionality
24 | *
25 | * @method Log
26 | */
27 | module.exports['Log'] = {
28 |
29 | setUp: function(callback) {callback();},
30 | tearDown: function(callback) {callback();},
31 |
32 | /**
33 | * Tests that Log class is in place
34 | * @method Log-Classes
35 | */
36 | Classes: function(test) {
37 | test.ok(Log.prototype, 'The Log model is in place');
38 | test.done();
39 | },
40 |
41 | /**
42 | * Tests that registering for '*' emits all logs
43 | * @method Log-AllLogs
44 | */
45 | AllLogs: function(test) {
46 |
47 | // Listen for all logs
48 | Log.once('*', function(log, module, name) {
49 | test.equals(log, 'info', 'The log type is correct');
50 | test.equals(module, 'log-test', 'found the log-test module when listening to *');
51 | test.equals(name, 'All logs test', 'found the correct log name *');
52 | test.done();
53 | });
54 | log.info('All logs test', 34);
55 | },
56 |
57 | /**
58 | * Tests for the inclusion of multiple arguments in logs
59 | * @method Log-MultiArg
60 | */
61 | MultiArg: function(test) {
62 |
63 | // Listen for all logs
64 | Log.once('*', function(log, module, name, hello, number, obj) {
65 | test.equals(log, 'error', 'The log type is correct');
66 | test.equals(name, 'MultiArg', 'found the correct log name *');
67 | test.equals(hello, 'hello', 'hello arg is found');
68 | test.equals(number, 34, 'Numeric argument is found');
69 | test.ok(obj, 'Object argument is found');
70 | test.equals(obj.there, 'world', 'Object elements are intact');
71 | test.equals(obj.num, 9273, 'Object elements are intact');
72 | test.done();
73 | });
74 | log.error('MultiArg', 'hello', 34, {there:'world', num: 9273});
75 | },
76 |
77 | /**
78 | * Tests for the trace log
79 | * @method Log-trace
80 | */
81 | TraceLog: function(test) {
82 |
83 | // Listen for trace logs
84 | Log.once('trace.*', function(log, module, name) {
85 | test.equals(log, 'trace', 'The log type is correct');
86 | test.equals(module, 'log-test', 'found the correct module name');
87 | test.equals(name, 'traceLog', 'found the correct log name');
88 | test.done();
89 | });
90 | log.trace('traceLog');
91 | },
92 |
93 | /**
94 | * Tests for the debug log
95 | * @method Log-debug
96 | */
97 | DebugLog: function(test) {
98 |
99 | // Listen for debug logs
100 | Log.once('*.*.debugLog', function(log, module, name) {
101 | test.equals(log, 'debug', 'The log type is correct');
102 | test.equals(module, 'log-test', 'found the correct module name');
103 | test.equals(name, 'debugLog', 'found the correct log name');
104 | test.done();
105 | });
106 | log.debug('debugLog');
107 | },
108 |
109 | /**
110 | * Tests for the info log
111 | * @method Log-info
112 | */
113 | InfoLog: function(test) {
114 |
115 | // Listen for info logs
116 | Log.once('*.log-test.infoLog', function(log, module, name) {
117 | test.equals(log, 'info', 'The log type is correct');
118 | test.equals(module, 'log-test', 'found the correct module name');
119 | test.equals(name, 'infoLog', 'found the correct log name');
120 | test.done();
121 | });
122 | log.info('infoLog');
123 | },
124 |
125 | /**
126 | * Tests for the warn log
127 | * @method Log-warn
128 | */
129 | WarnLog: function(test) {
130 |
131 | // Listen for warn logs
132 | Log.once('*.log-test.*', function(log, module, name) {
133 | test.equals(log, 'warn', 'The log type is correct');
134 | test.equals(module, 'log-test', 'found the correct module name');
135 | test.equals(name, 'warnLog', 'found the correct log name');
136 | test.done();
137 | });
138 | log.warn('warnLog');
139 | },
140 |
141 | /**
142 | * Tests for the error log
143 | * @method Log-error
144 | */
145 | ErrorLog: function(test) {
146 |
147 | // Listen for error logs
148 | Log.once('error.log-test.errorLog', function(log, module, name) {
149 | test.equals(log, 'error', 'The log type is correct');
150 | test.equals(module, 'log-test', 'found the correct module name');
151 | test.equals(name, 'errorLog', 'found the correct log name');
152 | test.done();
153 | });
154 | log.error('errorLog');
155 | },
156 |
157 | /**
158 | * Tests for the fatal log
159 | * @method Log-fatal
160 | */
161 | FatalLog: function(test) {
162 |
163 | // Listen for fatal logs
164 | Log.once('{error,fatal}.*.fatal[Ll]og', function(log, module, name) {
165 | test.equals(log, 'fatal', 'The log type is correct');
166 | test.equals(module, 'log-test', 'found the correct module name');
167 | test.equals(name, 'fatalLog', 'found the correct log name');
168 | test.done();
169 | });
170 | log.fatal('fatalLog');
171 | },
172 |
173 | /**
174 | * Make sure a stat is output for every log
175 | * @method Log-Stat
176 | */
177 | Stat: function(test) {
178 |
179 | // Listen for the stat
180 | Monitor.Stat.once('*', function(module, name, value, type) {
181 | test.equals(module, 'Log', 'The stat module is correct');
182 | test.equals(name, 'info.log-test.Checking for stat emission', 'found the correct stat name');
183 | test.equals(value, 1, 'Correctly incremented the log stat by 1');
184 | test.equals(type, 'c', 'Correct stat type - counter');
185 | test.done();
186 | });
187 | log.info('Checking for stat emission');
188 | },
189 |
190 | /**
191 | * Tests the Log probe
192 | * @method Log-ProbeTest
193 | */
194 | ProbeTest: function(test) {
195 | var logMonitor = new Monitor({probeClass:'Log', initParams:{interval:10, pattern:'*.log-test.*'}});
196 | logMonitor.connect(function(error) {
197 | test.ok(!error, 'Log monitor error: ' + JSON.stringify(error));
198 | logMonitor.on('change', function() {
199 | var bundle = logMonitor.get('bundle');
200 |
201 | // Omit the initial state of the monitor
202 | if (bundle.length === 0) {
203 | return;
204 | }
205 |
206 | test.ok(bundle, 'The log bundle is available');
207 | test.equals(bundle.length, 1, 'There is a single log in the bundle');
208 | var logArgs = bundle[0];
209 | test.equals(logArgs.length, 5, 'There are 5 log arguments');
210 | test.ok(Date.now() - (new Date(logArgs[0]).getTime()) < 1000, 'The first arg is a timestamp');
211 | test.equals(logArgs[1], 'info', 'There log type is correct');
212 | test.equals(logArgs[2], 'log-test', 'Ther module is correct');
213 | test.equals(logArgs[3], 'probeTest', 'There log name is correct');
214 | test.equals(logArgs[4], 22, 'The value is correct');
215 | logMonitor.off('change');
216 | test.done();
217 | });
218 | log.info('probeTest', 22);
219 | });
220 | },
221 |
222 | /**
223 | * Tests the probe streams multiple items at once
224 | * @method Log-ProbeStream
225 | */
226 | ProbeStream: function(test) {
227 |
228 | // This relies on the fact that the monitor was created in the prior
229 | // function, and it just emitted a single item.
230 | var logMonitor = new Monitor({probeClass:'Log', initParams:{interval:10, pattern:'*.log-test.*'}});
231 | logMonitor.connect(function(error) {
232 | test.ok(!error, 'Log monitor error: ' + JSON.stringify(error));
233 | logMonitor.on('change', function() {
234 | var bundle = logMonitor.get('bundle');
235 |
236 | // Omit the current state of the probe (first change event)
237 | if (bundle.length < 4) {
238 | return;
239 | }
240 |
241 | test.ok(bundle, 'The log bundle is available');
242 | test.equals(bundle.length, 4, 'There were the correct number of items in the stream');
243 | var logArgs = bundle[2];
244 | test.equals(logArgs.length, 7, 'There are 7 log arguments');
245 | test.ok(Date.now() - (new Date(logArgs[0]).getTime()) < 1000, 'The first arg is a timestamp');
246 | test.equals(logArgs[1], 'error', 'The log name is correct');
247 | test.equals(logArgs[2], 'log-test', 'The module is correct');
248 | test.equals(logArgs[3], 'probeTest3', 'The log name is correct');
249 | test.equals(logArgs[4], 420, 'The value is correct');
250 | test.equals(logArgs[5], 'hello', 'The next arg is correct');
251 | test.equals(logArgs[6].a, 'world', 'Objects are correctly represented');
252 | logMonitor.off('change');
253 | test.done();
254 | });
255 | log.info('probeTest1');
256 | log.warn('probeTest2');
257 | log.error('probeTest3', 420, "hello", {a:"world"});
258 | log.fatal('probeTest4', 2840);
259 | });
260 | }
261 |
262 | };
263 |
264 | }(this));
265 |
--------------------------------------------------------------------------------
/test/ProbeTest.js:
--------------------------------------------------------------------------------
1 | // ProbeTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Probe = Monitor.Probe, PollingProbe = Monitor.PollingProbe,
14 | Backbone = Monitor.Backbone, _ = Monitor._;
15 |
16 | /**
17 | * Unit tests for the Probe class.
18 | * @class ProbeTest
19 | */
20 |
21 | /**
22 | * Tests for Probe functionality
23 | *
24 | * @method ProbeTest
25 | */
26 | // Test that important namespaces are available
27 | var probeTests = module.exports['ProbeTest'] = {
28 |
29 | /**
30 | * Tests that Probe classes are in place
31 | * @method ProbeTest-Classes
32 | */
33 | Classes: function(test) {
34 | test.ok(Probe.prototype instanceof Backbone.Model, 'The Probe data model is in place');
35 | test.ok(Probe.List.prototype instanceof Backbone.Collection, 'The Probe.List collection is in place');
36 | test.ok(PollingProbe.prototype instanceof Probe, 'The PollingProbe base constructor is in place');
37 | test.done();
38 | },
39 |
40 | /**
41 | * Tests Probe instantiation
42 | * @method ProbeTest-Instantiate
43 | */
44 | Instantiate: function(test) {
45 | var process = probeTests.processMonitor = new Monitor({probeClass:'Process', initParams:{a:'b', pollInterval:100}});
46 | process.connect(function(err) {
47 | test.ifError(err);
48 | test.notEqual(process.get('probeId'), null, "Probe ID isn't null");
49 | test.done();
50 | });
51 | },
52 |
53 | /**
54 | * Test the same ID on subsequent probe instantiation with similar init params
55 | * @method ProbeTest-SameProbe
56 | */
57 | SameProbe: function(test) {
58 | var process = new Monitor({probeClass:'Process', initParams:{pollInterval:100, a:'b'}});
59 | process.connect(function(err) {
60 | test.ifError(err);
61 | test.notEqual(process.get('probeId'), null, "Probe ID isn't null");
62 | test.equal(probeTests.processMonitor.get('probeId'), process.get('probeId'), "Probes are the same with similar init params.");
63 | process.disconnect();
64 | test.done();
65 | });
66 | },
67 |
68 | /**
69 | * Test that different init params result in a different probe
70 | * @method ProbeTest-DifferentProbe
71 | */
72 | DifferentProbe: function(test) {
73 | var process = new Monitor({probeClass:'Process', initParams:{pollInterval:1000, a:'b'}});
74 | process.connect(function(err) {
75 | test.ifError(err);
76 | test.notEqual(process.get('probeId'), null, "Probe ID isn't null");
77 | test.notEqual(probeTests.processMonitor.get('probeId'), process.get('probeId'), "Probes are different with different init params.");
78 | process.disconnect();
79 | test.done();
80 | });
81 | },
82 |
83 | /**
84 | * Tests remote control functionality
85 | * @method ProbeTest-RemoteControl
86 | */
87 | RemoteControl: function(test) {
88 | var monitor = probeTests.processMonitor;
89 | monitor.control('ping', function(error, result) {
90 | test.ifError(error);
91 | test.equals(result, 'pong', 'Ping returned pong');
92 | test.done();
93 | });
94 | },
95 |
96 | /**
97 | * Test remote control failure (no control method)
98 | * @method ProbeTest-RemoteControlFail
99 | */
100 | RemoteControlFail: function(test) {
101 | var monitor = probeTests.processMonitor;
102 | monitor.control('pingPong', function(error, result) {
103 | test.ok(error != null, 'Correctly errored on un-available control method');
104 | test.done();
105 | });
106 | },
107 |
108 | /**
109 | * Test the change event
110 | * @method ProbeTest-ChangeEvent
111 | */
112 | ChangeEvent: function(test) {
113 | var monitor = probeTests.processMonitor;
114 | var onChange = function(){
115 | monitor.off('change', onChange);
116 | var changes = monitor.changedAttributes();
117 | test.ok(_.size(changes) > 0, 'Attribute changes came through');
118 | test.done();
119 | };
120 | monitor.on('change', onChange);
121 | },
122 |
123 | /**
124 | * Tests that Probe clean up works
125 | * @method ProbeTest-Cleanup
126 | */
127 | Cleanup: function(test) {
128 | var monitor = probeTests.processMonitor;
129 | monitor.disconnect(function(error) {
130 | test.ifError(error);
131 | test.done();
132 | });
133 | }
134 |
135 | };
136 |
137 | }(this));
138 |
--------------------------------------------------------------------------------
/test/RecipeProbeTest.js:
--------------------------------------------------------------------------------
1 | // RecipeProbeTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 | var testRecipe = null;
11 |
12 | /**
13 | * Unit tests for the Recipe class.
14 | * @class RecipeProbeTest
15 | */
16 |
17 | // Dependencies
18 | var Monitor = require('../lib/index'),
19 | RecipeProbe = Monitor.RecipeProbe,
20 | Backbone = Monitor.Backbone,
21 | recipeMonitor = null,
22 | dataModelMonitor = null,
23 | derivedRecipeMonitor = null,
24 | _ = Monitor._,
25 | NL = '\n';
26 |
27 | /**
28 | * Tests for verifying modules are loaded and exposed properly
29 | *
30 | * @method ModuleLoad
31 | */
32 | module.exports.ModuleLoad = {
33 |
34 | setUp: function(callback) {callback();},
35 | tearDown: function(callback) {callback();},
36 |
37 | /**
38 | * Tests that Recipe is exposed and of the correct type
39 | * @method ModuleLoad-Recipe
40 | */
41 | Recipe: function(test) {
42 | test.ok(RecipeProbe.prototype instanceof Backbone.Model, 'The data model is in place');
43 | test.ok(RecipeProbe.prototype instanceof Monitor.Probe, 'It is a probe');
44 | test.done();
45 | }
46 |
47 | };
48 |
49 | /**
50 | * Tests for a hand coded recipe stored in config/test.js
51 | * @method HandCoded
52 | */
53 | module.exports['HandCoded'] = {
54 |
55 | /**
56 | * Test Recipe initialization
57 | * @method HandCoded-Initialize
58 | */
59 | Initialize: function(test) {
60 | // Give the RecipeTest probe a chance to load
61 | setTimeout(function(){
62 | recipeMonitor = new Monitor({probeName:'RecipeTest'});
63 | recipeMonitor.connect(function(error) {
64 | test.ok(!error, 'No error on recipe connect');
65 | test.ok(recipeMonitor.get('started'), 'Recipe is started on init');
66 |
67 | // Get a monitor to the underlying data model the recipe manipulates
68 | dataModelMonitor = new Monitor({probeName:'DataModelTest'});
69 | dataModelMonitor.connect(function(error1) {
70 | test.ok(!error1, 'No error on data model connect');
71 | test.equal(dataModelMonitor.get('attr1'), 'attrValue1', 'Connected to the correct data model');
72 | test.done();
73 | });
74 | });
75 | }, 0);
76 | },
77 |
78 | /**
79 | * Test recipe stopping
80 | * @method HandCoded-Stop
81 | */
82 | Stop: function(test) {
83 | recipeMonitor.control('stop', function(error) {
84 | test.ok(!error, 'No error on stop');
85 | test.ok(!recipeMonitor.get('started'), 'Recipe is stopped on stop');
86 | test.done();
87 | });
88 | },
89 |
90 | /**
91 | * Test recipe starting
92 | * @method HandCoded-Start
93 | */
94 | Start: function(test) {
95 | recipeMonitor.control('start', function(error) {
96 | test.ok(!error, 'No error on start');
97 | test.ok(recipeMonitor.get('started'), 'Recipe is started on start');
98 | test.done();
99 | });
100 | },
101 |
102 | /**
103 | * Tests that the script is run on change
104 | * @method HandCoded-ScriptRun
105 | */
106 | ScriptRun: function(test) {
107 |
108 | // The test recipe is fired on change, and the script simply copies
109 | // the value of attr1 into attr2.
110 | var newValue = 'scriptRunTest';
111 |
112 | // Look for attr2 changing from the test
113 | var changeFn = function() {
114 |
115 | // This is triggered twice - once on attr1 change, once on attr2 change.
116 | // Only perform the test on new attr2 value
117 | var attr2 = dataModelMonitor.get('attr2');
118 | if (attr2 === newValue) {
119 | dataModelMonitor.off('change', changeFn);
120 | test.equals(attr2, newValue, 'The script was run');
121 | test.done();
122 | }
123 | };
124 |
125 | // Set attr1 to the new value. This should trigger the recipe.
126 | dataModelMonitor.on('change', changeFn);
127 | dataModelMonitor.set('attr1', newValue);
128 | }
129 |
130 | };
131 |
132 | /**
133 | * Tests that recipe autoStart works as advertised
134 | * @method Derived
135 | */
136 | module.exports['Derived'] = {
137 |
138 | /**
139 | * Construct a derived recipe
140 | * @method Derived-Construct
141 | */
142 | Construct: function(test) {
143 |
144 | // Build the class
145 | var DerivedRecipe = RecipeProbe.extend({
146 | probeClass: 'DerivedRecipe',
147 | initialize: function(){
148 | var t = this;
149 | t.set({
150 | autoStart: false,
151 | monitors: {
152 | dataModel: {probeName: 'DataModelTest'}
153 | }
154 | });
155 | RecipeProbe.prototype.initialize.apply(t, arguments);
156 | },
157 | run: function(context){
158 | var t = this;
159 | // Set derivedAttr2 to the value of derivedAttr1
160 | context.dataModel.set('derivedAttr2', context.dataModel.get('derivedAttr1'));
161 | }
162 | });
163 |
164 | // Get a monitor to the class
165 | derivedRecipeMonitor = new Monitor({probeClass:'DerivedRecipe'});
166 | derivedRecipeMonitor.connect(function(error){
167 | test.ok(!error, 'No error on derived recipe connect');
168 | test.equal(dataModelMonitor.get('derivedAttr1'), 'derivedAttrValue1', 'Data model is set for testing');
169 | test.equal(dataModelMonitor.get('derivedAttr2'), null, 'Derived test not run yet');
170 | test.done();
171 | });
172 | },
173 |
174 | /**
175 | * Tests that a non-autoStart recipe does not start automatically
176 | * @method Derived-IsNotRunning
177 | */
178 | IsNotRunning: function(test) {
179 | setTimeout(function(){
180 | test.equal(derivedRecipeMonitor.get('started'), false, 'Test has not started');
181 | test.equal(dataModelMonitor.get('derivedAttr2'), null, 'Still has not run');
182 | test.done();
183 | }, 10);
184 | },
185 |
186 | /**
187 | * Tests that the recipe can start
188 | * @method AutoRun-CanStart
189 | */
190 | CanStart: function(test) {
191 | derivedRecipeMonitor.control('start', function(error) {
192 | test.ok(!error, 'No error on start');
193 | test.ok(derivedRecipeMonitor.get('started'), 'Recipe is started on start');
194 | test.equal(dataModelMonitor.get('derivedAttr2'), null, 'First run still not performed');
195 | test.done();
196 | });
197 | }
198 |
199 |
200 | };
201 |
202 |
203 | }(this));
204 |
205 |
--------------------------------------------------------------------------------
/test/RouterTest.js:
--------------------------------------------------------------------------------
1 | // RouterTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Router = Monitor.Router, Backbone = Monitor.Backbone,
14 | server, serverPort, internal, external, gateway,
15 | probeConnection, gatewayConnection, eventConnection,
16 | _ = Monitor._;
17 |
18 | /**
19 | * Unit tests for the Router class.
20 | * @class RouterTest
21 | */
22 |
23 | /**
24 | * Tests for baseline Router functionality
25 | *
26 | * @method Router
27 | */
28 | module.exports['Router'] = {
29 |
30 | /**
31 | * Create a Server to test routing with
32 | * @method Router-SetUp
33 | */
34 | SetUp: function(test) {
35 | process.env.NODE_APP_INSTANCE = 'test';
36 | server = new Monitor.Server();
37 | server.start(test.done);
38 | },
39 |
40 | /**
41 | * Tests that Router classes are in place
42 | * @method Router-Classes
43 | */
44 | Classes: function(test) {
45 | test.ok(Router.prototype instanceof Backbone.Model, 'The Router data model is in place');
46 | test.done();
47 | },
48 |
49 | /**
50 | * Test that the router finds an internal probe
51 | * @method Router-ConnectInternal
52 | */
53 | ConnectInternal: function(test) {
54 | internal = new Monitor({probeClass:'Process', initParams:{a:'b'}});
55 | internal.connect(function(error) {
56 | test.ok(!error, 'The error was ' + JSON.stringify(error));
57 | var probeId = internal.get('probeId');
58 | test.ok(probeId, "The router found the internal probe");
59 | test.done();
60 | });
61 | },
62 |
63 | /**
64 | * Test that the same probe is connected when requested with the same initParams
65 | * @method Router-InternalSameProbe
66 | */
67 | InternalSameProbe: function(test) {
68 | var other = new Monitor({probeClass:'Process', initParams:{a:'b'}});
69 | other.connect(function(error) {
70 | test.ok(!error, 'The error was ' + JSON.stringify(error));
71 | var internalId = internal.get('probeId');
72 | var otherId = other.get('probeId');
73 | test.equal(internalId, otherId, "Two monitors created with the same initParams are connected to the same probe id: " + internalId);
74 | other.disconnect(test.done);
75 | });
76 | },
77 |
78 | /**
79 | * Test that different probes are connected when requested with the different initParams
80 | * @method Router-InternalDifferentProbe
81 | */
82 | InternalDifferentProbe: function(test) {
83 | var other = new Monitor({probeClass:'Process', initParams:{a:'c'}});
84 | other.connect(function(error) {
85 | test.ok(!error, 'The error was ' + JSON.stringify(error));
86 | var internalId = internal.get('probeId');
87 | var otherId = other.get('probeId');
88 | test.notEqual(internalId, otherId, "Two monitors created with the same initParams are connected to different probes");
89 | other.disconnect(test.done);
90 | });
91 | },
92 |
93 | /**
94 | * Test that the router finds an external probe
95 | * @method Router-ConnectExternal
96 | */
97 | ConnectExternal: function(test) {
98 | external = new Monitor({probeClass:'Process', hostName:'localhost', initParams:{a:'b'}});
99 | external.connect(function(error) {
100 | test.ok(!error, 'The error was ' + JSON.stringify(error));
101 | var probeId = external.get('probeId');
102 | test.ok(probeId, "The router found the remote probe based on class and host");
103 | test.done();
104 | });
105 | },
106 |
107 | /**
108 | * Test the getConnection method returns the connection
109 | * @method Router-GetConnection
110 | */
111 | GetConnection: function(test) {
112 | var connection = external.getConnection();
113 | test.ok(connection instanceof Monitor.Connection, "The remote connection is found");
114 | test.ok(connection.get('remoteHostName'), "The connection as a remote host name");
115 | test.done();
116 | },
117 |
118 | /**
119 | * Test that the same external probe is connected when requested with the same initParams
120 | * @method Router-ExternalSameProbe
121 | */
122 | ExternalSameProbe: function(test) {
123 | var other = new Monitor({probeClass:'Process', hostName:'localhost', initParams:{a:'b'}});
124 | other.connect(function(error) {
125 | test.ok(!error, 'The error was ' + JSON.stringify(error));
126 | var externalId = external.get('probeId');
127 | var otherId = other.get('probeId');
128 | test.equal(externalId, otherId, "Two monitors created with the same initParams are connected to the same probe id: " + externalId);
129 | other.disconnect(test.done);
130 | });
131 | },
132 |
133 | /**
134 | * Test that different external probes are connected when requested with the different initParams
135 | * @method Router-ExternalDifferentProbe
136 | */
137 | ExternalDifferentProbe: function(test) {
138 | var other = new Monitor({probeClass:'Process', hostName:'localhost', initParams:{a:'c'}});
139 | other.connect(function(error) {
140 | test.ok(!error, 'The error was ' + JSON.stringify(error));
141 | var externalId = external.get('probeId');
142 | var otherId = other.get('probeId');
143 | test.notEqual(externalId, otherId, "Two monitors created with the same initParams are connected to different probes");
144 | other.disconnect(test.done);
145 | });
146 | },
147 |
148 | /**
149 | * Test that the router can connect with a probe in an app by instance ID
150 | * @method Router-ByAppInstance
151 | */
152 | ByAppInstance: function(test) {
153 | // Don't connect locally
154 | delete process.env.NODE_APP_INSTANCE;
155 |
156 | external = new Monitor({probeClass:'Process', appInstance: 'test', initParams:{a:'b'}});
157 | external.connect(function(error) {
158 | test.ok(!error, 'The error was ' + JSON.stringify(error));
159 | test.equal('test', external.probe.connection.get('remoteAppInstance'), 'Verified the probe connected to the right instance');
160 | var probeId = external.get('probeId');
161 | test.ok(probeId, "The router found the remote probe based on instance id");
162 | test.done();
163 | });
164 | },
165 |
166 | /**
167 | * Test that the local probe forwards change events
168 | * @method Router-InternalChanges
169 | */
170 | InternalChanges: function(test) {
171 | var onChange = function() {
172 | test.ok(true, 'The change event was fired');
173 | var changes = internal.changedAttributes();
174 | test.ok(changes, 'Discovered changed attributes: ', _.keys(changes));
175 | internal.off('change', onChange);
176 | test.done();
177 | };
178 | internal.on('change', onChange);
179 | },
180 |
181 | /**
182 | * Test that the remote probe forwards change events
183 | * @method Router-ExternalChanges
184 | */
185 | ExternalChanges: function(test) {
186 | var onChange = function() {
187 | test.ok(true, 'The change event was fired');
188 | var changes = external.changedAttributes();
189 | test.ok(changes, 'Discovered changed attributes: ', _.keys(changes));
190 | external.off('change', onChange);
191 | test.done();
192 | };
193 | external.on('change', onChange);
194 | },
195 |
196 | /**
197 | * Test that the router can route control an external probe
198 | * @method Router-ControlExternal
199 | */
200 | ControlExternal: function(test) {
201 | external.control('ping', function(error, response) {
202 | test.ok(!error, 'The error was ' + JSON.stringify(error));
203 | test.ok(response, 'The response was ' + JSON.stringify(response));
204 | test.equal(response, 'pong', "Sent a message to, and received the expected response.");
205 | test.done();
206 | });
207 | },
208 |
209 | /**
210 | * Test that a disconnect to an internal probe works
211 | * @method Router-DisconnectInternal
212 | */
213 | DisconnectInternal: function(test) {
214 | internal.disconnect(function(error) {
215 | test.ok(!error, 'The error was ' + JSON.stringify(error));
216 | internal = null;
217 | test.done();
218 | });
219 | },
220 |
221 | /**
222 | * Test that a disconnect to an external probe works
223 | * @method Router-DisconnectExternal
224 | */
225 | DisconnectExternal: function(test) {
226 | external.disconnect(function(error) {
227 | test.ok(!error, 'The error was ' + JSON.stringify(error));
228 | test.done();
229 | });
230 | },
231 |
232 | /**
233 | * Test that the external connection:add event is fired on add.
234 | * @method Router-ConnectionAddEvent
235 | */
236 | ConnectionAddEvent: function(test) {
237 | var router = Monitor.getRouter();
238 | var onConnect = function(connection) {
239 | eventConnection = connection;
240 | test.ok(connection !== null, 'The connection was established');
241 | var hostName = connection.get('hostName');
242 | test.ok(hostName === '127.0.0.1', 'The connection was with the expected host');
243 | router.off('connection:add', onConnect);
244 | test.done();
245 | };
246 | router.on('connection:add', onConnect);
247 | external = new Monitor({probeClass:'Process', hostName:'127.0.0.1', initParams:{a:'b'}});
248 | external.connect();
249 | },
250 |
251 | /**
252 | * Test that the external connection:remove event is fired on connection remove.
253 | * @method Router-ConnectionRemoveEvent
254 | */
255 | ConnectionRemoveEvent: function(test) {
256 | var router = Monitor.getRouter();
257 | var onDisconnect = function(connection) {
258 | test.ok(connection !== null, 'The connection was removed');
259 | var hostName = connection.get('hostName');
260 | test.ok(hostName === '127.0.0.1', 'The disconnect was with the expected host');
261 | router.off('connection:remove', onDisconnect);
262 | test.done();
263 | };
264 | router.on('connection:remove', onDisconnect);
265 | router.removeConnection(eventConnection);
266 | },
267 |
268 | /**
269 | * Tear down the test Server and disconnect the test probes
270 | * @method Router-TearDown
271 | */
272 | TearDown: function(test) {
273 | server.stop(test.done);
274 | }
275 |
276 | };
277 |
278 | }(this));
279 |
--------------------------------------------------------------------------------
/test/ServerTest.js:
--------------------------------------------------------------------------------
1 | // ServerTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Server = Monitor.Server, Backbone = Monitor.Backbone;
14 |
15 | /**
16 | * Unit tests for the Server class.
17 | * @class ServerTest
18 | */
19 |
20 | /**
21 | * Test group for baseline Server functionality
22 | *
23 | * @method Server
24 | */
25 | module.exports['Server'] = {
26 |
27 | setUp: function(callback) {callback();},
28 | tearDown: function(callback) {callback();},
29 |
30 | /**
31 | * Tests that Server classes are in place
32 | * @method Server-Classes
33 | */
34 | Classes: function(test) {
35 | test.ok(Server.prototype instanceof Backbone.Model, 'The Server data model is in place');
36 | test.ok(Server.List.prototype instanceof Backbone.Collection, 'The Server.List collection is in place');
37 | test.done();
38 | },
39 |
40 | /**
41 | * Start and Stop a server
42 | * @method Server-StartStop
43 | */
44 | StartStop: function(test) {
45 | var server = new Monitor.Server();
46 | server.on('start', function() {
47 | test.ok(server.get('port') > 0, 'The server started accepting connections on a port');
48 | server.stop(function(){
49 | test.ok(true, 'The server has stopped');
50 | test.done();
51 | });
52 | });
53 | server.start();
54 | },
55 |
56 | /**
57 | * Verify multiple servers start on different ports
58 | * @method Server-MultipleStartStop
59 | */
60 | MultipleStartStop: function(test) {
61 | var server1 = new Monitor.Server(), port1, port2;
62 | server1.on('start', function() {
63 | port1 = server1.get('port');
64 | test.ok(port1 >= Monitor.Config.Monitor.serviceBasePort, 'The server started in the correct port range');
65 | var server2 = new Monitor.Server();
66 | server2.on('start', function() {
67 | port2 = server2.get('port');
68 | test.notEqual(port1, port2, 'Two servers started on two different ports');
69 | server1.stop(function(){
70 | server2.stop(function(){
71 | test.ok(true, 'Both servers have stopped');
72 | test.done();
73 | });
74 | });
75 | });
76 | server2.start();
77 | });
78 | server1.start();
79 | }
80 |
81 | };
82 |
83 | }(this));
84 |
--------------------------------------------------------------------------------
/test/StatTest.js:
--------------------------------------------------------------------------------
1 | // StatTest.js (c) 2010-2014 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Stat = Monitor.Stat, Backbone = Monitor.Backbone,
14 | stat = Monitor.getStatLogger('stat-test');
15 |
16 | /**
17 | * Unit tests for the Stat class.
18 | * @class StatTest
19 | */
20 |
21 | /**
22 | * Test group for baseline Stat functionality
23 | *
24 | * @method Stat
25 | */
26 | module.exports['Stat'] = {
27 |
28 | setUp: function(callback) {callback();},
29 | tearDown: function(callback) {callback();},
30 |
31 | /**
32 | * Tests that Stat class is in place
33 | * @method Stat-Classes
34 | */
35 | Classes: function(test) {
36 | test.ok(Stat.prototype, 'The Stat model is in place');
37 | test.done();
38 | },
39 |
40 | /**
41 | * Internal tests for Graphite type regular expressions
42 | * @method Stat-GraphiteRegExp
43 | */
44 | GraphiteRegExp: function(test) {
45 | var builder = Stat._buildRegex;
46 | var re;
47 |
48 | // Test the normal cases
49 | re = builder('hello.world');
50 | test.ok(re.test('hello.world'), 'normal case works');
51 | test.ok(!re.test('hello.wurld'), 'must be exact');
52 | test.ok(!re.test('Hello.wurld'), 'case sensitive');
53 | test.ok(!re.test('hello'), 'must have all words');
54 | test.ok(!re.test('Hello.world.now'), 'limited to just the specified fields');
55 |
56 | // Test the * wildcard
57 | re = builder('hello.*.world');
58 | test.ok(re.test('hello.nice.world'), 'normal case works');
59 | test.ok(re.test('hello.cruel.world'), 'normal case works');
60 | test.ok(!re.test('hello.cruel'), 'must have all segments');
61 | test.ok(!re.test('hello'), 'must have all segments');
62 | test.ok(!re.test('hello.nice.world.now'), 'too many segments');
63 | re = builder('*.*.world');
64 | test.ok(re.test('hello.nice.world'), 'normal case works');
65 | test.ok(re.test('Gello.nice.world'), 'this works too');
66 | test.ok(!re.test('Gello.nice'), 'but not this');
67 | re = builder('*.*.*');
68 | test.ok(re.test('a.b.c'), 'normal case works');
69 | test.ok(re.test('1.2.3'), '123 works');
70 | test.ok(!re.test('nice.world'), 'must have all fields');
71 |
72 | // Test the [] character list or range
73 | re = builder('hello.[123].world');
74 | test.ok(re.test('hello.1.world'), '1 case works');
75 | test.ok(re.test('hello.3.world'), '3 case works');
76 | test.ok(!re.test('hello.4.world'), '4 doesnt work');
77 | re = builder('hello.[1-3].world');
78 | test.ok(re.test('hello.1.world'), '1- case works');
79 | test.ok(re.test('hello.3.world'), '3- case works');
80 | test.ok(!re.test('hello.4.world'), '4- doesnt work');
81 |
82 | // Test the {list,in,str} list functionality
83 | re = builder('hello.{world,dolly,kitty}');
84 | test.ok(re.test('hello.world'), 'world works');
85 | test.ok(re.test('hello.kitty'), 'kitty works');
86 | test.ok(re.test('hello.dolly'), 'dolly works');
87 | test.ok(!re.test('hello.there'), 'there doesnt work');
88 |
89 | // Test the /regex/ functionality
90 | re = builder('/hello.world/');
91 | test.ok(re.test('hello.world'), 'hello world works');
92 | test.ok(!re.test('hello.kitty'), 'hello kitty fails');
93 | re = builder('/hello.world/i');
94 | test.ok(re.test('Hello World'), 'case insensitivity training');
95 |
96 | test.done();
97 |
98 | },
99 |
100 | /**
101 | * Tests that registering for '*' emits all stats
102 | * @method Stat-AllStats
103 | */
104 | AllStats: function(test) {
105 |
106 | // Listen for all stats
107 | Stat.once('*', function(module, name, value, type) {
108 | test.equals(module, 'stat-test', 'found the stat-test module when listening to *');
109 | test.equals(name, 'Some.Stat', 'found Some.Stat when listening to *');
110 | test.equals(value, 34, 'some.stat value is correct');
111 | test.equals(type, 'c', 'some.stat type is correct');
112 | test.done();
113 | });
114 | stat.increment('Some.Stat', 34);
115 | },
116 |
117 | /**
118 | * Tests the gauge functionality
119 | * @method Stat-Gauge
120 | */
121 | Gauge: function(test) {
122 | Stat.once('stat-test.gauge.stat', function(module, name, value, type) {
123 | test.equals(name, 'gauge.stat', 'gauge.stat emitted correctly');
124 | test.equals(value, -6273993, 'gauge.stat value is correct');
125 | test.equals(type, 'g', 'gauge.stat type is correct');
126 | test.done();
127 | });
128 | stat.gauge('gauge.stat', -6273993);
129 | },
130 |
131 | /**
132 | * Tests the time stat
133 | * @method Stat-Time
134 | */
135 | Time: function(test) {
136 | Stat.once('stat-test.time.stat', function(module, name, value, type) {
137 | test.equals(name, 'time.stat', 'time.stat emitted correctly');
138 | test.equals(value, 193, 'time.stat value is correct');
139 | test.equals(type, 'ms', 'time.stat type is correct');
140 | test.done();
141 | });
142 | stat.time('time.stat', 193);
143 | },
144 |
145 | /**
146 | * Tests the increment stat
147 | * @method Stat-Increment
148 | */
149 | Increment: function(test) {
150 | Stat.once('stat-test.incr1', function(module, name, value, type) {
151 | test.equals(name, 'incr1', 'incr1 stat emitted');
152 | test.equals(value, 1, 'default increment of 1 found');
153 | test.equals(type, 'c', 'incr1 type is correct');
154 | });
155 | Stat.once('stat-test.incr2', function(module, name, value, type) {
156 | test.equals(name, 'incr2', 'incr2 stat emitted');
157 | test.equals(value, 44, 'incr2 value is correct');
158 | test.equals(type, 'c', 'incr2 type is correct');
159 | test.done();
160 | });
161 | stat.increment('incr1');
162 | stat.increment('incr2', 44);
163 | },
164 |
165 | /**
166 | * Tests the decrement stat
167 | * @method Stat-Decrement
168 | */
169 | Decrement: function(test) {
170 | Stat.once('stat-test.decr1', function(module, name, value, type) {
171 | test.equals(name, 'decr1', 'decr1 stat emitted');
172 | test.equals(value, -1, 'default decrement of 1 found');
173 | test.equals(type, 'c', 'decr1 type is correct');
174 | });
175 | Stat.once('stat-test.decr2', function(module, name, value, type) {
176 | test.equals(name, 'decr2', 'decr2 stat emitted');
177 | test.equals(value, -29344, 'decr2 value is correct');
178 | test.equals(type, 'c', 'decr2 type is correct');
179 | test.done();
180 | });
181 | stat.decrement('decr1');
182 | stat.decrement('decr2', 29344);
183 | },
184 |
185 | /**
186 | * Tests the stat probe
187 | * @method Stat-ProbeTest
188 | */
189 | ProbeTest: function(test) {
190 | var statMonitor = new Monitor({probeClass:'Stat', initParams:{interval:10, pattern:'stat-test.*'}});
191 | statMonitor.connect(function(error) {
192 | test.ok(!error, 'Stat monitor error: ' + JSON.stringify(error));
193 | statMonitor.on('change', function() {
194 | var bundle = statMonitor.get('bundle');
195 |
196 | // Omit the initial state of the monitor
197 | if (bundle.length === 0) {
198 | return;
199 | }
200 |
201 | test.ok(bundle, 'The stat bundle is available');
202 | test.equals(bundle.length, 1, 'There is a single stat in the bundle');
203 | var statArgs = bundle[0];
204 | test.equals(statArgs.length, 5, 'There are 5 stat arguments');
205 | test.ok(Date.now() - (new Date(statArgs[0]).getTime()) < 1000, 'The first arg is a timestamp');
206 | test.equals(statArgs[1], 'stat-test', 'The module is correct');
207 | test.equals(statArgs[2], 'probeTest', 'The stat name is correct');
208 | test.equals(statArgs[3], 1, 'The value is correct');
209 | test.equals(statArgs[4], 'c', 'There stat type is correct');
210 | statMonitor.off('change');
211 | test.done();
212 | });
213 | stat.increment('probeTest');
214 | });
215 | },
216 |
217 | /**
218 | * Tests the probe streams multiple items at once
219 | * @method Stat-ProbeStream
220 | */
221 | ProbeStream: function(test) {
222 |
223 | // This relies on the fact that the monitor was created in the prior
224 | // function, and it just emitted a single item.
225 | var statMonitor = new Monitor({probeClass:'Stat', initParams:{interval:10, pattern:'stat-test.*'}});
226 | statMonitor.connect(function(error) {
227 | test.ok(!error, 'Stat monitor error: ' + JSON.stringify(error));
228 | statMonitor.on('change', function() {
229 | var bundle = statMonitor.get('bundle');
230 |
231 | // Omit the current state of the probe (first change event)
232 | if (bundle.length < 4) {
233 | return;
234 | }
235 |
236 | test.ok(bundle, 'The stat bundle is available');
237 | test.equals(bundle.length, 4, 'There were the correct number of items in the stream');
238 | var statArgs = bundle[2];
239 | test.equals(statArgs.length, 5, 'There are 5 stat arguments');
240 | test.ok(Date.now() - (new Date(statArgs[0]).getTime()) < 1000, 'The first arg is a timestamp');
241 | test.equals(statArgs[1], 'stat-test', 'The module is correct');
242 | test.equals(statArgs[2], 'statGauge', 'The stat name is correct');
243 | test.equals(statArgs[3], 420, 'The value is correct');
244 | test.equals(statArgs[4], 'g', 'There stat type is correct');
245 | statMonitor.off('change');
246 | test.done();
247 | });
248 | stat.increment('probeTestr');
249 | stat.decrement('probeTest');
250 | stat.gauge('statGauge', 420);
251 | stat.time('statTimer', 2840);
252 | });
253 | }
254 |
255 | };
256 |
257 | }(this));
258 |
--------------------------------------------------------------------------------
/test/SyncProbeTest.js:
--------------------------------------------------------------------------------
1 | // SyncProbeTest.js (c) 2012 Loren West and other contributors
2 | // May be freely distributed under the MIT license.
3 | // For further details and documentation:
4 | // http://lorenwest.github.com/node-monitor
5 | (function(root){
6 |
7 | // This should be run before other tests to set up configurations
8 | process.env.NODE_ENV='test';
9 | var config = require('config');
10 |
11 | // Dependencies
12 | var Monitor = require('../lib/index'),
13 | Backbone = Monitor.Backbone,
14 | _ = Monitor._,
15 | Path = require('path'),
16 | FS = require('fs'),
17 | Sync = Monitor.Sync,
18 | FileProbe = Monitor.FileProbe,
19 | SyncProbe = Monitor.SyncProbe,
20 | FileSyncProbe = SyncProbe.FileSyncProbe;
21 |
22 | // Constants
23 | var TEST_FILE_DIR = FileSyncProbe.getRootPath() || __dirname + '/syncProbeTest',
24 | TEST_OBJECT = {
25 | testNumber:1,
26 | testString:"two",
27 | testObject:{some:"sub_object"},
28 | testArray:[1, "two", 3]
29 | },
30 | TEST_ID = '228237-a',
31 | JSON_CONTENT = JSON.stringify(TEST_OBJECT, null, 2),
32 | TEST_MODEL = new Backbone.Model(TEST_OBJECT),
33 | LIVE_SYNC_CLASS = Backbone.Model.extend({
34 | sync: new Sync('LiveSyncTest')
35 | });
36 |
37 | // Old style watch takes *forever* to connect
38 | var WATCH_CONNECT_TIME = 1200;
39 |
40 | // Initialize the server-side sync probes
41 | var DEFAULT_PROBE_NAME = 'FileSyncProbe';
42 | SyncProbe.Config.defaultProbe = DEFAULT_PROBE_NAME;
43 | FileSyncProbe.setRootPath(TEST_FILE_DIR);
44 |
45 | /**
46 | * Unit tests for the SyncProbe probe.
47 | * @class SyncProbeTest
48 | */
49 |
50 | /**
51 | * Test group for baseline SyncProbe functionality
52 | *
53 | * @method SyncProbe
54 | */
55 | module.exports['SyncProbe'] = {
56 |
57 | /**
58 | * Tests that classes are in correct
59 | * @method SyncProbe-Classes
60 | */
61 | Classes: function(test) {
62 | test.ok(SyncProbe.prototype instanceof Backbone.Model, 'The data model is in place');
63 | test.ok(SyncProbe.prototype instanceof Monitor.Probe, 'It is a probe');
64 | test.done();
65 | },
66 |
67 | /**
68 | * The server decides what specific type of SyncProbe to instantiate
69 | * for classes of data models. When a SyncProbe is requested, the
70 | * SyncProbe instance becomes an instance of a specific type. This
71 | * tests that this coersion is made.
72 | *
73 | * @method SyncProbe-Coerce
74 | */
75 | Coerce: function(test) {
76 | var probe = new SyncProbe({className:'Book'},{});
77 | test.equal(probe.probeClass, DEFAULT_PROBE_NAME, 'The SyncProbe was successfully coerced into a ' + DEFAULT_PROBE_NAME + ' probe.');
78 | test.equal(probe.get('className'), 'Book', 'The probe instance is correctly set.');
79 | test.done();
80 | }
81 |
82 | };
83 |
84 | /**
85 | * Test group for base Sync (non LiveSync) probe usage
86 | *
87 | * @method BaseSync
88 | */
89 | module.exports['BaseSync'] = {
90 |
91 | /**
92 | * One time setup for BaseSync tests
93 | * @method BaseSync-SetUp
94 | */
95 | SetUp: function(test) {
96 | // Clear the directory
97 | FileProbe.rm_rf(TEST_FILE_DIR, function(error) {
98 | FileProbe.mkdir_r(TEST_FILE_DIR, function(error) {
99 | test.ok(!error, "Able to create the test directory");
100 | test.done();
101 | });
102 | });
103 | },
104 |
105 | /**
106 | * Tests that a new object without an ID gets an ID assigned
107 | * @method BaseSync-CreateWitoutId
108 | */
109 | CreateWithoutId: function(test) {
110 | test.ok(TEST_MODEL.isNew(), "The test model is a new object");
111 | TEST_MODEL.sync = new Sync('Test');
112 | TEST_MODEL.save({}, function(error, result){
113 | test.ok(!error, 'CreateId save error: ' + JSON.stringify(error));
114 | test.ok(TEST_MODEL.has('id'), "An ID was generated for the new object");
115 | test.done();
116 | });
117 | },
118 |
119 | /**
120 | * Tests that a new object with an ID gets saved
121 | * @method BaseSync-CreateWithId
122 | */
123 | CreateWithId: function(test) {
124 | TEST_MODEL = new Backbone.Model(TEST_OBJECT);
125 | test.ok(TEST_MODEL.isNew(), "The test model is a new object");
126 | TEST_MODEL.sync = new Sync('Test');
127 | TEST_MODEL.save({id: 'some/path'}, function(error, result){
128 | test.ok(!error, 'CreateWithId save error: ' + JSON.stringify(error));
129 | test.done();
130 | });
131 | },
132 |
133 | /**
134 | * Tests that a fetch by ID works
135 | * @method BaseSync-FetchById
136 | */
137 | FetchById: function(test) {
138 | var testId = "339284";
139 | TEST_MODEL = new Backbone.Model(TEST_OBJECT);
140 | test.ok(TEST_MODEL.isNew(), "The test model is a new object");
141 | TEST_MODEL.sync = new Sync('Test');
142 | TEST_MODEL.save({id: testId}, function(error, result){
143 | test.ok(!error, 'FetchById save error: ' + JSON.stringify(error));
144 |
145 | // Now fetch it
146 | var newModel = new Backbone.Model({id:testId});
147 | newModel.sync = new Sync('Test');
148 | newModel.fetch(function(error) {
149 | test.ok(!error, 'FetchById fetch error: ' + JSON.stringify(error));
150 | test.deepEqual(newModel.toJSON(), TEST_MODEL.toJSON(), 'Successful fetch');
151 | test.done();
152 | });
153 | });
154 | },
155 |
156 | /**
157 | * Tests the model.destroy functionality
158 | * @method BaseSync-DestroyById
159 | */
160 | DestroyById: function(test) {
161 | var testId = "339284";
162 | TEST_MODEL = new Backbone.Model(TEST_OBJECT);
163 | test.ok(TEST_MODEL.isNew(), "The test model is a new object");
164 | TEST_MODEL.sync = new Sync('Test');
165 | TEST_MODEL.save({id: testId}, function(error, result){
166 | test.ok(!error, 'DestroyById save error: ' + JSON.stringify(error));
167 |
168 | // Now fetch it
169 | var newModel = new Backbone.Model({id:testId});
170 | newModel.sync = new Sync('Test');
171 | newModel.fetch(function(error) {
172 | test.ok(!error, 'DestroyById fetch error: ' + JSON.stringify(error));
173 | test.deepEqual(newModel.toJSON(), TEST_MODEL.toJSON(), 'Successful fetch');
174 |
175 | // Now destroy it
176 | var anotherNewModel = new Backbone.Model({id:testId});
177 | anotherNewModel.sync = new Sync('Test');
178 | anotherNewModel.destroy(function(error) {
179 | test.ok(!error, 'DestroyById destroy error: ' + JSON.stringify(error));
180 |
181 | // Make sure it's gone
182 | var fullPath = Path.join(TEST_FILE_DIR, 'Test', testId + '.json');
183 | FS.stat(fullPath, function(error, stats) {
184 | test.ok(error && error.code === 'ENOENT', 'File is removed.');
185 | test.done();
186 | });
187 | });
188 | });
189 | });
190 | },
191 |
192 | /**
193 | * One time teardown up for BaseSync tests
194 | * @method BaseSync-TearDown
195 | */
196 | TearDown: function(test) {
197 | FileProbe.rm_rf(TEST_FILE_DIR, function(error) {
198 | test.done();
199 | });
200 | }
201 |
202 | };
203 |
204 | /**
205 | * Test group for Live model synchronization
206 | *
207 | * @method LiveSync
208 | */
209 | module.exports['LiveSync'] = {
210 |
211 | /**
212 | * One time setup for LiveSync tests
213 | * @method LiveSync-SetUp
214 | */
215 | SetUp: function(test) {
216 | // Clear the directory
217 | FileProbe.rm_rf(TEST_FILE_DIR, function(error) {
218 | FileProbe.mkdir_r(TEST_FILE_DIR, function(error) {
219 | test.ok(!error, "Able to create the test directory");
220 | TEST_MODEL = new LIVE_SYNC_CLASS(TEST_OBJECT);
221 | test.ok(TEST_MODEL.isNew(), "The test model is a new object");
222 | TEST_MODEL.save({id: TEST_ID}, function(error, result){
223 | test.ok(!error, 'LiveSync-Setup save error: ' + JSON.stringify(error));
224 | test.done();
225 | });
226 | });
227 | });
228 | },
229 |
230 | /**
231 | * Tests LiveSync connection
232 | * @method LiveSync-Connect
233 | */
234 | Connect: function(test) {
235 | var model = new LIVE_SYNC_CLASS({id: TEST_ID});
236 | model.fetch({liveSync:true}, function(error) {
237 | test.ok(!error, "No error on liveSync fetch");
238 | test.deepEqual(model.toJSON(), TEST_MODEL.toJSON(), 'Successful fetch');
239 | model.clear();
240 | test.done();
241 | });
242 | },
243 |
244 | /**
245 | * Tests LiveSync disconnect
246 | * @method LiveSync-Disconnect
247 | */
248 | Disconnect: function(test) {
249 | var model = new LIVE_SYNC_CLASS({id: TEST_ID});
250 | model.fetch({liveSync:true}, function(error) {
251 | test.ok(!error, "No error on liveSync fetch");
252 | test.ok(model.syncMonitor, "Live sync monitor is attached");
253 | model.clear();
254 | test.ok(!model.syncMonitor, "Live sync monitor is detached");
255 | test.done();
256 | });
257 | },
258 |
259 | /**
260 | * Tests that a client change in the data model persists to the server
261 | * @method LiveSync-ClientChange
262 | */
263 | ClientChange: function(test) {
264 | var model = new LIVE_SYNC_CLASS({id: TEST_ID});
265 | model.fetch({liveSync:true}, function(error) {
266 | test.ok(!error, "No error on liveSync fetch");
267 | test.deepEqual(model.toJSON(), TEST_MODEL.toJSON(), 'Successful fetch');
268 | model.set('testNumber', 2);
269 |
270 | // Wait a bit, then see if the file was updated onto the filesystem
271 | setTimeout(function(){
272 | var path = Path.join(TEST_FILE_DIR, 'LiveSyncTest', TEST_ID + '.json');
273 | var obj = JSON.parse(FS.readFileSync(path));
274 | test.ok(_.isEqual(obj, model.toJSON()), 'Live sync did auto-save');
275 | model.clear();
276 | test.done();
277 | }, WATCH_CONNECT_TIME);
278 | });
279 | },
280 |
281 | /**
282 | * Tests that a server change propagates the data to the client with a change event
283 | * @method LiveSync-ServerChange
284 | */
285 | ServerChange: function(test) {
286 | var model = new LIVE_SYNC_CLASS({id: TEST_ID});
287 | model.fetch({liveSync:true}, function(error) {
288 | test.ok(!error, "No error on liveSync fetch");
289 | test.ok(model.get('testNumber') === 2, 'File still in the state last left');
290 |
291 | // Watch for changes to the model
292 | var testStr = 'Hey diddle diddle';
293 | var onChange = function() {
294 | test.equal(model.get('testString'), testStr, 'Server change was sent to the client');
295 | model.off('change', onChange);
296 | model.clear();
297 | test.done();
298 | };
299 | model.on('change', onChange);
300 |
301 | // Manually alter the file
302 | var path = Path.join(TEST_FILE_DIR, 'LiveSyncTest', TEST_ID + '.json');
303 | var obj = model.toJSON();
304 | obj.testString = testStr;
305 | FS.writeFileSync(path, JSON.stringify(obj, null, 2));
306 | });
307 | },
308 |
309 | /**
310 | * Tests that a backend deletion is detected
311 | * @method LiveSync-ServerDelete
312 | */
313 | ServerDelete: function(test) {
314 | var model = new LIVE_SYNC_CLASS({id: TEST_ID});
315 | model.fetch({liveSync:true}, function(error) {
316 | test.ok(!error, "No error on liveSync fetch");
317 | test.ok(model.get('testNumber') === 2, 'File still in the state last left');
318 |
319 | // Watch for changes to the model
320 | var onChange = function() {
321 | test.ok(!model.get('id'), 'Model has been deleted');
322 | model.off('change', onChange);
323 | model.clear();
324 | test.done();
325 | };
326 | model.on('change', onChange);
327 |
328 | // Manually delete the file
329 | var path = Path.join(TEST_FILE_DIR, 'LiveSyncTest', TEST_ID + '.json');
330 | FS.unlinkSync(path);
331 | });
332 | },
333 |
334 | /**
335 | * One time teardown up for BaseSync tests
336 | * @method LiveSync-TearDown
337 | */
338 | TearDown: function(test) {
339 | FileProbe.rm_rf(TEST_FILE_DIR, function(error) {
340 | test.done();
341 | });
342 | }
343 |
344 | };
345 |
346 | }(this));
347 |
--------------------------------------------------------------------------------
/yuidoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "monitor",
3 | "description": "Node Monitor",
4 | "version": "0.0.0",
5 | "year": "2014",
6 | "url": "http://lorenwest.github.com/node-monitor",
7 | "logo": "",
8 | "themedir": "./config/doc",
9 | "options": {
10 | "external": {
11 | },
12 | "linkNatives": "true",
13 | "attributesEmit": "true",
14 | "paths": [
15 | "./lib"
16 | ],
17 | "outdir": "./doc"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------