Merge remote-tracking branch 'origin/EDDN-Monitor'

This commit is contained in:
AnthorNet 2015-05-19 14:21:28 +02:00
commit 53dcc85d04
39 changed files with 1654 additions and 261 deletions

3
Setup.bat Normal file
View File

@ -0,0 +1,3 @@
"python" %~dp0\setup.py install
pause

BIN
contrib/EDDN_Monitor.s3db Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
body {
padding-top: 70px;
}
section.container {
margin-bottom: 20px;
/* FIX ANCHORS */
padding-top:70px;
margin-top:-70px;
}
.container h2 {
margin-top: 0;
}
.container .chart {
height: 300px;
margin-bottom: 10px;
}
footer .navbar-default {
border-width: 1px 0 0;
margin-bottom: 0;
}
.highcharts-container{width:100% !important; height:100% !important;}

394
contrib/monitor/index.html Normal file
View File

@ -0,0 +1,394 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>EDDN Status</title>
<!-- Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<link href="./css/eddn.css" rel="stylesheet" />
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="./js/date.js"></script>
</head>
<body data-spy="scroll" data-target="#header-nav" data-offset="70">
<header>
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#header-nav">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="">EDDN Status</a>
</div>
<div class="collapse navbar-collapse" id="header-nav">
<ul class="nav navbar-nav">
<li><a href="#gateways">Gateways</a></li>
<li><a href="#relays">Relays</a></li>
<li><a href="#softwares">Softwares</a></li>
<li><a href="#uploaders">Uploaders</a></li>
<li><a href="#schemas">Schemas</a></li>
</ul>
</div>
</div>
</nav>
</header>
<section class="container">
<div class="row">
<div class="col-md-12">
<p>
This page shows transaction information (messages sent/received) for key points of the EDDN, over a one-, five- and sixty-minute period.<br />
Values automatically update every minute.
</p>
</div>
</div>
</section>
<section id="gateways" class="container">
<div class="row">
<div class="col-md-6">
<h2>Gateways</h2>
</div>
<div class="col-md-6">
<div class="text-right form-inline">
<select name="gateways" class="form-control"></select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<table class="table">
<thead>
<tr>
<th style="width: 50%;"></th>
<th>1 min</th>
<th>5 min</th>
<th>60 min</th>
</tr>
</thead>
<tbody>
<tr>
<th>Messages received</th>
<td class="inbound_1min stat">-</td>
<td class="inbound_5min stat">-</td>
<td class="inbound_60min stat">-</td>
</tr>
<tr>
<th>Invalid messages</th>
<td class="invalid_1min stat">-</td>
<td class="invalid_5min stat">-</td>
<td class="invalid_60min stat">-</td>
</tr>
<tr>
<th>Messages passed to relay</th>
<td class="outbound_1min stat">-</td>
<td class="outbound_5min stat">-</td>
<td class="outbound_60min stat">-</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-12 charts">
</div>
</div>
<div class="row">
<div class="col-md-4">
<p class="text-muted small">
EDDN version: <strong><span class="version">N/A</span></strong>.
</p>
</div>
<div class="col-md-4">
<p class="text-muted small">
Uptime: <strong><span class="uptime">N/A</span></strong>.
</p>
</div>
<div class="col-md-4">
<p class="text-muted small">
Last updated: <strong><span class="update_timestamp">N/A</span></strong>.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="relays" class="container">
<div class="row">
<div class="col-md-6">
<h2>Relays</h2>
</div>
<div class="col-md-6">
<div class="text-right form-inline">
<select name="relays" class="form-control"></select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-12">
<table class="table">
<thead>
<tr>
<th style="width: 50%;"></th>
<th>1 min</th>
<th>5 min</th>
<th>60 min</th>
</tr>
</thead>
<tbody>
<tr>
<th>Messages received</th>
<td class="inbound_1min stat">-</td>
<td class="inbound_5min stat">-</td>
<td class="inbound_60min stat">-</td>
</tr>
<tr>
<th>Messages duplicate</th>
<td class="duplicate_1min stat">-</td>
<td class="duplicate_5min stat">-</td>
<td class="duplicate_60min stat">-</td>
</tr>
<tr>
<th>Messages passed to subscribers</th>
<td class="outbound_1min stat">-</td>
<td class="outbound_5min stat">-</td>
<td class="outbound_60min stat">-</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-12 charts">
</div>
</div>
<div class="row">
<div class="col-md-4">
<p class="text-muted small">
EDDN version: <strong><span class="version">N/A</span></strong>.
</p>
</div>
<div class="col-md-4">
<p class="text-muted small">
Uptime: <strong><span class="uptime">N/A</span></strong>.
</p>
</div>
<div class="col-md-4">
<p class="text-muted small">
Last updated: <strong><span class="update_timestamp">N/A</span></strong>.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="softwares" class="container">
<div class="row">
<div class="col-md-6">
<h2>Softwares</h2>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width: 20px;"></th>
<th style="width: 50%;"></th>
<th>Today hits</th>
<th>Yesterday hits</th>
<th>Total hits</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="chart" style="height: 450px;"></div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-8">
<p class="text-muted small">
Last updated: <strong><span class="update_timestamp">N/A</span></strong>.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="uploaders" class="container">
<div class="row">
<div class="col-md-6">
<h2>Uploaders (Top 20)</h2>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width: 20px;"></th>
<th style="width: 50%;"></th>
<th>Today hits</th>
<th>Yesterday hits</th>
<th>Total hits</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="chart" style="height: 450px;"></div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-8">
<p class="text-muted small">
Last updated: <strong><span class="update_timestamp">N/A</span></strong>.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="schemas" class="container">
<div class="row">
<div class="col-md-6">
<h2>Schemas</h2>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-md-7">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width: 20px;"></th>
<th style="width: 50%;"></th>
<th>Today hits</th>
<th>Yesterday hits</th>
<th>Total hits</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="chart" style="height: 450px;"></div>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-8">
<p class="text-muted small">
Last updated: <strong><span class="update_timestamp">N/A</span></strong>.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<footer>
<nav class="navbar navbar-default">
<div class="container">
<p class="navbar-text">
The <em>Elite: Dangerous Data Network</em> is volunteer-run and not affiliated with <a href="https://frontier.co.uk/" target="_blank">Frontier Developments</a>.
|
Charts by <a href="http://www.highcharts.com/" target="_blank">HighCharts</a>
</p>
</div>
</nav>
</footer>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="http://code.highcharts.com/4.1.5/highcharts.js"></script>
<script src="./js/eddn.js"></script>
</body>
</html>

View File

@ -1,10 +1,10 @@
/**
* Version: 1.0 Alpha-1
* Build Date: 13-Nov-2007
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
*/
/**
* Version: 1.0 Alpha-1
* Build Date: 13-Nov-2007
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
*/
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
@ -101,4 +101,4 @@ return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};

535
contrib/monitor/js/eddn.js Normal file
View File

@ -0,0 +1,535 @@
var updateInterval = 60000,
monitorEndPoint = 'http://eddn-monitor.ed-td.space:9091/',
gatewayBottlePort = 8080,
relayBottlePort = 9090,
gateways = [
'eddn-gateway.elite-markets.net',
'eddn-gateway.ed-td.space'
]; // Must find a way to bind them to monitor,
relays = [
'eddn-relay.elite-markets.net',
'eddn-relay.ed-td.space'
]; // Must find a way to bind them to monitor
var stats = {
'gateways' : {},
'relays' : {}
}; // Stats placeholder
formatNumber = function(num) {
return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,")
}
makeSlug = function(str) {
var slugcontent_hyphens = str.replace(/\s/g,'-');
var finishedslug = slugcontent_hyphens.replace(/[^a-zA-Z0-9\-]/g,'');
return finishedslug.toLowerCase();
}
secondsToDurationString = function(seconds) {
var hours = Math.floor(seconds / 3600);
var minutes = Math.floor((seconds - (hours * 3600)) / 60);
var seconds = seconds - (hours * 3600) - (minutes * 60);
var days = 0;
if (hours > 24) {
days = Math.floor(hours / 24)
hours = Math.floor((hours - days * 24) / 3600);
}
if (hours < 10) {hours = "0" + hours;}
if (minutes < 10) {minutes = "0" + minutes;}
if (seconds < 10) {seconds = "0" + seconds;}
if (days > 0) {
return days + "d " + hours + ":" + minutes + ":" + seconds;
}
else {
return hours + ":" + minutes + ":" + seconds;
}
}
var doUpdateSoftwares = function()
{
var dToday = new Date(),
dYesterday = new (function(d){ d.setDate(d.getDate()-1); return d})(new Date),
yesterday = dYesterday.getUTCFullYear() + '-' + ("0" + (dYesterday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dYesterday.getUTCDate())).slice(-2),
today = dToday.getUTCFullYear() + '-' + ("0" + (dToday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dToday.getUTCDate())).slice(-2);
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getSoftwares/?dateStart=' + yesterday + '&dateEnd = ' + today,
success: function(softwares){
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getTotalSoftwares/',
success: function(softwaresTotal){
var chart = $('#softwares .chart').highcharts(),
series = chart.get('softwares');
$('#softwares .table tbody').empty();
$.each(softwaresTotal, function(software, hits){
$('#softwares .table tbody').append(
newTr = $('<tr>').attr('data-name', software).on('mouseover', function(){
chart.get('software-' + makeSlug(software)).setState('hover');
chart.tooltip.refresh(chart.get('software-' + makeSlug(software)));
}).on('mouseout', function(){
chart.get('software-' + makeSlug(software)).setState('');
chart.tooltip.hide();
}).append(
$('<td>').addClass('square')
).append(
$('<td>').html('<strong>' + software + '</strong>')
)
.append(
$('<td>').addClass('stat today').html(formatNumber(softwares[today][software] || 0))
)
.append(
$('<td>').addClass('stat yesterday').html(formatNumber(softwares[yesterday][software] || 0))
)
.append(
$('<td>').addClass('stat total').html('<strong>' + formatNumber(hits) + '</strong>')
)
);
if(!chart.get('software-' + makeSlug(software)))
series.addPoint({id: 'software-' + makeSlug(software), name: software, y: parseInt(hits)}, false);
else
chart.get('software-' + makeSlug(software)).update(parseInt(hits), false);
newTr.find('.square').css('background', chart.get('software-' + makeSlug(software)).color);
});
chart.redraw();
$('#softwares').find(".stat").removeClass("warning").each(function() {
if ($(this).html() == "0")
$(this).addClass("warning");
});
$('#softwares').find(".update_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss"));
}
});
}
});
}
var doUpdateUploaders = function()
{
var dToday = new Date(),
dYesterday = new (function(d){ d.setDate(d.getDate()-1); return d})(new Date),
yesterday = dYesterday.getUTCFullYear() + '-' + ("0" + (dYesterday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dYesterday.getUTCDate())).slice(-2),
today = dToday.getUTCFullYear() + '-' + ("0" + (dToday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dToday.getUTCDate())).slice(-2);
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getUploaders/?dateStart=' + yesterday + '&dateEnd = ' + today,
success: function(uploaders){
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getTotalUploaders/',
success: function(uploadersTotal){
var chart = $('#uploaders .chart').highcharts(),
series = chart.get('uploaders');
$('#uploaders .table tbody').empty();
$.each(uploadersTotal, function(uploader, hits){
if(uploader.length > 32)
truncateUploader = jQuery.trim(uploader).substring(0, 32)/*.split(" ").slice(0, -1).join(" ")*/ + "..."
else
truncateUploader = uploader
$('#uploaders .table tbody').append(
newTr = $('<tr>').attr('data-name', uploader).on('mouseover', function(){
chart.get('uploader-' + makeSlug(uploader)).setState('hover');
chart.tooltip.refresh(chart.get('uploader-' + makeSlug(uploader)));
}).on('mouseout', function(){
chart.get('uploader-' + makeSlug(uploader)).setState('');
chart.tooltip.hide();
}).append(
$('<td>').addClass('square')
).append(
$('<td>').html('<strong>' + truncateUploader + '</strong>')
)
.append(
$('<td>').addClass('stat today').html(formatNumber(uploaders[today][uploader] || 0))
)
.append(
$('<td>').addClass('stat yesterday').html(formatNumber(uploaders[yesterday][uploader] || 0))
)
.append(
$('<td>').addClass('stat total').html('<strong>' + formatNumber(hits) + '</strong>')
)
);
if(!chart.get('uploader-' + makeSlug(uploader)))
series.addPoint({id: 'uploader-' + makeSlug(uploader), name: uploader, y: parseInt(hits)}, false);
else
chart.get('uploader-' + makeSlug(uploader)).update(parseInt(hits), false);
newTr.find('.square').css('background', chart.get('uploader-' + makeSlug(uploader)).color);
});
chart.redraw();
$('#uploaders').find(".stat").removeClass("warning").each(function() {
if ($(this).html() == "0")
$(this).addClass("warning");
});
$('#uploaders').find(".update_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss"));
}
});
}
});
}
var doUpdateSchemas = function()
{
var dToday = new Date(),
dYesterday = new (function(d){ d.setDate(d.getDate()-1); return d})(new Date),
yesterday = dYesterday.getUTCFullYear() + '-' + ("0" + (dYesterday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dYesterday.getUTCDate())).slice(-2),
today = dToday.getUTCFullYear() + '-' + ("0" + (dToday.getUTCMonth() + 1)).slice(-2) + '-' + ("0" + (dToday.getUTCDate())).slice(-2);
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getSchemas/?dateStart=' + yesterday + '&dateEnd = ' + today,
success: function(schemas){
$.ajax({
dataType: "json",
url: monitorEndPoint + 'getTotalSchemas/',
success: function(schemasTotal){
var chart = $('#schemas .chart').highcharts(),
series = chart.get('schemas');
$('#schemas .table tbody').empty();
$.each(schemasTotal, function(schema, hits){
$('#schemas .table tbody').append(
newTr = $('<tr>').attr('data-name', schema).on('mouseover', function(){
chart.get('schema-' + makeSlug(schema)).setState('hover');
chart.tooltip.refresh(chart.get('schema-' + makeSlug(schema)));
}).on('mouseout', function(){
chart.get('schema-' + makeSlug(schema)).setState('');
chart.tooltip.hide();
}).append(
$('<td>').addClass('square')
).append(
$('<td>').html('<strong>' + schema + '</strong>')
)
.append(
$('<td>').addClass('stat today').html(formatNumber(schemas[today][schema] || 0))
)
.append(
$('<td>').addClass('stat yesterday').html(formatNumber(schemas[yesterday][schema] || 0))
)
.append(
$('<td>').addClass('stat total').html('<strong>' + formatNumber(hits) + '</strong>')
)
);
if(!chart.get('schema-' + makeSlug(schema)))
series.addPoint({id: 'schema-' + makeSlug(schema), name: schema, y: parseInt(hits)}, false);
else
chart.get('schema-' + makeSlug(schema)).update(parseInt(hits), false);
newTr.find('.square').css('background', chart.get('schema-' + makeSlug(schema)).color);
});
chart.redraw();
$('#schemas').find(".stat").removeClass("warning").each(function() {
if ($(this).html() == "0")
$(this).addClass("warning");
});
$('#schemas').find(".update_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss"));
}
});
}
});
}
var doUpdates = function(type){
$("select[name=" + type + "] option").each(function(){
var currentItem = $(this).html(),
isSelected = $(this).is(':selected');
$.ajax({
dataType: "json",
url: $(this).val(),
success: function(data){
d = new Date();
stats[type][currentItem]['lastUpdate'] = d.toString("yyyy-MM-dd HH:mm:ss");
stats[type][currentItem]['last'] = data;
if(isSelected)
showStats(type, currentItem);
var chart = $("#" + type + " .chart[data-name='" + currentItem + "']").highcharts();
shift = chart.get('inbound').data.length > 60;
chart.get('inbound').addPoint([d.getTime(), (data['inbound'] || {})['1min'] || 0], true, shift);
if(type == 'gateways')
{
shift = chart.get('invalid').data.length > 60;
chart.get('invalid').addPoint([d.getTime(), (data['invalid'] || {})['1min'] || 0], true, shift);
}
if(type == 'relays')
{
shift = chart.get('duplicate').data.length > 60;
chart.get('duplicate').addPoint([d.getTime(), (data['duplicate'] || {})['1min'] || 0], true, shift);
}
shift = chart.get('outbound').data.length > 60;
chart.get('outbound').addPoint([d.getTime(), (data['outbound'] || {})['1min'] || 0], true, shift);
}
});
});
};
var showStats = function(type, currentItem){
var el = $('#' + type),
currentItemStats = stats[type][currentItem]['last'];
el.find(".inbound_1min").html((currentItemStats['inbound'] || {})['1min'] || 0);
el.find(".inbound_5min").html((currentItemStats["inbound"] || {})['5min'] || 0);
el.find(".inbound_60min").html((currentItemStats["inbound"] || {})['60min'] || 0);
if(type == 'gateways')
{
el.find(".invalid_1min").html((currentItemStats["invalid"] || {})['1min'] || 0);
el.find(".invalid_5min").html((currentItemStats["invalid"] || {})['5min'] || 0);
el.find(".invalid_60min").html((currentItemStats["invalid"] || {})['60min'] || 0);
}
if(type == 'relays')
{
el.find(".duplicate_1min").html((currentItemStats["duplicate"] || {})['1min'] || 0);
el.find(".duplicate_5min").html((currentItemStats["duplicate"] || {})['5min'] || 0);
el.find(".duplicate_60min").html((currentItemStats["duplicate"] || {})['60min'] || 0);
}
el.find(".outbound_1min").html((currentItemStats["outbound"] || {})['1min'] || 0);
el.find(".outbound_5min").html((currentItemStats["outbound"] || {})['5min'] || 0);
el.find(".outbound_60min").html((currentItemStats["outbound"] || {})['60min'] || 0);
el.find(".update_timestamp").html(stats[type][currentItem]['lastUpdate']);
el.find(".version").html(currentItemStats['version'] || 'N/A');
if (currentItemStats['uptime'])
el.find(".uptime").html(secondsToDurationString(currentItemStats['uptime']));
else
el.find(".uptime").html('N/A');
el.find(".stat").removeClass("warning").each(function() {
if ($(this).html() == "0")
$(this).addClass("warning");
});
el.find(".chart").hide();
el.find(".chart[data-name='" + currentItem + "']").show();
$(window).trigger('resize'); // Fix wrong size in chart
};
/**
* Launch monitoring
*/
var start = function(){
Highcharts.setOptions({global: {useUTC: false}});
// Grab gateways
gateways = gateways.sort();
$.each(gateways, function(k, gateway){
gateway = gateway.replace('tcp://', '');
gateway = gateway.replace(':8500', '');
$("select[name=gateways]").append($('<option>', {
value: 'http://' + gateway + ':' + gatewayBottlePort + '/stats/',
text : gateway
}));
$('#gateways .charts').append(
$('<div>').addClass('chart')
.css('width', '100%')
.attr('data-name', gateway)
);
$("#gateways .chart[data-name='" + gateway + "']").highcharts({
chart: {
type: 'spline', animation: Highcharts.svg
},
title: { text: '', style: {display: 'none'} },
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {text: ''},
plotLines: [{value: 0, width: 1, color: '#808080'}],
min: 0
},
tooltip: { enabled: false },
credits: { enabled: false },
exporting: { enabled: false },
series: [
{id: 'inbound', data: [], name: 'Messages received', zIndex: 300},
{id: 'invalid', data: [], name: 'Invalid messages', zIndex: 1},
{id: 'outbound', data: [], name: 'Messages passed to relay', zIndex: 200}
]
}).hide();
stats['gateways'][gateway] = {};
});
doUpdates('gateways');
setInterval(function(){
doUpdates('gateways');
}, updateInterval);
// Grab relays
relays = relays.sort();
$.each(relays, function(k, relay){
$("select[name=relays]").append($('<option>', {
value: 'http://' + relay + ':' + relayBottlePort + '/stats/',
text : relay
}));
$('#relays .charts').append(
$('<div>').addClass('chart')
.css('width', '100%')
.attr('data-name', relay)
);
$("#relays .chart[data-name='" + relay + "']").highcharts({
chart: {
type: 'spline', animation: Highcharts.svg,
events: {
load: function(){ setTimeout(function(){$(window).trigger('resize');}, 250); }
},
marginRight: 10
},
title: { text: '', style: {display: 'none'} },
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {text: ''},
plotLines: [{value: 0, width: 1, color: '#808080'}],
min: 0
},
tooltip: { enabled: false },
credits: { enabled: false },
exporting: { enabled: false },
series: [
{id: 'inbound', data: [], name: 'Messages received', zIndex: 300},
{id: 'duplicate', data: [], name: 'Messages duplicate', zIndex: 1},
{id: 'outbound', data: [], name: 'Messages passed to subscribers', zIndex: 200}
]
}).hide();
stats['relays'][relay] = {};
});
doUpdates('relays');
setInterval(function(){
doUpdates('relays');
}, updateInterval);
// Grab software from monitor
$('#softwares .chart').highcharts({
chart: {
type: 'pie', animation: Highcharts.svg
},
title: { text: '', style: {display: 'none'} },
credits: { enabled: false },
tooltip: { headerFormat: '', pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' },
legend: { enabled: false },
plotOptions: {pie: {allowPointSelect: false,dataLabels: { enabled: false }}},
series: [{
id: 'softwares',
type: 'pie',
data: []
}]
});
doUpdateSoftwares();
setInterval(function(){
doUpdateSoftwares();
}, updateInterval);
// Grab uploader from monitor
$('#uploaders .chart').highcharts({
chart: {
type: 'pie', animation: Highcharts.svg
},
title: { text: '', style: {display: 'none'} },
credits: { enabled: false },
tooltip: { headerFormat: '', pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' },
legend: { enabled: false },
plotOptions: {pie: {allowPointSelect: false,dataLabels: { enabled: false }}},
series: [{
id: 'uploaders',
type: 'pie',
data: []
}]
});
doUpdateUploaders();
setInterval(function(){
doUpdateUploaders();
}, updateInterval);
// Grab schema from monitor
$('#schemas .chart').highcharts({
chart: {
type: 'pie', animation: Highcharts.svg
},
title: { text: '', style: {display: 'none'} },
credits: { enabled: false },
tooltip: { headerFormat: '', pointFormat: '{point.name}: <b>{point.percentage:.1f}%</b>' },
legend: { enabled: false },
plotOptions: {pie: {allowPointSelect: false,dataLabels: { enabled: false }}},
series: [{
id: 'schemas',
type: 'pie',
data: []
}]
});
doUpdateSchemas();
setInterval(function(){
doUpdateSchemas();
}, updateInterval);
// Attach events
$("select[name=gateways]").change(function(){
showStats('gateways', $(this).find('option:selected').html());
});
$("select[name=relays]").change(function(){
showStats('relays', $(this).find('option:selected').html());
});
}
$(document).ready(function(){
start();
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,95 +0,0 @@
secondsToDurationString = function(seconds) {
var hours = Math.floor(seconds / 3600);
var minutes = Math.floor((seconds - (hours * 3600)) / 60);
var seconds = seconds - (hours * 3600) - (minutes * 60);
var days = 0;
if (hours > 24) {
days = Math.floor(hours / 24)
hours = Math.floor((seconds - days * 24) / 3600);
}
if (hours < 10) {hours = "0" + hours;}
if (minutes < 10) {minutes = "0" + minutes;}
if (seconds < 10) {seconds = "0" + seconds;}
if (days > 0) {
return days + "d " + hours + ":" + minutes + ":" + seconds;
}
else {
return hours + ":" + minutes + ":" + seconds;
}
}
displayGatewayStats = function(stats) {
$("#gateway_inbound_1min").html((stats["inbound"] || {})["1min"] || 0);
$("#gateway_inbound_5min").html((stats["inbound"] || {})["5min"] || 0);
$("#gateway_inbound_60min").html((stats["inbound"] || {})["60min"] || 0);
$("#gateway_invalid_1min").html((stats["invalid"] || {})["1min"] || 0);
$("#gateway_invalid_5min").html((stats["invalid"] || {})["5min"] || 0);
$("#gateway_invalid_60min").html((stats["invalid"] || {})["60min"] || 0);
$("#gateway_outbound_1min").html((stats["outbound"] || {})["1min"] || 0);
$("#gateway_outbound_5min").html((stats["outbound"] || {})["5min"] || 0);
$("#gateway_outbound_60min").html((stats["outbound"] || {})["60min"] || 0);
d = new Date();
$("#update_gateway_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss"));
$("#gateway_version").html(stats['version'] || "N/A");
if (stats['uptime']) {
$("#gateway_uptime").html(secondsToDurationString(stats['uptime']));
}
formatStats();
}
displayRelayStats = function(stats) {
$("#relay_inbound_1min").html((stats["inbound"] || {})["1min"] || 0);
$("#relay_inbound_5min").html((stats["inbound"] || {})["5min"] || 0);
$("#relay_inbound_60min").html((stats["inbound"] || {})["60min"] || 0);
$("#relay_outbound_1min").html((stats["outbound"] || {})["1min"] || 0);
$("#relay_outbound_5min").html((stats["outbound"] || {})["5min"] || 0);
$("#relay_outbound_60min").html((stats["outbound"] || {})["60min"] || 0);
d = new Date();
$("#update_relay_timestamp").html(d.toString("yyyy-MM-dd HH:mm:ss"));
$("#relay_version").html(stats['version'] || "N/A");
if (stats['uptime']) {
$("#relay_uptime").html(secondsToDurationString(stats['uptime']));
}
formatStats();
}
formatStats = function() {
$(".stat").each(function() {
if ($(this).html() == "0") {
$(this).addClass("warning");
}
else {
$(this).removeClass("warning");
}
});
}
doUpdate = function(url, success, failure) {
$.ajax({
dataType: "json",
url: url,
success: success,
failure: failure
});
}
getGatewayUrl = function() {
return $("#gateway").val();
}
getRelayUrl = function() {
return $("#relay").val();
}
$("#gateway").change(function() {doUpdate(getGatewayUrl(), displayGatewayStats);})
$("#relay").change(function() {doUpdate(getRelayUrl(), displayRelayStats);})
doUpdate(getGatewayUrl(), displayGatewayStats);
doUpdate(getRelayUrl(), displayRelayStats);
setInterval(function() {doUpdate(getGatewayUrl(), displayGatewayStats); doUpdate(getRelayUrl(), displayRelayStats)}, 60000);

File diff suppressed because one or more lines are too long

View File

@ -1,109 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EDDN Status</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/eddn.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script src="js/jquery.min.js"></script>
<script src="js/date.js"></script>
</head>
<body>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h1>EDDN Status</h1>
</div>
<div class="panel-body">
<p>This page shows transaction information (messages sent/received) for key points of the EDDN, over a one-, five- and sixty-minute period. Values automatically update every minute.</p>
<h2>Gateway</h2>
<table class="table">
<thead>
<tr>
<td class="form-inline">
<select id="gateway" class="form-control">
<option value="http://eddn-gateway.elite-markets.net:8080/stats/">eddn-gateway.elite-markets.net</option>
</select>
</td>
<th>1 min</th>
<th>5 min</th>
<th>60 min</th>
</tr>
</thead>
<tbody>
<tr>
<th>Messages received</th>
<td id="gateway_inbound_1min" class="stat">-</td>
<td id="gateway_inbound_5min" class="stat">-</td>
<td id="gateway_inbound_60min" class="stat">-</td>
</tr>
<tr>
<th>Invalid messages</th>
<td id="gateway_invalid_1min" class="stat">-</td>
<td id="gateway_invalid_5min" class="stat">-</td>
<td id="gateway_invalid_60min" class="stat">-</td>
</tr>
<tr>
<th>Messages passed to relay</th>
<td id="gateway_outbound_1min" class="stat">-</td>
<td id="gateway_outbound_5min" class="stat">-</td>
<td id="gateway_outbound_60min" class="stat">-</td>
</tr>
</tbody>
</table>
<p class="text-muted small">EDDN version <span id="gateway_version">N/A</span>. Uptime: <span id="gateway_uptime">N/A</span>. Last updated <span id="update_gateway_timestamp">N/A</span></p>
<h2>Relay</h2>
<table class="table">
<thead>
<tr>
<td class="form-inline">
<select id="relay" class="form-control">
<option value="http://eddn-relay.elite-markets.net:9090/stats/">eddn-relay.elite-markets.net</option>
</select>
</td>
<th>1 min</th>
<th>5 min</th>
<th>60 min</th>
</tr>
</thead>
<tbody>
<tr>
<th>Messages received</th>
<td id="relay_inbound_1min" class="stat">-</td>
<td id="relay_inbound_5min" class="stat">-</td>
<td id="relay_inbound_60min" class="stat">-</td>
</tr>
<tr>
<th>Messages passed to subscribers</th>
<td id="relay_outbound_1min" class="stat">-</td>
<td id="relay_outbound_5min" class="stat">-</td>
<td id="relay_outbound_60min" class="stat">-</td>
</tr>
</tbody>
</table>
<p class="text-muted small">EDDN version <span id="relay_version">N/A</span>. Uptime: <span id="relay_uptime">N/A</span>. Last updated <span id="update_relay_timestamp">N/A</span></p>
</div>
<div class="panel-footer">
<p>The <em>Elite: Dangerous Data Network</em> is volunteer-run and not affiliated with Frontier Developments.</p>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
<script src="js/eddn.js"></script>
</body>
</html>

View File

@ -103,6 +103,9 @@ while (true)
try
{
$subscriber->connect($relayEDDN);
echoLog('Connect to ' . $relayEDDN);
echoLog('');
echoLog('');
while (true)
{
@ -111,6 +114,9 @@ while (true)
if ($message === false)
{
$subscriber->disconnect($relayEDDN);
echoLog('Disconnect from ' . $relayEDDN);
echoLog('');
echoLog('');
break;
}

View File

@ -52,13 +52,14 @@
class EDDN
{
static private $_debug = true;
private static $_debug = true;
static private $_gateways = array(
'http://eddn-gateway.elite-markets.net:8080/upload/'
private static $_gateways = array(
'http://eddn-gateway.elite-markets.net:8080/upload/',
'http://eddn-gateway.ed-td.space:8080/upload/'
);
static private $_schemas = array(
private static $_schemas = array(
'commodity-v1' => array(
'production' => 'http://schemas.elite-markets.net/eddn/commodity/1',
'test' => 'http://schemas.elite-markets.net/eddn/commodity/1/test'
@ -176,6 +177,8 @@ class EDDN
{
return $this->_uploaderID;
}
public function setSoftwareName($value)
{
$this->_softwareName = $value;
@ -185,6 +188,8 @@ class EDDN
{
return $this->_softwareName;
}
public function setSoftwareVersion($value)
{
$this->_softwareVersion = $value;
@ -194,4 +199,15 @@ class EDDN
{
return $this->_softwareVersion;
}
public function setDebug($value)
{
self::$_debug = $value;
}
public function getDebug()
{
return self::$_debug;
}
}

View File

@ -0,0 +1,3 @@
"C:\Python27\python.exe" "%~dp0\Client_Complete.py"
pause

View File

@ -1,5 +1,5 @@
import zlib
import zmq.green as zmq
import zmq
import simplejson
import sys, os, datetime, time
@ -96,12 +96,18 @@ def main():
while True:
try:
subscriber.connect(__relayEDDN)
echoLog('Connect to ' + __relayEDDN)
echoLog('')
echoLog('')
while True:
__message = subscriber.recv()
if __message == False:
subscriber.disconnect(__relayEDDN)
echoLog('Disconnect from ' + __relayEDDN)
echoLog('')
echoLog('')
break
__message = zlib.decompress(__message)
@ -112,8 +118,8 @@ def main():
# Handle commodity v1
if __json['$schemaRef'] == 'http://schemas.elite-markets.net/eddn/commodity/1' + ('/test' if (__debugEDDN == True) else ''):
echoLogJSON(__message)
echoLog('Receiving commodity-v1 message...');
echoLog(' - Converting to v2...');
echoLog('Receiving commodity-v1 message...')
echoLog(' - Converting to v2...')
__temp = {}
__temp['$schemaRef'] = 'http://schemas.elite-markets.net/eddn/commodity/2' + ('/test' if (__debugEDDN == True) else '')

View File

@ -0,0 +1,3 @@
"C:\Python27\python.exe" "%~dp0\Client_Simple.py"
pause

View File

@ -1,5 +1,5 @@
import zlib
import zmq.green as zmq
import zmq
import simplejson
import sys

View File

@ -0,0 +1,3 @@
"C:\Python34\python.exe" "%~dp0\Client_Complete.py"
pause

View File

@ -0,0 +1,218 @@
import zlib
import zmq
import simplejson
import sys, os, datetime, time
"""
" Configuration
"""
__relayEDDN = 'tcp://eddn-relay.elite-markets.net:9500'
__timeoutEDDN = 600000
# Set False to listen to production stream
__debugEDDN = True;
# Set to False if you do not want verbose logging
__logVerboseFile = os.path.dirname(__file__) + '/Logs_Verbose_EDDN_%DATE%.htm'
#__logVerboseFile = False
# Set to False if you do not want JSON logging
__logJSONFile = os.path.dirname(__file__) + '/Logs_JSON_EDDN_%DATE%.log'
#__logJSONFile = False
# A sample list of authorised softwares
__authorisedSoftwares = [
"EDCE",
"ED-TD.SPACE",
"EliteOCR",
"Maddavo's Market Share",
"RegulatedNoise",
"RegulatedNoise__DJ"
]
# Used this to excludes yourself for example has you don't want to handle your own messages ^^
__excludedSoftwares = [
'My Awesome Market Uploader'
]
"""
" Start
"""
def date(__format):
d = datetime.datetime.utcnow()
return d.strftime(__format)
__oldTime = False
def echoLog(__str):
global __oldTime, __logVerboseFile
if __logVerboseFile != False:
__logVerboseFileParsed = __logVerboseFile.replace('%DATE%', str(date('%Y-%m-%d')))
if __logVerboseFile != False and not os.path.exists(__logVerboseFileParsed):
f = open(__logVerboseFileParsed, 'w')
f.write('<style type="text/css">html { white-space: pre; font-family: Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace; }</style>')
f.close()
if (__oldTime == False) or (__oldTime != date('%H:%M:%S')):
__oldTime = date('%H:%M:%S')
__str = str(__oldTime) + ' | ' + str(__str)
else:
__str = ' ' + ' | ' + str(__str)
print (__str)
sys.stdout.flush()
if __logVerboseFile != False:
f = open(__logVerboseFileParsed, 'a')
f.write(__str + '\n')
f.close()
def echoLogJSON(__json):
global __logJSONFile
if __logJSONFile != False:
__logJSONFileParsed = __logJSONFile.replace('%DATE%', str(date('%Y-%m-%d')))
f = open(__logJSONFileParsed, 'a')
f.write(str(__json) + '\n')
f.close()
def main():
echoLog('Starting EDDN Subscriber')
echoLog('')
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
subscriber.setsockopt(zmq.SUBSCRIBE, b"")
subscriber.setsockopt(zmq.RCVTIMEO, __timeoutEDDN)
while True:
try:
subscriber.connect(__relayEDDN)
echoLog('Connect to ' + __relayEDDN)
echoLog('')
echoLog('')
while True:
__message = subscriber.recv()
if __message == False:
subscriber.disconnect(__relayEDDN)
echoLog('Disconnect from ' + __relayEDDN)
echoLog('')
echoLog('')
break
__message = zlib.decompress(__message)
__json = simplejson.loads(__message)
__converted = False
# Handle commodity v1
if __json['$schemaRef'] == 'http://schemas.elite-markets.net/eddn/commodity/1' + ('/test' if (__debugEDDN == True) else ''):
echoLogJSON(__message)
echoLog('Receiving commodity-v1 message...')
echoLog(' - Converting to v2...')
__temp = {}
__temp['$schemaRef'] = 'http://schemas.elite-markets.net/eddn/commodity/2' + ('/test' if (__debugEDDN == True) else '')
__temp['header'] = __json['header']
__temp['message'] = {}
__temp['message']['systemName'] = __json['message']['systemName']
__temp['message']['stationName'] = __json['message']['stationName']
__temp['message']['timestamp'] = __json['message']['timestamp']
__temp['message']['commodities'] = []
__commodity = {}
if 'itemName' in __json['message']:
__commodity['name'] = __json['message']['itemName']
if 'buyPrice' in __json['message']:
__commodity['buyPrice'] = __json['message']['buyPrice']
if 'stationStock' in __json['message']:
__commodity['supply'] = __json['message']['stationStock']
if 'supplyLevel' in __json['message']:
__commodity['supplyLevel'] = __json['message']['supplyLevel']
if 'sellPrice' in __json['message']:
__commodity['sellPrice'] = __json['message']['sellPrice']
if 'demand' in __json['message']:
__commodity['demand'] = __json['message']['demand']
if'demandLevel' in __json['message']:
__commodity['demandLevel'] = __json['message']['demandLevel']
__temp['message']['commodities'].append(__commodity)
__json = __temp
del __temp, __commodity
__converted = True
# Handle commodity v2
if __json['$schemaRef'] == 'http://schemas.elite-markets.net/eddn/commodity/2' + ('/test' if (__debugEDDN == True) else ''):
if __converted == False:
echoLogJSON(__message)
echoLog('Receiving commodity-v2 message...')
__authorised = False
__excluded = False
if __json['header']['softwareName'] in __authorisedSoftwares:
__authorised = True
if __json['header']['softwareName'] in __excludedSoftwares:
__excluded = True
echoLog(' - Software: ' + __json['header']['softwareName'] + ' / ' + __json['header']['softwareVersion'])
echoLog(' - ' + 'AUTHORISED' if (__authorised == True) else
('EXCLUDED' if (__excluded == True) else 'UNAUTHORISED')
)
if __authorised == True and __excluded == False:
# Do what you want with the data...
# Have fun !
# For example
echoLog(' - Timestamp: ' + __json['message']['timestamp'])
echoLog(' - Uploader ID: ' + __json['header']['uploaderID'])
echoLog(' - System Name: ' + __json['message']['systemName'])
echoLog(' - Station Name: ' + __json['message']['stationName'])
for __commodity in __json['message']['commodities']:
echoLog(' - Name: ' + __commodity['name'])
echoLog(' - Buy Price: ' + str(__commodity['buyPrice']))
echoLog(' - Supply: ' + str(__commodity['supply'])
+ ((' (' + __commodity['supplyLevel'] + ')') if 'supplyLevel' in __commodity else '')
)
echoLog(' - Sell Price: ' + str(__commodity['sellPrice']))
echoLog(' - Demand: ' + str(__commodity['demand'])
+ ((' (' + __commodity['demandLevel'] + ')') if 'demandLevel' in __commodity else '')
)
# End example
del __authorised, __excluded
echoLog('')
echoLog('')
del __converted
except zmq.ZMQError as e:
echoLog('')
echoLog('ZMQSocketException: ' + str(e))
echoLog('')
time.sleep(10)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,3 @@
"C:\Python34\python.exe" "%~dp0\Client_Simple.py"
pause

View File

@ -0,0 +1,50 @@
import zlib
import zmq
import simplejson
import sys
"""
" Configuration
"""
__relayEDDN = 'tcp://eddn-relay.elite-markets.net:9500'
__timeoutEDDN = 600000
"""
" Start
"""
def main():
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
subscriber.setsockopt(zmq.SUBSCRIBE, b"")
subscriber.setsockopt(zmq.RCVTIMEO, __timeoutEDDN)
while True:
try:
subscriber.connect(__relayEDDN)
while True:
__message = subscriber.recv()
if __message == False:
subscriber.disconnect(__relayEDDN)
break
__message = zlib.decompress(__message)
__json = simplejson.loads(__message)
print (__json)
sys.stdout.flush()
except zmq.ZMQError as e:
print ('ZMQSocketException: ' + str(e))
sys.stdout.flush()
time.sleep(10)
if __name__ == '__main__':
main()

View File

@ -1,3 +0,0 @@
"python" %~dp0\Client_Complete.py
pause

View File

@ -1,3 +0,0 @@
"python" %~dp0\Client_Simple.py
pause

View File

@ -10,6 +10,7 @@
"header" : {
"type" : "object",
"additionalProperties" : true,
"required": [ "uploaderID", "softwareName", "softwareVersion" ],
"properties" : {
"uploaderID" : { "type": "string" },
"softwareName" : { "type": "string" },

View File

@ -3,7 +3,7 @@ import re
import glob
VERSIONFILE = "src/eddn/_version.py"
VERSIONFILE = "src/eddn/_Conf/Version.py"
verstr = "unknown"
try:
verstrline = open(VERSIONFILE, "rt").read()
@ -34,6 +34,7 @@ setup(
'console_scripts': [
'eddn-gateway = eddn.Gateway:main',
'eddn-relay = eddn.Relay:main',
'eddn-monitor = eddn.Monitor:main',
],
}
)

View File

@ -14,7 +14,7 @@ from datetime import datetime
import os
from eddn._Conf.Settings import Settings, loadConfig
from eddn.Validator import Validator, ValidationSeverity
from eddn._Core.Validator import Validator, ValidationSeverity
from gevent import monkey
monkey.patch_all()
@ -29,7 +29,7 @@ sender = context.socket(zmq.PUB)
validator = Validator()
# This import must be done post-monkey-patching!
from eddn.StatsCollector import StatsCollector
from eddn._Core.StatsCollector import StatsCollector
statsCollector = StatsCollector()
statsCollector.start()
@ -42,11 +42,10 @@ def configure():
sender.bind(binding)
for schemaRef, schemaFile in Settings.GATEWAY_JSON_SCHEMAS.iteritems():
#filename = resource_filename(Requirement.parse("eddn"), schemaFile)
validator.addSchemaResource(schemaRef, os.path.dirname(__file__) + '/' + schemaFile)
def push_message(string_message):
def push_message(string_message, topic):
"""
Spawned as a greenlet to push messages (strings) through ZeroMQ.
This is a dumb method that just pushes strings; it assumes you've already validated
@ -54,9 +53,9 @@ def push_message(string_message):
"""
# Push a zlib compressed JSON representation of the message to
# announcers.
# announcers with schema as topic
compressed_msg = zlib.compress(string_message)
sender.send(compressed_msg)
sender.send("%s |-| %s" % (topic, compressed_msg))
statsCollector.tally("outbound")
@ -143,7 +142,7 @@ def parse_and_error_handle(data):
# Sends the parsed MarketOrderList or MarketHistoryList to the Announcers
# as compressed JSON.
gevent.spawn(push_message, simplejson.dumps(parsed_message))
gevent.spawn(push_message, simplejson.dumps(parsed_message), parsed_message['$schemaRef'])
logger.info("Accepted %s upload from %s" % (
parsed_message, get_remote_address()
))

251
src/eddn/Monitor.py Normal file
View File

@ -0,0 +1,251 @@
"""
Monitor sit below gateways, or another relay, and simply parse what it receives over SUB.
"""
from threading import Thread
import zlib
import gevent
import simplejson
import sqlite3
import datetime
import collections
import zmq.green as zmq
from bottle import get, request, response, run as bottle_run
from eddn._Conf.Settings import Settings, loadConfig
from gevent import monkey
monkey.patch_all()
if Settings.RELAY_DUPLICATE_MAX_MINUTES:
from eddn._Core.DuplicateMessages import DuplicateMessages
duplicateMessages = DuplicateMessages()
duplicateMessages.start()
def date(__format):
d = datetime.datetime.utcnow()
return d.strftime(__format)
@get('/getTotalSoftwares/')
def getTotalSoftwares():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
softwares = collections.OrderedDict()
maxDays = request.GET.get('maxDays', '31').strip()
maxDays = int(maxDays) -1;
query = """SELECT name, SUM(hits) AS total, MAX(dateStats) AS maxDate
FROM softwares
GROUP BY name
HAVING maxDate >= DATE('now', '""" + '-' + str(maxDays) + """ day')
ORDER BY total DESC"""
results = db.execute(query)
for row in results:
softwares[str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(softwares)
@get('/getSoftwares/')
def getSoftwares():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
softwares = collections.OrderedDict()
dateStart = request.GET.get('dateStart', str(date('%Y-%m-%d'))).strip()
dateEnd = request.GET.get('dateEnd', str(date('%Y-%m-%d'))).strip()
query = """SELECT *
FROM softwares
WHERE dateStats BETWEEN ? AND ?
ORDER BY hits DESC, dateStats ASC"""
results = db.execute(query, (dateStart, dateEnd))
for row in results:
if not str(row[2]) in softwares.keys():
softwares[str(row[2])] = collections.OrderedDict()
softwares[str(row[2])][str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(softwares)
@get('/getTotalUploaders/')
def getTotalUploaders():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
uploaders = collections.OrderedDict()
limit = request.GET.get('limit', '20').strip()
query = """SELECT name, SUM(hits) AS total
FROM uploaders
GROUP BY name
ORDER BY total DESC
LIMIT """ + limit
results = db.execute(query)
for row in results:
uploaders[str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(uploaders)
@get('/getUploaders/')
def getUploaders():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
uploaders = collections.OrderedDict()
dateStart = request.GET.get('dateStart', str(date('%Y-%m-%d'))).strip()
dateEnd = request.GET.get('dateEnd', str(date('%Y-%m-%d'))).strip()
query = """SELECT *
FROM uploaders
WHERE dateStats BETWEEN ? AND ?
ORDER BY hits DESC, dateStats ASC"""
results = db.execute(query, (dateStart, dateEnd))
for row in results:
if not str(row[2]) in uploaders.keys():
uploaders[str(row[2])] = collections.OrderedDict()
uploaders[str(row[2])][str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(uploaders)
@get('/getTotalSchemas/')
def getTotalSchemas():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
schemas = collections.OrderedDict()
query = """SELECT name, SUM(hits) AS total
FROM schemas
GROUP BY name
ORDER BY total DESC"""
results = db.execute(query)
for row in results:
schemas[str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(schemas)
@get('/getSchemas/')
def getSchemas():
response.set_header("Access-Control-Allow-Origin", "*")
db = sqlite3.connect(Settings.MONITOR_DB)
schemas = collections.OrderedDict()
dateStart = request.GET.get('dateStart', str(date('%Y-%m-%d'))).strip()
dateEnd = request.GET.get('dateEnd', str(date('%Y-%m-%d'))).strip()
query = """SELECT *
FROM schemas
WHERE dateStats BETWEEN ? AND ?
ORDER BY hits DESC, dateStats ASC"""
results = db.execute(query, (dateStart, dateEnd))
for row in results:
if not str(row[2]) in schemas.keys():
schemas[str(row[2])] = collections.OrderedDict()
schemas[str(row[2])][str(row[0])] = str(row[1])
db.close()
return simplejson.dumps(schemas)
class Monitor(Thread):
def run(self):
context = zmq.Context()
receiver = context.socket(zmq.SUB)
receiver.setsockopt(zmq.SUBSCRIBE, '')
for binding in Settings.MONITOR_RECEIVER_BINDINGS:
receiver.connect(binding)
def monitor_worker(message):
db = sqlite3.connect(Settings.MONITOR_DB)
# Separate topic from message
message = message.split(' |-| ')
# Handle gateway not sending topic
if len(message) > 1:
message = message[1]
else:
message = message[0]
if Settings.RELAY_DUPLICATE_MAX_MINUTES:
if duplicateMessages.isDuplicated(message):
schemaID = 'DUPLICATE MESSAGE'
c = db.cursor()
c.execute('UPDATE schemas SET hits = hits + 1 WHERE `name` = ? AND `dateStats` = DATE("now", "utc")', (schemaID, ))
c.execute('INSERT OR IGNORE INTO schemas (name, dateStats) VALUES (?, DATE("now", "utc"))', (schemaID, ))
db.commit()
return
if Settings.MONITOR_DECOMPRESS_MESSAGES:
message = zlib.decompress(message)
json = simplejson.loads(message)
# Update software count
softwareID = json['header']['softwareName'] + ' | ' + json['header']['softwareVersion']
c = db.cursor()
c.execute('UPDATE softwares SET hits = hits + 1 WHERE `name` = ? AND `dateStats` = DATE("now", "utc")', (softwareID, ))
c.execute('INSERT OR IGNORE INTO softwares (name, dateStats) VALUES (?, DATE("now", "utc"))', (softwareID, ))
db.commit()
# Update uploader count
uploaderID = json['header']['uploaderID']
if uploaderID: # Don't get empty uploaderID
c = db.cursor()
c.execute('UPDATE uploaders SET hits = hits + 1 WHERE `name` = ? AND `dateStats` = DATE("now", "utc")', (uploaderID, ))
c.execute('INSERT OR IGNORE INTO uploaders (name, dateStats) VALUES (?, DATE("now", "utc"))', (uploaderID, ))
db.commit()
# Update schemas count
schemaID = json['$schemaRef']
c = db.cursor()
c.execute('UPDATE schemas SET hits = hits + 1 WHERE `name` = ? AND `dateStats` = DATE("now", "utc")', (schemaID, ))
c.execute('INSERT OR IGNORE INTO schemas (name, dateStats) VALUES (?, DATE("now", "utc"))', (schemaID, ))
db.commit()
db.close()
while True:
inboundMessage = receiver.recv()
gevent.spawn(monitor_worker, inboundMessage)
def main():
loadConfig()
m = Monitor()
m.start()
bottle_run(host='0.0.0.0', port=9091, server='gevent')
if __name__ == '__main__':
main()

View File

@ -13,16 +13,21 @@ import gevent
import simplejson
import zmq.green as zmq
from bottle import get, response, run as bottle_run
from eddn.conf.Settings import Settings, loadConfig
from eddn._Conf.Settings import Settings, loadConfig
from gevent import monkey
monkey.patch_all()
from eddn.StatsCollector import StatsCollector
from eddn._Core.StatsCollector import StatsCollector
statsCollector = StatsCollector()
statsCollector.start()
if Settings.RELAY_DUPLICATE_MAX_MINUTES:
from eddn._Core.DuplicateMessages import DuplicateMessages
duplicateMessages = DuplicateMessages()
duplicateMessages.start()
@get('/stats/')
def stats():
@ -42,12 +47,23 @@ class Relay(Thread):
context = zmq.Context()
receiver = context.socket(zmq.SUB)
receiver.setsockopt(zmq.SUBSCRIBE, '')
# Filters on topics or not...
if Settings.RELAY_RECEIVE_ONLY_GATEWAY_EXTRA_JSON == True:
for schemaRef, schemaFile in Settings.GATEWAY_JSON_SCHEMAS.iteritems():
receiver.setsockopt(zmq.SUBSCRIBE, schemaRef)
for schemaRef, schemaFile in Settings.RELAY_EXTRA_JSON_SCHEMAS.iteritems():
receiver.setsockopt(zmq.SUBSCRIBE, schemaRef)
else:
receiver.setsockopt(zmq.SUBSCRIBE, '')
for binding in Settings.RELAY_RECEIVER_BINDINGS:
# Relays bind upstream to an Announcer, or another Relay.
receiver.connect(binding)
sender = context.socket(zmq.PUB)
for binding in Settings.RELAY_SENDER_BINDINGS:
# End users, or other relays, may attach here.
sender.bind(binding)
@ -58,9 +74,20 @@ class Relay(Thread):
to any subscribers.
:param str message: A JSON string to re-broadcast.
"""
# if is_message_duped(message):
# We've already seen this message recently. Discard it.
# return
# Separate topic from message
message = message.split(' |-| ')
# Handle gateway not sending topic
if len(message) > 1:
message = message[1]
else:
message = message[0]
if Settings.RELAY_DUPLICATE_MAX_MINUTES:
if duplicateMessages.isDuplicated(message):
# We've already seen this message recently. Discard it.
statsCollector.tally("duplicate")
return
if Settings.RELAY_DECOMPRESS_MESSAGES:
message = zlib.decompress(message)

View File

@ -6,7 +6,7 @@ Created on 15 Nov 2014
import argparse
import simplejson
from eddn._version import __version__ as version
from eddn._Conf.Version import __version__ as version
class _Settings(object):
@ -17,11 +17,22 @@ class _Settings(object):
# Relay settings
###############################################################################
RELAY_RECEIVER_BINDINGS = ["tcp://localhost:8500"]
#RELAY_RECEIVER_BINDINGS = ["tcp://localhost:8500"]
RELAY_RECEIVER_BINDINGS = ["tcp://eddn-gateway.elite-markets.net:8500", "tcp://eddn-gateway.ed-td.space:8500"]
RELAY_SENDER_BINDINGS = ["tcp://*:9500"]
RELAY_DECOMPRESS_MESSAGES = False
# If set to False, no deduplicate is made
RELAY_DUPLICATE_MAX_MINUTES = 15
# If set to false, don't listen to topic and accept all incoming messages
RELAY_RECEIVE_ONLY_GATEWAY_EXTRA_JSON = False
RELAY_EXTRA_JSON_SCHEMAS = {
}
###############################################################################
# Gateway settings
@ -36,10 +47,23 @@ class _Settings(object):
GATEWAY_JSON_SCHEMAS = {
"http://schemas.elite-markets.net/eddn/commodity/1": "schemas/commodity-v0.1.json",
"http://schemas.elite-markets.net/eddn/commodity/1/test": "schemas/commodity-v0.1.json",
"http://schemas.elite-markets.net/eddn/commodity/2": "schemas/commodity-v2.0-draft.json",
"http://schemas.elite-markets.net/eddn/commodity/2/test": "schemas/commodity-v2.0-draft.json"
"http://schemas.elite-markets.net/eddn/commodity/2": "schemas/commodity-v2.0.json",
"http://schemas.elite-markets.net/eddn/commodity/2/test": "schemas/commodity-v2.0.json"
}
###############################################################################
# Monitor settings
###############################################################################
MONITOR_RECEIVER_BINDINGS = ["tcp://eddn-gateway.elite-markets.net:8500", "tcp://eddn-gateway.ed-td.space:8500"]
MONITOR_DB = "/home/EDDN_Monitor.s3db"
MONITOR_DECOMPRESS_MESSAGES = True
def loadFrom(self, fileName):
f = open(fileName, 'r')
conf = simplejson.load(f)

View File

@ -0,0 +1 @@
__version__ = "0.4"

View File

@ -0,0 +1,49 @@
from datetime import datetime, timedelta
from threading import Lock, Thread
from time import sleep
import hashlib
import zlib
import simplejson
from eddn._Conf.Settings import Settings, loadConfig
class DuplicateMessages(Thread):
max_minutes = Settings.RELAY_DUPLICATE_MAX_MINUTES
caches = {}
lock = Lock()
def __init__(self):
super(DuplicateMessages, self).__init__()
self.daemon = True
def run(self):
while True:
sleep(60)
with self.lock:
maxTime = datetime.utcnow()
for key in self.caches.keys():
if self.caches[key] + timedelta(minutes=self.max_minutes) < maxTime:
del self.caches[key]
def isDuplicated(self, message):
with self.lock:
message = zlib.decompress(message)
message = simplejson.loads(message)
if message['header']['gatewayTimestamp']:
del message['header']['gatewayTimestamp'] # Prevent dupe with new timestamp ^^
if message['message']['timestamp']:
del message['message']['timestamp'] # Prevent dupe with new timestamp ^^
message = simplejson.dumps(message)
key = hashlib.sha256(message).hexdigest()
if key not in self.caches:
self.caches[key] = datetime.utcnow()
return False
else:
self.caches[key] = datetime.utcnow()
return True

View File

View File

@ -1 +0,0 @@
__version__ = "0.3"