Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Generate the activation ping and send it with Glean #1707

Merged
merged 1 commit into from
May 3, 2019

Conversation

Dexterp37
Copy link
Contributor

This fetches the Google Advertising ID, salts it and then applies hashing before sending a ping with it,
at startup. Hashing and salting are used in order to prevent ourselves to correlate advertising IDs
from the same user running different products we own off a single device. We will never send the
client_id and the Google Advertising ID in the same ping: we only send the client_id as a backup in case
the Google Advertising ID is not available.

Pull Request checklist

  • Quality: This PR builds and passes detekt/ktlint checks (A pre-push hook is recommended)
  • Tests: This PR includes thorough tests or an explanation of why it does not
  • Changelog: This PR includes a changelog entry or does not need one
  • Accessibility: The code in this PR follows accessibility best practices or does not include any user facing features

@colintheshots
Copy link
Contributor

Does the hashing algorithm require more CPU or disk I/O time? The IO dispatcher is more appropriate for disk. The CommonPool dispatcher, which is default, can be undersized on older devices with fewer cores and may deadlock. I don't know if they fixed this yet.

@Dexterp37
Copy link
Contributor Author

CommonPool

I suspect this is CPU-bound, there should be no I/O involved in this (other than the SharedPreferences, which is already taken care of by its API, I think).

@colintheshots
Copy link
Contributor

Launch, if you never join() on the job it returns, is fire and forget. It ignores exceptions and doesn't return a value. This could be what you intend, but it's important to be aware of it.

@Dexterp37
Copy link
Contributor Author

Launch, if you never join() on the job it returns, is fire and forget. It ignores exceptions and doesn't return a value. This could be what you intend, but it's important to be aware of it.

Good point, yes, I think this is matching the behaviour I want, which is the following:

  1. Fenix starts;
  2. Glean inits;
  3. Check if the ping was already generated;
  4. If not, attempt to generate the required data and send the ping (off-the main thread, async);
    4a. If something throws (and I forgot to catch and print some useful message), if the job dies for some reason, markAsTriggered is not called and a new collection is attempted at the next startup
    4b. if all goes ok, markAsTriggered will prevent the ping to be sent again next time it starts.

I really intend a "fire and forget off-the-main thread" behaviour, while attempting to catch weird behaviours.

@colintheshots
Copy link
Contributor

This is failing due to lint style checks:
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:71:16: Missing spacing after "catch"
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:113:1: Needless blank line(s)
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:138:58: Missing space after //

@Dexterp37
Copy link
Contributor Author

This is failing due to lint style checks:
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:71:16: Missing spacing after "catch"
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:113:1: Needless blank line(s)
/bitrise/src/app/src/main/java/org/mozilla/fenix/components/metrics/ActivationPing.kt:138:58: Missing space after //

Yeah, sorry about that, this is still a draft as I'm waiting for some additional implementation details to come first. I'll complete it tomorrow/next week.

@liuche
Copy link
Contributor

liuche commented Apr 19, 2019

Oops, this is a PR so I'm moving this comment to the Issue:
Is this something that will be restricted in who can use it? I'm concerned that including this in Glean makes it seem like this is telemetry that is "approved for inclusion" by any consumer apps, but in this case, the GAID activation ping should only be used with approval.
Maybe the thing to do here though is to add this warning/caveat in the usage docs.

Copy link
Contributor

@mdboom mdboom left a comment

Choose a reason for hiding this comment

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

LGTM.

My only concern (which you also sort of hinted at), is that we don't have a good way of checking if the ping was actually sent. Would it be worth adding such a thing to the Glean API?

app/metrics.yaml Outdated
@@ -539,3 +539,36 @@ custom_tab:
notification_emails:
- fenix-core@mozilla.com
expires: "2020-03-01"

activation.ping:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the .ping strictly necessary? Feels a bit redundant, but it might just be me...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. Removing that.

description: >
An hashed and salted version of the Google Advertising ID from the device.
send_in_pings:
- activation
Copy link
Contributor

Choose a reason for hiding this comment

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

über-nit: Inconsistent indentation between this and the metric below.


Logger.info("ActivationPing - generating ping (has `identifier`: ${hashedId != null})")
// TODO: change-me
Glean.sendPings(listOf("activation")) //, sendClientId=false)
Copy link
Contributor

Choose a reason for hiding this comment

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

You know this already, but this will change once mozilla-mobile/android-components#2792 lands

@Dexterp37
Copy link
Contributor Author

My only concern (which you also sort of hinted at), is that we don't have a good way of checking if the ping was actually sent. Would it be worth adding such a thing to the Glean API?

@mdboom "On this side of the barricade", we're the application: we shouldn't really care about the transport itself, we should trust Glean to do the right thing for us. We should also be explicit, especially if we write the docs, about the expectations :) Calling sendPing doesn't imply the ping is sent "right away", but "as soon as possible" (e.g. there might be no network). But the app shouldn't really care about that.

@mdboom
Copy link
Contributor

mdboom commented Apr 24, 2019

Yeah, thinking about this further, I think it's fine as long as Glean can guarantee that if a ping is queued it gets sent someday (and that's kind of on Glean to ensure). My concern was that this has code to check the ping is only sent once. If that first attempt fails somehow, it will never get sent, which would be a bad outcome.

@Dexterp37 Dexterp37 marked this pull request as ready for review April 24, 2019 18:37
@Dexterp37 Dexterp37 requested a review from a team as a code owner April 24, 2019 18:37
@Dexterp37
Copy link
Contributor Author

@liuche I'd appreciate if you could review the documentation here (the activation.md file), the metrics.yaml and pings.yaml changes, given your knowledge of the background for this ping.

@colintheshots are you the right person to review this fully? If not, any chance you could kindly redirect the review? This is introducing a new custom ping for Fenix, which should be generated only once on new installs and never ever again. See the included docs.

@mdboom could you please review my usage of the Glean APIs (and the docs too, for clarity :) )?

app/metrics.yaml Outdated
bugs:
- 1538011
data_reviews:
- TODO
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: this need to be updated with a link to the review.

app/metrics.yaml Outdated
bugs:
- 1538011
data_reviews:
- TODO
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@liuche how can I make the data review available? Should it be copied over the related Fenix issue or in my implementation bug on bugzilla?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Dexterp37 you can copy the data request here, and I can re-review now that the implementation is also here! I'll flag myself for review here since you mentioned you don't have perms.

Copy link
Contributor

@mdboom mdboom left a comment

Choose a reason for hiding this comment

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

🚢

* Checks whether or not the activation ping was already
* triggered by the application.
*
* Note that this only tells us that Fenix did trigger the
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Note that this only tells us that Fenix did trigger the
* Note that this only tells us that Fenix triggered the


Logger.info("ActivationPing - generating ping (has `identifier`: ${hashedId != null})")
Pings.activation.send()
markAsTriggered()
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe move this to the top of the launch block to avoid the (unlikely) race condition where calling checkAndSend twice quickly could send two pings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd prefer leaving this as it is: if, for some weird reason, we crash/get killed after that line gets executed, we'd never re-attempt to send the ping. We can easily detect dupes via the hash, on the other hand. So two pings shouldn't be a big deal in this specific case.

// Apply hashing.
try {
val saltedID = unhashedID + salt
val digest = MessageDigest.getInstance("SHA-256")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there any chance we could use something else other than this? Is there anything else available for us to use (we really want bcrypt here) in the Fenix repo or Android API?

Copy link
Member

@badboy badboy left a comment

Choose a reason for hiding this comment

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

As promised in chat, here's a quick review of the documentation only.
(I did not look at any code).

This ping is intended to provide a measure of the activation of mobile products.

## Scheduling
The `activation` ping is automatically sent at startup, after Glean is initialized, and contains
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The `activation` ping is automatically sent at startup, after Glean is initialized, and contains
The `activation` ping is automatically sent at first startup, after Glean is initialized, and contains

Copy link
Member

Choose a reason for hiding this comment

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

We should make this clear this ping is sent only once, only during the very first startup (or during subsequent startups if it hasn't been sent yet?).

# The `activation` ping

## Description
This ping is intended to provide a measure of the activation of mobile products.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
This ping is intended to provide a measure of the activation of mobile products.
This ping provides a measure of the activation of mobile products.

Copy link
Member

Choose a reason for hiding this comment

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

"intended to" sounds so passive. It is sent and it is as reliable as other pings.


## Scheduling
The `activation` ping is automatically sent at startup, after Glean is initialized, and contains
the following fields:
Copy link
Member

Choose a reason for hiding this comment

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

Don't mention this in the scheduling section? It is its own section anyway.

| `identifier` | String | An hashed and salted version of the Google Advertising ID from the device. |
| `activation_id` | UUID | An alternate identifier, not correlated with the client_id, generated once and only sent with the activation ping. |

The `activation` ping also includes the common [ping sections]https://github.com/mozilla-mobile/android-components/blob/master/components/service/glean/docs/pings/pings.md#ping-sections)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The `activation` ping also includes the common [ping sections]https://github.com/mozilla-mobile/android-components/blob/master/components/service/glean/docs/pings/pings.md#ping-sections)
The `activation` ping also includes the common [ping sections](https://github.com/mozilla-mobile/android-components/blob/master/components/service/glean/docs/pings/pings.md#ping-sections)

@@ -14,6 +14,10 @@ Fenix creates and tries to send a "baseline" ping. It is defined inside the [`me

Fenix sends event pings that allows us to measure feature performance. These are defined inside the [`metrics.yaml`](https://github.com/mozilla-mobile/fenix/blob/master/app/metrics.yaml) file.

## Activation

Fenix sends an activation ping once, at startup. The ping is documented [`here`](activation.md) file.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Fenix sends an activation ping once, at startup. The ping is documented [`here`](activation.md) file.
Fenix sends an activation ping once, at startup. Further documentation can be found in [Activation Ping](activation.md).

@liuche liuche self-requested a review April 25, 2019 00:02
Copy link
Contributor

@liuche liuche left a comment

Choose a reason for hiding this comment

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

Documentation looks good to me with one nit.
Can you post the data request here and I'll fill it out again here so it's public and associated with the code.

type: string
lifetime: ping
description: >
An hashed and salted version of the Google Advertising ID from the device.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please also include here that this is never sent with the client id. Also document the case where this is the client id.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've addressed the first request. We are no longer sending the client_id, not even when the GAID is not available. In that case, we're generating a random UUID (the activation_id), which will be stable throughout the life of the application, and exclusively sent with this ping.

@Dexterp37
Copy link
Contributor Author

data-review? @liuche

1) What questions will you answer with this data?

We plan to count the exact number of activations, per distribution, per manufacturer.

2) Why does Mozilla need to answer these questions? Are there benefits for users? Do we need this information to address product or business requirements? Some example responses:

The BD team needs an exact method for counting these activations across manufacturers.

The activation data will also help us evaluate the difference in activations from the other data sources which are currently being used for activation counts (e.g. adjust)

3) What alternative methods did you consider to answer these questions? Why were they not sufficient?

We tried many alternatives. For example, client-ids gave more clients than device shipments (see bug 1481215). There can be a variety of reasons for this, outlined in the bug. We don’t include distribution information in other data sources, like adjust, so we couldn’t count using those.

4) Can current instrumentation answer these questions?
For the reasons listed above, no.

5) List all proposed measurements and indicate the category of data collection for each measurement, using the Firefox data collection categories on the Mozilla wiki.

Measurement Description Data Collection Category Tracking Information
activation.identifier Category 4 Bug 1501822
activation.activation_id Category 4  

Glean pings also include metadata which is being sent with this ping as well, with the exclusion of the client_id.

Additional documentation for this ping is provided as part of this PR.

6) How long will this data be collected? Choose one of the following:

  • I want this data to be collected for 6 months initially. We will probably renew when BD needs it, possible on additional mobile products.

7) What populations will you measure?

  • Which release channels?
    All channels.

  • Which countries?
    All countries.

  • Which locales?
    All locales.

  • Any other filters? Please describe in detail below.
    Initially only Fenix.

8) If this data collection is default on, what is the opt-out mechanism for users?
There is none.

9) Please provide a general description of how you will analyze this data.
Per-distribution and per-manufacturer, we will count the number of unique (hashed) Google Ad ID. Some metadata (e.g. model) can be used to stratify the results.

10) Where do you intend to share the results of your analysis?
This will be used internally by BD. They can do the analysis in re:dash, locked down to the search datasets if necessary.

@Dexterp37 Dexterp37 force-pushed the activation_ping branch 2 times, most recently from e12ce0b to 44558d1 Compare April 29, 2019 08:56
Copy link
Contributor

@liuche liuche left a comment

Choose a reason for hiding this comment

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

Data Review Form (to be filled by Data Stewards)

  1. Is there or will there be documentation that describes the schema for the ultimate data set available publicly, complete and accurate?
    Documentation in metrics.yaml, and an additional activation.md file what is being collected in this activation ping, and under what circumstances.

  2. Is there a control mechanism that allows the user to turn the data collection on and off?
    No, this ping is sent at activation. This is separate from any client_id, and is used to measure activations only for partnership distribution versions in order to count activations through a distribution, which is necessary for BD.

If the answer to either of the first two questions is no, reviewers give an r-. Incremental changes to measurements or systems that have previously gone through analysis review may not require additional review.

This is only for partner distributions, and we've gone through an additional approval process for this specific use case - this ping is totally isolated from client ID and is necessary for counting Fenix activations in distributions.

  1. If the request is for permanent data collection, is there someone who will monitor the data over time?**
    6 months, arana will monitor this from the BD side.

  2. Using the category system of data types on the Mozilla wiki, what collection type of data do the requested measurements fall under? **
    Type 4 - Google Ad ID

  3. Is the data collection request for default-on or default-off?
    Default on

  4. Does the instrumentation include the addition of any new identifiers (whether anonymous or otherwise; e.g., username, random IDs, etc. See the appendix for more details)?
    Yes, the hashed + salted GAID, and a randomly generated id sent only once for disambiguation

  5. Is the data collection covered by the existing Firefox privacy notice? If unsure: escalate to legal if:

  • The data includes new identifiers; OR

  • The data falls within the Web activity category AND is default-on.

Yes, we have discussed this with Marshall and this data is approved to be collected in this specific use case of partner distributions of Fenix.

  1. Does there need to be a check-in in the future to determine whether to renew the data? (Yes/No) (If yes, set a todo reminder or file a bug if appropriate)**
    Yes, this probe will expire

  2. Does the data collection use a third-party collection tool? If yes, escalate to legal.
    No

Copy link
Member

@travis79 travis79 left a comment

Choose a reason for hiding this comment

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

I really like how the pings.yaml has turned out for documentation purposes.

getAdvertisingID()?.let { unhashedID ->
// Add some salt to the ID, before hashing. For this specific use-case, it's ok
// to use the same salt value for all the hashes. We want hashes to be stable
// within a single product, but we don't want hashes to be the same across different
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI: The packageName is not stable between Fenix Beta and Fenix Nightly, etc. They're treated as different products.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. @fbertsch, is this ok? Or should we use a Fenix-specific salt (regardless of the channel), e.g. org.mozilla.fenix?

Copy link

Choose a reason for hiding this comment

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

We probably want the same salt across all the Fenix products, to reduce double counting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, fixed this by providing a static salt.

// Generate the activation_id.
Activation.activationId.generateAndSet()

CoroutineScope(Dispatchers.Default).launch {
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that calling launch without joining on its job will fire-and-forget. Exceptions will be swallowed and there's no guarantee it ever completes. This might be your intent, but you should be aware.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, yes, this is intended behaviour: we should be fine to swallow exceptions here and re-attempt, if something went wrong, next time we start.

@@ -195,9 +198,9 @@ class GleanMetricsService(private val context: Context) : MetricsService {
code.set(defaultEngine.identifier)
name.set(defaultEngine.name)
submissionUrl.set(defaultEngine.buildSearchUrl(""))

Glean.setUploadEnabled(true)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hawkinsw I noticed that Glean.setUploadEnabled was moved here instead of being called before Glean.initialize in #2088 . I'm moving it back in this PR, since this is the right usage of the API. Was there a specific reason to move it here? Maybe some bug on the Glean end we need to fix?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nope! Thanks for chatting about this offline and thank you for fixing it!

This fetches the Google Advertising ID, salts it and
then applies hashing before sending a ping with it,
at startup. Hashing and salting are used in order
to prevent ourselves to correlate advertising IDs
from the same user running different products we
own off a single device. We will never send the
client_id and the Google Advertising ID in the same
ping.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants