Merge "Update SyncStorageEngine to use protos."

This commit is contained in:
TreeHugger Robot
2019-11-12 17:55:03 +00:00
committed by Android (Google) Code Review
4 changed files with 811 additions and 74 deletions

View File

@@ -20,6 +20,9 @@ import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.util.Pair;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.Calendar;
@@ -139,10 +142,10 @@ public class SyncStatusInfo implements Parcelable {
public final long[] perSourceLastSuccessTimes = new long[SOURCE_COUNT];
public final long[] perSourceLastFailureTimes = new long[SOURCE_COUNT];
// Warning: It is up to the external caller to ensure there are
// no race conditions when accessing this list
@UnsupportedAppUsage
private ArrayList<Long> periodicSyncTimes;
// Warning: It is up to the external caller to ensure there are
// no race conditions when accessing this list
@UnsupportedAppUsage
private ArrayList<Long> periodicSyncTimes;
private final ArrayList<Long> mLastEventTimes = new ArrayList<>();
private final ArrayList<String> mLastEvents = new ArrayList<>();
@@ -292,9 +295,28 @@ public class SyncStatusInfo implements Parcelable {
}
}
/**
* Copies all data from the given SyncStatusInfo object.
*
* @param other the SyncStatusInfo object to copy data from
*/
public SyncStatusInfo(SyncStatusInfo other) {
authorityId = other.authorityId;
copyFrom(other);
}
/**
* Copies all data from the given SyncStatusInfo object except for its authority id.
*
* @param authorityId the new authority id
* @param other the SyncStatusInfo object to copy data from
*/
public SyncStatusInfo(int authorityId, SyncStatusInfo other) {
this.authorityId = authorityId;
copyFrom(other);
}
private void copyFrom(SyncStatusInfo other) {
other.totalStats.copyTo(totalStats);
other.todayStats.copyTo(todayStats);
other.yesterdayStats.copyTo(yesterdayStats);
@@ -323,6 +345,14 @@ public class SyncStatusInfo implements Parcelable {
System.arraycopy(from, 0, to, 0, to.length);
}
public int getPeriodicSyncTimesSize() {
return periodicSyncTimes == null ? 0 : periodicSyncTimes.size();
}
public void addPeriodicSyncTime(long time) {
periodicSyncTimes = ArrayUtils.add(periodicSyncTimes, time);
}
@UnsupportedAppUsage
public void setPeriodicSyncTime(int index, long when) {
// The list is initialized lazily when scheduling occurs so we need to make sure
@@ -347,6 +377,24 @@ public class SyncStatusInfo implements Parcelable {
}
}
/**
* Populates {@code mLastEventTimes} and {@code mLastEvents} with the given list. <br>
* <i>Note: This method is mainly used to repopulate the event info from disk and it will clear
* both {@code mLastEventTimes} and {@code mLastEvents} before populating.</i>
*
* @param lastEventInformation the list to populate with
*/
public void populateLastEventsInformation(ArrayList<Pair<Long, String>> lastEventInformation) {
mLastEventTimes.clear();
mLastEvents.clear();
final int size = lastEventInformation.size();
for (int i = 0; i < size; i++) {
final Pair<Long, String> lastEventInfo = lastEventInformation.get(i);
mLastEventTimes.add(lastEventInfo.first);
mLastEvents.add(lastEventInfo.second);
}
}
/** */
public void addEvent(String message) {
if (mLastEventTimes.size() >= MAX_EVENT_COUNT) {

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto2";
package com.android.server.content;
import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;
/**
* Stores relevant information from a DayStats object in SyncStorageEngine.
*/
message SyncStatisticsProto {
message DayStats {
optional int32 day = 1; // day of the year - defined by SyncStorageEngine#getCurrentDayLocked()
optional int32 success_count = 2;
optional int64 success_time = 3; // time since epoch
optional int32 failure_count = 4;
optional int64 failure_time = 5; // time since epoch
}
repeated DayStats stats = 1;
}
/**
* Stores relevant information from a SyncStatusInfo object.
*/
message SyncStatusProto {
message StatusInfo {
message Stats {
optional int64 total_elapsed_time = 1; // time since epoch
optional int32 num_syncs = 2;
optional int32 num_failures = 3;
optional int32 num_cancels = 4;
optional int32 num_source_other = 5;
optional int32 num_source_local = 6;
optional int32 num_source_poll = 7;
optional int32 num_source_user = 8;
optional int32 num_source_periodic = 9;
optional int32 num_source_feed = 10;
}
message LastEventInfo {
optional int64 last_event_time = 1; // time since epoch
optional string last_event = 2;
}
// Note: version doesn't need to be stored in proto because of how protos store information but
// leaving field number 1 open in case we find a usage for it in the future.
optional int32 authority_id = 2;
optional int64 last_success_time = 3; // time since epoch
optional int32 last_success_source = 4;
optional int64 last_failure_time = 5; // time since epoch
optional int32 last_failure_source = 6;
optional string last_failure_message = 7;
optional int64 initial_failure_time = 8; // time since epoch
optional bool pending = 9;
optional bool initialize = 10;
repeated int64 periodic_sync_times = 11; // times since epoch
repeated LastEventInfo last_event_info = 12;
optional int64 last_today_reset_time = 13; // time since epoch
optional Stats total_stats = 14;
optional Stats today_stats = 15;
optional Stats yesterday_stats = 16;
repeated int64 per_source_last_success_times = 17; // times since epoch
repeated int64 per_source_last_failure_times = 18; // times since epoch
}
repeated StatusInfo status = 1;
}

View File

@@ -51,6 +51,8 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -64,6 +66,9 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
@@ -428,7 +433,8 @@ public class SyncStorageEngine {
}
// Primary list of all syncable authorities. Also our global lock.
private final SparseArray<AuthorityInfo> mAuthorities =
@VisibleForTesting
final SparseArray<AuthorityInfo> mAuthorities =
new SparseArray<AuthorityInfo>();
private final HashMap<AccountAndUser, AccountInfo> mAccounts
@@ -437,7 +443,8 @@ public class SyncStorageEngine {
private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
= new SparseArray<ArrayList<SyncInfo>>();
private final SparseArray<SyncStatusInfo> mSyncStatus =
@VisibleForTesting
final SparseArray<SyncStatusInfo> mSyncStatus =
new SparseArray<SyncStatusInfo>();
private final ArrayList<SyncHistoryItem> mSyncHistory =
@@ -453,7 +460,8 @@ public class SyncStorageEngine {
private int mNextAuthorityId = 0;
// We keep 4 weeks of stats.
private final DayStats[] mDayStats = new DayStats[7*4];
@VisibleForTesting
final DayStats[] mDayStats = new DayStats[7*4];
private final Calendar mCal;
private int mYear;
private int mYearInDays;
@@ -464,6 +472,18 @@ public class SyncStorageEngine {
private int mSyncRandomOffset;
// STOPSHIP: b/143656271 this should be true on launch
private static final boolean DELETE_LEGACY_PARCEL_FILES = false;
private static final String LEGACY_STATUS_FILE_NAME = "status.bin";
private static final String LEGACY_STATISTICS_FILE_NAME = "stats.bin";
private static final String SYNC_DIR_NAME = "sync";
private static final String ACCOUNT_INFO_FILE_NAME = "accounts.xml";
private static final String STATUS_FILE_NAME = "status";
private static final String STATISTICS_FILE_NAME = "stats";
private File mSyncDir;
/**
* This file contains the core engine state: all accounts and the
* settings for them. It must never be lost, and should be changed
@@ -508,14 +528,15 @@ public class SyncStorageEngine {
com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
syncDir.mkdirs();
mSyncDir = new File(systemDir, SYNC_DIR_NAME);
mSyncDir.mkdirs();
maybeDeleteLegacyPendingInfoLocked(syncDir);
maybeDeleteLegacyPendingInfoLocked(mSyncDir);
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"), "sync-accounts");
mStatusFile = new AtomicFile(new File(syncDir, "status.bin"), "sync-status");
mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"), "sync-stats");
mAccountInfoFile = new AtomicFile(new File(mSyncDir, ACCOUNT_INFO_FILE_NAME),
"sync-accounts");
mStatusFile = new AtomicFile(new File(mSyncDir, STATUS_FILE_NAME), "sync-status");
mStatisticsFile = new AtomicFile(new File(mSyncDir, STATISTICS_FILE_NAME), "sync-stats");
readAccountInfoLocked();
readStatusLocked();
@@ -2017,15 +2038,10 @@ public class SyncStorageEngine {
public static final int STATUS_FILE_END = 0;
public static final int STATUS_FILE_ITEM = 100;
/**
* Read all sync status back in to the initial engine state.
*/
private void readStatusLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Slog.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
}
private void readStatusParcelLocked(File parcel) {
try {
byte[] data = mStatusFile.readFully();
final AtomicFile parcelFile = new AtomicFile(parcel);
byte[] data = parcelFile.readFully();
Parcel in = Parcel.obtain();
in.unmarshall(data, 0, data.length);
in.setDataPosition(0);
@@ -2036,9 +2052,6 @@ public class SyncStorageEngine {
SyncStatusInfo status = new SyncStatusInfo(in);
if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
status.pending = false;
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Slog.v(TAG_FILE, "Adding status for id " + status.authorityId);
}
mSyncStatus.put(status.authorityId, status);
}
} catch (Exception e) {
@@ -2050,15 +2063,247 @@ public class SyncStorageEngine {
break;
}
}
} catch (java.io.IOException e) {
} catch (IOException e) {
Slog.i(TAG, "No initial status");
}
}
private void upgradeStatusIfNeededLocked() {
final File parcelStatus = new File(mSyncDir, LEGACY_STATUS_FILE_NAME);
if (parcelStatus.exists() && !mStatusFile.exists()) {
readStatusParcelLocked(parcelStatus);
writeStatusLocked();
}
// if upgrade to proto was successful, delete parcel file
if (DELETE_LEGACY_PARCEL_FILES && mStatusFile.exists()) {
parcelStatus.delete();
}
}
/**
* Read all sync status back in to the initial engine state.
*/
@VisibleForTesting
void readStatusLocked() {
upgradeStatusIfNeededLocked();
if (!mStatusFile.exists()) {
return;
}
try {
try (FileInputStream in = mStatusFile.openRead()) {
readStatusInfoLocked(in);
}
} catch (IOException e) {
Slog.e(TAG, "Unable to read status info file.", e);
}
}
private void readStatusInfoLocked(InputStream in) throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
while (true) {
switch (proto.nextField()) {
case (int) SyncStatusProto.STATUS:
final long token = proto.start(SyncStatusProto.STATUS);
final SyncStatusInfo status = readSyncStatusInfoLocked(proto);
proto.end(token);
if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
status.pending = false;
mSyncStatus.put(status.authorityId, status);
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
}
}
}
private SyncStatusInfo readSyncStatusInfoLocked(ProtoInputStream proto) throws IOException {
SyncStatusInfo status;
if (proto.nextField(SyncStatusProto.StatusInfo.AUTHORITY_ID)) {
//fast-path; this should work for most cases since the authority id is written first
status = new SyncStatusInfo(proto.readInt(SyncStatusProto.StatusInfo.AUTHORITY_ID));
} else {
// placeholder to read other data; assume the default authority id as 0
status = new SyncStatusInfo(0);
}
int successTimesCount = 0;
int failureTimesCount = 0;
ArrayList<Pair<Long, String>> lastEventInformation = new ArrayList<>();
while (true) {
switch (proto.nextField()) {
case (int) SyncStatusProto.StatusInfo.AUTHORITY_ID:
// fast-path failed for some reason, rebuild the status from placeholder object
Slog.w(TAG, "Failed to read the authority id via fast-path; "
+ "some data might not have been read.");
status = new SyncStatusInfo(
proto.readInt(SyncStatusProto.StatusInfo.AUTHORITY_ID), status);
break;
case (int) SyncStatusProto.StatusInfo.LAST_SUCCESS_TIME:
status.lastSuccessTime = proto.readLong(
SyncStatusProto.StatusInfo.LAST_SUCCESS_TIME);
break;
case (int) SyncStatusProto.StatusInfo.LAST_SUCCESS_SOURCE:
status.lastSuccessSource = proto.readInt(
SyncStatusProto.StatusInfo.LAST_SUCCESS_SOURCE);
break;
case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_TIME:
status.lastFailureTime = proto.readLong(
SyncStatusProto.StatusInfo.LAST_FAILURE_TIME);
break;
case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_SOURCE:
status.lastFailureSource = proto.readInt(
SyncStatusProto.StatusInfo.LAST_FAILURE_SOURCE);
break;
case (int) SyncStatusProto.StatusInfo.LAST_FAILURE_MESSAGE:
status.lastFailureMesg = proto.readString(
SyncStatusProto.StatusInfo.LAST_FAILURE_MESSAGE);
break;
case (int) SyncStatusProto.StatusInfo.INITIAL_FAILURE_TIME:
status.initialFailureTime = proto.readLong(
SyncStatusProto.StatusInfo.INITIAL_FAILURE_TIME);
break;
case (int) SyncStatusProto.StatusInfo.PENDING:
status.pending = proto.readBoolean(SyncStatusProto.StatusInfo.PENDING);
break;
case (int) SyncStatusProto.StatusInfo.INITIALIZE:
status.initialize = proto.readBoolean(SyncStatusProto.StatusInfo.INITIALIZE);
break;
case (int) SyncStatusProto.StatusInfo.PERIODIC_SYNC_TIMES:
status.addPeriodicSyncTime(
proto.readLong(SyncStatusProto.StatusInfo.PERIODIC_SYNC_TIMES));
break;
case (int) SyncStatusProto.StatusInfo.LAST_EVENT_INFO:
final long eventToken = proto.start(SyncStatusProto.StatusInfo.LAST_EVENT_INFO);
final Pair<Long, String> lastEventInfo = parseLastEventInfoLocked(proto);
if (lastEventInfo != null) {
lastEventInformation.add(lastEventInfo);
}
proto.end(eventToken);
break;
case (int) SyncStatusProto.StatusInfo.LAST_TODAY_RESET_TIME:
status.lastTodayResetTime = proto.readLong(
SyncStatusProto.StatusInfo.LAST_TODAY_RESET_TIME);
break;
case (int) SyncStatusProto.StatusInfo.TOTAL_STATS:
final long totalStatsToken = proto.start(
SyncStatusProto.StatusInfo.TOTAL_STATS);
readSyncStatusStatsLocked(proto, status.totalStats);
proto.end(totalStatsToken);
break;
case (int) SyncStatusProto.StatusInfo.TODAY_STATS:
final long todayStatsToken = proto.start(
SyncStatusProto.StatusInfo.TODAY_STATS);
readSyncStatusStatsLocked(proto, status.todayStats);
proto.end(todayStatsToken);
break;
case (int) SyncStatusProto.StatusInfo.YESTERDAY_STATS:
final long yesterdayStatsToken = proto.start(
SyncStatusProto.StatusInfo.YESTERDAY_STATS);
readSyncStatusStatsLocked(proto, status.yesterdayStats);
proto.end(yesterdayStatsToken);
break;
case (int) SyncStatusProto.StatusInfo.PER_SOURCE_LAST_SUCCESS_TIMES:
final long successTime = proto.readLong(
SyncStatusProto.StatusInfo.PER_SOURCE_LAST_SUCCESS_TIMES);
if (successTimesCount == status.perSourceLastSuccessTimes.length) {
Slog.w(TAG, "Attempted to read more per source last success times "
+ "than expected; data might be corrupted.");
break;
}
status.perSourceLastSuccessTimes[successTimesCount] = successTime;
successTimesCount++;
break;
case (int) SyncStatusProto.StatusInfo.PER_SOURCE_LAST_FAILURE_TIMES:
final long failureTime = proto.readLong(
SyncStatusProto.StatusInfo.PER_SOURCE_LAST_FAILURE_TIMES);
if (failureTimesCount == status.perSourceLastFailureTimes.length) {
Slog.w(TAG, "Attempted to read more per source last failure times "
+ "than expected; data might be corrupted.");
break;
}
status.perSourceLastFailureTimes[failureTimesCount] = failureTime;
failureTimesCount++;
break;
case ProtoInputStream.NO_MORE_FIELDS:
status.populateLastEventsInformation(lastEventInformation);
return status;
}
}
}
private Pair<Long, String> parseLastEventInfoLocked(ProtoInputStream proto) throws IOException {
long time = 0;
String message = null;
while (true) {
switch (proto.nextField()) {
case (int) SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT_TIME:
time = proto.readLong(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT_TIME);
break;
case (int) SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT:
message = proto.readString(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return message == null ? null : new Pair<>(time, message);
}
}
}
private void readSyncStatusStatsLocked(ProtoInputStream proto, SyncStatusInfo.Stats stats)
throws IOException {
while (true) {
switch (proto.nextField()) {
case (int) SyncStatusProto.StatusInfo.Stats.TOTAL_ELAPSED_TIME:
stats.totalElapsedTime = proto.readLong(
SyncStatusProto.StatusInfo.Stats.TOTAL_ELAPSED_TIME);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SYNCS:
stats.numSyncs = proto.readInt(SyncStatusProto.StatusInfo.Stats.NUM_SYNCS);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_FAILURES:
stats.numFailures = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_FAILURES);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_CANCELS:
stats.numCancels = proto.readInt(SyncStatusProto.StatusInfo.Stats.NUM_CANCELS);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_OTHER:
stats.numSourceOther = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_OTHER);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_LOCAL:
stats.numSourceLocal = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_LOCAL);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_POLL:
stats.numSourcePoll = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_POLL);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_USER:
stats.numSourceUser = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_USER);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_PERIODIC:
stats.numSourcePeriodic = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_PERIODIC);
break;
case (int) SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_FEED:
stats.numSourceFeed = proto.readInt(
SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_FEED);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
}
}
}
/**
* Write all sync status to the sync status file.
*/
private void writeStatusLocked() {
@VisibleForTesting
void writeStatusLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Slog.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
}
@@ -2070,26 +2315,87 @@ public class SyncStorageEngine {
FileOutputStream fos = null;
try {
fos = mStatusFile.startWrite();
Parcel out = Parcel.obtain();
final int N = mSyncStatus.size();
for (int i=0; i<N; i++) {
SyncStatusInfo status = mSyncStatus.valueAt(i);
out.writeInt(STATUS_FILE_ITEM);
status.writeToParcel(out, 0);
}
out.writeInt(STATUS_FILE_END);
fos.write(out.marshall());
out.recycle();
writeStatusInfoLocked(fos);
mStatusFile.finishWrite(fos);
} catch (java.io.IOException e1) {
Slog.w(TAG, "Error writing status", e1);
if (fos != null) {
mStatusFile.failWrite(fos);
}
fos = null;
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to write sync status to proto.", e);
} finally {
// when fos is null (successful write), this is a no-op.
mStatusFile.failWrite(fos);
}
}
private void writeStatusInfoLocked(OutputStream out) {
final ProtoOutputStream proto = new ProtoOutputStream(out);
final int size = mSyncStatus.size();
for (int i = 0; i < size; i++) {
final SyncStatusInfo info = mSyncStatus.valueAt(i);
final long token = proto.start(SyncStatusProto.STATUS);
// authority id should be written first to take advantage of the fast path in read
proto.write(SyncStatusProto.StatusInfo.AUTHORITY_ID, info.authorityId);
proto.write(SyncStatusProto.StatusInfo.LAST_SUCCESS_TIME, info.lastSuccessTime);
proto.write(SyncStatusProto.StatusInfo.LAST_SUCCESS_SOURCE, info.lastSuccessSource);
proto.write(SyncStatusProto.StatusInfo.LAST_FAILURE_TIME, info.lastFailureTime);
proto.write(SyncStatusProto.StatusInfo.LAST_FAILURE_SOURCE, info.lastFailureSource);
proto.write(SyncStatusProto.StatusInfo.LAST_FAILURE_MESSAGE, info.lastFailureMesg);
proto.write(SyncStatusProto.StatusInfo.INITIAL_FAILURE_TIME, info.initialFailureTime);
proto.write(SyncStatusProto.StatusInfo.PENDING, info.pending);
proto.write(SyncStatusProto.StatusInfo.INITIALIZE, info.initialize);
final int periodicSyncTimesSize = info.getPeriodicSyncTimesSize();
for (int j = 0; j < periodicSyncTimesSize; j++) {
proto.write(SyncStatusProto.StatusInfo.PERIODIC_SYNC_TIMES,
info.getPeriodicSyncTime(j));
}
final int lastEventsSize = info.getEventCount();
for (int j = 0; j < lastEventsSize; j++) {
final long eventToken = proto.start(SyncStatusProto.StatusInfo.LAST_EVENT_INFO);
proto.write(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT_TIME,
info.getEventTime(j));
proto.write(SyncStatusProto.StatusInfo.LastEventInfo.LAST_EVENT, info.getEvent(j));
proto.end(eventToken);
}
proto.write(SyncStatusProto.StatusInfo.LAST_TODAY_RESET_TIME, info.lastTodayResetTime);
final long totalStatsToken = proto.start(SyncStatusProto.StatusInfo.TOTAL_STATS);
writeStatusStatsLocked(proto, info.totalStats);
proto.end(totalStatsToken);
final long todayStatsToken = proto.start(SyncStatusProto.StatusInfo.TODAY_STATS);
writeStatusStatsLocked(proto, info.todayStats);
proto.end(todayStatsToken);
final long yesterdayStatsToken = proto.start(
SyncStatusProto.StatusInfo.YESTERDAY_STATS);
writeStatusStatsLocked(proto, info.yesterdayStats);
proto.end(yesterdayStatsToken);
final int lastSuccessTimesSize = info.perSourceLastSuccessTimes.length;
for (int j = 0; j < lastSuccessTimesSize; j++) {
proto.write(SyncStatusProto.StatusInfo.PER_SOURCE_LAST_SUCCESS_TIMES,
info.perSourceLastSuccessTimes[j]);
}
final int lastFailureTimesSize = info.perSourceLastFailureTimes.length;
for (int j = 0; j < lastFailureTimesSize; j++) {
proto.write(SyncStatusProto.StatusInfo.PER_SOURCE_LAST_FAILURE_TIMES,
info.perSourceLastFailureTimes[j]);
}
proto.end(token);
}
proto.flush();
}
private void writeStatusStatsLocked(ProtoOutputStream proto, SyncStatusInfo.Stats stats) {
proto.write(SyncStatusProto.StatusInfo.Stats.TOTAL_ELAPSED_TIME, stats.totalElapsedTime);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SYNCS, stats.numSyncs);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_FAILURES, stats.numFailures);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_CANCELS, stats.numCancels);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_OTHER, stats.numSourceOther);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_LOCAL, stats.numSourceLocal);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_POLL, stats.numSourcePoll);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_USER, stats.numSourceUser);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_PERIODIC, stats.numSourcePeriodic);
proto.write(SyncStatusProto.StatusInfo.Stats.NUM_SOURCE_FEED, stats.numSourceFeed);
}
private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras,
@SyncExemption int syncExemptionFlag, int callingUid, int callingPid) {
if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
@@ -2126,20 +2432,17 @@ public class SyncStorageEngine {
public static final int STATISTICS_FILE_ITEM_OLD = 100;
public static final int STATISTICS_FILE_ITEM = 101;
/**
* Read all sync statistics back in to the initial engine state.
*/
private void readStatisticsLocked() {
private void readStatsParcelLocked(File parcel) {
try {
byte[] data = mStatisticsFile.readFully();
final AtomicFile parcelFile = new AtomicFile(parcel);
byte[] data = parcelFile.readFully();
Parcel in = Parcel.obtain();
in.unmarshall(data, 0, data.length);
in.setDataPosition(0);
int token;
int index = 0;
while ((token=in.readInt()) != STATISTICS_FILE_END) {
if (token == STATISTICS_FILE_ITEM
|| token == STATISTICS_FILE_ITEM_OLD) {
if (token == STATISTICS_FILE_ITEM || token == STATISTICS_FILE_ITEM_OLD) {
int day = in.readInt();
if (token == STATISTICS_FILE_ITEM_OLD) {
day = day - 2009 + 14245; // Magic!
@@ -2159,15 +2462,110 @@ public class SyncStorageEngine {
break;
}
}
} catch (java.io.IOException e) {
} catch (IOException e) {
Slog.i(TAG, "No initial statistics");
}
}
private void upgradeStatisticsIfNeededLocked() {
final File parcelStats = new File(mSyncDir, LEGACY_STATISTICS_FILE_NAME);
if (parcelStats.exists() && !mStatisticsFile.exists()) {
readStatsParcelLocked(parcelStats);
writeStatisticsLocked();
}
// if upgrade to proto was successful, delete parcel file
if (DELETE_LEGACY_PARCEL_FILES && mStatisticsFile.exists()) {
parcelStats.delete();
}
}
/**
* Read all sync statistics back in to the initial engine state.
*/
private void readStatisticsLocked() {
upgradeStatisticsIfNeededLocked();
if (!mStatisticsFile.exists()) {
return;
}
try {
try (FileInputStream in = mStatisticsFile.openRead()) {
readDayStatsLocked(in);
}
} catch (IOException e) {
Slog.e(TAG, "Unable to read day stats file.", e);
}
}
private void readDayStatsLocked(InputStream in) throws IOException {
final ProtoInputStream proto = new ProtoInputStream(in);
int statsCount = 0;
while (true) {
switch (proto.nextField()) {
case (int) SyncStatisticsProto.STATS:
final long token = proto.start(SyncStatisticsProto.STATS);
final DayStats stats = readIndividualDayStatsLocked(proto);
proto.end(token);
mDayStats[statsCount] = stats;
statsCount++;
if (statsCount == mDayStats.length) {
return;
}
break;
case ProtoInputStream.NO_MORE_FIELDS:
return;
}
}
}
private DayStats readIndividualDayStatsLocked(ProtoInputStream proto) throws IOException {
DayStats stats;
if (proto.nextField(SyncStatisticsProto.DayStats.DAY)) {
// fast-path; this should work for most cases since the day is written first
stats = new DayStats(proto.readInt(SyncStatisticsProto.DayStats.DAY));
} else {
// placeholder to read other data; assume the default day as 0
stats = new DayStats(0);
}
while (true) {
switch (proto.nextField()) {
case (int) SyncStatisticsProto.DayStats.DAY:
// fast-path failed for some reason, rebuild stats from placeholder object
Slog.w(TAG, "Failed to read the day via fast-path; some data "
+ "might not have been read.");
final DayStats temp = new DayStats(
proto.readInt(SyncStatisticsProto.DayStats.DAY));
temp.successCount = stats.successCount;
temp.successTime = stats.successTime;
temp.failureCount = stats.failureCount;
temp.failureTime = stats.failureTime;
stats = temp;
break;
case (int) SyncStatisticsProto.DayStats.SUCCESS_COUNT:
stats.successCount = proto.readInt(SyncStatisticsProto.DayStats.SUCCESS_COUNT);
break;
case (int) SyncStatisticsProto.DayStats.SUCCESS_TIME:
stats.successTime = proto.readLong(SyncStatisticsProto.DayStats.SUCCESS_TIME);
break;
case (int) SyncStatisticsProto.DayStats.FAILURE_COUNT:
stats.failureCount = proto.readInt(SyncStatisticsProto.DayStats.FAILURE_COUNT);
break;
case (int) SyncStatisticsProto.DayStats.FAILURE_TIME:
stats.failureTime = proto.readLong(SyncStatisticsProto.DayStats.FAILURE_TIME);
break;
case ProtoInputStream.NO_MORE_FIELDS:
return stats;
}
}
}
/**
* Write all sync statistics to the sync status file.
*/
private void writeStatisticsLocked() {
@VisibleForTesting
void writeStatisticsLocked() {
if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
Slog.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
}
@@ -2179,33 +2577,38 @@ public class SyncStorageEngine {
FileOutputStream fos = null;
try {
fos = mStatisticsFile.startWrite();
Parcel out = Parcel.obtain();
final int N = mDayStats.length;
for (int i=0; i<N; i++) {
DayStats ds = mDayStats[i];
if (ds == null) {
break;
}
out.writeInt(STATISTICS_FILE_ITEM);
out.writeInt(ds.day);
out.writeInt(ds.successCount);
out.writeLong(ds.successTime);
out.writeInt(ds.failureCount);
out.writeLong(ds.failureTime);
}
out.writeInt(STATISTICS_FILE_END);
fos.write(out.marshall());
out.recycle();
writeDayStatsLocked(fos);
mStatisticsFile.finishWrite(fos);
} catch (java.io.IOException e1) {
Slog.w(TAG, "Error writing stats", e1);
if (fos != null) {
mStatisticsFile.failWrite(fos);
}
fos = null;
} catch (IOException | IllegalArgumentException e) {
Slog.e(TAG, "Unable to write day stats to proto.", e);
} finally {
// when fos is null (successful write), this is a no-op.
mStatisticsFile.failWrite(fos);
}
}
private void writeDayStatsLocked(OutputStream out)
throws IOException, IllegalArgumentException {
final ProtoOutputStream proto = new ProtoOutputStream(out);
final int size = mDayStats.length;
for (int i = 0; i < size; i++) {
final DayStats stats = mDayStats[i];
if (stats == null) {
break;
}
final long token = proto.start(SyncStatisticsProto.STATS);
// day should be written first to take advantage of the fast path in read
proto.write(SyncStatisticsProto.DayStats.DAY, stats.day);
proto.write(SyncStatisticsProto.DayStats.SUCCESS_COUNT, stats.successCount);
proto.write(SyncStatisticsProto.DayStats.SUCCESS_TIME, stats.successTime);
proto.write(SyncStatisticsProto.DayStats.FAILURE_COUNT, stats.failureCount);
proto.write(SyncStatisticsProto.DayStats.FAILURE_TIME, stats.failureTime);
proto.end(token);
}
proto.flush();
}
/**
* Let the BackupManager know that account sync settings have changed. This will trigger
* {@link com.android.server.backup.SystemBackupAgent} to run.

View File

@@ -0,0 +1,200 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.content;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import android.content.Context;
import android.content.SyncStatusInfo;
import android.util.Pair;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Random;
/**
* Tests for {@link SyncStorageEngine}.
*/
@RunWith(AndroidJUnit4.class)
public class SyncStorageEngineTest {
private Context mContext;
private SyncStorageEngine mSyncStorageEngine;
private static final int NUM_SYNC_STATUS = 100;
private static final int NUM_PERIODIC_SYNC_TIMES = 20;
private static final int NUM_EVENTS = 10;
private static final int NUM_SOURCES = 6;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mSyncStorageEngine = SyncStorageEngine.newTestInstance(mContext);
}
@Test
public void testStatisticsReadWrite() {
populateDayStats(mSyncStorageEngine.mDayStats);
mSyncStorageEngine.writeStatisticsLocked();
final SyncStorageEngine other = SyncStorageEngine.newTestInstance(mContext);
verifyDayStats(mSyncStorageEngine.mDayStats, other.getDayStatistics());
}
@Test
public void testStatusReadWrite() {
populateStatus(mSyncStorageEngine.mSyncStatus);
mSyncStorageEngine.writeStatusLocked();
final SyncStorageEngine other = SyncStorageEngine.newTestInstance(mContext);
for (int i = 0; i < NUM_SYNC_STATUS; i++) {
other.mAuthorities.put(i, null);
}
other.readStatusLocked();
verifyStatus(mSyncStorageEngine.mSyncStatus, other.mSyncStatus);
}
private void populateDayStats(SyncStorageEngine.DayStats[] dayStats) {
final Random r = new Random(1);
for (int i = 0; i < dayStats.length; i++) {
final SyncStorageEngine.DayStats ds = new SyncStorageEngine.DayStats(i);
ds.successCount = r.nextInt();
ds.successTime = r.nextLong();
ds.failureCount = r.nextInt();
ds.failureTime = r.nextLong();
dayStats[i] = ds;
}
}
private void verifyDayStats(SyncStorageEngine.DayStats[] dayStats,
SyncStorageEngine.DayStats[] dayStatsOther) {
assertEquals(dayStatsOther.length, dayStats.length);
for (int i = 0; i < dayStatsOther.length; i++) {
final SyncStorageEngine.DayStats ds = dayStats[i];
final SyncStorageEngine.DayStats dsOther = dayStatsOther[i];
assertEquals(dsOther.day, ds.day);
assertEquals(dsOther.successCount, ds.successCount);
assertEquals(dsOther.successTime, ds.successTime);
assertEquals(dsOther.failureCount, ds.failureCount);
assertEquals(dsOther.failureTime, ds.failureTime);
}
}
private void populateStatus(SparseArray<SyncStatusInfo> syncStatus) {
final Random r = new Random(1);
for (int i = 0; i < NUM_SYNC_STATUS; i++) {
final SyncStatusInfo ss = new SyncStatusInfo(i);
ss.lastSuccessTime = r.nextLong();
ss.lastSuccessSource = r.nextInt();
ss.lastFailureTime = r.nextLong();
ss.lastFailureSource = r.nextInt();
ss.lastFailureMesg = "fail_msg_" + r.nextInt();
ss.initialFailureTime = r.nextLong();
ss.initialize = r.nextBoolean();
for (int j = 0; j < NUM_PERIODIC_SYNC_TIMES; j++) {
ss.addPeriodicSyncTime(r.nextLong());
}
final ArrayList<Pair<Long, String>> lastEventInfos = new ArrayList<>();
for (int j = 0; j < NUM_EVENTS; j++) {
lastEventInfos.add(new Pair<>(r.nextLong(), "event_" + r.nextInt()));
}
ss.populateLastEventsInformation(lastEventInfos);
ss.lastTodayResetTime = r.nextLong();
populateStats(ss.totalStats, r);
populateStats(ss.todayStats, r);
populateStats(ss.yesterdayStats, r);
for (int j = 0; j < NUM_SOURCES; j++) {
ss.perSourceLastSuccessTimes[j] = r.nextLong();
}
for (int j = 0; j < NUM_SOURCES; j++) {
ss.perSourceLastFailureTimes[j] = r.nextLong();
}
syncStatus.put(i, ss);
}
}
private void populateStats(SyncStatusInfo.Stats stats, Random r) {
stats.totalElapsedTime = r.nextLong();
stats.numSyncs = r.nextInt();
stats.numFailures = r.nextInt();
stats.numCancels = r.nextInt();
stats.numSourceOther = r.nextInt();
stats.numSourceLocal = r.nextInt();
stats.numSourcePoll = r.nextInt();
stats.numSourceUser = r.nextInt();
stats.numSourcePeriodic = r.nextInt();
stats.numSourceFeed = r.nextInt();
}
private void verifyStatus(SparseArray<SyncStatusInfo> syncStatus,
SparseArray<SyncStatusInfo> syncStatusOther) {
assertEquals(syncStatusOther.size(), syncStatus.size());
for (int i = 0; i < NUM_SYNC_STATUS; i++) {
final SyncStatusInfo ss = syncStatus.valueAt(i);
final SyncStatusInfo ssOther = syncStatusOther.valueAt(i);
assertEquals(ssOther.authorityId, ss.authorityId);
assertEquals(ssOther.lastSuccessTime, ss.lastSuccessTime);
assertEquals(ssOther.lastSuccessSource, ss.lastSuccessSource);
assertEquals(ssOther.lastFailureTime, ss.lastFailureTime);
assertEquals(ssOther.lastFailureSource, ss.lastFailureSource);
assertEquals(ssOther.lastFailureMesg, ss.lastFailureMesg);
assertFalse(ssOther.pending); // pending is always set to false when read
assertEquals(ssOther.initialize, ss.initialize);
assertEquals(ssOther.getPeriodicSyncTimesSize(), NUM_PERIODIC_SYNC_TIMES);
for (int j = 0; j < NUM_PERIODIC_SYNC_TIMES; j++) {
assertEquals(ssOther.getPeriodicSyncTime(j), ss.getPeriodicSyncTime(j));
}
assertEquals(ssOther.getEventCount(), NUM_EVENTS);
for (int j = 0; j < NUM_EVENTS; j++) {
assertEquals(ssOther.getEventTime(j), ss.getEventTime(j));
assertEquals(ssOther.getEvent(j), ss.getEvent(j));
}
assertEquals(ssOther.lastTodayResetTime, ss.lastTodayResetTime);
verifyStats(ss.totalStats, ssOther.totalStats);
verifyStats(ss.todayStats, ssOther.todayStats);
verifyStats(ss.yesterdayStats, ssOther.yesterdayStats);
assertEquals(ssOther.perSourceLastSuccessTimes.length, NUM_SOURCES);
for (int j = 0; j < NUM_SOURCES; j++) {
assertEquals(ssOther.perSourceLastSuccessTimes[j], ss.perSourceLastSuccessTimes[j]);
}
assertEquals(ssOther.perSourceLastFailureTimes.length, NUM_SOURCES);
for (int j = 0; j < NUM_SOURCES; j++) {
assertEquals(ssOther.perSourceLastFailureTimes[j], ss.perSourceLastFailureTimes[j]);
}
}
}
private void verifyStats(SyncStatusInfo.Stats stats, SyncStatusInfo.Stats statsOther) {
assertEquals(statsOther.totalElapsedTime, stats.totalElapsedTime);
assertEquals(statsOther.numSyncs, stats.numSyncs);
assertEquals(statsOther.numFailures, stats.numFailures);
assertEquals(statsOther.numCancels, stats.numCancels);
assertEquals(statsOther.numSourceOther, stats.numSourceOther);
assertEquals(statsOther.numSourceLocal, stats.numSourceLocal);
assertEquals(statsOther.numSourcePoll, stats.numSourcePoll);
assertEquals(statsOther.numSourceUser, stats.numSourceUser);
assertEquals(statsOther.numSourcePeriodic, stats.numSourcePeriodic);
assertEquals(statsOther.numSourceFeed, stats.numSourceFeed);
}
}