Skip to content

Commit

Permalink
Pass Sync telemetry from a-s to consumers.
Browse files Browse the repository at this point in the history
  • Loading branch information
linabutler committed Apr 25, 2019
1 parent a122c6c commit 579391e
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 47 deletions.
1 change: 1 addition & 0 deletions components/browser/storage-sync/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies {
implementation project(':support-utils')

implementation Dependencies.kotlin_stdlib
implementation Dependencies.mozilla_appservices_support

testImplementation project(':support-test')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,36 @@ import android.support.annotation.GuardedBy
import mozilla.appservices.places.PlacesApi
import mozilla.appservices.places.PlacesReaderConnection
import mozilla.appservices.places.PlacesWriterConnection
import mozilla.appservices.support.SyncTelemetryPing
import mozilla.components.concept.sync.StoreStats
import java.io.Closeable
import java.io.File

const val DB_NAME = "places.sqlite"

fun pingsToStats(ping: SyncTelemetryPing): Map<String, StoreStats> {
return ping.syncs.flatMap { it.engines }
.groupingBy { it.name }
.aggregate { _, stats: StoreStats?, engine, _ ->
val (sent, failed) = engine.outgoing.fold(Pair(0, 0)) { acc, outgoing ->
Pair(acc.first + outgoing.sent, acc.second + outgoing.failed)
}
StoreStats(
name = engine.name,
at = engine.at,
took = engine.took,
applied = (stats?.applied ?: 0) + (engine.incoming?.applied ?: 0),
failedToApply = (stats?.failedToApply ?: 0) +
(engine.incoming?.failed ?: 0) + (engine.incoming?.newFailed ?: 0),
reconciled = (stats?.reconciled ?: 0) + (engine.incoming?.reconciled ?: 0),
uploaded = (stats?.uploaded ?: 0) + sent,
failedToUpload = (stats?.failedToUpload ?: 0) + failed,
uploadBatches = (stats?.uploadBatches ?: 0) + engine.outgoing.size
)
}

}

/**
* A slight abstraction over [PlacesApi].
*
Expand All @@ -28,8 +53,8 @@ interface Connection : Closeable {

// Until we get a real SyncManager in application-services libraries, we'll have to live with this
// strange split that doesn't quite map all that well to our internal storage model.
fun syncHistory(syncInfo: SyncAuthInfo)
fun syncBookmarks(syncInfo: SyncAuthInfo)
fun syncHistory(syncInfo: SyncAuthInfo): StoreStats?
fun syncBookmarks(syncInfo: SyncAuthInfo): StoreStats?
}

/**
Expand Down Expand Up @@ -65,14 +90,16 @@ internal object RustPlacesConnection : Connection {
return api!!.getWriter()
}

override fun syncHistory(syncInfo: SyncAuthInfo) {
override fun syncHistory(syncInfo: SyncAuthInfo): StoreStats? {
check(api != null) { "must call init first" }
api!!.syncHistory(syncInfo)
val ping = api!!.syncHistory(syncInfo)
return pingsToStats(ping).get("history")
}

override fun syncBookmarks(syncInfo: SyncAuthInfo) {
override fun syncBookmarks(syncInfo: SyncAuthInfo): StoreStats? {
check(api != null) { "must call init first" }
api!!.syncBookmarks(syncInfo)
val ping = api!!.syncBookmarks(syncInfo)
return pingsToStats(ping).get("bookmarks")
}

override fun close() = synchronized(this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.concept.sync.AuthInfo
import mozilla.components.concept.sync.StoreSyncStatus
import mozilla.components.concept.sync.SyncStatus
import mozilla.components.concept.sync.SyncableStore

Expand Down Expand Up @@ -175,14 +176,14 @@ open class PlacesBookmarksStorage(context: Context) : PlacesStorage(context), Bo
* @param authInfo The authentication information to sync with.
* @return Sync status of OK or Error
*/
override suspend fun sync(authInfo: AuthInfo): SyncStatus {
override suspend fun sync(authInfo: AuthInfo): StoreSyncStatus {
return try {
withContext(scope.coroutineContext) {
places.syncBookmarks(authInfo.into())
SyncStatus.Ok
val stats = places.syncBookmarks(authInfo.into())
StoreSyncStatus(SyncStatus.Ok, stats)
}
} catch (e: PlacesException) {
SyncStatus.Error(e)
StoreSyncStatus(SyncStatus.Error(e), null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import mozilla.components.concept.storage.SearchResult
import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType
import mozilla.components.concept.sync.AuthInfo
import mozilla.components.concept.sync.StoreSyncStatus
import mozilla.components.concept.sync.SyncStatus
import mozilla.components.concept.sync.SyncableStore
import mozilla.components.support.utils.segmentAwareDomainMatch
Expand Down Expand Up @@ -162,14 +163,14 @@ open class PlacesHistoryStorage(context: Context) : PlacesStorage(context), Hist
* @param authInfo The authentication information to sync with.
* @return Sync status of OK or Error
*/
override suspend fun sync(authInfo: AuthInfo): SyncStatus {
override suspend fun sync(authInfo: AuthInfo): StoreSyncStatus {
return try {
withContext(scope.coroutineContext) {
places.syncHistory(authInfo.into())
SyncStatus.Ok
val stats = places.syncHistory(authInfo.into())
StoreSyncStatus(SyncStatus.Ok, stats)
}
} catch (e: PlacesException) {
SyncStatus.Error(e)
StoreSyncStatus(SyncStatus.Error(e), null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import mozilla.appservices.places.PlacesWriterConnection
import mozilla.appservices.places.SearchResult
import mozilla.appservices.places.VisitInfo
import mozilla.appservices.places.VisitObservation
import mozilla.appservices.support.SyncTelemetryPing
import mozilla.components.concept.storage.PageObservation
import mozilla.components.concept.storage.VisitType
import mozilla.components.concept.sync.AuthInfo
import mozilla.components.concept.sync.StoreStats
import mozilla.components.concept.sync.SyncStatus
import mozilla.components.support.test.eq
import mozilla.components.support.test.mock
Expand Down Expand Up @@ -343,12 +345,13 @@ class PlacesHistoryStorageTest {
return mock()
}

override fun syncHistory(syncInfo: SyncAuthInfo) {
override fun syncHistory(syncInfo: SyncAuthInfo): StoreStats? {
assertNull(passedAuthInfo)
passedAuthInfo = syncInfo
return null
}

override fun syncBookmarks(syncInfo: SyncAuthInfo) {
override fun syncBookmarks(syncInfo: SyncAuthInfo): StoreStats? {
fail()
}

Expand Down Expand Up @@ -379,9 +382,13 @@ class PlacesHistoryStorageTest {
return mock()
}

override fun syncHistory(syncInfo: SyncAuthInfo) {}
override fun syncHistory(syncInfo: SyncAuthInfo): StoreStats? {
return null
}

override fun syncBookmarks(syncInfo: SyncAuthInfo) {}
override fun syncBookmarks(syncInfo: SyncAuthInfo): StoreStats? {
return null
}

override fun close() {
fail()
Expand All @@ -407,12 +414,13 @@ class PlacesHistoryStorageTest {
return mock()
}

override fun syncHistory(syncInfo: SyncAuthInfo) {
override fun syncHistory(syncInfo: SyncAuthInfo): StoreStats? {
throw exception
}

override fun syncBookmarks(syncInfo: SyncAuthInfo) {
override fun syncBookmarks(syncInfo: SyncAuthInfo): StoreStats? {
fail()
return null
}

override fun close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ package mozilla.components.concept.sync
import mozilla.components.support.base.observer.Observable
import java.lang.Exception

/**
* Timestamps and counts for a synced store.
*
* @param name The name of this store.
* @param at When this store was synced.
* @param took How long it took this store to sync.
* @param applied The number of incoming changes that applied successfully.
* @param failedToApply The number of incoming changes that failed to apply.
* @param reconciled The number of incoming changes merged with local changes.
* @param uploaded The number of outgoing changes that uploaded successfully.
* @param failedToUpload The number of outgoing changes that failed to upload.
* @param uploadBatches The number of batches required to upload all changes.
*/
data class StoreStats(
val name: String,
val at: Int = 0,
val took: Int = 0,
val applied: Int = 0,
val failedToApply: Int = 0,
val reconciled: Int = 0,
val uploaded: Int = 0,
val failedToUpload: Int = 0,
val uploadBatches: Int = 0
)

/**
* Results of running a sync via [SyncableStore.sync].
*/
Expand All @@ -32,7 +57,7 @@ interface SyncableStore {
* @param authInfo Auth information necessary for syncing this store.
* @return [SyncStatus] A status object describing how sync went.
*/
suspend fun sync(authInfo: AuthInfo): SyncStatus
suspend fun sync(authInfo: AuthInfo): StoreSyncStatus
}

/**
Expand Down Expand Up @@ -83,6 +108,11 @@ interface SyncStatusObserver {
*/
fun onStarted()

/**
* Gets called when a store finishes syncing, and has telemetry to report.
*/
fun onStoreSynced(stats: StoreStats)

/**
* Gets called at the end of a sync, after every configured syncable has been synchronized.
*/
Expand All @@ -99,4 +129,4 @@ interface SyncStatusObserver {
* A set of results of running a sync operation for multiple instances of [SyncableStore].
*/
typealias SyncResult = Map<String, StoreSyncStatus>
data class StoreSyncStatus(val status: SyncStatus)
data class StoreSyncStatus(val status: SyncStatus, val stats: StoreStats?)
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

package mozilla.components.feature.sync

import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.SyncManager
import mozilla.components.concept.sync.SyncStatusObserver
import mozilla.components.concept.sync.SyncableStore
import mozilla.components.concept.sync.*
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
Expand Down Expand Up @@ -168,6 +165,10 @@ abstract class GeneralSyncManager : SyncManager, Observable<SyncStatusObserver>
notifyObservers { onStarted() }
}

override fun onStoreSynced(stats: StoreStats) {
notifyObservers { onStoreSynced(stats) }
}

override fun onIdle() {
notifyObservers { onIdle() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ package mozilla.components.feature.sync
import android.support.annotation.VisibleForTesting
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import mozilla.components.concept.sync.AuthException
import mozilla.components.concept.sync.AuthInfo
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.StoreSyncStatus
import mozilla.components.concept.sync.SyncResult
import mozilla.components.concept.sync.SyncStatus
import mozilla.components.concept.sync.SyncStatusObserver
import mozilla.components.concept.sync.SyncableStore
import mozilla.components.concept.sync.*
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
Expand Down Expand Up @@ -52,8 +45,9 @@ class StorageSync(
val authInfo = try {
account.authInfo(syncScope)
} catch (e: AuthException) {
// TODO: Record telemetry for auth errors.
syncableStores.keys.forEach { storeName ->
results[storeName] = StoreSyncStatus(SyncStatus.Error(e))
results[storeName] = StoreSyncStatus(SyncStatus.Error(e), null)
}
return@withListeners results
}
Expand All @@ -70,18 +64,23 @@ class StorageSync(
storeName: String,
auth: AuthInfo
): StoreSyncStatus {
return StoreSyncStatus(store.sync(auth).also {
if (it is SyncStatus.Error) {
logger.error("Error synchronizing a $storeName store", it.exception)
return store.sync(auth).also { (status, _) ->
if (status is SyncStatus.Error) {
logger.error("Error synchronizing a $storeName store", status.exception)
} else {
logger.info("Synchronized $storeName store.")
}
})
}
}

private suspend fun withListeners(block: suspend () -> SyncResult): SyncResult {
notifyObservers { onStarted() }
val result = block()
result.values.forEach {
it.stats?.let {
notifyObservers { onStoreSynced(it) }
}
}
notifyObservers { onIdle() }
return result
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object ErrorRecording {

logger.warn("${metricData.identifier}: $message")

// There are two reasons for using `CountersStorageEngine.record` below
// There are two reasons for using `CountersStorageEngine.add` below
// and not just using the public `CounterMetricType` API.

// 1) The labeled counter metrics that store the errors are defined in the
Expand All @@ -80,7 +80,7 @@ object ErrorRecording {
// 2) We want to bybass the restriction that there are only N values in a
// dynamically labeled metric. Error reporting should never report errors
// in the __other__ category.
CountersStorageEngine.record(
CountersStorageEngine.add(
errorMetric,
amount = 1
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ data class CounterMetricType(

private val logger = Logger("glean/CounterMetricType")

/**
* ...
*
* @param value This is the new value for the counter.
*/
fun set(value: Int) {
if (!shouldRecord(logger)) {
return
}

@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.launch {
// Delegate storing the new counter value to the storage engine.
CountersStorageEngine.set(
this@CounterMetricType,
value = value
)
}
}

/**
* Add to counter value.
*
Expand All @@ -44,7 +64,7 @@ data class CounterMetricType(
@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.launch {
// Delegate storing the new counter value to the storage engine.
CountersStorageEngine.record(
CountersStorageEngine.add(
this@CounterMetricType,
amount = amount
)
Expand Down
Loading

0 comments on commit 579391e

Please sign in to comment.