Browse Source

Added basic functionality for statistics.

KrisVos130 8 years ago
parent
commit
f563c740ce

+ 4 - 0
backend/index.js

@@ -14,6 +14,7 @@ const songs = require('./logic/songs');
 const playlists = require('./logic/playlists');
 const cache = require('./logic/cache');
 const notifications = require('./logic/notifications');
+const logger = require('./logic/logger');
 const config = require('config');
 
 process.on('uncaughtException', err => {
@@ -57,6 +58,9 @@ async.waterfall([
 	// setup the API
 	(next) => api.init(next),
 
+	// setup the logger
+	(next) => logger.init(next),
+
 	// setup the frontend for local setups
 	(next) => {
 		if (!config.get("isDocker")) {

+ 1 - 1
backend/logic/actions/apis.js

@@ -86,7 +86,7 @@ module.exports = {
 	 * @param cb
 	 */
 	joinAdminRoom: hooks.adminRequired((session, page, cb) => {
-		if (page === 'queue' || page === 'songs' || page === 'stations' || page === 'reports' || page === 'news' || page === 'users') {
+		if (page === 'queue' || page === 'songs' || page === 'stations' || page === 'reports' || page === 'news' || page === 'users' || page === 'statistics') {
 			utils.socketJoinRoom(session.socketId, `admin.${page}`);
 		}
 		cb({});

+ 120 - 0
backend/logic/logger.js

@@ -1,11 +1,69 @@
 'use strict';
 
 const fs = require('fs');
+let utils;
 /*const log_file = fs.createWriteStream(__dirname + '/../../all.log', {flags : 'w'});
 const success_log_file = fs.createWriteStream(__dirname + '/../../success.log', {flags : 'w'});
 const error_log_file = fs.createWriteStream(__dirname + '/../../error.log', {flags : 'w'});
 const info_log_file = fs.createWriteStream(__dirname + '/../../info.log', {flags : 'w'});*/
 
+let started;
+let success = 0;
+let successThisMinute = 0;
+let successThisHour = 0;
+let error = 0;
+let errorThisMinute = 0;
+let errorThisHour = 0;
+let info = 0;
+let infoThisMinute = 0;
+let infoThisHour = 0;
+
+let successUnitsPerMinute = [0,0,0,0,0,0,0,0,0,0];
+let errorUnitsPerMinute = [0,0,0,0,0,0,0,0,0,0];
+let infoUnitsPerMinute = [0,0,0,0,0,0,0,0,0,0];
+
+let successUnitsPerHour = [0,0,0,0,0,0,0,0,0,0];
+let errorUnitsPerHour = [0,0,0,0,0,0,0,0,0,0];
+let infoUnitsPerHour = [0,0,0,0,0,0,0,0,0,0];
+
+function calculateUnits(units, unit) {
+	units.push(unit);
+	if (units.length > 10) units.shift();
+	return units;
+}
+
+function calculateHourUnits() {
+	successUnitsPerHour = calculateUnits(successUnitsPerHour, successThisHour);
+	errorUnitsPerHour = calculateUnits(errorUnitsPerHour, errorThisHour);
+	infoUnitsPerHour = calculateUnits(infoUnitsPerHour, infoThisHour);
+
+	successThisHour = 0;
+	errorThisHour = 0;
+	infoThisHour = 0;
+
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.success.units.hour', successUnitsPerHour);
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.error.units.hour', errorUnitsPerHour);
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.info.units.hour', infoUnitsPerHour);
+
+	setTimeout(calculateHourUnits, 1000 * 60 * 60)
+}
+
+function calculateMinuteUnits() {
+	successUnitsPerMinute = calculateUnits(successUnitsPerMinute, successThisMinute);
+	errorUnitsPerMinute = calculateUnits(errorUnitsPerMinute, errorThisMinute);
+	infoUnitsPerMinute = calculateUnits(infoUnitsPerMinute, infoThisMinute);
+
+	successThisMinute = 0;
+	errorThisMinute = 0;
+	infoThisMinute = 0;
+
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.success.units.minute', successUnitsPerMinute);
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.error.units.minute', errorUnitsPerMinute);
+	utils.emitToRoom('admin.statistics', 'event:admin.statistics.info.units.minute', infoUnitsPerMinute);
+	
+	setTimeout(calculateMinuteUnits, 1000 * 60)
+}
+
 let twoDigits = (num) => {
 	return (num < 10) ? '0' + num : num;
 };
@@ -23,7 +81,20 @@ let getTime = (cb) => {
 };
 
 module.exports = {
+	init: function(cb) {
+		utils = require('./utils');
+		started = Date.now();
+
+		setTimeout(calculateMinuteUnits, 1000 * 60);
+		setTimeout(calculateHourUnits, 1000 * 60 * 60);
+		setTimeout(this.calculate, 1000 * 30);
+
+		cb();
+	},
 	success: (type, message) => {
+		success++;
+		successThisMinute++;
+		successThisHour++;
 		getTime((time) => {
 			let timeString = `${time.year}-${twoDigits(time.month)}-${twoDigits(time.day)} ${twoDigits(time.hour)}:${twoDigits(time.minute)}:${twoDigits(time.second)}`;
 			fs.appendFile(__dirname + '/../../all.log', `${timeString} SUCCESS - ${type} - ${message}\n`);
@@ -32,6 +103,9 @@ module.exports = {
 		});
 	},
 	error: (type, message) => {
+		error++;
+		errorThisMinute++;
+		errorThisHour++;
 		getTime((time) => {
 			let timeString = `${time.year}-${twoDigits(time.month)}-${twoDigits(time.day)} ${twoDigits(time.hour)}:${twoDigits(time.minute)}:${twoDigits(time.second)}`;
 			fs.appendFile(__dirname + '/../../all.log', `${timeString} ERROR - ${type} - ${message}\n`);
@@ -40,6 +114,9 @@ module.exports = {
 		});
 	},
 	info: (type, message) => {
+		info++;
+		infoThisMinute++;
+		infoThisHour++;
 		getTime((time) => {
 			let timeString = `${time.year}-${twoDigits(time.month)}-${twoDigits(time.day)} ${twoDigits(time.hour)}:${twoDigits(time.minute)}:${twoDigits(time.second)}`;
 			fs.appendFile(__dirname + '/../../all.log', `${timeString} INFO - ${type} - ${message}\n`);
@@ -47,5 +124,48 @@ module.exports = {
 
 			console.info('\x1b[36m', timeString, 'INFO', '-', type, '-', message, '\x1b[0m');
 		});
+	},
+	calculatePerSecond: function(number) {
+		let secondsRunning = Math.floor((Date.now() - started) / 1000);
+		let perSecond = number / secondsRunning;
+		return perSecond;
+	},
+	calculatePerMinute: function(number) {
+		let perMinute = this.calculatePerSecond(number) * 60;
+		return perMinute;
+	},
+	calculatePerHour: function(number) {
+		let perHour = this.calculatePerMinute(number) * 60;
+		return perHour;
+	},
+	calculatePerDay: function(number) {
+		let perDay = this.calculatePerHour(number) * 24;
+		return perDay;
+	},
+	calculate: function() {
+		let _this = module.exports;
+		utils.emitToRoom('admin.statistics', 'event:admin.statistics.logs', {
+			second: {
+				success: _this.calculatePerSecond(success),
+				error: _this.calculatePerSecond(error),
+				info: _this.calculatePerSecond(info)
+			},
+			minute: {
+				success: _this.calculatePerMinute(success),
+				error: _this.calculatePerMinute(error),
+				info: _this.calculatePerMinute(info)
+			},
+			hour: {
+				success: _this.calculatePerHour(success),
+				error: _this.calculatePerHour(error),
+				info: _this.calculatePerHour(info)
+			},
+			day: {
+				success: _this.calculatePerDay(success),
+				error: _this.calculatePerDay(error),
+				info: _this.calculatePerDay(info)
+			}
+		});
+		setTimeout(_this.calculate, 1000 * 30);
 	}
 };

+ 300 - 0
frontend/components/Admin/Statistics.vue

@@ -0,0 +1,300 @@
+<template>
+	<div class='container'>
+		<div class="columns">
+			<div class='card column is-10-desktop is-offset-1-desktop is-12-mobile'>
+				<header class='card-header'>
+					<p class='card-header-title'>
+						Average Logs
+					</p>
+				</header>
+				<div class='card-content'>
+					<div class='content'>
+						<table class="table">
+							<thead>
+								<tr>
+									<th> </th>
+									<th>Success</th>
+									<th>Error</th>
+									<th>Info</th>
+								</tr>
+							</thead>
+							<tbody>
+								<tr>
+									<th><strong>Average per second</strong></th>
+									<th v-bind:title="logs.second.success">{{round(logs.second.success)}}</th>
+									<th v-bind:title="logs.second.error">{{round(logs.second.error)}}</th>
+									<th v-bind:title="logs.second.info">{{round(logs.second.info)}}</th>
+								</tr>
+								<tr>
+									<th><strong>Average per minute</strong></th>
+									<th v-bind:title="logs.minute.success">{{round(logs.minute.success)}}</th>
+									<th v-bind:title="logs.minute.error">{{round(logs.minute.error)}}</th>
+									<th v-bind:title="logs.minute.info">{{round(logs.minute.info)}}</th>
+								</tr>
+								<tr>
+									<th><strong>Average per hour</strong></th>
+									<th v-bind:title="logs.hour.success">{{round(logs.hour.success)}}</th>
+									<th v-bind:title="logs.hour.error">{{round(logs.hour.error)}}</th>
+									<th v-bind:title="logs.hour.info">{{round(logs.hour.info)}}</th>
+								</tr>
+								<tr>
+									<th><strong>Average per day</strong></th>
+									<th v-bind:title="logs.day.success">{{round(logs.day.success)}}</th>
+									<th v-bind:title="logs.day.error">{{round(logs.day.error)}}</th>
+									<th v-bind:title="logs.day.info">{{round(logs.day.info)}}</th>
+								</tr>
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+		</div>
+		<br>
+		<div class="columns">
+			<div class='card column is-10-desktop is-offset-1-desktop is-12-mobile'>
+				<div class='card-content'>
+					<div class='content'>
+						<canvas id="minuteChart" height="400"></canvas>
+					</div>
+				</div>
+			</div>
+		</div>
+		<br>
+		<div class="columns">
+			<div class='card column is-10-desktop is-offset-1-desktop is-12-mobile'>
+				<div class='card-content'>
+					<div class='content'>
+						<canvas id="hourChart" height="400"></canvas>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	import EditUser from '../Modals/EditUser.vue';
+	import io from '../../io';
+	import Chart from 'chart.js'
+
+	export default {
+		components: {},
+		data() {
+			return {
+				successUnitsPerMinute: [0,0,0,0,0,0,0,0,0,0],
+				errorUnitsPerMinute: [0,0,0,0,0,0,0,0,0,0],
+				infoUnitsPerMinute: [0,0,0,0,0,0,0,0,0,0],
+				successUnitsPerHour: [0,0,0,0,0,0,0,0,0,0],
+				errorUnitsPerHour: [0,0,0,0,0,0,0,0,0,0],
+				infoUnitsPerHour: [0,0,0,0,0,0,0,0,0,0],
+				minuteChart: null,
+				hourChart: null,
+				logs: {
+					second: {
+						success: 0,
+						error: 0,
+						info: 0
+					},
+					minute: {
+						success: 0,
+						error: 0,
+						info: 0
+					},
+					hour: {
+						success: 0,
+						error: 0,
+						info: 0
+					},
+					day: {
+						success: 0,
+						error: 0,
+						info: 0
+					}
+				}
+			}
+		},
+		methods: {
+			init: function () {
+				this.socket.emit('apis.joinAdminRoom', 'statistics', () => {});
+				this.socket.on('event:admin.statistics.success.units.minute', units => {
+					this.successUnitsPerMinute = units;
+					this.minuteChart.data.datasets[0].data = units;
+					this.minuteChart.update();
+				});
+				this.socket.on('event:admin.statistics.error.units.minute', units => {
+					this.errorUnitsPerMinute = units;
+					this.minuteChart.data.datasets[1].data = units;
+					this.minuteChart.update();
+				});
+				this.socket.on('event:admin.statistics.info.units.minute', units => {
+					this.infoUnitsPerMinute = units;
+					this.minuteChart.data.datasets[2].data = units;
+					this.minuteChart.update();
+				});
+				this.socket.on('event:admin.statistics.success.units.hour', units => {
+					this.successUnitsPerHour = units;
+					this.hourChart.data.datasets[0].data = units;
+					this.hourChart.update();
+				});
+				this.socket.on('event:admin.statistics.error.units.hour', units => {
+					this.errorUnitsPerHour = units;
+					this.hourChart.data.datasets[1].data = units;
+					this.hourChart.update();
+				});
+				this.socket.on('event:admin.statistics.info.units.hour', units => {
+					this.infoUnitsPerHour = units;
+					this.hourChart.data.datasets[2].data = units;
+					this.hourChart.update();
+				});
+				this.socket.on('event:admin.statistics.logs', logs => {
+					this.logs = logs;
+				});
+			},
+			round: function(number) {
+				return Math.round(number);
+			}
+		},
+		ready: function () {
+			let _this = this;
+			var minuteCtx = document.getElementById("minuteChart");
+			var hourCtx = document.getElementById("hourChart");
+
+			_this.minuteChart = new Chart(minuteCtx, {
+				type: 'line',
+				data: {
+					labels: ["-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1"],
+					datasets: [
+						{
+							label: 'Success',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(75, 192, 192, 0.2)'
+							],
+							borderColor: [
+								'rgba(75, 192, 192, 1)'
+							],
+							borderWidth: 1
+						},
+						{
+							label: 'Error',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(255, 99, 132, 0.2)'
+							],
+							borderColor: [
+								'rgba(255,99,132,1)'
+							],
+							borderWidth: 1
+						},
+						{
+							label: 'Info',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(54, 162, 235, 0.2)'
+							],
+							borderColor: [
+								'rgba(54, 162, 235, 1)'
+							],
+							borderWidth: 1
+						}
+					]
+				},
+				options: {
+					title: {
+						display: true,
+						text: 'Logs per minute'
+					},
+					scales: {
+						yAxes: [{
+							ticks: {
+								beginAtZero: true,
+								stepSize: 1
+							}
+						}]
+					},
+					responsive: true,
+					maintainAspectRatio: false
+				}
+			});
+
+			_this.hourChart = new Chart(hourCtx, {
+				type: 'line',
+				data: {
+					labels: ["-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1"],
+					datasets: [
+						{
+							label: 'Success',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(75, 192, 192, 0.2)'
+							],
+							borderColor: [
+								'rgba(75, 192, 192, 1)'
+							],
+							borderWidth: 1
+						},
+						{
+							label: 'Error',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(255, 99, 132, 0.2)'
+							],
+							borderColor: [
+								'rgba(255,99,132,1)'
+							],
+							borderWidth: 1
+						},
+						{
+							label: 'Info',
+							data: [0,0,0,0,0,0,0,0,0,0],
+							backgroundColor: [
+								'rgba(54, 162, 235, 0.2)'
+							],
+							borderColor: [
+								'rgba(54, 162, 235, 1)'
+							],
+							borderWidth: 1
+						}
+					]
+				},
+				options: {
+					title: {
+						display: true,
+						text: 'Logs per hour'
+					},
+					scales: {
+						yAxes: [{
+							ticks: {
+								beginAtZero: true,
+								stepSize: 1
+							}
+						}]
+					},
+					responsive: true,
+					maintainAspectRatio: false
+				}
+			});
+
+
+			io.getSocket(socket => {
+				_this.socket = socket;
+				if (_this.socket.connected) _this.init();
+				io.onConnect(() => _this.init());
+			});
+		}
+	}
+</script>
+
+<style lang='scss' scoped>
+	body { font-family: 'Roboto', sans-serif; }
+
+	.user-avatar {
+		display: block;
+		max-width: 50px;
+		margin: 0 auto;
+	}
+
+	td { vertical-align: middle; }
+
+	.is-primary:focus { background-color: #029ce3 !important; }
+</style>

+ 13 - 1
frontend/components/pages/Admin.vue

@@ -39,6 +39,12 @@
 						<span>&nbsp;Users</span>
 					</a>
 				</li>
+				<li :class='{ "is-active": currentTab == "statistics" }' @click='showTab("statistics")'>
+					<a v-link="{ path: '/admin/statistics' }">
+						<i class="material-icons">person</i>
+						<span>&nbsp;Statistics</span>
+					</a>
+				</li>
 			</ul>
 		</div>
 
@@ -48,6 +54,7 @@
 		<reports v-if='currentTab == "reports"'></reports>
 		<news v-if='currentTab == "news"'></news>
 		<users v-if='currentTab == "users"'></users>
+		<statistics v-if='currentTab == "statistics"'></statistics>
 	</div>
 </template>
 
@@ -61,6 +68,7 @@
 	import Reports from '../Admin/Reports.vue';
 	import News from '../Admin/News.vue';
 	import Users from '../Admin/Users.vue';
+	import Statistics from '../Admin/Statistics.vue';
 
 	export default {
 		components: {
@@ -71,7 +79,8 @@
 			Stations,
 			Reports,
 			News,
-			Users
+			Users,
+			Statistics
 		},
 		ready() {
 			switch(window.location.pathname) {
@@ -93,6 +102,9 @@
 				case '/admin/users':
 					this.currentTab = 'users';
 					break;
+				case '/admin/statistics':
+					this.currentTab = 'statistics';
+					break;
 				default:
 					this.currentTab = 'queueSongs';
 			}

+ 2 - 1
frontend/package.json

@@ -28,10 +28,11 @@
     "vue-loader": "^8.5.2",
     "vue-style-loader": "^1.0.0",
     "whatwg-fetch": "^0.11.1",
-	"webpack": "^1.14.0",
+    "webpack": "^1.14.0",
     "webpack-dev-server": "^1.15.1"
   },
   "dependencies": {
+    "chart.js": "^2.5.0",
     "node-sass": "^3.13.0",
     "vue": "^1.0.26",
     "vue-roaster": "^1.1.1",