Showing performance graphs

merge-requests/30/head
Bob Mottram 2021-10-19 21:08:24 +01:00
parent f721189769
commit 029d8bfcbf
4 changed files with 243 additions and 1 deletions

View File

@ -348,6 +348,8 @@ from speaker import getSSMLbox
from city import getSpoofedCity from city import getSpoofedCity
from fitnessFunctions import fitnessPerformance from fitnessFunctions import fitnessPerformance
from fitnessFunctions import fitnessThread from fitnessFunctions import fitnessThread
from fitnessFunctions import sortedWatchPoints
from fitnessFunctions import htmlWatchPointsGraph
import os import os
@ -12675,6 +12677,33 @@ class PubServer(BaseHTTPRequestHandler):
'_GET', 'rss3 done', '_GET', 'rss3 done',
self.server.debug) self.server.debug)
# show a performance graph
if authorized and self.path.startswith('/performance?graph='):
graph = self.path.split('?graph=')[1]
if htmlGET and not graph.endswith('.json'):
msg = \
htmlWatchPointsGraph(self.server.baseDir,
self.server.fitness,
graph, 16)
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain, False)
self._write(msg)
fitnessPerformance(GETstartTime, self.server.fitness,
'_GET', 'graph',
self.server.debug)
return
else:
graph = graph.replace('.json', '')
watchPointsJson = sortedWatchPoints(self.server.fitness, graph)
msg = json.dumps(watchPointsJson,
ensure_ascii=False).encode('utf-8')
msglen = len(msg)
self._set_headers('application/json',
msglen,
None, callingDomain, False)
self._write(msg)
# show the main blog page # show the main blog page
if htmlGET and (self.path == '/blog' or if htmlGET and (self.path == '/blog' or
self.path == '/blog/' or self.path == '/blog/' or

141
epicyon-graph.css 100644
View File

@ -0,0 +1,141 @@
body, table, input, select, textarea {
}
.graph {
margin-bottom:1em;
font:normal 100%/150% arial,helvetica,sans-serif;
}
.graph caption {
font:bold 150%/120% arial,helvetica,sans-serif;
padding-bottom:0.33em;
}
.graph tbody th {
text-align:right;
}
@supports (display:grid) {
@media (min-width:32em) {
.graph {
display:block;
width:600px;
height:300px;
}
.graph caption {
display:block;
}
.graph thead {
display:none;
}
.graph tbody {
position:relative;
display:grid;
grid-template-columns:repeat(auto-fit, minmax(2em, 1fr));
column-gap:2.5%;
align-items:end;
height:100%;
margin:3em 0 1em 2.8em;
padding:0 1em;
border-bottom:2px solid rgba(0,0,0,0.5);
background:repeating-linear-gradient(
180deg,
rgba(170,170,170,0.7) 0,
rgba(170,170,170,0.7) 1px,
transparent 1px,
transparent 20%
);
}
.graph tbody:before,
.graph tbody:after {
position:absolute;
left:-3.2em;
width:2.8em;
text-align:right;
font:bold 80%/120% arial,helvetica,sans-serif;
}
.graph tbody:before {
content:"100%";
top:-0.6em;
}
.graph tbody:after {
content:"0%";
bottom:-0.6em;
}
.graph tr {
position:relative;
display:block;
}
.graph tr:hover {
z-index:999;
}
.graph th,
.graph td {
display:block;
text-align:center;
}
.graph tbody th {
position:absolute;
top:-3em;
left:0;
width:100%;
font-weight:normal;
text-align:center;
white-space:nowrap;
text-indent:0;
transform:rotate(-45deg);
}
.graph tbody th:after {
content:"";
}
.graph td {
width:100%;
height:100%;
background:#F63;
border-radius:0.5em 0.5em 0 0;
transition:background 0.5s;
}
.graph tr:hover td {
opacity:0.7;
}
.graph td span {
overflow:hidden;
position:absolute;
left:50%;
top:50%;
width:0;
padding:0.5em 0;
margin:-1em 0 0;
font:normal 85%/120% arial,helvetica,sans-serif;
/* background:white; */
/* box-shadow:0 0 0.25em rgba(0,0,0,0.6); */
font-weight:bold;
opacity:0;
transition:opacity 0.5s;
color:white;
}
.toggleGraph:checked + table td span,
.graph tr:hover td span {
width:4em;
margin-left:-2em;
opacity:1;
}
}
}

View File

@ -7,7 +7,11 @@ __email__ = "bob@libreserver.org"
__status__ = "Production" __status__ = "Production"
__module_group__ = "Core" __module_group__ = "Core"
import os
import time import time
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from utils import getConfigParam
from utils import saveJson from utils import saveJson
@ -41,6 +45,74 @@ def fitnessPerformance(startTime, fitnessState: {},
watchPoint + '/' + str(total * 1000 / ctr)) watchPoint + '/' + str(total * 1000 / ctr))
def sortedWatchPoints(fitness: {}, fitnessId: str) -> []:
"""Returns a sorted list of watchpoints
"""
if not fitness.get(fitnessId):
return []
result = []
for watchPoint, item in fitness[fitnessId].items():
if not item.get('total'):
continue
averageTime = item['total'] / item['ctr']
result.append(str(averageTime) + ' ' + watchPoint)
result.sort(reverse=True)
return result
def htmlWatchPointsGraph(baseDir: str, fitness: {}, fitnessId: str,
maxEntries: int) -> str:
"""Returns the html for a graph of watchpoints
"""
watchPointsList = sortedWatchPoints(fitness, fitnessId)
cssFilename = baseDir + '/epicyon-graph.css'
if os.path.isfile(baseDir + '/graph.css'):
cssFilename = baseDir + '/graph.css'
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
htmlStr += \
'<table class="graph">\n' + \
'<caption>Watchpoints for ' + fitnessId + '</caption>\n' + \
'<thead>\n' + \
' <tr>\n' + \
' <th scope="col">Item</th>\n' + \
' <th scope="col">Percent</th>\n' + \
' </tr>\n' + \
'</thead><tbody>\n'
# get the maximum time
maxAverageTime = float(0.00001)
if len(watchPointsList) > 0:
maxAverageTime = float(watchPointsList[0].split(' ')[0])
for watchPoint in watchPointsList:
averageTime = float(watchPoint.split(' ')[0])
if averageTime > maxAverageTime:
maxAverageTime = averageTime
ctr = 0
for watchPoint in watchPointsList:
name = watchPoint.split(' ')[1]
averageTime = float(watchPoint.split(' ')[0])
heightPercent = int(averageTime * 100 / maxAverageTime)
timeMS = int(averageTime * 1000)
if timeMS == 0:
break
htmlStr += \
'<tr style="height:' + str(heightPercent) + '%">\n' + \
' <th scope="row">' + name + '</th>\n' + \
' <td><span>' + str(timeMS) + 'mS</span></td>\n' + \
'</tr>\n'
ctr += 1
if ctr >= maxEntries:
break
htmlStr += '</tbody></table>\n' + htmlFooter()
return htmlStr
def fitnessThread(baseDir: str, fitness: {}): def fitnessThread(baseDir: str, fitness: {}):
"""Thread used to save fitness function scores """Thread used to save fitness function scores
""" """

View File

@ -104,7 +104,7 @@ def _getThemeFiles() -> []:
return ('epicyon.css', 'login.css', 'follow.css', return ('epicyon.css', 'login.css', 'follow.css',
'suspended.css', 'calendar.css', 'blog.css', 'suspended.css', 'calendar.css', 'blog.css',
'options.css', 'search.css', 'links.css', 'options.css', 'search.css', 'links.css',
'welcome.css') 'welcome.css', 'graph.css')
def isNewsThemeName(baseDir: str, themeName: str) -> bool: def isNewsThemeName(baseDir: str, themeName: str) -> bool: