* stats wip

This commit is contained in:
Seth Call 2015-12-10 05:21:59 -06:00
parent 6314748225
commit 3388ec14db
7 changed files with 485 additions and 234 deletions

View File

@ -7,7 +7,15 @@ MixerActions = @MixerActions
mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")]
onStatsChanged: (stats) ->
@setState({stats: stats})
@setState({stats: stats[@props.participant.client_id]})
getInitialState: () ->
stats = window.SessionStatsStore.stats
if stats?
clientStats = stats[@props.participant.client_id]
else
clientStats = null
{stats: clientStats}
handleMute: (e) ->
e.preventDefault()
@ -53,9 +61,13 @@ MixerActions = @MixerActions
WebkitTransform: "rotate(#{pan}deg)"
}
connectionState = @state.stats.clientState(this.props.participant.client_id)
classification = @state.stats?.classification
if !classification?
classification = 'unknown'
connectionStateClasses = { 'track-connection-state': true}
connectionStateClasses[connectionState] = true
connectionStateClasses[classification] = true
`<div className={componentClasses}>
<div className="disabled-track-overlay" />
@ -68,7 +80,7 @@ MixerActions = @MixerActions
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixerId} onClick={this.handleMute}/>
<div className="track-icon-pan" style={panStyle}/>
<div className={classNames()}/>
<div className={classNames(connectionStateClasses)}/>
</div>
<br className="clearall"/>
</div>
@ -87,6 +99,7 @@ MixerActions = @MixerActions
$root = $(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
$connectionState = $root.find('.track-connection-state')
context.JK.interactReactBubble(
$mute,
@ -106,6 +119,14 @@ MixerActions = @MixerActions
,
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
context.JK.interactReactBubble(
$connectionState,
'SessionStatsHover',
() =>
{participant: this.props.participant}
,
{width:380, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
unless this.props.hasMixer
$mute.on("mouseenter", false)
$mute.on("mouseleave", false)

View File

@ -3,6 +3,21 @@ ChannelGroupIds = context.JK.ChannelGroupIds
MixerActions = @MixerActions
ptrCount = 0
StatsInfo = {
system: {
cpu: {
good: "Your computer's processor is not overworked by JamKazam or the your system.",
warn: "Your computer's processor is being heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues.",
poor: "Your computer's processor is being very heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues."
},
network: {
},
audio: {
}
}
}
@SessionStatsHover = React.createClass({
propTypes: {
@ -11,16 +26,116 @@ ptrCount = 0
mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")]
render: () ->
`<div>
hover: (type, field) ->
logger.debug("hover! #{type} #{field}")
@setState({hoverType: type, hoverField:field})
hoverOut: () ->
logger.debug("hover out!")
@setState({hoverType: null, hoverField: null})
stat: (properties, type, name, field, value) ->
classes = {'status-icon': true}
classifier = properties[field + '_level']
classes[classifier] = true
`<div className='stat' onMouseOver={this.hover.bind(this, type, field)} onMouseOut={this.hoverOut.bind(this)}><span className="title">- {name}</span><div className={classNames(classes)}></div><span className="stat-value">{value}</span></div>`
render: () ->
extraInfo = 'Hover over a stat to learn more.'
if @state.hoverType?
type = @state.hoverType
field = @state.hoverField
extraInfo = 'No extra info for this metric.'
classifier = @state.stats?[type]?[field + '_level']
if classifier?
info = StatsInfo[type]?[field]?[classifier]
if info?
extraInfo = info
# "Windows WDM-KS"
computerStats = []
networkStats = []
audioStats = []
network = @state.stats?.network
system = @state.stats?.system
audio = @state.stats?.audio
if system?
if system.cpu?
computerStats.push(@stat(system, 'system', 'Processor', 'cpu', Math.round(system.cpu) + ' %'))
if audio?
if audio.latency?
audioStats.push(@stat(audio, 'audio', 'Latency', 'latency', audio.latency.toFixed(1) + ' ms'))
if audio.input_jitter?
audioStats.push(@stat(audio, 'audio', 'Input Jitter', 'input_jitter', audio.input_jitter.toFixed(2)))
if audio.output_jitter?
audioStats.push(@stat(audio, 'audio', 'Output Jitter', 'output_jitter', audio.output_jitter.toFixed(2)))
if network?
if network.net_bitrate?
networkStats.push(@stat(network, 'network', 'Bandwidth', 'net_bitrate', Math.round(network.net_bitrate) + ' k'))
if network.ping?
networkStats.push(@stat(network, 'network', 'Latency', 'ping', (network.ping / 2).toFixed(1) + ' ms'))
#if network.jitter?
# networkStats.
if network.pkt_loss?
networkStats.push(@stat(network, 'network', 'Packet Loss', 'pkt_loss', network.pkt_loss.toFixed(1) + ' %'))
if network.wifi?
if network.wifi
value = 'Wi-Fi'
else
value = 'Ethernet'
networkStats.push(@stat(network, 'network', 'Connectivity', 'wifi', value))
`<div className="stats-hover">
<h3>Session Diagnostics &amp; Stats: {this.props.participant.user.name}</h3>
<div className="stats-area">
<div className="computer-stats stats-holder">
<h3>Computer</h3>
{computerStats}
</div>
<div className="audio-stats stats-holder">
<h3>Audio Interface</h3>
{audioStats}
</div>
<div className="network-stats stats-holder">
<h3>Internet</h3>
{networkStats}
</div>
</div>
<div className="stats-info">
{extraInfo}
</div>
</div>`
onStatsChanged: (stats) ->
@setState({stats: stats})
stats = window.SessionStatsStore.stats
if stats?
clientStats = stats[@props.participant.client_id]
else
clientStats = null
@setState({stats: clientStats})
getInitialState: () ->
{stats: window.SessionStatsStore.stats}
stats = window.SessionStatsStore.stats
if stats?
clientStats = stats[@props.participant.client_id]
else
clientStats = null
{stats: clientStats, hoverType: null, hoverField: null}
closeHover: (e) ->

View File

@ -13,98 +13,123 @@ SystemThresholds = SessionStatThresholds.system
AudioThresholds = SessionStatThresholds.audio
@SessionStatsStore = Reflux.createStore(
{
listenables: @SessionStatsActions
rawStats: null
rawStats: null
onPushStats: (stats) ->
@rawStats = stats
@changed()
onPushStats: (stats) ->
classify: (holder, field, threshold) ->
value = holder[field]
fieldLevel = field + '_level'
fieldThreshold = threshold[field]
if value? && fieldThreshold?
logger.debug("connection stats", connectionStats)
@rawStats = stats
@changed()
classify: (holder, field, threshold) ->
value = holder[field]
fieldLevel = field = '-level'
fieldThreshold = threshold[field]
if value? && fieldThreshold?
if fieldThreshold.inverse
if value <= threshold.poor
holder[fieldLevel] = 'poor'
else if value <= threshold.warn
holder[fieldLevel] = 'warn'
if fieldThreshold.inverse
if value <= fieldThreshold.poor
holder[fieldLevel] = 'poor'
@participantClassification = 3
else if value <= fieldThreshold.warn
holder[fieldLevel] = 'warn'
@participantClassification = 2 if @participantClassification == 1
else
holder[fieldLevel] = 'good'
else if fieldThreshold.eql
if value == fieldThreshold.poor
holder[fieldLevel] = 'poor'
@participantClassification = 3
else if value == fieldThreshold.warn
holder[fieldLevel] = 'warn'
@participantClassification = 2 if @participantClassification == 1
else
holder[fieldLevel] = 'good'
else
holder[fieldLevel] = 'good'
else if fieldThreshold.eql
if value == threshold.poor
holder[fieldLevel] = 'poor'
else if value == threshold.warn
holder[fieldLevel] = 'warn'
else
holder[fieldLevel] = 'good'
else
if value >= threshold.poor
holder[fieldLevel] = 'poor'
else if value >= threshold.warn
holder[fieldLevel] = 'warn'
else
holder[fieldLevel] = 'good'
if value >= fieldThreshold.poor
holder[fieldLevel] = 'poor'
@participantClassification = 3
else if value >= fieldThreshold.warn
holder[fieldLevel] = 'warn'
@participantClassification = 2 if @participantClassification == 1
else
holder[fieldLevel] = 'good'
changed: () ->
for participant in @rawStats
changed: () ->
@stats = {}
# CPU is 0-100
if participant.cpu?
@classify(participant, 'cpu', SystemThresholds)
console.log("raw stats", @rawStats)
for participant in @rawStats
network = participant.network
@participantClassification = 1 # 1=good, 2=warn, 3=poor
if network?
# audio_bitrate: 256
# net_bitrate: 286.19244384765625
# ping: 0.08024691045284271
# ping_var: 0.6403124332427979
# pkt_loss: 100
# wifi: false
# CPU is 0-100
if participant.cpu?
system = {cpu: participant.cpu}
@classify(network, 'audio_bitrate', NetworkThresholds)
@classify(network, 'ping', NetworkThresholds)
@classify(network, 'pkt_loss', NetworkThresholds)
@classify(network, 'wifi', NetworkThresholds)
@classify(system, 'cpu', SystemThresholds)
audio = participant.audio
participant.system = system
if audio?
# acpu: 5.148329734802246
# audio_in: "Fast Track"
# audio_in_type: "Core Audio"
# cpu: 22.44668960571289
# framesize: 2.5
# in_latency: 5.020833492279053
# input_iio_jitter: -0.0015926361083984375
# input_jitter: 0.2977011799812317
# input_median: 400.16632080078125
# io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]"
# out_latency: 4.520833492279053
# output_iio_jitter: -0.07366180419921875
# output_jitter: 0.40290364623069763
# output_median: 400.0581970214844
# output_name: 4
# samplerate: 48000
network = participant.network
if audio.in_latency? and audio.out_latency?
audio.latency = in_latency + out_latency
if network?
# audio_bitrate: 256
# net_bitrate: 286.19244384765625
# ping: 0.08024691045284271
# ping_var: 0.6403124332427979
# pkt_loss: 100
# wifi: false
@classify(audio, 'audio_in_type', AudioThresholds)
@classify(audio, 'framesize', AudioThresholds)
@classify(audio, 'latency', AudioThresholds)
@classify(audio, 'input_jitter', AudioThresholds)
@classify(audio, 'output_jitter', AudioThresholds)
@classify(network, 'net_bitrate', NetworkThresholds)
@classify(network, 'ping', NetworkThresholds)
@classify(network, 'pkt_loss', NetworkThresholds)
@classify(network, 'wifi', NetworkThresholds)
@stats = @rawStats
@trigger(@stats)
audio = participant.audio
if audio?
# acpu: 5.148329734802246
# audio_in: "Fast Track"
# audio_in_type: "Core Audio"
# cpu: 22.44668960571289
# framesize: 2.5
# in_latency: 5.020833492279053
# input_iio_jitter: -0.0015926361083984375
# input_jitter: 0.2977011799812317
# input_median: 400.16632080078125
# io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]"
# out_latency: 4.520833492279053
# output_iio_jitter: -0.07366180419921875
# output_jitter: 0.40290364623069763
# output_median: 400.0581970214844
# output_name: 4
# samplerate: 48000
if audio.cpu?
system = {cpu: audio.cpu}
@classify(system, 'cpu', SystemThresholds)
participant.system = system
if audio.in_latency? and audio.out_latency?
audio.latency = audio.in_latency + audio.out_latency
@classify(audio, 'framesize', AudioThresholds)
@classify(audio, 'latency', AudioThresholds)
@classify(audio, 'input_jitter', AudioThresholds)
@classify(audio, 'output_jitter', AudioThresholds)
switch @participantClassification
when 1 then participant.classification = 'good'
when 2 then participant.classification = 'warn'
when 3 then participant.classification = 'poor'
else
participant.classification = 'unknown'
@stats[participant.id] = participant
@trigger(@stats)
}
)

View File

@ -6,6 +6,7 @@ $session-screen-divider: 1190px;
@content;
}
}
@mixin session-normal {
@media (min-width: #{$session-screen-divider}) {
@content;
@ -13,97 +14,166 @@ $session-screen-divider: 1190px;
}
#session-screen {
.session-container {
overflow-x: hidden;
.session-container {
overflow-x: hidden;
}
.session-track {
@include session-small {
max-width: 120px;
}
.session-track {
&.metronome, &.jam-track, &.recorded-track, &.backing-track {
@include session-small {
max-width: 120px;
height: auto;
}
&.metronome, &.jam-track, &.recorded-track, &.backing-track {
.track-icon-pan {
@include session-small {
height:auto;
}
.track-icon-pan {
@include session-small {
margin-right:0;
}
}
.track-buttons {
@include session-small {
margin:12px 0 0;
}
}
table.vu {
@include session-small {
margin-top:5px;
}
}
.track-controls {
@include session-small {
margin-right:8px;
}
}
.track-icon-pan {
@include session-small {
margin-right:2px;
}
}
.track-instrument {
@include session-small {
margin: -4px 12px 0 0;
}
margin-right: 0;
}
}
&.jam-track-category, &.recorded-category {
.track-controls {
@include session-small {
margin-top:5px;
margin-right:8px;
}
.track-buttons {
@include session-small {
margin: 12px 0 0;
}
table.vu {
@include session-small {
margin-top:0;
}
}
table.vu {
@include session-small {
margin-top: 5px;
}
.jam-track-header {
@include session-small {
float:left;
}
}
.track-controls {
@include session-small {
margin-right: 8px;
}
.name {
@include session-small {
float:left;
}
}
.track-icon-pan {
@include session-small {
margin-right: 2px;
}
.track-buttons {
@include session-small {
margin-top:12px;
}
}
.track-instrument {
@include session-small {
margin: -4px 12px 0 0;
}
}
}
&.jam-track-category, &.recorded-category {
.track-controls {
@include session-small {
margin-top: 5px;
margin-right: 8px;
}
}
table.vu {
@include session-small {
margin-top: 0;
}
}
.jam-track-header {
@include session-small {
float: left;
}
}
.name {
@include session-small {
float: left;
}
}
.track-buttons {
@include session-small {
margin-top: 12px;
}
}
}
}
.track-controls {
@include session-small {
margin-top:8px;
.track-controls {
@include session-small {
margin-top: 8px;
}
}
.track-buttons {
@include session-small {
margin-left: 14px;
}
}
.stats-info {
float: left;
width: 120px;
border-radius: 15px;
border-color: #fc0;
border-width: 1px;
border-style: solid;
padding:10px;
height: 313px;
margin-top: 20px;
}
.stats-area {
float: left;
padding: 20px;
color: #cccccc;
h3 {
color: white;
}
.stat {
margin: 7px 0;
}
.title {
display: inline-block;
width: 100px;
line-height: 20px;
height: 20px;
vertical-align: middle;
}
.stats-holder {
margin-top: 20px;
h3 {
margin: 0 0 10px 0;
}
}
.status-icon {
border-radius: 10px;
width: 20px;
height: 20px;
margin-right: 10px;
display: inline-block;
vertical-align: middle;
&.poor {
background-color: $poor;
}
&.warn {
background-color: $fair;
}
&.unknown {
background-color: $unknown;
}
&.good {
background-color: $good;
}
}
.track-buttons {
@include session-small {
margin-left:14px;
}
.stat-value {
width: 50px;
display: inline-block;
height: 20px;
vertical-align: middle;
line-height: 20px;
}
h2 {
}
h2 {
color: #fff;
font-weight: 600;
font-size: 24px;
@ -125,18 +195,18 @@ $session-screen-divider: 1190px;
padding: 10px;
height: 100%;
margin-bottom: 15px;
color:$ColorTextTypical;
overflow:hidden;
position:relative;
color: $ColorTextTypical;
overflow: hidden;
position: relative;
}
.session-media-tracks {
width:34%;
width: 34%;
}
.session-notifications {
border-right-width: 0;
display:none; //temp
display: none; //temp
}
.in-session-controls {
@ -162,20 +232,20 @@ $session-screen-divider: 1190px;
a {
img, .volume-icon {
vertical-align:top;
vertical-align: top;
margin-right: 4px;
}
span {
vertical-align:middle;
vertical-align: middle;
}
}
.button-grey {
margin:0 5px;
margin: 0 5px;
padding: 3px 7px;
&.session-leave {
margin-right:10px;
margin-right: 10px;
}
}
}
@ -192,15 +262,15 @@ $session-screen-divider: 1190px;
bottom: 0;
left: 0;
right: 0;
text-align:left;
text-align: left;
&.media-options-showing {
top:180px;
top: 180px;
}
border-right: 1px solid #4c4c4c;
margin-bottom:10px;
margin-top:10px;
margin-bottom: 10px;
margin-top: 10px;
}
p {
@ -209,57 +279,56 @@ $session-screen-divider: 1190px;
}
.download-jamtrack {
margin-top:20px;
margin-top: 20px;
}
.when-empty {
margin-top:25px;
margin-left:22px;
color:$ColorTextTypical;
overflow:hidden;
margin-top: 25px;
margin-left: 22px;
color: $ColorTextTypical;
overflow: hidden;
}
.session-track-settings {
height:20px;
cursor:pointer;
padding-bottom:1px; // to line up with SessionOtherTracks
color:$ColorTextTypical;
height: 20px;
cursor: pointer;
padding-bottom: 1px; // to line up with SessionOtherTracks
color: $ColorTextTypical;
&:hover {
color:white;
color: white;
}
span {
top: -5px;
position: relative;
left:3px;
left: 3px;
}
}
.session-invite-musicians {
height:20px;
height: 20px;
cursor: pointer;
color:$ColorTextTypical;
color: $ColorTextTypical;
&:hover {
color:white;
color: white;
}
span {
top:-5px;
position:relative;
left:3px;
top: -5px;
position: relative;
left: 3px;
}
}
.closeAudio, .session-clear-notifications {
cursor: pointer;
color:$ColorTextTypical;
height:20px;
color: $ColorTextTypical;
height: 20px;
img {
top:-2px
top: -2px
}
span {
top: -5px;
@ -268,115 +337,113 @@ $session-screen-divider: 1190px;
}
}
.open-media-file-header, .use-metronome-header {
font-size:14px;
line-height:100%;
margin:0;
font-size: 14px;
line-height: 100%;
margin: 0;
img {
position:relative;
top:3px;
position: relative;
top: 3px;
}
}
.open-media-file-header {
img {
vertical-align:middle;
vertical-align: middle;
}
.open-text {
margin-left:5px;
vertical-align:bottom;
margin-left: 5px;
vertical-align: bottom;
}
}
.use-metronome-header {
clear: both;
a {
color:$ColorTextTypical;
color: $ColorTextTypical;
&:hover {
text-decoration: underline;
color:white;
color: white;
}
}
}
.open-media-file-options {
font-size:14px;
font-size: 14px;
margin: 7px 0 0 7px !important;
color:$ColorTextTypical;
color: $ColorTextTypical;
li {
margin-bottom:5px !important;
margin-left:38px !important;
margin-bottom: 5px !important;
margin-left: 38px !important;
a {
text-decoration: none;
&:hover {
text-decoration: underline;
color:white;
color: white;
}
color:$ColorTextTypical;
color: $ColorTextTypical;
}
}
}
.open-metronome {
margin-left:5px;
margin-left: 5px;
}
.media-options {
padding-bottom:10px;
padding-bottom: 10px;
}
.session-mytracks-notracks p.notice {
font-size:14px;
font-size: 14px;
}
.session-notification {
color: white;
background-color: #666666;
border-radius: 6px;
min-height: 36px;
width:100%;
position:relative;
width: 100%;
position: relative;
@include border_box_sizing;
padding:6px;
margin:10px 0;
padding: 6px;
margin: 10px 0;
&.has-details {
cursor:pointer;
cursor: pointer;
}
.msg {
font-size:14px;
font-size: 14px;
}
.detail {
font-size:12px;
margin-top:5px;
font-size: 12px;
margin-top: 5px;
}
.notify-help {
color:#ffcc00;
color: #ffcc00;
text-decoration: none;
font-size:12px;
margin-left:5px;
font-size: 12px;
margin-left: 5px;
&:hover {
text-decoration:underline !important;
text-decoration: underline !important;
}
}
}
.close-window {
text-align:center;
clear:both;
text-align: center;
clear: both;
}
.session-volume-settings .volume-icon {
display:inline-block;
width:14px;
height:14px;
background-image:url('/assets/content/icon_mute_sm.png');
background-repeat:no-repeat;
display: inline-block;
width: 14px;
height: 14px;
background-image: url('/assets/content/icon_mute_sm.png');
background-repeat: no-repeat;
&.muted {
background-position: 0 0;

View File

@ -126,10 +126,20 @@
cursor:pointer;
text-align: center;
margin-left:10px;
}
.circle {
border-radius:10px;
&.poor {
background-color:$poor;
}
&.warn {
background-color:$fair;
}
&.unknown {
background-color:$unknown;
}
&.good {
background-color:$good;
}
}
.track-icon-equalizer {
@ -370,6 +380,7 @@
}
}
&.SessionTrackVolumeHover {
.session-track {
margin-bottom:0;
@ -381,6 +392,18 @@
height:380px ! important;
}
&.SessionStatsHover {
width:380px;
height:400px;
@include border_box_sizing;
h3 {
margin-top:20px;
color: white;
margin-left:20px;
}
}
&.SessionTrackPanHover {
width:331px;
height:197px;

View File

@ -380,7 +380,7 @@ if defined?(Bundler)
config.session_stat_thresholds = {
network: {
wifi: {warn: true, poor: true, eql: true},
audio_bitrate: {warn: 200, poor: 135, inverse:true},
net_bitrate: {warn: 210, poor: 155, inverse:true},
ping: {warn: 40, poor: 70},
pkt_loss: {warn: 3, poor: 10}
},
@ -388,7 +388,7 @@ if defined?(Bundler)
cpu: {warn: 70, poor:85}
},
audio: {
audio_in_type: {warn: 'WDM', poor: 'WDM', eql: true},
framesize: {warn: 2.6, poor: 2.6},
latency: {warn: 10, poor: 20},
input_jitter: {warn: 0.5, poor: 1},