Skip to content

Commit 7a3a334

Browse files
committed
Add status debugging HTTP server
1 parent 81cf31a commit 7a3a334

File tree

6 files changed

+146
-32
lines changed

6 files changed

+146
-32
lines changed

README.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,33 @@ The-Fast-and-the-Furious
8383
## Server options
8484

8585
```
86-
usage: nodegun [-h] [-v] [--tcp [TCP] | --local [LOCAL]] [--workers [WORKERS]]
86+
usage: main.js [-h] [-v] [--tcp [TCP] | --local [LOCAL]]
87+
[--status-tcp TCP | --status-local LOCAL] [--workers [WORKERS]]
88+
8789
88-
Start Node.js server that supports the Nailgun protocol.
90+
Node.js server that supports the Nailgun protocol.
8991
9092
Optional arguments:
91-
-h, --help Show this help message and exit.
92-
-v, --version Show program's version number and exit.
93-
--tcp [TCP] TCP address to listen to, given as ip, port, or
94-
ip:port. IP defaults to 0.0.0.0, and port defaults to
95-
2113. If no other transport is specified, TCP is used.
96-
--local [LOCAL] Local address to listen to. Defaults to /tmp/nodegun.
97-
sock.
98-
--workers [WORKERS] If present, number of worker processes to start. A
99-
flag with no argument starts one per CPU.
93+
-h, --help Show this help message and exit.
94+
-v, --version Show program's version number and exit.
95+
--workers [WORKERS] If present, number of worker processes to start. A
96+
flag with no argument starts one per CPU.
97+
98+
Transport:
99+
Transport and address. TCP is used by default.
100+
101+
--tcp [TCP] TCP address to listen to, given as ip, port, or
102+
ip:port. IP defaults to 0.0.0.0, and port defaults to
103+
2113.
104+
--local [LOCAL] Local address to listen to. Defaults to /tmp/nodegun.
105+
sock.
106+
107+
Status:
108+
Optionally expose internal status information via HTTP server.
109+
110+
--status-tcp TCP TCP address to listen to for status, given as ip,
111+
port, or ip:port. IP defaults to 0.0.0.0.
112+
--status-local LOCAL Local address to listen to for status.
100113
```
101114

102115
## Nails

cluster.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {BaseServer} from './server';
22
import {CommandFactory} from './commandfactory';
33
import * as childProcess from 'child_process';
4+
import * as events from 'events';
45
import * as net from 'net';
56

67
export class MasterServer {
@@ -27,6 +28,20 @@ export class MasterServer {
2728
worker.child.send('connection', tcp);
2829
});
2930
}
31+
32+
status() {
33+
return Promise.all(this.workers.map(({child, connections}) => {
34+
return new Promise<string>(resolve => {
35+
child.send('status', error => error && resolve(error.toString()));
36+
child.on('message', function listener(this: events.EventEmitter, message) {
37+
if (message && message.type === 'status') {
38+
resolve(message.value);
39+
this.removeListener('message', listener);
40+
}
41+
});
42+
}).then(process => ({process, connections}));
43+
})).then(workers => ({workers}));
44+
}
3045
}
3146

3247
export class WorkerServer extends BaseServer {
@@ -38,6 +53,8 @@ export class WorkerServer extends BaseServer {
3853
handle.push(message.data);
3954
}
4055
this.connection(socket);
56+
} else if (message === 'status') {
57+
this.status().then(value => process.send!({type:'status', value}));
4158
}
4259
});
4360
super(commandFactory, (process as any).channel || (process as any)._channel);

handler.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,33 @@ import {CommandFactory} from './commandfactory';
44
import {Lock} from './lock';
55
import {Readable, Writable} from 'stream';
66

7+
export class TrackingRef {
8+
private value = true;
9+
10+
constructor(readonly delegate: Ref) {
11+
}
12+
13+
getValue() {
14+
return this.value;
15+
}
16+
17+
ref() {
18+
this.value = true;
19+
this.delegate.ref();
20+
}
21+
22+
unref() {
23+
this.value = false;
24+
this.delegate.unref();
25+
}
26+
}
27+
728
export class Handler {
829
private readonly lock = new Lock();
30+
private readonly ref: TrackingRef
931

10-
constructor(private readonly commandFactory: CommandFactory, private readonly ref: Ref) {
32+
constructor(private readonly commandFactory: CommandFactory, ref: Ref) {
33+
this.ref = new TrackingRef(ref);
1134
}
1235

1336
handle(reader: Readable, writer: Writable) {
@@ -61,6 +84,13 @@ export class Handler {
6184
}
6285
});
6386
}
87+
88+
status() {
89+
return {
90+
lock: this.lock.status(),
91+
waitingOnInput: this.ref.getValue(),
92+
}
93+
}
6494
}
6595

6696
class MissingWorkingDirectory extends Error {

lock.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export class Lock {
99
return Promise.resolve(() => this.next());
1010
}
1111

12+
status() {
13+
return {
14+
'available': !this.queue,
15+
'queue': this.queue ? this.queue.length : 0,
16+
};
17+
}
18+
1219
private next() {
1320
if (this.queue) {
1421
const current = this.queue.shift();

main.ts

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
22
import {ArgumentParser} from 'argparse';
33
import * as fs from 'fs';
4+
import * as http from 'http';
45
import * as net from 'net';
56
import * as os from 'os';
67
import {CommandFactory} from './commandfactory';
@@ -13,24 +14,43 @@ if (fs.existsSync(__filename.replace(/\.js$/, '.ts'))) {
1314

1415
const npmPackage = require('./package.json');
1516

16-
const parser = new ArgumentParser({description: 'Start Node.js server that supports the Nailgun protocol.', version: npmPackage.version});
17-
const transport = parser.addMutuallyExclusiveGroup();
18-
transport.addArgument(['--tcp'], {
19-
constant: '127.0.0.1:2113',
20-
help: 'TCP address to listen to, given as ip, port, or ip:port. IP defaults to 0.0.0.0, and port defaults to 2113. If no other transport is specified, TCP is used.',
21-
nargs: '?',
22-
});
23-
transport.addArgument(['--local'], {
24-
constant:'/tmp/nodegun.sock',
25-
help: 'Local address to listen to. Defaults to /tmp/nodegun.sock.',
26-
nargs:'?',
27-
});
28-
parser.addArgument(['--workers'], {
29-
constant:os.cpus().length,
30-
help: 'If present, number of worker processes to start. A flag with no argument starts one per CPU.',
31-
nargs:'?',
32-
});
33-
const args: {tcp:string|undefined, local:string|undefined, workers:number|undefined} = parser.parseArgs();
17+
const parser = new ArgumentParser({description: 'Node.js server that supports the Nailgun protocol.', version: npmPackage.version});
18+
{
19+
const transportGroup = parser.addArgumentGroup({
20+
description: 'Transport and address. TCP is used by default.',
21+
title:'Transport',
22+
});
23+
const transport = transportGroup.addMutuallyExclusiveGroup();
24+
transport.addArgument(['--tcp'], {
25+
constant: '127.0.0.1:2113',
26+
help: 'TCP address to listen to, given as ip, port, or ip:port. IP defaults to 0.0.0.0, and port defaults to 2113.',
27+
nargs: '?',
28+
});
29+
transport.addArgument(['--local'], {
30+
constant:'/tmp/nodegun.sock',
31+
help: 'Local address to listen to. Defaults to /tmp/nodegun.sock.',
32+
nargs:'?',
33+
});
34+
const debugGroup = parser.addArgumentGroup({
35+
description: 'Optionally expose internal status information via HTTP server.',
36+
title: 'Status',
37+
});
38+
const debugTransport = debugGroup.addMutuallyExclusiveGroup();
39+
debugTransport.addArgument(['--status-tcp'], {
40+
help: 'TCP address to listen to for status, given as ip, port, or ip:port. IP defaults to 0.0.0.0.',
41+
metavar: 'TCP',
42+
});
43+
debugTransport.addArgument(['--status-local'], {
44+
help: 'Local address to listen to for status.',
45+
metavar: 'LOCAL',
46+
});
47+
parser.addArgument(['--workers'], {
48+
constant:os.cpus().length,
49+
help: 'If present, number of worker processes to start. A flag with no argument starts one per CPU.',
50+
nargs:'?',
51+
});
52+
}
53+
const args: {status_local:string|undefined, status_tcp:string|undefined, tcp:string|undefined, local:string|undefined, workers:number|undefined} = parser.parseArgs();
3454

3555
function listen(server: net.Server) {
3656
if (args.local) {
@@ -62,10 +82,34 @@ if (args.local) {
6282
}
6383
}
6484

65-
let server;
85+
let server: {server: net.Server, status():Promise<any>};
6686
if (!args.workers || args.workers <= 0) {
6787
server = new Server(new CommandFactory());
6888
} else {
6989
server = new MasterServer(require.resolve('./worker.js'), args.workers);
7090
}
7191
listen(server.server);
92+
93+
94+
{
95+
const statusServer = http.createServer((request, response) => {
96+
response.writeHead(200, {'Content-Type': 'application/json'});
97+
server.status().then(status => {
98+
response.write(JSON.stringify(status, undefined, 4));
99+
response.write('\n');
100+
response.end();
101+
});
102+
});
103+
statusServer.unref();
104+
if (args.status_local) {
105+
fs.unlinkSync(args.status_local);
106+
statusServer.listen(args.status_local);
107+
} else if (args.status_tcp) {
108+
const [first, second] = args.status_tcp.split(':', 2) as [string, string|undefined];
109+
if (second == null) {
110+
statusServer.listen(+first);
111+
} else {
112+
statusServer.listen(+first, second);
113+
}
114+
}
115+
}

server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export abstract class BaseServer {
2626

2727
socket.on('timeout', () => socket.destroy(new Error('timeout')));
2828
}
29+
30+
status() {
31+
return Promise.resolve(this.handler.status());
32+
}
2933
}
3034

3135
export class Server extends BaseServer {
@@ -35,6 +39,5 @@ export class Server extends BaseServer {
3539
const server = net.createServer(socket => this.connection(socket));
3640
super(commandFactory, server);
3741
this.server = server;
38-
3942
}
4043
}

0 commit comments

Comments
 (0)