From 6314748225502f061bfed07b9a791e739fb7a4e4 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 9 Dec 2015 11:32:24 -0600 Subject: [PATCH] * wip --- admin/app/admin/crash_dumps.rb | 27 +++-- admin/app/admin/download_tracker.rb | 3 +- ruby/lib/jam_ruby/models/crash_dump.rb | 5 + ruby/lib/jam_ruby/models/jam_track_right.rb | 8 +- .../assets/javascripts/react-components.js | 1 + .../SessionOtherTrack.js.jsx.coffee | 10 ++ .../SessionStatsHover.js.jsx.coffee | 30 +++++ .../actions/SessionStatsActions.js.coffee | 5 + .../helpers/ConnectionStatsHelper.js.coffee | 0 .../stores/SessionStatsStore.js.coffee | 110 ++++++++++++++++++ .../stores/SessionStore.js.coffee | 13 +++ .../react-components/SessionTrack.css.scss | 13 +++ web/app/helpers/client_helper.rb | 1 + web/config/application.rb | 18 +++ 14 files changed, 225 insertions(+), 19 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee create mode 100644 web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee create mode 100644 web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee diff --git a/admin/app/admin/crash_dumps.rb b/admin/app/admin/crash_dumps.rb index bcfcd9106..4d85ae8ae 100644 --- a/admin/app/admin/crash_dumps.rb +++ b/admin/app/admin/crash_dumps.rb @@ -1,28 +1,27 @@ ActiveAdmin.register JamRuby::CrashDump, :as => 'Crash Dump' do # Note: a lame thing is it's not obvious how to make it search on email instead of user_id. filter :timestamp - filter :user_email, :as => :string filter :client_id + filter :user_id + menu :parent => 'Misc' + config.sort_order = 'created_at DESC' + index do + column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end + column "Client Version", :client_version + column "Client Type", :client_type + column "Download" do |post| + link_to 'Link', post.sign_url + end column "Timestamp" do |post| (post.timestamp || post.created_at).strftime('%b %d %Y, %H:%M') end - column "Client Type", :client_type - column "Dump URL" do |post| - link_to post.uri, post.uri + column "Description" do |post| + post.description end - - column "User ID", :user_id - - # FIXME (?): This isn't performant (though it likely doesn't matter). Could probably do a join. - column "User Email" do |post| - unless post.user_id.nil? - post.user_email - end - end - column "Client ID", :client_id actions + end end diff --git a/admin/app/admin/download_tracker.rb b/admin/app/admin/download_tracker.rb index 2577736e5..6d0daa013 100644 --- a/admin/app/admin/download_tracker.rb +++ b/admin/app/admin/download_tracker.rb @@ -10,10 +10,11 @@ ActiveAdmin.register JamRuby::DownloadTracker, :as => 'DownloadTrackers' do index do column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end - column 'Remote IP' do |oo| oo.remote_ip end + column 'Created' do |oo| oo.created_at end column 'JamTrack' do |oo| oo.jam_track end column 'Paid' do |oo| oo.paid end column 'Blacklisted?' do |oo| IpBlacklist.listed(oo.remote_ip) ? 'Yes' : 'No' end + column 'Remote IP' do |oo| oo.remote_ip end column "" do |oo| link_to 'Blacklist This IP', "download_trackers/#{oo.id}/blacklist_by_ip" end diff --git a/ruby/lib/jam_ruby/models/crash_dump.rb b/ruby/lib/jam_ruby/models/crash_dump.rb index 13087fed0..22874a299 100644 --- a/ruby/lib/jam_ruby/models/crash_dump.rb +++ b/ruby/lib/jam_ruby/models/crash_dump.rb @@ -1,6 +1,8 @@ module JamRuby class CrashDump < ActiveRecord::Base + include JamRuby::S3ManagerMixin + self.table_name = "crash_dumps" self.primary_key = 'id' @@ -23,5 +25,8 @@ module JamRuby self.user.email end + def sign_url(expiration_time = 3600 * 24 * 7, secure=true) + s3_manager.sign_url(self[:ri], {:expires => expiration_time, :secure => secure}) + end end end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 63558f84a..b400771a5 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -111,10 +111,10 @@ module JamRuby # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared - def sign_url(expiration_time = 120, bitrate=48, secure=true) - field_name = (bitrate==48) ? "url_48" : "url_44" - s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => secure}) - end + def sign_url(expiration_time = 120, bitrate=48, secure=true) + field_name = (bitrate==48) ? "url_48" : "url_44" + s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => secure}) + end def delete_s3_files remove_url_48! diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index ff86ff8e6..2b0e733d8 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -7,6 +7,7 @@ //= require ./react-components/stores/RecordingStore //= require ./react-components/stores/VideoStore //= require ./react-components/stores/SessionStore +//= require ./react-components/stores/SessionStatsStore //= require ./react-components/stores/MixerStore //= require ./react-components/stores/JamTrackStore //= require ./react-components/stores/SessionNotificationStore diff --git a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee index 68a6ccf70..0964bf602 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee @@ -4,6 +4,11 @@ MixerActions = @MixerActions @SessionOtherTrack = React.createClass({ + mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")] + + onStatsChanged: (stats) -> + @setState({stats: stats}) + handleMute: (e) -> e.preventDefault() @@ -48,6 +53,10 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } + connectionState = @state.stats.clientState(this.props.participant.client_id) + connectionStateClasses = { 'track-connection-state': true} + connectionStateClasses[connectionState] = true + `
@@ -59,6 +68,7 @@ MixerActions = @MixerActions
+

diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee new file mode 100644 index 000000000..a2e17c8cc --- /dev/null +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -0,0 +1,30 @@ +context = window +ChannelGroupIds = context.JK.ChannelGroupIds +MixerActions = @MixerActions +ptrCount = 0 + +@SessionStatsHover = React.createClass({ + + propTypes: { + clientId: React.PropTypes.string + } + + mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")] + + render: () -> + `
+ +
` + + onStatsChanged: (stats) -> + @setState({stats: stats}) + + getInitialState: () -> + {stats: window.SessionStatsStore.stats} + + + closeHover: (e) -> + e.preventDefault() + $container = $(this.getDOMNode()).closest('.react-holder') + $container.data('bt').btOff() +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee b/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee new file mode 100644 index 000000000..23dd5ef48 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee @@ -0,0 +1,5 @@ +context = window + +@SessionStatsActions = Reflux.createActions({ + pushStats: {} +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee new file mode 100644 index 000000000..70f3e28ed --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -0,0 +1,110 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() +EVENTS = context.JK.EVENTS +MIX_MODES = context.JK.MIX_MODES + +SessionActions = @SessionActions + +SessionStatThresholds = gon.session_stat_thresholds +NetworkThresholds = SessionStatThresholds.network +SystemThresholds = SessionStatThresholds.system +AudioThresholds = SessionStatThresholds.audio + +@SessionStatsStore = Reflux.createStore( + + rawStats: null + + onPushStats: (stats) -> + + 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' + 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' + + + changed: () -> + for participant in @rawStats + + # CPU is 0-100 + if participant.cpu? + @classify(participant, 'cpu', SystemThresholds) + + network = participant.network + + if network? + # audio_bitrate: 256 + # net_bitrate: 286.19244384765625 + # ping: 0.08024691045284271 + # ping_var: 0.6403124332427979 + # pkt_loss: 100 + # wifi: false + + @classify(network, 'audio_bitrate', NetworkThresholds) + @classify(network, 'ping', NetworkThresholds) + @classify(network, 'pkt_loss', NetworkThresholds) + @classify(network, 'wifi', NetworkThresholds) + + 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.in_latency? and audio.out_latency? + audio.latency = in_latency + out_latency + + @classify(audio, 'audio_in_type', AudioThresholds) + @classify(audio, 'framesize', AudioThresholds) + @classify(audio, 'latency', AudioThresholds) + @classify(audio, 'input_jitter', AudioThresholds) + @classify(audio, 'output_jitter', AudioThresholds) + + @stats = @rawStats + @trigger(@stats) + + + +) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee index b0bbb0ac7..61f28e2d6 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -731,6 +731,8 @@ VideoActions = @VideoActions $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document @handleAutoOpenJamTrack() + + @watchBackendStats() ) .fail((xhr) => @updateCurrentSession(null) @@ -762,6 +764,13 @@ VideoActions = @VideoActions @app.notifyServerError(xhr, 'Unable to Join Session'); ) + watchBackendStats: () -> + @backendStatsInterval = window.setInterval((() => (@updateBackendStats())), 1000) + + updateBackendStats: () -> + connectionStats = window.jamClient.getConnectionDetail('') + SessionStatsActions.pushStats(connectionStats) + trackChanges: (header, payload) -> if @currentTrackChanges < payload.track_changes_counter # we don't have the latest info. try and go get it @@ -1050,6 +1059,10 @@ VideoActions = @VideoActions @userTracks = null; @startTime = null; + if @backendStatsInterval? + window.clearInterval(@backendStatsInterval) + @backendStatsInterval = null + if @joinDeferred?.state() == 'resolved' $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}}) diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 82614b21e..700469329 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -119,6 +119,19 @@ } } + .track-connection-state { + width:20px; + height:20px; + float:left; + cursor:pointer; + text-align: center; + margin-left:10px; + } + + .circle { + + } + .track-icon-equalizer { float:left; cursor: pointer; diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb index f6febfb1d..d9dba9952 100644 --- a/web/app/helpers/client_helper.rb +++ b/web/app/helpers/client_helper.rb @@ -69,6 +69,7 @@ module ClientHelper gon.ftue_maximum_gear_latency = Rails.application.config.ftue_maximum_gear_latency gon.musician_search_meta = MusicianSearch.search_filter_meta gon.band_search_meta = BandSearch.search_filter_meta + gon.session_stat_thresholds = Rails.application.config.session_stat_thresholds # is this the native client or browser? @nativeClient = is_native_client? diff --git a/web/config/application.rb b/web/config/application.rb index 5d9fc97d6..84296bfd3 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -377,5 +377,23 @@ if defined?(Bundler) config.download_tracker_day_range = 30 config.max_user_ip_address = 10 config.max_multiple_users_same_ip = 2 + config.session_stat_thresholds = { + network: { + wifi: {warn: true, poor: true, eql: true}, + audio_bitrate: {warn: 200, poor: 135, inverse:true}, + ping: {warn: 40, poor: 70}, + pkt_loss: {warn: 3, poor: 10} + }, + system: { + 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}, + output_jitter: {warn: 0.5, poor: 1}, + } + } end end