Initial commit
This commit is contained in:
commit
75b3296f2d
6 changed files with 802 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2013 Mikko Ahlroth
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
35
README.md
Normal file
35
README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
Sysmon-ws is a simple Python WebSocket server which broadcasts system
|
||||||
|
information to all listening clients. It also includes a small HTML5 UI which
|
||||||
|
displays the information in a somewhat sensible manner. I pieced this together
|
||||||
|
one night when inspiration hit, so the code is written in a quick and dirty
|
||||||
|
manner.
|
||||||
|
|
||||||
|
Sysmon-ws uses 3rd party code:
|
||||||
|
* AutoBahnPython WebSocketServer from https://github.com/tavendo/AutobahnPython/blob/master/examples/websocket/broadcast/server.py
|
||||||
|
* jquery-graceful-websocket from https://code.google.com/p/jquery-graceful-websocket/
|
||||||
|
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Clone the files to a directory, create a virtualenv if you like and then
|
||||||
|
install libs with pip:
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Usage example from my server (I use a virtualenv for it):
|
||||||
|
|
||||||
|
[nicd@nytsoi ~]$ cd /srv/sysmon-ws
|
||||||
|
[nicd@nytsoi sysmon-ws]$ . venv-sysmon-ws/bin/activate
|
||||||
|
(venv-sysmon-ws)[nicd@nytsoi sysmon-ws]$ python server.py >/dev/null
|
||||||
|
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
* Original AutoBahnPython code in server.py is licensed with Apache 2.0, read
|
||||||
|
server.py for more details.
|
||||||
|
* jquery-graceful-websocket (ws.js) is licensed with the MIT License.
|
||||||
|
* My code (index.html and modifications in server.py) is licensed with the
|
||||||
|
MIT License. See LICENSE for more details.
|
411
htmlui/index.html
Normal file
411
htmlui/index.html
Normal file
|
@ -0,0 +1,411 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>nytsoi.net system monitor</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
html, body {
|
||||||
|
background-color: #123456;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
color: #abcdef;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0px 10px;
|
||||||
|
margin: 0px auto;
|
||||||
|
width: 1100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container h1, h2, h3, h4, h5, h6 {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container h2, h3, h4, h5, h6 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container, #container>div {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu {
|
||||||
|
/*width: 48%;*/
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mem {
|
||||||
|
/*width: 48%;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#virt, #swap {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu_times, #io {
|
||||||
|
/*width: 48%;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.t {
|
||||||
|
border-top: 1px solid #abcdef;
|
||||||
|
}
|
||||||
|
.r {
|
||||||
|
border-right: 1px solid #abcdef;
|
||||||
|
}
|
||||||
|
.b {
|
||||||
|
border-bottom: 1px solid #abcdef;
|
||||||
|
}
|
||||||
|
.l {
|
||||||
|
border-left: 1px solid #abcdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.np {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu_time_legend, #cpu_time_values, #cpu_time_bars, #io_legend, #io_values {
|
||||||
|
white-space: pre;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu {
|
||||||
|
white-space: pre;
|
||||||
|
border-top: 1px solid #abcdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cpu:first-of-type {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<h1><span id="connecting"></span>nytsoi.net system monitor
|
||||||
|
<span id="version"></span>
|
||||||
|
| Uptime: <span id="uptime">?</span>
|
||||||
|
| <span id="users">?</span> users, <span id="processes">?</span> processes
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div id="cpu" class="r t l b">
|
||||||
|
<!--<h2 class="b">CPU</h2>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mem" class="t r">
|
||||||
|
<div id="virt" class="p b"></div>
|
||||||
|
<div id="swap" class="p"></div>
|
||||||
|
</div>
|
||||||
|
<div id="cpu_times" class="r t b">
|
||||||
|
<h2 class="b">CPU times </h2>
|
||||||
|
<div id="cpu_time_legend" class="p">user
|
||||||
|
system
|
||||||
|
idle
|
||||||
|
nice
|
||||||
|
iowait
|
||||||
|
irq
|
||||||
|
softirq
|
||||||
|
steal
|
||||||
|
guest
|
||||||
|
guest_nice</div>
|
||||||
|
<div id="cpu_time_values" class="p"></div>
|
||||||
|
<div id="cpu_time_bars" class="p"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="io" class="r b">
|
||||||
|
<h2 class="b">I/O </h2>
|
||||||
|
<div id="io_legend" class="p">Network
|
||||||
|
Bytes in
|
||||||
|
Bytes out
|
||||||
|
|
||||||
|
Disk
|
||||||
|
Reads
|
||||||
|
Writes</div>
|
||||||
|
<div id="io_values" class="p"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
||||||
|
<script src="ws.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var connect_chars = "·oOo· ";
|
||||||
|
var connect_index = -1;
|
||||||
|
var connecting = true;
|
||||||
|
var im_hiding_behind_my_facebokk = false;
|
||||||
|
var bar_char = "#";
|
||||||
|
var cpu_bar_length = 55;
|
||||||
|
var cpu_bar_height = 15;
|
||||||
|
var cpu_bars = [];
|
||||||
|
var cpu_amount = 0;
|
||||||
|
var cpu_time_bar_length = 40;
|
||||||
|
var mem_bar_length = 55;
|
||||||
|
var swap_bar_length = mem_bar_length;
|
||||||
|
var boot_time = 0;
|
||||||
|
var version = "";
|
||||||
|
var io_net_in = 0;
|
||||||
|
var io_net_out = 0;
|
||||||
|
var io_disk_in = 0;
|
||||||
|
var io_disk_out = 0;
|
||||||
|
|
||||||
|
String.prototype.repeat = function(num)
|
||||||
|
{
|
||||||
|
num = Math.round(num);
|
||||||
|
|
||||||
|
if (num <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Array(num + 1).join(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
function pad(str, length, filler) {
|
||||||
|
if (typeof(filler) === "undefined") {
|
||||||
|
filler = " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
str = String(str);
|
||||||
|
|
||||||
|
if (str.length >= length) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return str + filler.repeat(length - str.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_connecting() {
|
||||||
|
if (connecting) {
|
||||||
|
++connect_index;
|
||||||
|
if (connect_index >= connect_chars.length) {
|
||||||
|
connect_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#connecting').text(connect_chars[connect_index] + " ");
|
||||||
|
im_hiding_behind_my_facebokk = false;
|
||||||
|
}
|
||||||
|
else if (!im_hiding_behind_my_facebokk) {
|
||||||
|
$('#connecting').hide();
|
||||||
|
im_hiding_behind_my_facebokk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_time = (new Date).getTime() / 1000;
|
||||||
|
interval = current_time - boot_time;
|
||||||
|
|
||||||
|
days = Math.floor(interval / 86400);
|
||||||
|
hours = Math.floor((interval - (days * 86400)) / 3600);
|
||||||
|
minutes = Math.floor((interval - (days * 86400) - (hours * 3600)) / 60);
|
||||||
|
seconds = Math.floor(interval - (days * 86400) - (hours * 3600) - (minutes * 60));
|
||||||
|
|
||||||
|
$('#uptime').text("" + days + "d " + hours + "h " + minutes + "m " + seconds + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
function bar(percent, max_length) {
|
||||||
|
var bar_str = bar_char.repeat(max_length * (percent / 100));
|
||||||
|
return pad(bar_str, max_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hb(bytes, round) {
|
||||||
|
if (typeof(round) === "undefined") {
|
||||||
|
round = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return "0 B";
|
||||||
|
}
|
||||||
|
|
||||||
|
var kilo = 1000;
|
||||||
|
var prefixes = [
|
||||||
|
[Math.pow(kilo, 4), 'T'],
|
||||||
|
[Math.pow(kilo, 3), 'G'],
|
||||||
|
[Math.pow(kilo, 2), 'M'],
|
||||||
|
[kilo, 'k']
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i = 0; i < prefixes.length; ++i) {
|
||||||
|
if (bytes >= prefixes[i][0]) {
|
||||||
|
return "" + (bytes / prefixes[i][0]).toFixed(2) + " " + prefixes[i][1] + "B";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "" + bytes + " B";
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
return $.gracefulWebSocket("ws://sysmon-ws.nytsoi.net");
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(draw_connecting, 1000);
|
||||||
|
var ws = connect();
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
connecting = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
connecting = true;
|
||||||
|
|
||||||
|
ws = connect();
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = ws.onclose;
|
||||||
|
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
var json = $.parseJSON(event.data);
|
||||||
|
|
||||||
|
if (json.boot !== undefined) {
|
||||||
|
boot_time = json.boot;
|
||||||
|
cpu_amount = json.cpus;
|
||||||
|
version = json.version;
|
||||||
|
|
||||||
|
$('#version').text(version);
|
||||||
|
|
||||||
|
for (var i = 0; i < cpu_amount; ++i) {
|
||||||
|
cpu_bars.push([]);
|
||||||
|
|
||||||
|
new_div = $('<div />');
|
||||||
|
new_div.addClass("cpu p");
|
||||||
|
new_div.attr({id: "cpu" + i});
|
||||||
|
$('#cpu').append(new_div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// CPU BARS
|
||||||
|
var cpu_bar_chars = [];
|
||||||
|
for (var cpu = 0; cpu < cpu_amount; ++cpu) {
|
||||||
|
new_val = json.cpu[cpu];
|
||||||
|
var cbar = cpu_bars[cpu];
|
||||||
|
var chars = [];
|
||||||
|
|
||||||
|
cbar.push(new_val);
|
||||||
|
if (cbar.length > cpu_bar_length) {
|
||||||
|
cbar.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < cbar.length; ++i) {
|
||||||
|
chars.push(bar(cbar[i], cpu_bar_height));
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = "";
|
||||||
|
var legend = "CPU" + cpu + " " + Math.round(new_val) + "%";
|
||||||
|
// Draw bar chart by transposing
|
||||||
|
for (var y = 0; y < cpu_bar_height; ++y) {
|
||||||
|
for (var x = 0; x < cpu_bar_length; ++x) {
|
||||||
|
// Draw legend
|
||||||
|
if (y == 0 && x < legend.length) {
|
||||||
|
output += legend[x];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var cell = undefined;
|
||||||
|
var column = chars[x];
|
||||||
|
|
||||||
|
if (column !== undefined) {
|
||||||
|
cell = column[cpu_bar_height-y-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell === undefined) {
|
||||||
|
output += " ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output += cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#cpu' + cpu).text(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// CPU TIMES
|
||||||
|
var ct = json.cpu_times;
|
||||||
|
$('#cpu_time_values').text("" +
|
||||||
|
pad(Math.round(ct.user), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.system), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.idle), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.nice), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.iowait), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.irq), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.softirq), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.steal), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.guest), 3) + "\n" +
|
||||||
|
pad(Math.round(ct.guest_nice), 3)
|
||||||
|
);
|
||||||
|
$('#cpu_time_bars').text("" +
|
||||||
|
bar(ct.user, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.system, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.idle, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.nice, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.iowait, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.irq, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.softirq, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.steal, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.guest, cpu_time_bar_length) + "\n" +
|
||||||
|
bar(ct.guest_nice, cpu_time_bar_length)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// I/O
|
||||||
|
var net_in_diff = json.net.bytes_in - io_net_in;
|
||||||
|
var net_out_diff = json.net.bytes_out - io_net_out;
|
||||||
|
var disk_in_diff = json.disk.read_count - io_disk_in;
|
||||||
|
var disk_out_diff = json.disk.write_count - io_disk_out;
|
||||||
|
|
||||||
|
io_net_in = json.net.bytes_in;
|
||||||
|
io_net_out = json.net.bytes_out;
|
||||||
|
io_disk_in = json.disk.read_count;
|
||||||
|
io_disk_out = json.disk.write_count;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$('#io_values').text(pad("", 20) + "\n" +
|
||||||
|
hb(json.net.bytes_in) + " (" + hb(net_in_diff) + ")\n" +
|
||||||
|
hb(json.net.bytes_out) + " (" + hb(net_out_diff) + ")\n" +
|
||||||
|
"\n" +
|
||||||
|
"\n" +
|
||||||
|
json.disk.read_count + " (" + disk_in_diff + ")\n" +
|
||||||
|
json.disk.write_count + " (" + disk_out_diff + ")"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MEMORY
|
||||||
|
var mem_str = "Mem " + bar(json.mem, mem_bar_length);
|
||||||
|
var swap_str = "Swap " + bar(json.swap, swap_bar_length);
|
||||||
|
|
||||||
|
var mem_end = "" + json.mem + " %";
|
||||||
|
var swap_end = "" + json.swap + " %";
|
||||||
|
mem_str = mem_str.substr(0, mem_str.length - mem_end.length) + mem_end;
|
||||||
|
swap_str = swap_str.substr(0, swap_str.length - swap_end.length) + swap_end;
|
||||||
|
|
||||||
|
$('#virt').text(mem_str);
|
||||||
|
$('#swap').text(swap_str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// MISC
|
||||||
|
$('#users').text(json.users);
|
||||||
|
$('#processes').text(json.procs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
165
htmlui/ws.js
Normal file
165
htmlui/ws.js
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* WebSocket with graceful degradation - jQuery plugin
|
||||||
|
* @author David Lindkvist
|
||||||
|
* @version 0.1
|
||||||
|
*
|
||||||
|
* Returns an object implementing the WebSocket API.
|
||||||
|
*
|
||||||
|
* If browser supports WebSockets a native WebSocket instance is returned.
|
||||||
|
* If not, a simulated half-duplex implementation is returned which uses polling
|
||||||
|
* over HTTP to retrieve new messages
|
||||||
|
*
|
||||||
|
* OPTIONS
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* {Number} fallbackOpenDelay number of ms to delay simulated open
|
||||||
|
* event for fallback
|
||||||
|
* {Number} fallbackPollInterval number of ms between requests for
|
||||||
|
* fallback polling
|
||||||
|
* {Object} fallbackPollParams optional params to pass with each poll
|
||||||
|
* requests
|
||||||
|
*
|
||||||
|
* EXAMPLES
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* var websocket = $.gracefulWebSocket("ws://127.0.0.1:8080/");
|
||||||
|
*
|
||||||
|
* var websocket = $.gracefulWebSocket({
|
||||||
|
* fallbackPollParams: {
|
||||||
|
* "latestMessageID": function () {
|
||||||
|
* return latestMessageID;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function ($) {
|
||||||
|
|
||||||
|
$.extend({
|
||||||
|
|
||||||
|
gracefulWebSocket: function (url, options) {
|
||||||
|
|
||||||
|
// Default properties
|
||||||
|
this.defaults = {
|
||||||
|
keepAlive: false, // not implemented - should ping server to keep socket open
|
||||||
|
autoReconnect: false, // not implemented - should try to reconnect silently if socket is closed
|
||||||
|
fallback: true, // not implemented - always use HTTP fallback if native browser support is missing
|
||||||
|
fallbackSendURL: url.replace('ws:', 'http:').replace('wss:', 'https:'),
|
||||||
|
fallbackSendMethod: 'POST',
|
||||||
|
fallbackPollURL: url.replace('ws:', 'http:').replace('wss:', 'https:'),
|
||||||
|
fallbackPollMethod: 'GET',
|
||||||
|
fallbackOpenDelay: 100, // number of ms to delay simulated open event
|
||||||
|
fallbackPollInterval: 3000, // number of ms between poll requests
|
||||||
|
fallbackPollParams: {} // optional params to pass with poll requests
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override defaults with user properties
|
||||||
|
var opts = $.extend({}, this.defaults, options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a fallback object implementing the WebSocket interface
|
||||||
|
*/
|
||||||
|
function FallbackSocket() {
|
||||||
|
|
||||||
|
// WebSocket interface constants
|
||||||
|
const CONNECTING = 0;
|
||||||
|
const OPEN = 1;
|
||||||
|
const CLOSING = 2;
|
||||||
|
const CLOSED = 3;
|
||||||
|
|
||||||
|
var pollInterval;
|
||||||
|
var openTimout;
|
||||||
|
|
||||||
|
// create WebSocket object
|
||||||
|
var fws = {
|
||||||
|
// ready state
|
||||||
|
readyState: CONNECTING,
|
||||||
|
bufferedAmount: 0,
|
||||||
|
send: function (data) {
|
||||||
|
var success = true;
|
||||||
|
$.ajax({
|
||||||
|
async: false, // send synchronously
|
||||||
|
type: opts.fallbackSendMethod,
|
||||||
|
url: opts.fallbackSendURL + '?' + $.param( getFallbackParams() ),
|
||||||
|
data: data,
|
||||||
|
dataType: 'text',
|
||||||
|
contentType : "application/x-www-form-urlencoded; charset=utf-8",
|
||||||
|
success: pollSuccess,
|
||||||
|
error: function (xhr) {
|
||||||
|
success = false;
|
||||||
|
$(fws).triggerHandler('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return success;
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
clearTimeout(openTimout);
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
this.readyState = CLOSED;
|
||||||
|
$(fws).triggerHandler('close');
|
||||||
|
},
|
||||||
|
onopen: function () {},
|
||||||
|
onmessage: function () {},
|
||||||
|
onerror: function () {},
|
||||||
|
onclose: function () {},
|
||||||
|
previousRequest: null,
|
||||||
|
currentRequest: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFallbackParams() {
|
||||||
|
|
||||||
|
// update timestamp of previous and current poll request
|
||||||
|
fws.previousRequest = fws.currentRequest;
|
||||||
|
fws.currentRequest = new Date().getTime();
|
||||||
|
|
||||||
|
// extend default params with plugin options
|
||||||
|
return $.extend({"previousRequest": fws.previousRequest, "currentRequest": fws.currentRequest}, opts.fallbackPollParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} data
|
||||||
|
*/
|
||||||
|
function pollSuccess(data) {
|
||||||
|
|
||||||
|
// trigger onmessage
|
||||||
|
var messageEvent = {"data" : data};
|
||||||
|
fws.onmessage(messageEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function poll() {
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: opts.fallbackPollMethod,
|
||||||
|
url: opts.fallbackPollURL,
|
||||||
|
dataType: 'text',
|
||||||
|
data: getFallbackParams(),
|
||||||
|
success: pollSuccess,
|
||||||
|
error: function (xhr) {
|
||||||
|
$(fws).triggerHandler('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulate open event and start polling
|
||||||
|
openTimout = setTimeout(function () {
|
||||||
|
fws.readyState = OPEN;
|
||||||
|
//fws.currentRequest = new Date().getTime();
|
||||||
|
$(fws).triggerHandler('open');
|
||||||
|
poll();
|
||||||
|
pollInterval = setInterval(poll, opts.fallbackPollInterval);
|
||||||
|
|
||||||
|
}, opts.fallbackOpenDelay);
|
||||||
|
|
||||||
|
// return socket impl
|
||||||
|
return fws;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new websocket or fallback
|
||||||
|
var ws = window.WebSocket ? new WebSocket(url) : new FallbackSocket();
|
||||||
|
$(window).unload(function () { ws.close(); ws = null });
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
autobahn==0.5.14
|
||||||
|
psutil==0.7.1
|
||||||
|
|
167
server.py
Normal file
167
server.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
###############################################################################
|
||||||
|
##
|
||||||
|
## Copyright 2011,2012 Tavendo GmbH
|
||||||
|
##
|
||||||
|
## Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
## you may not use this file except in compliance with the License.
|
||||||
|
## You may obtain a copy of the License at
|
||||||
|
##
|
||||||
|
## http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
##
|
||||||
|
## Unless required by applicable law or agreed to in writing, software
|
||||||
|
## distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
## See the License for the specific language governing permissions and
|
||||||
|
## limitations under the License.
|
||||||
|
##
|
||||||
|
##
|
||||||
|
## Modified by Mikko Ahlroth, 2013
|
||||||
|
## Original is part of AutobahnPython and can be found at
|
||||||
|
## https://github.com/tavendo/AutobahnPython/blob/master/examples/websocket/broadcast/server.py
|
||||||
|
## Modifications are licensed with the MIT License, see LICENSE for details.
|
||||||
|
##
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.python import log
|
||||||
|
|
||||||
|
from autobahn.websocket import WebSocketServerFactory, \
|
||||||
|
WebSocketServerProtocol, \
|
||||||
|
listenWS
|
||||||
|
|
||||||
|
TICK_INTERVAL = 2
|
||||||
|
CPU_INTERVAL = 1
|
||||||
|
WEBSOCKET = 'ws://localhost:8081/'
|
||||||
|
NET_INTERFACE = 'eth0'
|
||||||
|
|
||||||
|
INITIAL_INFO = {
|
||||||
|
'cpus': psutil.NUM_CPUS,
|
||||||
|
'boot': psutil.get_boot_time(),
|
||||||
|
'version': '0.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_sys_status():
|
||||||
|
cpu = psutil.cpu_percent(interval=CPU_INTERVAL, percpu=True)
|
||||||
|
mem = psutil.virtual_memory().percent
|
||||||
|
swap = psutil.swap_memory().percent
|
||||||
|
procs = len(psutil.get_pid_list())
|
||||||
|
ct = psutil.cpu_times_percent()
|
||||||
|
cpu_times = {
|
||||||
|
'user': ct[0],
|
||||||
|
'system': ct[1],
|
||||||
|
'idle': ct[2],
|
||||||
|
'nice': ct[3],
|
||||||
|
'iowait': ct[4],
|
||||||
|
'irq': ct[5],
|
||||||
|
'softirq': ct[6],
|
||||||
|
'steal': ct[7],
|
||||||
|
'guest': ct[8],
|
||||||
|
'guest_nice': ct[9],
|
||||||
|
}
|
||||||
|
net = psutil.network_io_counters(pernic=True)[NET_INTERFACE]
|
||||||
|
net_out = {
|
||||||
|
'bytes_out': net.bytes_sent,
|
||||||
|
'bytes_in': net.bytes_recv,
|
||||||
|
}
|
||||||
|
disk = psutil.disk_io_counters()
|
||||||
|
disk_out = {
|
||||||
|
'read_count': disk.read_count,
|
||||||
|
'write_count': disk.write_count,
|
||||||
|
}
|
||||||
|
|
||||||
|
users = len(psutil.get_users())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cpu': cpu,
|
||||||
|
'mem': mem,
|
||||||
|
'swap': swap,
|
||||||
|
'procs': procs,
|
||||||
|
'cpu_times': cpu_times,
|
||||||
|
'net': net_out,
|
||||||
|
'disk': disk_out,
|
||||||
|
'users': users,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastServerProtocol(WebSocketServerProtocol):
|
||||||
|
|
||||||
|
def onOpen(self):
|
||||||
|
self.factory.register(self)
|
||||||
|
|
||||||
|
def onMessage(self, msg, binary):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connectionLost(self, reason):
|
||||||
|
WebSocketServerProtocol.connectionLost(self, reason)
|
||||||
|
self.factory.unregister(self)
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastServerFactory(WebSocketServerFactory):
|
||||||
|
|
||||||
|
def __init__(self, url, debug=False, debugCodePaths=False):
|
||||||
|
WebSocketServerFactory.__init__(self, url, debug=debug, debugCodePaths=debugCodePaths)
|
||||||
|
self.clients = []
|
||||||
|
self.tick()
|
||||||
|
|
||||||
|
def tick(self):
|
||||||
|
self.broadcast(json.dumps(get_sys_status()))
|
||||||
|
reactor.callLater(TICK_INTERVAL - CPU_INTERVAL, self.tick)
|
||||||
|
|
||||||
|
def register(self, client):
|
||||||
|
if not client in self.clients:
|
||||||
|
print "registered client " + client.peerstr
|
||||||
|
self.clients.append(client)
|
||||||
|
client.sendMessage(json.dumps(INITIAL_INFO))
|
||||||
|
|
||||||
|
def unregister(self, client):
|
||||||
|
if client in self.clients:
|
||||||
|
print "unregistered client " + client.peerstr
|
||||||
|
self.clients.remove(client)
|
||||||
|
|
||||||
|
def broadcast(self, msg):
|
||||||
|
print "broadcasting message '%s' .." % msg
|
||||||
|
for c in self.clients:
|
||||||
|
c.sendMessage(msg)
|
||||||
|
print "message sent to " + c.peerstr
|
||||||
|
|
||||||
|
|
||||||
|
class BroadcastPreparedServerFactory(BroadcastServerFactory):
|
||||||
|
"""
|
||||||
|
Functionally same as above, but optimized broadcast using
|
||||||
|
prepareMessage and sendPreparedMessage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def broadcast(self, msg):
|
||||||
|
print "broadcasting prepared message '%s' .." % msg
|
||||||
|
preparedMsg = self.prepareMessage(msg)
|
||||||
|
for c in self.clients:
|
||||||
|
c.sendPreparedMessage(preparedMsg)
|
||||||
|
print "prepared message sent to " + c.peerstr
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
|
||||||
|
log.startLogging(sys.stdout)
|
||||||
|
debug = True
|
||||||
|
else:
|
||||||
|
debug = False
|
||||||
|
|
||||||
|
#ServerFactory = BroadcastServerFactory
|
||||||
|
ServerFactory = BroadcastPreparedServerFactory
|
||||||
|
|
||||||
|
factory = ServerFactory(WEBSOCKET,
|
||||||
|
debug=debug,
|
||||||
|
debugCodePaths=debug)
|
||||||
|
|
||||||
|
factory.protocol = BroadcastServerProtocol
|
||||||
|
factory.setProtocolOptions(allowHixie76=True)
|
||||||
|
listenWS(factory)
|
||||||
|
|
||||||
|
reactor.run()
|
Loading…
Reference in a new issue