Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live tracking for memory leaks #168

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Copyright © 2023 Stream.io Inc. All rights reserved.
//

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