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 fitnessFunctions import fitnessPerformance
from fitnessFunctions import fitnessThread
from fitnessFunctions import sortedWatchPoints
from fitnessFunctions import htmlWatchPointsGraph
import os
@ -12675,6 +12677,33 @@ class PubServer(BaseHTTPRequestHandler):
'_GET', 'rss3 done',
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
if htmlGET and (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"
__module_group__ = "Core"
import os
import time
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from utils import getConfigParam
from utils import saveJson
@ -41,6 +45,74 @@ def fitnessPerformance(startTime, fitnessState: {},
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: {}):
"""Thread used to save fitness function scores
"""

View File

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