Skip to content

Commit

Permalink
Telemetry (#2129)
Browse files Browse the repository at this point in the history
Co-authored-by: Aleksei Sapitskii <[email protected]>
  • Loading branch information
OdNairy and aleksproger authored May 8, 2024
1 parent cd70206 commit f6bb3af
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Sources/MapboxMaps/ContentBuilders/Tree/MapContentNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ final class MapContentNode: Identifiable {
self.context = context
}

var childrenIsEmpty: Bool { children.isEmpty }

func withChildrenNodes(_ closure: (() -> MapContentNode) -> Void) {
var idx = 0

Expand Down
11 changes: 11 additions & 0 deletions Sources/MapboxMaps/ContentBuilders/Tree/MapContentReconciler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final class MapContentReconciler {
private let context: MapContentNodeContext
private var root: MapContentNode
private var loadingToken: AnyCancelable?
private var sendTelemetryOncePerStyle = Once()

init(styleManager: StyleManagerProtocol, sourceManager: StyleSourceManagerProtocol, styleIsLoaded: Signal<Bool>) {
self.context = MapContentNodeContext(
Expand All @@ -44,13 +45,23 @@ final class MapContentReconciler {
defer { trace?.end() }

context.update(mapContent: content, root: root)
triggerTelemetryIfNeeded(for: root)
}

private func reloadStyle(with content: any MapContent) {
let trace = OSLog.platform.beginInterval("MapContent update on style reload")
defer { trace?.end() }
sendTelemetryOncePerStyle.reset()

context.reload(mapContent: content, root: root)
triggerTelemetryIfNeeded(for: root)
}

/// Increment telemetry counter once per StyleDSL usage on the single style
func triggerTelemetryIfNeeded(for node: MapContentNode) {
guard sendTelemetryOncePerStyle.continueOnce(), !node.childrenIsEmpty else { return }
Log.info(forMessage: "triggerTelemetryIfNeeded")
sendTelemetry(\.styleDSL)
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/MapboxMaps/Foundation/CoreAliases.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@_implementationOnly import MapboxCoreMaps_Private
@_implementationOnly import MapboxCommon_Private

typealias CoreCameraOptions = MapboxCoreMaps_Private.CameraOptions
typealias CoreCameraState = MapboxCoreMaps_Private.CameraState
Expand Down Expand Up @@ -32,3 +33,4 @@ typealias CoreObservable = MapboxCoreMaps_Private.Observable
typealias CoreViewAnnotationPositionsUpdateListener = MapboxCoreMaps_Private.ViewAnnotationPositionsUpdateListener
typealias CoreMapSnapshotter = MapboxCoreMaps_Private.MapSnapshotter
typealias CorePerformanceSamplerOptions = MapboxCoreMaps_Private.PerformanceSamplerOptions
typealias TelemetryCounter = MapboxCommon_Private.FeatureTelemetryCounter
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import CarPlay
#endif
import UIKit

@available(iOS 13.0, *)
extension UIWindow {

/// The `UIScene` containing this window.
@available(iOS 13.0, *)
internal var parentScene: UIScene? {
#if canImport(CarPlay)
switch self {
Expand All @@ -17,6 +17,14 @@ extension UIWindow {
}
#else
return windowScene
#endif
}

var isCarPlay: Bool {
#if canImport(CarPlay)
return self is CPWindow
#else
return false
#endif
}
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/MapboxMaps/Foundation/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ open class MapView: UIView, SizeTrackingLayerDelegate {
}
}

private(set) var didMoveToCarPlayWindow = false

open override func didMoveToWindow() {
super.didMoveToWindow()

Expand All @@ -642,6 +644,11 @@ open class MapView: UIView, SizeTrackingLayerDelegate {
return
}

if window.isCarPlay, !didMoveToCarPlayWindow {
didMoveToCarPlayWindow = true
sendTelemetry(\.carPlay)
}

displayLink = dependencyProvider.makeDisplayLink(
window: window,
target: ForwardingDisplayLinkTarget { [weak self] in
Expand Down
56 changes: 56 additions & 0 deletions Sources/MapboxMaps/Foundation/TelemetryCounter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation

extension TelemetryCounter {
fileprivate static let sdkPrefix = "maps-mobile"
fileprivate static let swiftUI = TelemetryCounter.create(name: "map", category: "swift-ui")
fileprivate static let viewportCameraState = TelemetryCounter.viewport(name: "state/camera")
fileprivate static let viewportFollowState = TelemetryCounter.viewport(name: "state/follow-puck")
fileprivate static let viewportOverviewState = TelemetryCounter.viewport(name: "state/overview")
fileprivate static let viewportTransition = TelemetryCounter.viewport(name: "transition")
fileprivate static let styleDSL = TelemetryCounter.create(name: "dsl", category: "style")
fileprivate static let carPlay = TelemetryCounter.create(forName: sdkPrefix + "/carplay")

private static func viewport(name: String) -> TelemetryCounter {
.create(name: name, category: "viewport")
}

private static func create(name: String, category: String) -> TelemetryCounter {
.create(forName: [sdkPrefix, category, name].joined(separator: "/"))
}
}

/// Default scope for telemetry events
/// This scope and all posible future scopes should be singleton to get rid of spawning several equal counters
/// Also singleton allows the usage of KeyPath in sendTelemetry (static members on metatype not allowed in KeyPath)
struct TelemetryEvents {
let swiftUI = TelemetryEvent(counter: .swiftUI)
let viewportCameraState = TelemetryEvent(counter: .viewportCameraState)
let viewportFollowState = TelemetryEvent(counter: .viewportFollowState)
let viewportOverviewState = TelemetryEvent(counter: .viewportOverviewState)
let viewportTransition = TelemetryEvent(counter: .viewportTransition)
let styleDSL = TelemetryEvent(counter: .styleDSL)
let carPlay = TelemetryEvent(counter: .carPlay)

static let shared = TelemetryEvents()

fileprivate init() {}
}

/// Abstraction over the actiual telemetry implementation, which hides all the implementtion details
/// All it's properties should be fileprivate to keep implementation inside the file
struct TelemetryEvent {
fileprivate let counter: TelemetryCounter
}

/// Interface for sending telemetry using the default events scope
/// Allows to send events in type-safe manner while keeping implementstion details hidden from client code
func sendTelemetry(_ eventName: KeyPath<TelemetryEvents, TelemetryEvent>) {
sendTelemetry(eventName: eventName, category: TelemetryEvents.shared)
}

/// Interface for sending telemetry using the custom scope
/// Allows to send events in type-safe manner while keeping implementstion details hidden from client code and specify custom scope to group event when their number will grow
func sendTelemetry<T>(eventName: KeyPath<T, TelemetryEvent>, category: T) {
let event = category[keyPath: eventName]
event.counter.increment()
}
2 changes: 1 addition & 1 deletion Sources/MapboxMaps/SwiftUI/Map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public struct Map: UIViewControllerRepresentable {

public func makeCoordinator() -> Coordinator {
let urlOpener = ClosureURLOpener()

sendTelemetry(\.swiftUI)
let mapView = MapView(frame: .zero, urlOpener: urlOpener)
let viewController = MapViewController(mapView: mapView)

Expand Down
8 changes: 6 additions & 2 deletions Sources/MapboxMaps/Viewport/ViewportManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public final class ViewportManager {
public func transition(to toState: ViewportState,
transition: ViewportTransition? = nil,
completion: ((_ success: Bool) -> Void)? = nil) {
sendTelemetry(\.viewportTransition)
impl.transition(to: toState, transition: transition, completion: completion)
}

Expand All @@ -124,7 +125,8 @@ public final class ViewportManager {
@_documentation(visibility: public)
@_spi(Experimental)
public func makeCameraViewportState(camera: CameraOptions) -> ViewportState {
CameraViewportState(cameraOptions: Signal(just: camera), mapboxMap: mapboxMap, safeAreaPadding: impl.safeAreaPadding)
sendTelemetry(\.viewportCameraState)
return CameraViewportState(cameraOptions: Signal(just: camera), mapboxMap: mapboxMap, safeAreaPadding: impl.safeAreaPadding)
}

func makeDefaultStyleViewportState(padding: UIEdgeInsets) -> ViewportState {
Expand All @@ -138,7 +140,8 @@ public final class ViewportManager {
/// with the default value specified for all parameters.
/// - Returns: The newly-created ``FollowPuckViewportState``.
public func makeFollowPuckViewportState(options: FollowPuckViewportStateOptions = .init()) -> FollowPuckViewportState {
FollowPuckViewportState(
sendTelemetry(\.viewportFollowState)
return FollowPuckViewportState(
options: options,
mapboxMap: mapboxMap,
onPuckRender: onPuckRender,
Expand All @@ -149,6 +152,7 @@ public final class ViewportManager {
/// - Parameter options: configuration options used when creating ``OverviewViewportState``.
/// - Returns: The newly-created ``OverviewViewportState``.
public func makeOverviewViewportState(options: OverviewViewportStateOptions) -> OverviewViewportState {
sendTelemetry(\.viewportOverviewState)
return OverviewViewportState(
options: options,
mapboxMap: mapboxMap,
Expand Down

0 comments on commit f6bb3af

Please sign in to comment.