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