Skip to content

Commit

Permalink
Add live user count.
Browse files Browse the repository at this point in the history
  • Loading branch information
agrawal-d committed Dec 24, 2024
1 parent 9898c66 commit b6fbb6e
Show file tree
Hide file tree
Showing 24 changed files with 334 additions and 83 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,8 @@ approach. It makes reviewing and accepting the PR much easier.**

## Telemetry

The extension collects basic events defined in `src/telmetry.ts`. To disable,
modify the setting `telemetry.telemetryLevel` (applies to all VSCode
extensions).
To show live user count, the extension sends a request to the server every few
seconds. No information is sent with the request.

## License

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@
"type": "boolean",
"default": true,
"description": "Automatically show the judge view when opening a file that has a problem associated with it"
},
"cph.general.remoteServerAddress": {
"type": "string",
"default": "http://20.244.105.138:4546",
"description": "The address of the remote server to which the extension will send requests. (Currently used for live user count)"
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Remote webserver for CPH

Currently implements a heartbeat server for live user count.

## Installation on a VM

- Install node
- Copy `server.js` to `/opt/`
- Install the service:

```
sudo cp server.service /etc/systemd/system
sudo systemctl daemon-reload
sudo systemctl start server.service
sudo systemctl enable server.service
```
- Check status using `sudo systemctl status server.service`
- In CPH, configure the address + port of the server.
## Uninstall
```
sudo systemctl stop server.service
sudo systemctl disable server.service
sudo rm /etc/systemd/system/server.service
```
52 changes: 52 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const http = require('http');
const { env } = require('process');

class HeartbeatServer {
constructor() {
console.log("Creating new HeartbeatServer instance");

this.ips = new Map();
this.refresh_interval_seconds = parseInt(env.REFRESH_INTERVAL) || 30;
this.port = parseInt(env.PORT) || 8080;
console.log(`Refresh interval: ${this.refresh_interval_seconds} seconds`);

this.runServer(this.port);

setInterval(() => {
this.runCleanup();
}, this.refresh_interval_seconds * 1000);
}

runServer(port) {
console.log("Starting heartbeat server on port " + port);
this.serverInstance = http.createServer((req, res) => {
this.processHeartbeat(req.socket.remoteAddress);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.setHeader('Access-Control-Allow-Origin', '*');
res.end(this.ips.size.toString());
});
this.serverInstance.listen(port);
console.log("Heartbeat server started.");
}

runCleanup() {
const oldLen = this.ips.size;
const now = Date.now();
for (const [ip, timestamp] of this.ips.entries()) {
if (now - timestamp > this.refresh_interval_seconds * 1000) {
this.ips.delete(ip);
console.log("Deleted stale IP: " + ip);
}
}
const newLen = this.ips.size;
console.log(`Cleaned up ${oldLen - newLen} stale IPs. New count: ${newLen}`);
}

// Called when a heartbeat is received from an IP
processHeartbeat(ip) {
this.ips.set(ip, Date.now());
}
}

console.log("Starting app");
new HeartbeatServer();
15 changes: 15 additions & 0 deletions server/server.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Node.js heartbeat server
After=network.target

[Service]
ExecStart=/usr/bin/env node /opt/server.js
Restart=always
RestartSec=5
Environment=PORT=4546
User=server
Group=server
WorkingDirectory=/opt

[Install]
WantedBy=multi-user.target
26 changes: 16 additions & 10 deletions src/companion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ export const submitKattisProblem = (problem: Problem) => {
pyshell.stdin.end();

pyshell.stdout.on('data', function (data) {
console.log(data.toString());
globalThis.logger.log(data.toString());
getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'new-problem',
problem,
});
({ command: 'submit-finished' });
});
pyshell.stderr.on('data', function (data) {
console.log(data.tostring());
globalThis.logger.log(data.tostring());
vscode.window.showErrorMessage(data);
});
};
Expand All @@ -89,7 +89,7 @@ export const storeSubmitProblem = (problem: Problem) => {
languageId,
};
globalThis.reporter.sendTelemetryEvent(telmetry.SUBMIT_TO_CODEFORCES);
console.log('Stored savedResponse', savedResponse);
globalThis.logger.log('Stored savedResponse', savedResponse);
};

export const setupCompanionServer = () => {
Expand All @@ -99,7 +99,8 @@ export const setupCompanionServer = () => {
let rawProblem = '';

req.on('data', (chunk) => {
COMPANION_LOGGING && console.log('Companion server got data');
COMPANION_LOGGING &&
globalThis.logger.log('Companion server got data');
rawProblem += chunk;
});
req.on('close', function () {
Expand All @@ -110,7 +111,9 @@ export const setupCompanionServer = () => {
const problem: Problem = JSON.parse(rawProblem);
handleNewProblem(problem);
COMPANION_LOGGING &&
console.log('Companion server closed connection.');
globalThis.logger.log(
'Companion server closed connection.',
);
} catch (e) {
vscode.window.showErrorMessage(
`Error parsing problem from companion "${e}. Raw problem: '${rawProblem}'"`,
Expand All @@ -120,7 +123,7 @@ export const setupCompanionServer = () => {
res.write(JSON.stringify(savedResponse));
if (headers['cph-submit'] == 'true') {
COMPANION_LOGGING &&
console.log(
globalThis.logger.log(
'Request was from the cph-submit extension; sending savedResponse and clearing it',
savedResponse,
);
Expand All @@ -140,18 +143,21 @@ export const setupCompanionServer = () => {
`Are multiple VSCode windows open? CPH will work on the first opened window. CPH server encountered an error: "${err.message}" , companion may not work.`,
);
});
console.log('Companion server listening on port', config.port);
globalThis.logger.log(
'Companion server listening on port',
config.port,
);
return server;
} catch (e) {
console.error('Companion server error :', e);
globalThis.logger.error('Companion server error :', e);
}
};

export const getProblemFileName = (problem: Problem, ext: string) => {
if (isCodeforcesUrl(new URL(problem.url)) && useShortCodeForcesName()) {
return `${getProblemName(problem.url)}.${ext}`;
} else {
console.log(
globalThis.logger.log(
isCodeforcesUrl(new URL(problem.url)),
useShortCodeForcesName(),
);
Expand Down Expand Up @@ -204,7 +210,7 @@ const handleNewProblem = async (problem: Problem) => {
try {
url = new URL(problem.url);
} catch (err) {
console.error(err);
globalThis.logger.error(err);
return null;
}
if (url.hostname == 'open.kattis.com') {
Expand Down
20 changes: 10 additions & 10 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export let onlineJudgeEnv = false;

export const setOnlineJudgeEnv = (value: boolean) => {
onlineJudgeEnv = value;
console.log('online judge env:', onlineJudgeEnv);
globalThis.logger.log('online judge env:', onlineJudgeEnv);
};

/**
Expand Down Expand Up @@ -211,7 +211,7 @@ const createDotnetProject = async (
getDotnetProjectLocation(language, srcPath),
);

console.log('Creating new .NET project');
globalThis.logger.log('Creating new .NET project');
const args = ['new', 'console', '--force', '-o', projDir];
const newProj = spawn(language.compiler, args);

Expand Down Expand Up @@ -244,7 +244,7 @@ const createDotnetProject = async (
}

const destPath = path.join(projDir, 'Program.cs');
console.log(
globalThis.logger.log(
'Copying source code to the project',
srcPath,
destPath,
Expand All @@ -265,15 +265,15 @@ const createDotnetProject = async (
}
resolve(true);
} catch (err) {
console.error('Error while copying source code', err);
globalThis.logger.error('Error while copying source code', err);
ocWrite('Errors while creating new .NET project:\n' + err);
ocShow();
resolve(false);
}
});

newProj.on('error', (err) => {
console.log(err);
globalThis.logger.log(err);
ocWrite('Errors while creating new .NET project:\n' + err);
ocShow();
resolve(false);
Expand All @@ -294,7 +294,7 @@ const createDotnetProject = async (
* @param srcPath location of the source code
*/
export const compileFile = async (srcPath: string): Promise<boolean> => {
console.log('Compilation Started');
globalThis.logger.log('Compilation Started');
await vscode.workspace.openTextDocument(srcPath).then((doc) => doc.save());
ocHide();
const language: Language = getLanguage(srcPath);
Expand Down Expand Up @@ -332,7 +332,7 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
command: 'compiling-start',
});
const flags: string[] = getFlags(language, srcPath);
console.log('Compiling with flags', flags);
globalThis.logger.log('Compiling with flags', flags);
const result = new Promise<boolean>((resolve) => {
let compiler;
try {
Expand All @@ -350,7 +350,7 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
});

compiler.on('error', (err) => {
console.error(err);
globalThis.logger.error(err);
ocWrite(
'Errors while compiling:\n' +
err.message +
Expand All @@ -375,7 +375,7 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
`Exit code: ${exitCode} Errors while compiling:\n` + error,
);
ocShow();
console.error('Compilation failed');
globalThis.logger.error('Compilation failed');
getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'compiling-stop',
});
Expand All @@ -394,7 +394,7 @@ export const compileFile = async (srcPath: string): Promise<boolean> => {
ocShow();
}

console.log('Compilation passed');
globalThis.logger.log('Compilation passed');
getJudgeViewProvider().extensionToJudgeViewMessage({
command: 'compiling-stop',
});
Expand Down
20 changes: 10 additions & 10 deletions src/executions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const runTestCase = (
binPath: string,
input: string,
): Promise<Run> => {
console.log('Running testcase', language, binPath, input);
globalThis.logger.log('Running testcase', language, binPath, input);
const result: Run = {
stdout: '',
stderr: '',
Expand Down Expand Up @@ -120,7 +120,7 @@ export const runTestCase = (
}

process.on('error', (err) => {
console.error(err);
globalThis.logger.error(err);
vscode.window.showErrorMessage(
`Could not launch testcase process. Is '${language.compiler}' in your PATH?`,
);
Expand All @@ -136,7 +136,7 @@ export const runTestCase = (
result.signal = signal;
result.time = end - begin;
runningBinaries.pop();
console.log('Run Result:', result);
globalThis.logger.log('Run Result:', result);
resolve(result);
});

Expand All @@ -145,11 +145,11 @@ export const runTestCase = (
});
process.stderr.on('data', (data) => (result.stderr += data));

console.log('Wrote to STDIN');
globalThis.logger.log('Wrote to STDIN');
try {
process.stdin.write(input);
} catch (err) {
console.error('WRITEERROR', err);
globalThis.logger.error('WRITEERROR', err);
}

process.stdin.end();
Expand All @@ -160,7 +160,7 @@ export const runTestCase = (
result.signal = err.name;
result.time = end - begin;
runningBinaries.pop();
console.log('Run Error Result:', result);
globalThis.logger.log('Run Error Result:', result);
resolve(result);
});
});
Expand All @@ -171,12 +171,12 @@ export const runTestCase = (
/** Remove the generated binary from the file system, if present */
export const deleteBinary = (language: Language, binPath: string) => {
if (language.skipCompile) {
console.log(
globalThis.logger.log(
"Skipping deletion of binary as it's not a compiled language.",
);
return;
}
console.log('Deleting binary', binPath);
globalThis.logger.log('Deleting binary', binPath);
try {
const isLinux = platform() == 'linux';
const isFile = path.extname(binPath);
Expand All @@ -200,13 +200,13 @@ export const deleteBinary = (language: Language, binPath: string) => {
}
}
} catch (err) {
console.error('Error while deleting binary', err);
globalThis.logger.error('Error while deleting binary', err);
}
};

/** Kill all running binaries. Usually, only one should be running at a time. */
export const killRunning = () => {
globalThis.reporter.sendTelemetryEvent(telmetry.KILL_RUNNING);
console.log('Killling binaries');
globalThis.logger.log('Killling binaries');
runningBinaries.forEach((process) => process.kill());
};
Loading

0 comments on commit b6fbb6e

Please sign in to comment.