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

Metrics sdk #13

Merged
merged 18 commits into from
Feb 8, 2018
Merged

Metrics sdk #13

merged 18 commits into from
Feb 8, 2018

Conversation

wtrocki
Copy link
Contributor

@wtrocki wtrocki commented Feb 6, 2018

Motivation

Initial version of Metrics Swift SDK

Progress

  • Initial SDK setup
  • Client ID
  • mobile service configuration (URL)
  • Investigate SDK version in source code
  • General refactoring and styles
  • Convert to new mobile-config.json format

}

fileprivate func initId() {
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure if identifierForVendor fits the purpose here.

According to this from apple:

The value of this property is the same for apps that come from the same vendor running on the same device.

I believe it will return the same value for the same developer. In another word, if I have 3 different apps installed on the same device, it will return the same value for all 3 apps. Not sure this is what you are looking for?

Copy link
Contributor Author

@wtrocki wtrocki Feb 6, 2018

Choose a reason for hiding this comment

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

I checked couple options:

Was thinking that identifierForVendor will be the most appropriate option, but in fact I will go with aproach to generate this random string per installation.

I see that metrics and analytics for most of the cases are tied to user like here:
https://developers.google.com/analytics/devguides/collection/ios/v3/user-id

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, I think generating the device id and save it locally is enough for this use case here.

}

func applicationDidBecomeActive(_: UIApplication) {
metrics.foreground();
Copy link
Contributor

Choose a reason for hiding this comment

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

the method name is a bit confusing. In metrics it is called foreground, but you are calling it from applicationDidBecomeActive, while there is also a applicationWillEnterForeground method here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is temporary and will be replaced with proper metrics implementation.
Our metrics can hook into application lifecycle and other specific events.

Going to rename method and remove background method as it's not used.

}

fileprivate func setupUri() {
if let uri = core.getConfiguration("metrics")?.config?.uri {
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

@wtrocki wtrocki Feb 7, 2018

Choose a reason for hiding this comment

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

We using old format for Android and IOS. New format has uri field on the top level.
Once cmd line tool will be changed and released after sprint we will adjust formats.
I'm going to create issue to get that aligned, but it will be separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually going to fix that here: https://issues.jboss.org/browse/AGIOS-648


fileprivate func initId() {
let defaults = UserDefaults.standard
if let versionId = defaults.string(forKey: "metrics-sdk-installation-id") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use a constant for metrics-sdk-installation-id ?

AgsCore.logger.error("clientId! \(clientId)")
} else {
let uuid = UUID().uuidString
clientId = uuid
Copy link
Contributor

Choose a reason for hiding this comment

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

clientId vs. installationId, i think we should have consistent naming (will also use installationId on Android).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+100 - Going to refactor names here as they are not consistent.

let requestDictionary = try? payload.adaptToDictionary()
core.getHttp().post(uri, body: requestDictionary, { (response, error) -> Void in
if let error = error {
AgsCore.logger.error("An error has occurred when sending app metrics! \(error)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Just my preference but i would remove the exclamation mark from the message.

Copy link
Contributor

@pb82 pb82 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, but added a few comments.

open func collectMetrics() {
if config.metricsEnabled() {
for metricsEngine: Collectable in metrics {
// TODO: Use single URL payload vs metrics sending data to individual URLS.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aliok @pb82 - What will be your take on this? Should we have single UDP/HTTP metrics endpoint or each metrics should be as separate endpoint?

From mobile point of view it may be better to just have single endpoint and collect metrics into single payload that is being sent when device is online. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

+1 on this. not just when offline though. even when the device is online, we should send stuff in batches into a single URL.
but I wouldn't worry about this right now. perhaps we can start a discussion with the wider group.

Copy link
Member

Choose a reason for hiding this comment

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

@wtrocki maybe it is better to talk about this once we have more metrics (custom or system). currently all we have is 2 metrics (sdk version and app version) that can be joined in a single payload. but we shouldn't forget this subject.

@wtrocki
Copy link
Contributor Author

wtrocki commented Feb 7, 2018

Retrieving Version in IOS

After experimenting with multiple options like custom cocoapods plugins, plist appenders I decided to include version in core. Version will be changed by release script so there will be no additional overhead for developers to update that.

}

func collect() {
// TODO: Store version in User Preferences and detect that was changed?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aliok @pb82 WDYT? Technically this metrics will change only when user will update application so sending this metrics every time we launch app seems to be battery drainer, antipattern.

SDK version can be saved in preferences and we can detect when it was changed.
Additionally both IOS and Android have way to detect if application was installed.
However if we agree to have one single endpoint for all "OnStart" metrics then this optimization will not make sense as we could always send this information as metrics payload.

Copy link
Member

Choose a reason for hiding this comment

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

I think sending SDK init events every time is very useful. Anybody could make use of this data.
Sending once would answer "how many clients use SDK 1.2.3?".
Sending every time would also answer "how many init events happened for SDK 1.2.3 in last N days?" which is basically the how actively an SDK version is used. This is IMO important because there can be 1 installation of SDK 1.0.0 with a very active user, but there can be 100 installations of SDK 0.0.1 with nobody actually using the app.

Also, probably sending one request when app starts won't drain too much battery IMO :)

If we do this, from backend point of view it doesn't change too much. We would just need to adjust the Grafana dashboard so that it doesn't show sdk init event time series data.

Copy link
Contributor

Choose a reason for hiding this comment

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

@wtrocki I also agree with @aliok here. Sending the data every time the app is launched is far more useful from a metrics perspective.

@wtrocki
Copy link
Contributor Author

wtrocki commented Feb 7, 2018

@danielpassos Apart from couple unanswered questions I think we are in good shape to review this.
Build is failing intentionally - I left TODO statements in code.

@wtrocki
Copy link
Contributor Author

wtrocki commented Feb 8, 2018

@aliok Thanks for responding. Fully agree with you. To summarize:
We are going to send metrics everytime application is launched (initially) and use single payload url that will be provided in mobile-config.json

Going to push that to list as well.

@@ -27,6 +29,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

func applicationDidBecomeActive(_: UIApplication) {
metrics.collectMetrics()
Copy link
Collaborator

Choose a reason for hiding this comment

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

MetricsContainer don't have the collectMetrics method. I think it should be AgsMetrics.collectMetrics

Copy link
Contributor Author

@wtrocki wtrocki Feb 8, 2018

Choose a reason for hiding this comment

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

Outdated. I have fixed that before.

import Foundation
import XCTest

class MetricsTests: XCTestCase {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this test testing? 😕

// Metadata.swift
// AGSCore
//
// Created by Wojciech Trocki on 2/7/18.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs copyright update

* Metrics publisher that logs metrics to console
* Can be used when remote server is not available/supported
*/
public class MetricsLoggerPublisher: MetricsPublisher {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Like I said before I think we should not have more than one class/protocol/whatever in the same file, It means things easy to be found.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+100, Sorry I missed that one

import AGSCore
import Foundation

struct MetricsConfigData {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Where are we using it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's wrapper to get url and future. I wanted to showcase common swift pattern here.
While this class has little code it's good showcase (metrics is first and it will be good to show some good patterns here like optional unwrapping. Initially I had much more code here but decided to simplify that.

/**
* Holds static application data that can be used for metrics
*/
public class AppData {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just wondering if this kind of class should not be on core to be used in other modules or in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1

/**
* Protocol used for for classes that will colect metrics
*/
public protocol Collectable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this protocol are in processor folder and the implementations are in the metrics folder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

metrics folder contains all metrics. "processor' is maybe bad name - I will rename that to engine

/**
* Protocol used for for classes that will colect metrics
*/
public protocol Collectable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it should have metrics in the name to be more explicit it's a Collectable for Metrics. Makes sense?

private let core: AgsCore
private let appData: AppData
private let config: MetricsConfig
private var publisher: MetricsPublisher!
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about to have more than one MetricsPublisher. I mean what if I wanna have Log and Network or whatever new I write?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Idea now is to use single one - strategy pattern. If there will be use case for two we could change that and iterate on publishers without hassle. Currently we support only app init event. Once more triggers will appear this functionality may change.

* Protocol used for mobile metrics management
* Allows other SDK and implementations to manage (add) metrics
*/
public protocol MetricsContainer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we really need a protocol for this? Why not only a simple method in AgsMetrics?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was created in order to use it in different SDK in order to register metrics.
I'm not sure if this Idea will last, but I wanted to propose some solution for extending metrics without implementing them directly in the metrics SDK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternative path will be to just get specific metrics injected on user app.
I think we could talk about couple options here with @pb82 as we will still continue work on this by adding security etc.

@danielpassos
Copy link
Collaborator

Approval Intense Clapping

@danielpassos danielpassos merged commit 661390e into aerogear:master Feb 8, 2018
@wtrocki wtrocki deleted the metrics-sdk branch February 8, 2018 19:51
@aliok
Copy link
Member

aliok commented Feb 9, 2018

@wtrocki I know this is merged, but do you mind updating the PR description? Seems outdated and could be confusing in the future.

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

5 participants