Skip to content

Conversation

@olegzil
Copy link
Contributor

@olegzil olegzil commented Jan 31, 2020

Description

Telemetry implementation. MapboxNavigationTelemetry is a singleton object that transmits all telemetry events to the back end servers. The usage of this class requires that it is thread thread safe. Although it has a single public method, postTelemetryEvent() that handles all possible telemetry data to be transmitted to the server, it also registers itself with MapboxNavigation to receive various updates. These updates are asynchronous and may arrive at the same time that postTelemetryEvent() call is active. The code makes sure that the notification and the call do not cause a race condition. This is done using two channels one receive location updates the other receives onProgress events. All calls that may read or write the internal buffers of MapboxNavigationTelemetry are serialized using LocationBufferControl. It contains the commend to execute and a lambda that represents the command. The method monitorChannels() is what serializes access.

Currently only User Feedback events are handled. Each event becomes a member variable of TelemetryUserFeedbackWrapper. One of these will have to be written for each event to be sent to the back end servers. So to generate the required event data, you have to implement a TelemetryWrapper that collects the required data for this event.

Please include a brief summary of the change and which issue is fixed (e.g., fixes #issue)

  • I have added any issue links
  • I have added all related labels (bug, feature, new API(s), SEMVER, etc.)
  • I have added the appropriate milestone and project boards

Goal

Please describe the PR goals. Just the stuff needed to implement the fix / feature and a simple rationale. It could contain many check points if needed

Implementation

Please include all the relevant things implemented and also rationale, clarifications / disclaimers etc. related to the approach used. It could be as self code companion comments

Screenshots or Gifs

Please include all the media files to give some context about what's being implemented or fixed. It's not mandatory to upload screenshots or gifs, but for most of the cases it becomes really handy to get into the scope of the feature / bug being fixed and also it's REALLY useful for UI related PRs screenshot gif

Testing

Please describe the manual tests that you ran to verify your changes

  • I have tested locally (including SNAPSHOT upstream dependencies if needed) through testapp/demo app and run all activities to avoid regressions
  • I have tested via a test drive, or a simulation/mock location app
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have updated the CHANGELOG including this PR

import android.location.Location
import com.google.gson.Gson

internal enum class FeedbackType(val feedbackType: String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never used.

Copy link
Contributor Author

@olegzil olegzil Feb 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be used once UserFeedback events are posted to Telemetry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 removed

Copy link
Contributor

@RingerJK RingerJK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good 👍 need some changes

import com.mapbox.navigation.core.telemetry.audio.AudioTypeChain
import kotlin.math.floor

internal object NavigationUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also would be great have it as extensions

Copy link

@LukasPaczos LukasPaczos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress @olegzil. Adding to all other comments, I'd love to see an initial test structure implementation. The concepts you're introducing might allow us to collect metrics from any thread (another question is, do we need it?), but it'd be great if the architecture was designed in a testable manner from the get-go.

.build()

// Call back that receives
private val routeProgressListener = object : RouteProgressObserver {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and preferably it should already be parsed into whatever format the telemetry wrappers expect. This way, we're removing the cross dependency.

@olegzil
Copy link
Contributor Author

olegzil commented Feb 3, 2020

Good progress @olegzil. Adding to all other comments, I'd love to see an initial test structure implementation. The concepts you're introducing might allow us to collect metrics from any thread (another question is, do we need it?), but it'd be great if the architecture was designed in a testable manner from the get-go.

I am working on a set of Unit tests that should test this code extensively. Regarding the need for thread safety. Two answers regarding this subject. In this particular case the Telemetry class exposes one public method (not counting initialized()) and subscribes to two interfaces that have a combined three callbacks.
postTelemetryEvent() -- a method that can be called from any thread. This method cannot block the caller. It is used to report telemetry events from any part of the SDK, running on any thread.
The call backs are:
RouteProgressObserver and LocationEngineCallback. These two are usually called from the Main thread. Both the public method and the callbacks operate on the same data: locationBuffer and telemetryEventsQueue. As you can see, since object state can potentially be modified from multiple threads, access to the state should be serialized.

On a more general note, the SDK has multiple Singletons, each maintains a state that can be accessed via the singletons public methods. We should consider making these singletons thread safe. Finally, do we want the SDK to be reactive? Making it reactive requires that no public method blocks the caller in addition to guaranteeing thread sefety.

@korshaknn korshaknn self-requested a review February 3, 2020 19:37
@olegzil olegzil force-pushed the oz-telemetry-implementation branch from 46d8acf to f55d5b7 Compare February 4, 2020 21:37
@olegzil olegzil marked this pull request as ready for review February 5, 2020 17:26
@olegzil olegzil force-pushed the oz-telemetry-implementation branch from f55d5b7 to ac7f487 Compare February 5, 2020 19:21
}

private fun validateAccessToken(accessToken: String?) {
if (accessToken.isNullOrEmpty() ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might check it here

Suggested change
if (accessToken.isNullOrEmpty() ||
if (accessToken.isNullOrEmpty()
|| ( !accessToken.toLowerCase(Locale.US).startsWith("pk.")
|| !accessToken.toLowerCase(Locale.US).startsWith("sk."))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be incorrect. a token can start with pk or sk. If we use an "or" than if either one is false, the entire if-statement fails. We want it to fail only if neither is correct. Neither is modeled with "and". Like this:
if (accessToken.isNullOrEmpty()  || ( !accessToken.toLowerCase(Locale.US).startsWith("pk.")  && !accessToken.toLowerCase(Locale.US).startsWith("sk."))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, you're right, thanks!

@olegzil olegzil force-pushed the oz-telemetry-implementation branch from 2c918b3 to dbea0ed Compare February 6, 2020 21:04
@olegzil olegzil force-pushed the oz-telemetry-implementation branch from 5597613 to 2908021 Compare February 12, 2020 01:30
implementation dependenciesList.mapboxSdkTurf
implementation dependenciesList.mapboxAndroidAccounts
implementation dependenciesList.mapboxEvents
implementation project(':liblogger')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not depend on the liblogger module. We have to remove it anyway and wait for mapbox/mapbox-base-android#21 to be released. Please use android.util.Log until then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

/**
* Immutable and can't be changed after passing into [MapboxNavigation].
*/
data class MapboxNavigationOptions(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this class and use already present NavigationOptions if needed.

*
* @since 0.1.0
*/
object NavigationConstants {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this class as well and move only the required constant into the correct file.

.build()

// Call back that receives
private val routeProgressListener = object : RouteProgressObserver {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still valid.

/**
* Register a callback to receive location events. At most [MAX_LOCATION_VALUES] are stored
*/
locationEngine.requestLocationUpdates(locationEngineRequest, locationCallback, null)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the legacy SDK's behavior? Were we collecting telemetry when navigation was not started? If we weren't we should listen from trip session updates instead.

settings.gradle Outdated
@@ -1,4 +1,4 @@
include ':app',
include ':app', ':libnavigation-telemetry',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no libnavigation-telemetry module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olegzil adding to the recent comments I left some other ones and questions.

Also, could you explain briefly how the approach we're taking is implemented? What's the overall APIs flow and architecture design? Could you provide examples / tests? I think that'd greatly help understanding the changes we're adding here and reviewing the PR will be easier.

@@ -1,12 +1,8 @@
package com.mapbox.navigation.metrics
package com.mapbox.navigation.core.metrics
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we move the code from libnavigation-metrics to libnavigation-core? Wouldn't adding libnavigation-metrics dependency in libnavigation-core work? If something needs to be changed, we should update in libnavigation-metrics, that's fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The telemetry package classes are marked internal. For core to use them, they have to be in the included

* Default implementation of [MetricsReporter] interface.
*/
object MapboxMetricsReporter : MetricsReporter {
class MapboxMetricsReporter(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation directs the anonymized data to Mapbox metric pipelines and exposes an observer that allows for inspecting and collecting any events that are going to be pushed into the Mapbox pipeline. In other words, it's just a wrapper around MapboxTelemetry and handles MetricsObserver logic and lifetime. Does it need to be a class? Why not an object anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 replaced

/**
* Register a callback to receive location events. At most [MAX_LOCATION_VALUES] are stored
*/
locationEngine.requestLocationUpdates(locationEngineRequest, locationCallback, null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we're not currently collecting navigation specific telemetry until a session has started. When it comes to location updates though, telemetry is collected by Telemetry core / Maps if the end user grants her consent.

Related to location updates, an interesting question was raised: for these before and after location buffers, do we want to keep track of raw location updates or the fix locations coming from NN? Currently, in the legacy we're collecting and sending the location updates generated by the LocationEngine but we probably want to gather the fixed ones. What do you think? cc @coxchapman

* Defines a contract in which a custom notification must adhere to when
* given to [com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions].
*/
interface NavigationNotification {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this for Telemetry? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Defaulted values are optional

@SuppressLint("ParcelCreator")
class TelemetryDepartureEvent(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above NavigationDepartEvent

Copy link
Contributor Author

@olegzil olegzil Feb 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NavigationEvent have been replaced by TelemetryEvent

// Defaulted values are optional

@SuppressLint("ParcelCreator")
class TelemetryReroute(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above NavigationRerouteEvent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NavigationEvent have been replaced by TelemetryEvent


// Defaulted values are optional

data class TelemetryStep(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above NavigationStepData

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TelemetryStep data is required for some of the feedback types. NavigationStepData no longer exists.

Copy link
Contributor Author

@olegzil olegzil Feb 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NavigationEvent have been replaced by TelemetryEvent


// Defaulted values are optional
@SuppressLint("ParcelCreator")
class TelemetryUserFeedback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above NavigationFeedbackEvent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated file. NavigationFeedbackEvent no longer exists. TelemetryFeedbackEvent is used instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All NavigationEvent have been replaced by TelemetryEvent

@@ -0,0 +1,16 @@
package com.mapbox.navigation.core.telemetry.audio
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package directive doesn't match file location

Same thing applies to other classes 👇

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 removed

@Guardiola31337
Copy link
Contributor

Also there are some conflicts (e.g. this https://github.com/mapbox/mapbox-navigation-android/pull/2445/files#diff-e3c60303e8ee1a7d01f2af9369235fa9R25 already changed in master) so rebase from master when you have a chance, that'll also help making the PR easier to review 🚀

@Guardiola31337 Guardiola31337 force-pushed the oz-telemetry-implementation branch from 16ca439 to 9584a6a Compare March 10, 2020 23:17
@Guardiola31337 Guardiola31337 force-pushed the oz-telemetry-implementation branch from 83fa365 to 965f9c1 Compare March 11, 2020 03:18
@Guardiola31337
Copy link
Contributor

Great work @olegzil 🚀

Going ahead and merging here. Will ticket out the tailwork.

cc @zugaldia

@Guardiola31337 Guardiola31337 merged commit 9dbc0da into master Mar 11, 2020
@Guardiola31337 Guardiola31337 mentioned this pull request Jun 12, 2020
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants