Skip to content

Commit 6692786

Browse files
committed
Merge pull request #1 from inexplicable/master
cluster monitor ready
2 parents 71fde23 + 52e0fe6 commit 6692786

File tree

7 files changed

+678
-0
lines changed

7 files changed

+678
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.idea
2+
temp
3+
node_modules
4+
reports
5+
target
6+
docs
7+
logs
8+
log
9+
coverage
10+
lib-cov

index.js

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
'use strict';
2+
3+
var express = require('express'),
4+
cluster = require('cluster'),
5+
when = require('when'),
6+
path = require('path'),
7+
ejs = require('ejs'),
8+
fs = require('graceful-fs'),
9+
_ = require('underscore');
10+
11+
var logger = function logger(){
12+
13+
if(!_.isFunction(process.getLogger)){
14+
return {
15+
'info': _.bind(console.log, console),
16+
'debug': _.bind(console.log, console)
17+
};
18+
}
19+
20+
return process.getLogger(__filename);
21+
},
22+
monApp = express(),
23+
emitter = require('cluster-emitter'),
24+
status = require('cluster-status');
25+
26+
//the monitor app should support the following:
27+
28+
//debug
29+
var debugMiddleware = exports.debugMiddleware = function(req, res, next){
30+
31+
if(req.url === '/help'){
32+
33+
return res.sendfile(path.join(__dirname, './public/images/live-debugging.png'));
34+
}
35+
36+
if(req.url === '/deps'){
37+
38+
require('./utils').npmls
39+
.then(function(deps){
40+
res.type('json').send(deps);
41+
})
42+
.otherwise(function(error){
43+
res.send(500);
44+
});
45+
46+
return;
47+
}
48+
49+
if(req.url !== '/debug'){
50+
51+
return next();
52+
}
53+
54+
logger().debug('[monitor][index] renders');
55+
56+
res.render('index', {
57+
58+
});
59+
};
60+
61+
monApp.use('/scripts', express.static(path.join(__dirname, './public/scripts')));
62+
monApp.use('/stylesheets', express.static(path.join(__dirname, './public/stylesheets')));
63+
monApp.engine('.ejs', require('ejs').__express);
64+
monApp.set('views', path.join(__dirname, './views'));
65+
monApp.set('view engine', 'ejs');
66+
monApp.use(debugMiddleware);
67+
68+
exports.monApp = monApp;
69+
70+
exports.monCreateServer = function createServer(app){
71+
72+
var server = require('http').createServer(app),
73+
io = require('socket.io').listen(server, {'log': false}),
74+
debugging = undefined,
75+
inspector = null,
76+
state = null,
77+
statuses = {},
78+
sockets = [],
79+
pauses = {};//all of these vars are now scoped in monCreateServer to make unique
80+
81+
logger().info('[monitor] server started');
82+
83+
var workers = _.once(function tick(){
84+
85+
var pids = _.map(cluster.workers, function(w){
86+
return w.process.pid;
87+
});
88+
89+
logger().debug('[monitor][workers] pids:%j', pids);
90+
91+
when.map(status.statuses(), function(s){
92+
93+
return status.getStatus(s);
94+
})
95+
.then(function(statusesOfWorkers){
96+
97+
logger().debug('[monitor][workers] statusesOfWorkers:%j', statusesOfWorkers);
98+
99+
_.each(statusesOfWorkers, function(statusOfWorkers){
100+
101+
_.each(statusOfWorkers, function(statusOfWorker){
102+
103+
statuses[statusOfWorker.name] = statuses[statusOfWorker.name] || {};
104+
statuses[statusOfWorker.name][statusOfWorker.pid] = statusOfWorker.status;
105+
});
106+
});
107+
108+
var arrOfStatues = _.map(statuses, function(v, k){
109+
v.name = k;
110+
return v;
111+
});
112+
113+
_.each(sockets, function(socket){
114+
115+
if(_.isEmpty(_.difference(socket.knownPids, pids)) && _.isEmpty(_.difference(pids, socket.knownPids))){
116+
117+
logger().debug('[monitor][workers] emits status changes:%j', arrOfStatues);
118+
socket.emit('status-change', pids, arrOfStatues);
119+
}
120+
else{
121+
122+
logger().debug('[monitor][workers] detects workers changed:%j from:%j', pids, socket.knownPids);
123+
socket.knownPids = pids;//update pids
124+
125+
fs.readFile(path.join(__dirname, '/views/workers.ejs'), {
126+
'encoding': 'utf-8'
127+
},
128+
function(err, read){
129+
130+
var html = ejs.render(read, {
131+
'pids': pids,
132+
'debugging': debugging,
133+
'inspector': inspector,
134+
'state': state,
135+
'statuses': arrOfStatues,
136+
'pauses': pauses
137+
});
138+
139+
logger().debug('[monitor][workers] renders workers view:%s', html);
140+
socket.emit('workers', {
141+
'view': 'html',
142+
'html': html
143+
});
144+
});
145+
}
146+
});
147+
});
148+
149+
setTimeout(tick, 1000);//everyone 1s
150+
});
151+
152+
var watchings = [],
153+
watchExists = function watchExists(usr, namespace){
154+
155+
if(_.contains(watchings, namespace)){
156+
return;
157+
}
158+
159+
watchings.push(namespace);
160+
usr.watch(namespace, null, function(value, key){
161+
162+
logger().debug('[monitor][cache][%s] detects change of:%s', namespace, key);
163+
usr.inspect(namespace, key).then(function(status){
164+
165+
logger().debug('[monitor][cache][%s] emits cache-changed event over websocket', namespace);
166+
_.invoke(sockets, 'emit', 'cache-changed', namespace, key, status[0], status[1], status[2]);
167+
});
168+
});
169+
},
170+
watchFresh = _.once(function(usr){
171+
172+
usr.watch('', null, function(value, namespace){
173+
174+
logger().info('[monitor][cache] detects new namespace:%s', namespace);
175+
fs.readFile(path.join(__dirname, '/views/caches.ejs'), {
176+
'encoding': 'utf-8'
177+
},
178+
function(err, read){
179+
180+
var html = ejs.render(read, {
181+
'namespace': namespace,
182+
'caches': []
183+
});
184+
185+
logger().debug('[monitor][cache][%s] renders empty caches view', namespace);
186+
_.invoke(sockets, 'emit', 'caches', {
187+
'view': 'html',
188+
'namespace': namespace,
189+
'html': html
190+
});
191+
});
192+
193+
watchExists(usr, namespace);
194+
});
195+
});
196+
197+
function caches(){
198+
199+
require('cluster-cache').user.use().then(function(usr){
200+
201+
usr.ns()
202+
.then(function(namespaces){
203+
204+
logger().debug('[monitor][cache] existing namespaces:%s', namespaces);
205+
206+
_.each(namespaces || [], function(namespace){
207+
208+
watchExists(usr, namespace);
209+
210+
usr.keys(namespace).then(function(keys){
211+
212+
logger().debug('[monitor][cache][%s] existing keys:%s', namespace, keys);
213+
214+
when.map(keys, function(k){
215+
216+
return usr.inspect(namespace, k);
217+
})
218+
.then(function(values){
219+
220+
fs.readFile(path.join(__dirname, '/views/caches.ejs'), {
221+
'encoding': 'utf-8'
222+
},
223+
function(err, read){
224+
225+
var html = ejs.render(read, {
226+
'namespace': namespace,
227+
'caches': _.map(keys, function(k, i){
228+
return {
229+
'key': k,
230+
'value': values[i][0],
231+
'persist': values[i][1],
232+
'expire': values[i][2]
233+
};
234+
})
235+
});
236+
237+
logger().debug('[monitor][cache][%s] inspected:\n%s\nand renders caches view:\n%s', JSON.stringify(values), namespace, html);
238+
_.each(sockets, function(socket){
239+
if(!_.contains(socket.namespaces, namespace)){
240+
241+
socket.namespaces.push(namespace);
242+
socket.emit('caches', {
243+
'view': 'html',
244+
'namespace': namespace,
245+
'html': html
246+
});
247+
}
248+
})
249+
});
250+
});
251+
});
252+
});
253+
});
254+
255+
watchFresh(usr);
256+
});
257+
}
258+
259+
//the debug flow is as the following:
260+
//app view accepts debug request, and we call master#debug to start
261+
//app view shows the debug as preparing till we hear 'debug-inspector' event from the master
262+
//app view shows the inspector's url for user to go to
263+
//if it's debugging live, the worker is still running and debug is started as well
264+
//otherwise the debugging fresh, the worker won't start till user comes back to the app view and 'resume' the debug (after he/she puts all the breakpoints needed)
265+
//user could continue the debug till the app view accepts 'debug-finshed' request and propagate this event to the master
266+
267+
io.sockets.on('connection', function (socket) {
268+
269+
logger().info('[monitor] accepted new websocket and sending workers & caches view');
270+
271+
socket.knownPids = [];
272+
socket.namespaces = [];
273+
sockets.push(socket);
274+
275+
workers();
276+
277+
caches();
278+
279+
socket.once('debug', function debug(pid){
280+
281+
debugging = pid;
282+
inspector = null;
283+
state = null;
284+
285+
logger().info('[debug] %s requested', pid);
286+
287+
socket.once('debug-started', function(){
288+
289+
state = 'debug-started';
290+
emitter.to(['master']).emit('debug-started');
291+
logger().info('debug:%s start requested', pid);
292+
});
293+
294+
socket.once('debug-finished', function(){
295+
296+
debugging = null;
297+
inspector = null;
298+
state = 'debug-finshed';
299+
300+
emitter.to(['master']).emit('debug-finished');
301+
logger().info('[debug] %s finished', pid);
302+
socket.once('debug', debug);
303+
});
304+
305+
emitter.once('debug-inspector', function(inspectorUrl){
306+
307+
inspector = inspectorUrl;
308+
logger().info('[debug] %s inspector ready:%s', pid, inspector);
309+
socket.knownPids = [];//force an update
310+
});
311+
312+
emitter.to(['master']).emit('debug', pid);
313+
});
314+
315+
socket.on('pause', function pause(pid){
316+
317+
if(!pauses[pid]){
318+
pauses[pid] = true;
319+
emitter.to(['master']).emit('pause', pid);
320+
}
321+
});
322+
323+
socket.on('resume', function resume(pid){
324+
325+
if(pauses[pid]){
326+
emitter.to(['master']).emit('resume', pid);
327+
delete pauses[pid];
328+
}
329+
});
330+
331+
socket.once('close', function close(){
332+
333+
sockets = _.without(sockets, socket);
334+
});
335+
});
336+
337+
return server;
338+
};

package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "cluster-monitor",
3+
"version": "0.5.0-SNAPSHOT",
4+
"description": "cluster-monitor ===============",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "mocha --ui bdd --timeout 10s --reporter spec ./test/*.js"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/cubejs/cluster-monitor.git"
12+
},
13+
"author": "[email protected]",
14+
"licenses": [
15+
{
16+
"type": "MIT",
17+
"url": "http://www.opensource.org/licenses/mit-license.php"
18+
}
19+
],
20+
"bugs": {
21+
"url": "https://github.com/cubejs/cluster-monitor/issues"
22+
},
23+
"dependencies": {
24+
"cluster-emitter": "~0.5.0",
25+
"cluster-status": "~0.5.0",
26+
"cluster-cache": "~0.5.0",
27+
"graceful-fs": "~2.0.1",
28+
"underscore": "~1.5.2",
29+
"socket.io": "~0.9.0",
30+
"express": "~3.1.0",
31+
"when": "~2.7.0",
32+
"ejs": "~0.8.0"
33+
},
34+
"devDependencies": {
35+
"optimist": "~0.6.0",
36+
"mocha": "~1.15.1",
37+
"should": "~2.1.1"
38+
},
39+
"publishConfig": {
40+
"registry": "https://registry.npmjs.org"
41+
}
42+
}

public/images/live-debugging.png

834 KB
Loading

0 commit comments

Comments
 (0)