Skip to content

Commit

Permalink
Live tracking for memory leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlidakis committed Sep 27, 2023
1 parent 5c7982f commit 84d2290
Show file tree
Hide file tree
Showing 44 changed files with 418 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// MemoryLeaksViewer.swift
// DemoApp
//
// Created by Ilias Pavlidakis on 27/9/23.
//

import Foundation
import SwiftUI
import StreamVideo

struct MemoryLeaksViewer: View {

@Injected(\.appearance) var appearance

@StateObject private var snapshot = MemorySnapshot(
includeDeallocatedObjects: false,
includeNotLeaked: false
)
@State private var entries: [MemorySnapshot.Entry] = []

var body: some View {
List {
ForEach(entries, id: \.typeName) { entry in
makeEntryView(for: entry)
}
}
.onReceive(snapshot.$items) { entries = $0 }
.navigationTitle("MemoryLeaks Viewer")
.modifier(
SearchableModifier { query in
if query.isEmpty {
entries = snapshot.items
} else {
entries = snapshot
.items
.filter { $0.typeName.contains(query) }
}
})
}

@ViewBuilder
func makeEntryView(for entry: MemorySnapshot.Entry) -> some View {
Label {
HStack {
Text(entry.typeName)
.font(appearance.fonts.body)
.foregroundColor(appearance.colors.text)
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)

Text("\(entry.refCount)/\(entry.maxCount)")
.font(appearance.fonts.caption1)
.foregroundColor(appearance.colors.text)
.lineLimit(1)
}
} icon: {
if entry.refCount > entry.maxCount {
Image(systemName: "exclamationmark.triangle")
.foregroundColor(appearance.colors.accentRed)
} else {
EmptyView()
}
}
}
}
12 changes: 12 additions & 0 deletions DemoApp/Sources/Views/CallTopView/DemoCallTopView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ struct DemoCallTopView: View {
@ObservedObject var appState = AppState.shared
@State var sharingPopupDismissed = false

@State private var isLogsViewerVisible: Bool = false
@State private var isMemoryLeaksViewerVisible: Bool = false

init(viewModel: CallViewModel) {
self.viewModel = viewModel
}
Expand Down Expand Up @@ -53,6 +56,13 @@ struct DemoCallTopView: View {


reactionsList()

if AppEnvironment.configuration == .debug {
RealtimeDebugMenu(
isLogsViewerVisible: $isLogsViewerVisible,
isMemoryLeaksViewerVisible: $isMemoryLeaksViewerVisible
)
}
} label: {
Image(systemName: "ellipsis")
.foregroundColor(.white)
Expand Down Expand Up @@ -94,6 +104,8 @@ struct DemoCallTopView: View {
.opacity(sharingPopupDismissed ? 0 : 1)
: nil
)
.sheet(isPresented: $isLogsViewerVisible) { NavigationView { MemoryLogViewer() } }
.sheet(isPresented: $isMemoryLeaksViewerVisible) { NavigationView { MemoryLeaksViewer() } }
}

private var hideLayoutMenu: Bool {
Expand Down
60 changes: 46 additions & 14 deletions DemoApp/Sources/Views/Login/DebugMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct DebugMenu: View {
}

@State private var isLogsViewerVisible: Bool = false
@State private var isMemoryLeaksViewerVisible: Bool = false

var body: some View {
Menu {
Expand Down Expand Up @@ -96,24 +97,16 @@ struct DebugMenu: View {
label: "Performance Tracker"
) { self.performanceTrackerVisibility = $0 }

Button {
isLogsViewerVisible = true
} label: {
Label {
Text("Show logs")
} icon: {
Image(systemName: "text.insert")
}
}

RealtimeDebugMenu(
isLogsViewerVisible: $isLogsViewerVisible,
isMemoryLeaksViewerVisible: $isMemoryLeaksViewerVisible
)
} label: {
Image(systemName: "gearshape.fill")
.foregroundColor(colors.text)
}.sheet(isPresented: $isLogsViewerVisible) {
NavigationView {
MemoryLogViewer()
}
}
.sheet(isPresented: $isLogsViewerVisible) { NavigationView { MemoryLogViewer() } }
.sheet(isPresented: $isMemoryLeaksViewerVisible) { NavigationView { MemoryLeaksViewer() } }
}

@ViewBuilder
Expand Down Expand Up @@ -168,3 +161,42 @@ struct DebugMenu: View {
}
}
}

struct RealtimeDebugMenu: View {

@Injected(\.colors) var colors

@Binding var isLogsViewerVisible: Bool
@Binding var isMemoryLeaksViewerVisible: Bool

var body: some View {
Menu {
Button {
isLogsViewerVisible = true
} label: {
Label {
Text("Show logs")
} icon: {
Image(systemName: "text.insert")
}
}

Button {
isMemoryLeaksViewerVisible = true
} label: {
Label {
Text("Show MemoryLeaks")
} icon: {
Image(systemName: "memorychip")
}
}
} label: {
Label {
Text("Realtime")
} icon: {
Image(systemName: "flowchart")
}
.foregroundColor(colors.text)
}
}
}
2 changes: 2 additions & 0 deletions Sources/StreamVideo/Call.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
)
self.callController.call = self
self.subscribeToLocalCallSettingsChanges()

MemoryLeakDetector.track(self)
}

convenience internal init(
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/CallSettings/CameraManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public final class CameraManager: ObservableObject, CallSettingsManager, @unchec
self.callController = callController
self.status = initialStatus
self.direction = initialDirection

MemoryLeakDetector.track(self)
}

/// Toggles the camera state.
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/CallSettings/MicrophoneManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public final class MicrophoneManager: ObservableObject, CallSettingsManager, @un
init(callController: CallController, initialStatus: CallSettingsStatus) {
self.callController = callController
self.status = initialStatus

MemoryLeakDetector.track(self)
}

/// Toggles the microphone state.
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/CallSettings/SpeakerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public final class SpeakerManager: ObservableObject, CallSettingsManager, @unche
self.callController = callController
self.status = initialSpeakerStatus
self.audioOutputStatus = initialAudioOutputStatus

MemoryLeakDetector.track(self)
}

/// Toggles the speaker during a call.
Expand Down
6 changes: 5 additions & 1 deletion Sources/StreamVideo/CallState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ public class CallState: ObservableObject {

private var localCallSettingsUpdate = false
private var durationTimer: Foundation.Timer?


public init() {
MemoryLeakDetector.track(self)
}

internal func updateState(from event: VideoEvent) {
switch event {
case .typeBlockedUserEvent(let event):
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/Controllers/CallController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class CallController {
self.environment = environment
self.defaultAPI = defaultAPI
self.cachedLocation = cachedLocation

MemoryLeakDetector.track(self)
}

/// Joins a call with the provided information.
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/Controllers/CallsController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class CallsController: ObservableObject {
self.streamVideo = streamVideo
self.subscribeToWatchEvents()
self.subscribeToConnectionUpdates()

MemoryLeakDetector.track(self)
}

/// Loads the next page of calls.
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/HTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ final class URLSessionClient: HTTPClient, @unchecked Sendable {
) {
self.urlSession = urlSession
self.tokenProvider = tokenProvider

MemoryLeakDetector.track(self)
}

func execute(request: URLRequest) async throws -> Data {
Expand Down
8 changes: 7 additions & 1 deletion Sources/StreamVideo/HTTPClient/InternetConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class InternetConnection {
status = monitor.status
monitor.delegate = self
monitor.start()

MemoryLeakDetector.track(self)
}

deinit {
Expand Down Expand Up @@ -202,7 +204,11 @@ extension InternetConnection {

return .available(quality)
}


init() {
MemoryLeakDetector.track(self)
}

deinit {
stop()
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/Models/CallSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public final class CallSettings: ObservableObject, Sendable {
#else
self.videoOn = videoOn
#endif

MemoryLeakDetector.track(self)
}

public var shouldPublish: Bool {
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/OpenApi/URLSessionTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ final class URLSessionTransport: DefaultAPITransport, @unchecked Sendable {
) {
self.urlSession = urlSession
self.tokenProvider = tokenProvider

MemoryLeakDetector.track(self)
}

func setTokenUpdater(_ tokenUpdater: @escaping UserTokenUpdater) {
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamVideo/OpenApi/generated/APIs/DefaultAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ open class DefaultAPI: DefaultAPIEndpoints, @unchecked Sendable {
self.transport = transport
self.middlewares = middlewares
self.jsonDecoder = jsonDecoder

MemoryLeakDetector.track(self)
}

func send<Response: Codable>(
Expand Down
6 changes: 4 additions & 2 deletions Sources/StreamVideo/StreamVideo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ public class StreamVideo: ObservableObject {
try await self.connectUser(isInitial: true)
}
}

MemoryLeakDetector.track(self)
}

/// Connects the current user.
Expand Down Expand Up @@ -242,7 +244,7 @@ public class StreamVideo: ObservableObject {
public func disconnect() async {
eventHandlers.removeAll()

await withCheckedContinuation { [webSocketClient] continuation in
await withCheckedContinuation { [weak webSocketClient] continuation in
if let webSocketClient = webSocketClient {
webSocketClient.disconnect {
continuation.resume()
Expand Down Expand Up @@ -563,7 +565,7 @@ extension StreamVideo: ConnectionStateDelegate {
case let .disconnected(source):
if let serverError = source.serverError, serverError.isInvalidTokenError
|| (source.serverError as? APIError)?.isTokenExpiredError == true {
Task {
Task { [weak webSocketClient] in
do {
guard let apiTransport = apiTransport as? URLSessionTransport else { return }
self.token = try await apiTransport.refreshToken()
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamVideo/Utils/LocationFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Foundation

class LocationFetcher {
enum LocationFetcher {

static func getLocation() async throws -> String {
guard let url = URL(string: "https://hint.stream-io-video.com/") else {
Expand Down
17 changes: 15 additions & 2 deletions Sources/StreamVideo/Utils/Logger/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ public struct LogSubsystem: OptionSet {
}

/// All subsystems within the SDK.
public static let all: LogSubsystem = [.database, .httpRequests, .webSocket, .webRTC, .other, .offlineSupport]
public static let all: LogSubsystem = [
.database,
.httpRequests,
.webSocket,
.webRTC,
.other,
.offlineSupport,
.thermalState,
.memoryLeaks
]

/// The subsystem responsible for any other part of the SDK.
/// This is the default subsystem value for logging, to be used when `subsystem` is not specified.
Expand All @@ -31,8 +40,12 @@ public struct LogSubsystem: OptionSet {
public static let webSocket = Self(rawValue: 1 << 3)
/// The subsystem responsible for offline support.
public static let offlineSupport = Self(rawValue: 1 << 4)
// The subsustem responsible for web rtc.
/// The subsystem responsible for WebRTC.
public static let webRTC = Self(rawValue: 1 << 5)
/// The subsystem responsible for ThermalState observation.
public static let thermalState = Self(rawValue: 1 << 6)
/// The subsystem responsible for memory leaks observation.
public static let memoryLeaks = Self(rawValue: 1 << 7)
}

public enum LogConfig {
Expand Down
Loading

0 comments on commit 84d2290

Please sign in to comment.