diff --git a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/AndroidDeviceTest.kt b/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/AndroidDeviceTest.kt index 22c873c2b2..c3ddd72332 100644 --- a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/AndroidDeviceTest.kt +++ b/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/AndroidDeviceTest.kt @@ -295,8 +295,6 @@ class AndroidDeviceTest : IntegrationTestBase(connectCoordinatorWS = false) { delay(1000) - clientImpl.debugInfo.log() - // leave and cleanup the joining call call.leave() call.cleanup() @@ -471,7 +469,6 @@ class AndroidDeviceTest : IntegrationTestBase(connectCoordinatorWS = false) { // log debug info logger.d { networkOut.toString() } - clientImpl.debugInfo.log() // leave and clean up a call call.leave() diff --git a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/CallSwitchingTest.kt b/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/CallSwitchingTest.kt deleted file mode 100644 index f4e80fa3af..0000000000 --- a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/CallSwitchingTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core - -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.utils.Timer -import kotlinx.coroutines.test.runTest -import org.junit.Test -import kotlin.time.Duration.Companion.seconds - -class CallSwitchingTest : IntegrationTestBase(connectCoordinatorWS = false) { - - private val logger by taggedLogger("Test:AndroidDeviceTest") - - @Test - fun switch() = runTest(timeout = 30.seconds) { - val location = clientImpl.getCachedLocation() - println("location: $location") - val numberOfCalls = 10 - // create 3 calls - val calls = (0 until numberOfCalls).map { - val call = client.call("audio_room", "switch-test-$it") - val result = call.create(custom = mapOf("switchtest" to "1")) - assertSuccess(result) - call - } - - // loop over the calls, join them and leave - val timer = Timer("switch to location $location") - (0 until numberOfCalls).map { - val call = client.call("audio_room", "switch-test-$it") - val result = call.join() - assertSuccess(result) - assertSuccess(result) - timer.split("iteration $it") - call.leave() - } - timer.finish() - - timer.let { - logger.i { "${it.name} took ${it.duration}" } - it.durations.forEach { (s, t) -> - logger.i { " - ${it.name}:$s took $t" } - } - } - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 71f8b4c24c..18e16145af 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -380,14 +380,12 @@ public class Call( } // step 1. call the join endpoint to get a list of SFUs - val timer = clientImpl.debugInfo.trackTime("call.join") val locationResult = clientImpl.getCachedLocation() if (locationResult !is Success) { return locationResult as Failure } location = locationResult.value - timer.split("location found") val options = createOptions ?: if (create) { @@ -403,7 +401,6 @@ public class Call( val sfuToken = result.value.credentials.token val sfuUrl = clientImpl.testSfuAddress ?: result.value.credentials.server.url val iceServers = result.value.credentials.iceServers.map { it.toIceServer() } - timer.split("join request completed") session = if (testInstanceProvider.rtcSessionCreator != null) { testInstanceProvider.rtcSessionCreator!!.invoke() @@ -425,21 +422,18 @@ public class Call( session?.let { state._connection.value = RealtimeConnection.Joined(it) } - timer.split("rtc session init") try { session?.connect() } catch (e: Exception) { return Failure(Error.GenericError(e.message ?: "RtcSession error occurred.")) - } finally { - timer.split("rtc connect completed") } scope.launch { // wait for the first stream to be added session?.let { rtcSession -> val mainRtcSession = rtcSession.lastVideoStreamAdded.filter { it != null }.first() - timer.finish("stream added, rtc completed, ready to display video $mainRtcSession") + logger.d { "stream added, rtc completed, ready to display video $mainRtcSession" } } } @@ -458,8 +452,6 @@ public class Call( } } - timer.finish() - return Success(value = session!!) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt index 8b4163ab6b..6b4b201966 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt @@ -54,7 +54,6 @@ import io.getstream.video.android.core.socket.ErrorResponse import io.getstream.video.android.core.socket.PersistentSocket import io.getstream.video.android.core.socket.SocketState import io.getstream.video.android.core.sounds.Sounds -import io.getstream.video.android.core.utils.DebugInfo import io.getstream.video.android.core.utils.LatencyResult import io.getstream.video.android.core.utils.getLatencyMeasurementsOKHttp import io.getstream.video.android.core.utils.safeCall @@ -175,8 +174,6 @@ internal class StreamVideoImpl internal constructor( CoroutineScope(_scope.coroutineContext + SupervisorJob() + coroutineExceptionHandler) /** if true we fail fast on errors instead of logging them */ - var developmentMode = true - val debugInfo = DebugInfo(this) /** session id is generated client side */ public val sessionId = UUID.randomUUID().toString() @@ -201,7 +198,6 @@ internal class StreamVideoImpl internal constructor( override fun cleanup() { // remove all cached calls calls.clear() - debugInfo.stop() // stop all running coroutines scope.cancel() // stop the socket @@ -411,8 +407,6 @@ internal class StreamVideoImpl internal constructor( } } } - - debugInfo.start() } var location: String? = null @@ -456,10 +450,10 @@ internal class StreamVideoImpl internal constructor( // wait for the guest user setup if we're using guest users guestUserJob?.await() try { - val timer = debugInfo.trackTime("coordinator connect") + val startTime = System.currentTimeMillis() socketImpl.connect() - timer.finish() - Success(timer.duration) + val duration = System.currentTimeMillis() - startTime + Success(duration) } catch (e: ErrorResponse) { if (e.code == VideoErrorCode.TOKEN_EXPIRED.code) { refreshToken(e) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index ddec0150ec..838aeb4b8c 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -380,9 +380,6 @@ public class RtcSession internal constructor( errorJob = coroutineScope.launch { sfuConnectionModule.sfuSocket.errors.collect { logger.e(it) { "permanent failure on socket connection" } - if (clientImpl.developmentMode) { - throw it - } } } } @@ -422,10 +419,7 @@ public class RtcSession internal constructor( } suspend fun connect() { - val timer = clientImpl.debugInfo.trackTime("sfu ws") sfuConnectionModule.sfuSocket.connect() - timer.finish() - // ensure that the join event has been handled before starting RTC try { withTimeout(2000L) { @@ -605,7 +599,6 @@ public class RtcSession internal constructor( private suspend fun connectRtc() { val settings = call.state.settings.value - val timer = clientImpl.debugInfo.trackTime("connectRtc") // turn of the speaker if needed if (settings?.audio?.speakerDefaultOn == false) { @@ -620,14 +613,12 @@ public class RtcSession internal constructor( if (canPublish) { publisher = createPublisher() - timer.split("createPublisher") } else { // enable the publisher if you receive the send audio or send video capability coroutineScope.launch { call.state.ownCapabilities.collect { if (it.any { it == OwnCapability.SendAudio || it == OwnCapability.SendVideo }) { publisher = createPublisher() - timer.split("createPublisher") } } } @@ -653,7 +644,6 @@ public class RtcSession internal constructor( // step 2 ensure all tracks are setup correctly // start capturing the video - timer.split("media enabled") // step 4 add the audio track to the publisher setLocalTrack( TrackType.TRACK_TYPE_AUDIO, @@ -687,7 +677,6 @@ public class RtcSession internal constructor( } // step 6 - onNegotiationNeeded will trigger and complete the setup using SetPublisherRequest - timer.finish() listenToMediaChanges() // subscribe to the tracks of other participants @@ -1735,7 +1724,6 @@ public class RtcSession internal constructor( failedToSwitch: () -> Unit, ) { logger.i { "[switchSfu] from ${this.sfuUrl} to $sfuUrl" } - val timer = clientImpl.debugInfo.trackTime("call.switchSfu") // Prepare SDP val getSdp = suspend { @@ -1767,7 +1755,6 @@ public class RtcSession internal constructor( when (it) { is SocketState.Connected -> { logger.d { "[switchSfu] Migration SFU socket state changed to Connected" } - timer.split("SFU socket connected") // Disconnect the old SFU and stop listening to SFU stateflows eventJob?.cancel() @@ -1807,7 +1794,6 @@ public class RtcSession internal constructor( subscriber?.state?.collect { if (it == PeerConnectionState.CONNECTED) { logger.d { "[switchSfu] Migration subscriber state changed to Connected" } - timer.split("Subscriber connected") tempSubscriber?.let { tempSubscriberValue -> tempSubscriberValue.connection.close() tempSubscriber = null @@ -1815,7 +1801,6 @@ public class RtcSession internal constructor( onMigrationCompleted.invoke() - timer.finish() cancel() } else if (it == PeerConnectionState.CLOSED || it == PeerConnectionState.DISCONNECTED || diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/DebugInfo.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/DebugInfo.kt deleted file mode 100644 index bc65500f55..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/DebugInfo.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.utils - -import android.os.Build -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.StreamVideoImpl -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import java.util.Collections - -internal data class Timer(val name: String, val start: Long = System.currentTimeMillis()) { - var end: Long = 0 - var duration: Long = 0 - var splits: List> = mutableListOf() - var durations: List> = mutableListOf() - - fun split(s: String) { - val now = System.currentTimeMillis() - val last = splits.lastOrNull()?.second ?: start - splits += s to now - durations += s to (now - last) - } - - fun finish(s: String? = null): Long { - s?.let { - split(s) - } - end = System.currentTimeMillis() - duration = end - start - return duration - } -} - -/** - * Handy helper gathering all relevant debug information - */ -internal class DebugInfo(val client: StreamVideoImpl) { - private var job: Job? = null - val scope = CoroutineScope(DispatcherProvider.IO) - - private val logger by taggedLogger("DebugInfo") - - // timers to help track performance issues in prod - val timers = Collections.synchronizedList(mutableListOf()) - // last 20 events - - // phone type - val phoneModel = Build.MODEL - - // android version - val version = Build.VERSION.SDK_INT - - // how many times the network dropped - - // how often the sockets reconnected - - // supported codecs - // resolution - val resolution by lazy { } - val availableResolutions by lazy { } - - fun start() { - if (client.developmentMode && !DispatcherProvider.inTest) { - job = scope.launch { - while (true) { - delay(20000) - log() - } - } - } - } - - fun stop() { - job?.cancel() - } - - fun log() { - val call = client.state.activeCall.value - val sessionId = call?.sessionId - val subscriber = call?.session?.subscriber - val publisher = call?.session?.publisher - val resolution = call?.camera?.resolution?.value - val availableResolutions = call?.camera?.availableResolutions?.value - val maxResolution = availableResolutions?.maxByOrNull { it.width * it.height } - - val publisherIce = publisher?.connection?.iceConnectionState() - val subIce = subscriber?.connection?.iceConnectionState() - - val videoTrackState = call?.mediaManager?.videoTrack?.state() - val coordinatorSocket = client.socketImpl.connectionState.value.javaClass.name - val sfuSocket = - call?.session?.sfuConnectionModule?.sfuSocket?.connectionState?.value?.javaClass?.name - - // good spot to attach your debugger - - logger.i { "Debug info $phoneModel running android $version" } - logger.i { "Active call is ${call?.cid}, session id $sessionId" } - logger.i { - "video quality: current resolution $resolution max resolution for camera is $maxResolution" - } - logger.i { - "Coordinator socket: $coordinatorSocket, SFU socket: $sfuSocket Subscriber: $publisherIce Publisher: $subIce" - } - logger.i { "Performance details" } - timers.forEach { - logger.i { "${it.name} took ${it.duration}" } - it.durations.forEach { (s, t) -> - logger.i { " - ${it.name}:$s took $t" } - } - } - /* - Stats wishlist - - selected sfu - - max resolution & fps capture - - incoming, rendering at resolution vs receiving resolution - - jitter & latency - - fir, pli, nack etc - - video limit reasons - - selected resolution - - TCP instead of UDP - - TODO: - - thermal profiles: https://proandroiddev.com/thermal-in-android-26cc202e9d3b - - webrtc get FPS levels (actually it's in the logs, but the format is clunky) - - match participant and track id.. - - */ - } - - fun listCodecs() { - // see https://developer.android.com/reference/kotlin/android/media/MediaCodecInfo - } - - fun trackTime(s: String): Timer { - val timer = Timer(s, System.currentTimeMillis()) - timers += timer - return timer - } -}