From 176b15ee49d0c3c28659e1166a1c11497848e5d0 Mon Sep 17 00:00:00 2001
From: Evert Prants
Date: Tue, 7 Aug 2018 00:15:23 +0300
Subject: [PATCH] metrics
---
app.py | 78 ++++++++++++++++++++++++++++++++++++++--
src/dashboard.js | 33 ++++++++++++++++-
src/player.js | 2 ++
templates/dashboard.html | 57 ++++++++++++++++++++++++++++-
4 files changed, 166 insertions(+), 4 deletions(-)
diff --git a/app.py b/app.py
index 9a727f9..c710d80 100644
--- a/app.py
+++ b/app.py
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
from flask import Flask, request, make_response, session, redirect, render_template, send_from_directory, jsonify
-from time import gmtime, strftime
+from time import gmtime, strftime, time
from uuid import uuid4
+from bs4 import BeautifulSoup
import requests
import json
@@ -73,9 +74,13 @@ oauth_secret = config['OAuth2']['ClientSecret']
# Database
conn = sqlite3.connect(config['Streaming']['Database'], check_same_thread=False)
-# Streamer Cache
+# Streamer memcache
stream_cache = {}
+# Stat memcache
+stat_cache = False
+stat_cache_updated = 0
+
# Check if user is a streamer
def valid_streamer(uuid):
streamer = None
@@ -94,6 +99,60 @@ def valid_streamer(uuid):
return streamer
+def pull_stream_metrics(uuid):
+ global stat_cache
+ global stat_cache_updated
+ updated = False
+ if not stat_cache or stat_cache_updated < int(time()) - 5:
+ metric_path = stream_server + "stat"
+ r = requests.get(metric_path)
+ stat_cache = BeautifulSoup(r.text, "xml")
+ updated = True
+ print("Updating per request..")
+
+ allapps = stat_cache.find_all("application")
+ relevant = None
+ for app in allapps:
+ if str(app.find("name").string) == "live":
+ relevant = app
+
+ if not relevant:
+ return None
+ streams = relevant.find_all("stream")
+
+ if not streams:
+ return None
+
+ if updated:
+ stat_cache_updated = int(time())
+
+ relevant = None
+ for stream in streams:
+ name = stream.find("name").string
+ if name == uuid:
+ relevant = stream
+
+ if not relevant:
+ return None
+
+ data = {
+ 'time' : relevant.time.string,
+ 'bytes' : relevant.bytes_in.string,
+ 'video': {
+ 'width': relevant.meta.video.width.string,
+ 'height': relevant.meta.video.height.string,
+ 'frame_rate': relevant.meta.video.frame_rate.string,
+ 'codec': relevant.meta.video.codec.string,
+ },
+ 'audio': {
+ 'sample_rate': relevant.meta.audio.sample_rate.string,
+ 'channels': relevant.meta.audio.channels.string,
+ 'codec': relevant.meta.audio.codec.string,
+ }
+ }
+
+ return data
+
@app.route("/")
def index():
streamer = False
@@ -115,6 +174,21 @@ def dashboard():
return render_template("dashboard.html", stream = streamkey,
server = config['Streaming']['PublishAddress'].format(streamer = "", host = config['Streaming']['ServerHost']))
+@app.route("/dashboard/stats")
+def dashboard_stats():
+ if not 'uuid' in session:
+ return jsonify({'error': 'Unauthorized'})
+
+ streamkey = valid_streamer(session['uuid'])
+ if not streamkey:
+ return jsonify({'error': 'Unauthorized'})
+
+ data = pull_stream_metrics(streamkey)
+ if not data:
+ return jsonify({'error': 'No data was returned..'})
+
+ return jsonify(data)
+
@app.route("/dashboard/data")
def dashboard_data():
if not 'uuid' in session:
diff --git a/src/dashboard.js b/src/dashboard.js
index 449ccdc..b4bdccf 100644
--- a/src/dashboard.js
+++ b/src/dashboard.js
@@ -1,5 +1,27 @@
import $ from 'jquery'
+// https://stackoverflow.com/a/18650828
+function formatBytes(a,b){if(0==a)return"0 Bytes";var c=1024,d=b||2,e=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],f=Math.floor(Math.log(a)/Math.log(c));return parseFloat((a/Math.pow(c,f)).toFixed(d))+" "+e[f]}
+
+function recursive_stats(table, subtable) {
+ var key
+ for (key in table) {
+ var val = table[key]
+ if (typeof(val) == "object") {
+ recursive_stats(val, key)
+ } else {
+ if (key == "time") {
+ var date = new Date(null);
+ date.setSeconds(Math.floor(parseInt(val)/1000)); // specify value for SECONDS here
+ val = date.toISOString().substr(11, 8);
+ } else if (key.indexOf("bytes") != -1) {
+ val = formatBytes(val, 3)
+ }
+ $("#stat_" + (subtable ? subtable + "_" : "") + key).text(val)
+ }
+ }
+}
+
function dashboard () {
$.get("/dashboard/data", function (res) {
if (res.error) {
@@ -7,14 +29,23 @@ function dashboard () {
return
}
- var fullURL = window.location.origin + "/player/" + res.name
+ var fullURL = window.location.origin + "/watch/" + res.name
$('#myStream').attr('src', fullURL)
$('#stream_url').text(fullURL).attr("href", fullURL)
+ console.log(res)
+ $('#stream_live').text(res.live ? 'Yes' : 'No')
})
$('#show_key').click(function () {
$('#show_key').html(window.STREAM_KEY)
})
+
+ setInterval(function () {
+ $.get("/dashboard/stats", function (res) {
+ if (res.error) return
+ recursive_stats(res, "")
+ })
+ }, 5000)
}
export default {start: dashboard}
diff --git a/src/player.js b/src/player.js
index 9e5000d..04be302 100644
--- a/src/player.js
+++ b/src/player.js
@@ -1,6 +1,8 @@
import css from './css/player.css'
import Hls from 'hls.js'
+css.ref()
+
let player
let vid
let btn
diff --git a/templates/dashboard.html b/templates/dashboard.html
index 32fbf9c..6e8087c 100644
--- a/templates/dashboard.html
+++ b/templates/dashboard.html
@@ -32,7 +32,7 @@
-
+
Dashboard
@@ -40,6 +40,15 @@
Information
+
+
@@ -67,6 +76,52 @@
+
+ Metrics
+
+
+
+ Time |
+ Bytes |
+ Video |
+ Audio |
+
+
+ |
+ |
+
+
+
+ Width |
+ Height |
+ Frame Rate |
+ Codec |
+
+
+ |
+ |
+ |
+ |
+
+
+ |
+
+
+
+ Channels |
+ Sample Rate |
+ Codec |
+
+
+ |
+ |
+ |
+
+
+ |
+
+
+