Changeset - a745bee5c419
[Not reviewed]
default
0 8 1
Drew Perttula - 6 years ago 2019-06-06 09:31:36
drewp@bigasterisk.com
new process stats visualizers
Ignore-this: 8e47516baad0bfb9cd02a712571c5a5f
9 files changed with 123 insertions and 8 deletions:
0 comments (0 inline, 0 general)
bin/ascoltami2
Show inline comments
 
@@ -2,27 +2,29 @@
 
from run_local import log
 
from twisted.internet import reactor
 
import sys, optparse, logging
 
from rdflib import URIRef
 
import gi
 
gi.require_version('Gst', '1.0')
 
gi.require_version('Gtk', '3.0')
 

	
 
from light9.ascoltami.player import Player
 
from light9.ascoltami.playlist import Playlist, NoSuchSong
 
from light9.ascoltami.webapp import makeWebApp, songUri, songLocation
 
from light9 import networking, showconfig
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
from gi.repository import GObject, Gst
 

	
 
gatherProcessStats()
 

	
 
class App(object):
 

	
 
    def __init__(self, graph, show):
 
        self.graph = graph
 
        self.player = Player(onEOS=self.onEOS)
 
        self.show = show
 
        self.playlist = Playlist.fromShow(graph, show)
 

	
 
    def onEOS(self, song):
 
        self.player.pause()
 
        self.player.seek(0)
bin/collector
Show inline comments
 
@@ -16,41 +16,43 @@ import optparse
 
import traceback
 
import cyclone.web, cyclone.websocket
 
from greplin import scales
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9 import networking
 
from light9.collector.collector import Collector
 
from light9.collector.weblisteners import WebListeners
 
from greplin.scales.cyclonehandler import StatsHandler
 
from light9.namespaces import L9
 
from light9.zmqtransport import parseJsonMessage, startZmq
 
from rdfdb.syncedgraph import SyncedGraph
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
from light9.collector.output import Udmx, DummyOutput  # noqa
 

	
 

	
 
class Updates(cyclone.websocket.WebSocketHandler):
 

	
 
    def connectionMade(self, *args, **kwargs):
 
        log.info('socket connect %s', self)
 
        self.settings.listeners.addClient(self)
 

	
 
    def connectionLost(self, reason):
 
        self.settings.listeners.delClient(self)
 

	
 
    def messageReceived(self, message):
 
        json.loads(message)
 

	
 

	
 
gatherProcessStats()
 
stats = scales.collection(
 
    '/webServer',
 
    scales.PmfStat('setAttr', recalcPeriod=1),
 
    scales.RecentFpsStat('setAttrFps'),
 
)
 

	
 

	
 
class Attrs(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def put(self):
 
        stats.setAttrFps.mark()
 
        with stats.setAttr.time():
bin/effecteval
Show inline comments
 
@@ -7,28 +7,30 @@ import cyclone.web, cyclone.websocket, c
 
import sys, optparse, logging, json, itertools
 
from rdflib import URIRef, Literal
 

	
 
from light9 import networking, showconfig
 
from light9.effecteval.effect import EffectNode
 
from light9.effect.edit import getMusicStatus, songNotePatch
 
from light9.effecteval.effectloop import makeEffectLoop
 
from greplin.scales.cyclonehandler import StatsHandler
 
from light9.namespaces import L9
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph import SyncedGraph
 
from greplin import scales
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9.coffee import StaticCoffee
 

	
 
gatherProcessStats()
 

	
 
class EffectEdit(PrettyErrorHandler, cyclone.web.RequestHandler):
 

	
 
    def get(self):
 
        self.set_header('Content-Type', 'text/html')
 
        self.write(open("light9/effecteval/effect.html").read())
 

	
 
    def delete(self):
 
        graph = self.settings.graph
 
        uri = URIRef(self.get_argument('uri'))
 
        with graph.currentState(tripleFilter=(None, L9['effect'], uri)) as g:
 
            song = ctx = list(g.subjects(L9['effect'], uri))[0]
bin/vidref
Show inline comments
 
@@ -21,31 +21,33 @@ import logging, optparse, json, base64, 
 

	
 
from greplin import scales
 
from greplin.scales.cyclonehandler import StatsHandler
 
from rdflib import URIRef
 
from twisted.internet import reactor, defer
 
import cyclone.web, cyclone.httpclient, cyclone.websocket
 

	
 
from cycloneerr import PrettyErrorHandler
 
from light9 import networking, showconfig
 
from light9.newtypes import Song
 
from light9.vidref import videorecorder
 
from rdfdb.syncedgraph import SyncedGraph
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
parser = optparse.OptionParser()
 
parser.add_option("-v", "--verbose", action="store_true", help="logging.DEBUG")
 
(options, args) = parser.parse_args()
 

	
 
log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
 

	
 
gatherProcessStats()
 
stats = scales.collection(
 
    '/webServer',
 
    scales.RecentFpsStat('liveWebsocketFrameFps'),
 
    scales.IntStat('liveClients'),
 
)
 

	
 

	
 
class Snapshot(cyclone.web.RequestHandler):
 

	
 
    @defer.inlineCallbacks
 
    def post(self):
 
        # save next pic
light9/effect/sequencer.py
Show inline comments
 
@@ -11,30 +11,32 @@ from twisted.python.filepath import File
 
import cyclone.sse
 
import logging, bisect, time
 
import traceback
 
from typing import Any, Callable, Dict, List, Tuple, cast, Union
 

	
 
from light9.ascoltami.musictime_client import MusicTime
 
from light9.effect import effecteval
 
from light9.effect.settings import DeviceSettings
 
from light9.effect.simple_outputs import SimpleOutputs
 
from light9.namespaces import L9, RDF
 
from light9.newtypes import DeviceUri, DeviceAttr, NoteUri, Curve, Song
 
from rdfdb.syncedgraph import SyncedGraph
 
from standardservice.scalessetup import gatherProcessStats
 

	
 
from greplin import scales
 
import imp
 

	
 
log = logging.getLogger('sequencer')
 

	
 
gatherProcessStats()
 
updateStats = scales.collection(
 
    '/update/',
 
    scales.PmfStat('s0_getMusic', recalcPeriod=1),
 
    scales.PmfStat('s1_eval', recalcPeriod=1),
 
    scales.PmfStat('s2_sendToWeb', recalcPeriod=1),
 
    scales.PmfStat('s3_send', recalcPeriod=1),
 
    scales.PmfStat('sendPhase', recalcPeriod=1),
 
    scales.PmfStat('updateLoopLatency', recalcPeriod=1),
 
    scales.DoubleStat('updateLoopLatencyGoal'),
 
    scales.RecentFpsStat('updateFps'),
 
    scales.DoubleStat('goalFps'),
 
)
light9/web/index.html
Show inline comments
 
<!doctype html>
 
<html>
 
  <head>
 
    <title>light9 home</title>
 
    <meta charset="utf-8" />
 
    <script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
 
    <link rel="stylesheet" href="/style.css">
 
    <link rel="import" href="/lib/polymer/polymer.html">
 
  </head>
 
  <body>
 
      <script type="module"  src="stats-line.js"></script>
 
      <script type="module"  src="stats-process.js"></script>
 

	
 

	
 
     
 
    <dom-module id="service-button-row">
 
      <template>
 
        <style>
 
         :host { padding-bottom: 10px;  }
 
         a {
 
             color: #7d7dec;
 
         }
 
         div {
 
             display: flex;
light9/web/stats-line.js
Show inline comments
 
@@ -11,31 +11,30 @@ class StatsLine extends LitElement {
 
                
 
            },
 
            stats: Object // to be refreshed with ws
 
        };
 
    }
 

	
 
    updated(changedProperties) {
 
        changedProperties.forEach((oldValue, propName) => {
 
            if (propName == 'name') {
 
                const reload = () => {
 
                    fetch(this.name + '/stats/?format=json').then((resp) => {
 
                        if (resp.ok) {
 
                    resp.json().then((msg) => {
 
                        
 
                        this.stats = msg;
 
                    setTimeout(reload, 1000);
 
                            resp.json().then((msg) => {
 
                                this.stats = msg;
 
                                setTimeout(reload, 1000);
 
                            });
 
                        }
 
                    });
 
                        }
 
                });
 
                }
 
                reload();
 
            }
 
        });
 
    }
 
    
 
    static get styles() {
 
        return css`
 
        :host {
 
            border: 2px solid #46a79f;
 
            display: inline-block;
 
        }
 
@@ -69,28 +68,39 @@ class StatsLine extends LitElement {
 
        .recents > div {
 
            width: 3px;
 
            background: red;
 
            border-right: 1px solid black;
 
        }
 
        .bigInt {
 
            min-width: 6em;
 
        }
 
        `;
 
    }
 
    
 
    render() {
 
        const now = Date.now() / 1000;
 
        const table = (d, path) => {
 

	
 
            const cols = Object.keys(d);
 
            let cols = Object.keys(d);
 
            cols.sort();
 

	
 
            if (path.length == 0) {
 
                ['webServer', 'process'].forEach((earlyKey) => {
 
                    let i = cols.indexOf(earlyKey);
 
                    if (i != -1) {
 
                        cols = [earlyKey].concat(cols.slice(0, i), cols.slice(i + 1));
 
                    }
 
                });
 
            }
 
            
 
            const th = (col) =>  {
 
                return html`<th>${col}</th>`;
 
            };
 
            const td = (col)  => {
 
                const cell = d[col];
 
                return html`${drawLevel(cell, path.concat(col))}`;
 
            };
 
            return html`
 
             <table>
 
               <tr>
 
                 ${cols.map(th)}
 
               </tr>
 
@@ -121,25 +131,32 @@ class StatsLine extends LitElement {
 

	
 
        };
 
        const pmf = (d, path) => {
 
            return tdWrap(table({
 
                count: d.count,
 
                'values [ms]': html`
 
                   <div>mean=${rounding(d.mean*1000, 3)}</div>
 
                   <div>sd=${rounding(d.stddev*1000, 3)}</div>
 
                   <div>99=${rounding(d['99percentile']*1000, 3)}</div>
 
                 `
 
            }, path));
 
        };
 
        const drawLevel = (d, path) => {           
 
        const drawLevel = (d, path) => {
 
            if (path.length == 1 && path[0] === 'process') {
 
                 const elem = this.shadowRoot.querySelector('#proc');
 
                if (elem) {
 
                    elem.data = d;
 
                }
 
                return html`<stats-process id="proc"></stats-process>`;
 
            }
 
            if (typeof d === 'object') {
 
                if (d instanceof TemplateResult) {
 
                    return html`<td class="val">${d}</td>`;
 
                } else if (d.count !== undefined && d.min !== undefined) {
 
                    return pmf(d, path);
 
                } else if (d.average !== undefined && d.recents !== undefined) {
 
                    return recents(d, path);
 
                } else {
 
                    return tdWrap(table(d, path));
 
                }
 
            } else {             
 
                return html`<td class="val bigInt">${d}</td>`;
light9/web/stats-process.js
Show inline comments
 
new file 100644
 
import { LitElement, TemplateResult, html, css } from '/node_modules/lit-element/lit-element.js';
 
import debug from '/lib/debug/debug-build-es6.js';
 
import { rounding }  from '/node_modules/significant-rounding/index.js';
 

	
 
const log = debug('process');
 

	
 
const remap = (x, lo, hi, outLo, outHi) => {
 
    return outLo + (outHi - outLo) * Math.max(0, Math.min(1, (x - lo) / (hi - lo)));
 
};
 

	
 
class StatsProcess extends LitElement {
 
    
 
    static get properties() {
 
        return {
 
            data: { type: Object },
 
        };
 
    }
 

	
 
    firstUpdated() {
 
        // inspired by https://codepen.io/qiruiyin/pen/qOopQx
 
        var context = this.shadowRoot.firstElementChild,
 
	    ctx = context.getContext('2d'),
 
	    w = 64,
 
	    h = 64,
 
	    revs = 0;   
 
	
 
	context.width = w;
 
	context.height = h;
 

	
 
        let prev = Date.now() / 1000;
 

	
 
        var animate = () => {
 
	    requestAnimationFrame( animate );
 

	
 
            const now = Date.now() / 1000;
 
            ctx.beginPath();
 
            // wrong type of fade- never goes to 0
 
            ctx.fillStyle = '#00000003';
 
            ctx.fillRect(0, 0, w, h);
 
            if (this.data.time < now - 2) {
 
                return;
 
            }
 
            const dt = now - prev;
 
            prev = now;
 

	
 
            const size = remap(this.data.memMb, /*in*/ 20, 600, /*out*/ 3, 30);
 
	    revs += dt * remap(this.data.cpuPercent, /*in*/ 0, 100, /*out*/ 4, 120);
 
            const rad  = remap(size, /*in*/ 3, 30, /*out*/ 14, 5);
 

	
 
	    var x = w/2 + rad * Math.cos(revs / 6.28),
 
		y = h/2 + rad * Math.sin(revs / 6.28);
 

	
 
	    ctx.save();
 
	    ctx.beginPath();
 
	    ctx.fillStyle = "hsl(194, 100%, 42%)";
 
	    ctx.arc(x, y, size, 0, 2*Math.PI);
 
	    ctx.fill();
 
	    ctx.restore();
 
	    
 
        };
 
        animate();
 
    }
 
    
 
    updated(changedProperties) {
 
        if (changedProperties.has('data')) {
 
            this.shadowRoot.firstElementChild.setAttribute('title', `cpu ${this.data.cpuPercent}% mem ${this.data.memMb}MB`);
 
        }
 
    }
 

	
 
    static get styles() {
 
        return css`
 
        :host {
 
           display: inline-block;
 
           width: 64px;
 
           height: 64px;
 
        }
 
        `;
 
    }
 
    
 
    render() {
 
        return html`<canvas></canvas>`;
 

	
 
    }
 
}
 
customElements.define('stats-process', StatsProcess);
 

	
requirements.txt
Show inline comments
 
@@ -36,12 +36,13 @@ hunter
 
ipdb==0.10.2
 
ipython==5.3.0
 
mypy==0.701
 
typing_extensions
 

	
 
git+http://github.com/drewp/scales.git@4b011434f7469a442c3fc1d7e81685c0bfa56eeb#egg=scales
 
git+http://github.com/11craft/louie.git@f18bb71010c114eca9c6b88c96453340e3b39454#egg=louie
 
git+http://github.com/webpy/webpy.git@ace0f8954c28311004b33c7a513c6e40a3c02470#egg=web
 
https://github.com/drewp/cyclone/archive/python3.zip#egg=cyclone
 

	
 
cycloneerr==0.3.0
 
rdfdb==0.19.0
 
standardservice==0.6.0
0 comments (0 inline, 0 general)