Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6f5fb605 authored by Ben Kwa's avatar Ben Kwa Committed by Android (Google) Code Review
Browse files

Merge "Add metrics logging for file operations."

parents da20086a faa27202
Loading
Loading
Loading
Loading
+256 −4
Original line number Original line Diff line number Diff line
@@ -17,7 +17,10 @@
package com.android.documentsui;
package com.android.documentsui;


import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.Shared.DEBUG;
import static com.android.internal.util.Preconditions.checkArgument;


import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.pm.ResolveInfo;
@@ -25,9 +28,16 @@ import android.net.Uri;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract;
import android.util.Log;
import android.util.Log;


import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsLogger;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;

/** @hide */
/** @hide */
public final class Metrics {
public final class Metrics {
    private static final String TAG = "Metrics";
    private static final String TAG = "Metrics";
@@ -47,6 +57,9 @@ public final class Metrics {
    private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
    private static final String COUNT_BROWSE_ROOT = "docsui_browse_root";
    private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
    private static final String COUNT_MANAGE_ROOT = "docsui_manage_root";
    private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
    private static final String COUNT_MULTI_WINDOW = "docsui_multi_window";
    private static final String COUNT_FILEOP_SYSTEM = "docsui_fileop_system";
    private static final String COUNT_FILEOP_EXTERNAL = "docsui_fileop_external";
    private static final String COUNT_FILEOP_CANCELED = "docsui_fileop_canceled";


    // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
    // Indices for bucketing roots in the roots histogram. "Other" is the catch-all index for any
    // root that is not explicitly recognized by the Metrics code (see {@link
    // root that is not explicitly recognized by the Metrics code (see {@link
@@ -65,6 +78,21 @@ public final class Metrics {
    // are logged analogously to roots. Use negative numbers to identify apps.
    // are logged analogously to roots. Use negative numbers to identify apps.
    private static final int ROOT_THIRD_PARTY_APP = -1;
    private static final int ROOT_THIRD_PARTY_APP = -1;


    @IntDef(flag = true, value = {
            ROOT_NONE,
            ROOT_OTHER,
            ROOT_AUDIO,
            ROOT_DEVICE_STORAGE,
            ROOT_DOWNLOADS,
            ROOT_HOME,
            ROOT_IMAGES,
            ROOT_RECENTS,
            ROOT_VIDEOS,
            ROOT_THIRD_PARTY_APP
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Root {}

    // Indices for bucketing mime types.
    // Indices for bucketing mime types.
    private static final int MIME_OTHER = -2; // anything not enumerated below
    private static final int MIME_OTHER = -2; // anything not enumerated below
    private static final int MIME_NONE = -1; // null mime
    private static final int MIME_NONE = -1; // null mime
@@ -77,6 +105,66 @@ public final class Metrics {
    private static final int MIME_TEXT = 6; // text/*
    private static final int MIME_TEXT = 6; // text/*
    private static final int MIME_VIDEO = 7; // video/*
    private static final int MIME_VIDEO = 7; // video/*


    @IntDef(flag = true, value = {
            MIME_OTHER,
            MIME_NONE,
            MIME_ANY,
            MIME_APPLICATION,
            MIME_AUDIO,
            MIME_IMAGE,
            MIME_MESSAGE,
            MIME_MULTIPART,
            MIME_TEXT,
            MIME_VIDEO
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Mime {}

    // Codes representing different kinds of file operations. These are used for bucketing
    // operations in the COUNT_FILEOP_{SYSTEM|EXTERNAL} histograms.
    private static final int FILEOP_OTHER = 0; // any file operation not listed below
    private static final int FILEOP_COPY_INTRA_PROVIDER = 1; // Copy within a provider
    private static final int FILEOP_COPY_SYSTEM_PROVIDER = 2; // Copy to a system provider.
    private static final int FILEOP_COPY_EXTERNAL_PROVIDER = 3; // Copy to a 3rd-party provider.
    private static final int FILEOP_MOVE_INTRA_PROVIDER = 4; // Move within a provider.
    private static final int FILEOP_MOVE_SYSTEM_PROVIDER = 5; // Move to a system provider.
    private static final int FILEOP_MOVE_EXTERNAL_PROVIDER = 6; // Move to a 3rd-party provider.
    private static final int FILEOP_DELETE = 7;
    private static final int FILEOP_OTHER_ERROR = -1;
    private static final int FILEOP_COPY_ERROR = -2;
    private static final int FILEOP_MOVE_ERROR = -3;
    private static final int FILEOP_DELETE_ERROR = -4;

    @IntDef(flag = true, value = {
            FILEOP_OTHER,
            FILEOP_COPY_INTRA_PROVIDER,
            FILEOP_COPY_SYSTEM_PROVIDER,
            FILEOP_COPY_EXTERNAL_PROVIDER,
            FILEOP_MOVE_INTRA_PROVIDER,
            FILEOP_MOVE_SYSTEM_PROVIDER,
            FILEOP_MOVE_EXTERNAL_PROVIDER,
            FILEOP_DELETE,
            FILEOP_OTHER_ERROR,
            FILEOP_COPY_ERROR,
            FILEOP_MOVE_ERROR,
            FILEOP_DELETE_ERROR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FileOp {}

    // Codes representing different provider types.  Used for sorting file operations when logging.
    private static final int PROVIDER_INTRA = 0;
    private static final int PROVIDER_SYSTEM = 1;
    private static final int PROVIDER_EXTERNAL = 2;

    @IntDef(flag = true, value = {
            PROVIDER_INTRA,
            PROVIDER_SYSTEM,
            PROVIDER_EXTERNAL
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Provider {}

    /**
    /**
     * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
     * Logs when DocumentsUI is started, and how. Call this when DocumentsUI first starts up.
     *
     *
@@ -139,6 +227,99 @@ public final class Metrics {
        logCount(context, COUNT_MULTI_WINDOW);
        logCount(context, COUNT_MULTI_WINDOW);
    }
    }


    /**
     * Logs file operation stats. Call this when a file operation has completed. The given
     * DocumentInfo is only used to distinguish broad categories of actions (e.g. copying from one
     * provider to another vs copying within a given provider).  No PII is logged.
     *
     * @param context
     * @param operationType
     * @param srcs
     * @param dst
     */
    public static void logFileOperation(
            Context context,
            @OpType int operationType,
            List<DocumentInfo> srcs,
            @Nullable DocumentInfo dst) {
        ProviderCounts counts = countProviders(srcs, dst);

        if (counts.intraProvider > 0) {
            logIntraProviderFileOps(context, dst.authority, operationType);
        }
        if (counts.systemProvider > 0) {
            // Log file operations on system providers.
            logInterProviderFileOps(context, COUNT_FILEOP_SYSTEM, dst, operationType);
        }
        if (counts.externalProvider > 0) {
            // Log file operations on external providers.
            logInterProviderFileOps(context, COUNT_FILEOP_EXTERNAL, dst, operationType);
        }
    }

    /**
     * Logs some kind of file operation error. Call this when a file operation (e.g. copy, delete)
     * fails.
     *
     * @param context
     * @param operationType
     * @param failedFiles
     */
    public static void logFileOperationErrors(Context context, @OpType int operationType,
            List<DocumentInfo> failedFiles) {
        ProviderCounts counts = countProviders(failedFiles, null);

        @FileOp int opCode = FILEOP_OTHER_ERROR;
        switch (operationType) {
            case FileOperationService.OPERATION_COPY:
                opCode = FILEOP_COPY_ERROR;
                break;
            case FileOperationService.OPERATION_DELETE:
                opCode = FILEOP_DELETE_ERROR;
                break;
            case FileOperationService.OPERATION_MOVE:
                opCode = FILEOP_MOVE_ERROR;
                break;
        }
        if (counts.systemProvider > 0) {
            logHistogram(context, COUNT_FILEOP_SYSTEM, opCode);
        }
        if (counts.externalProvider > 0) {
            logHistogram(context, COUNT_FILEOP_EXTERNAL, opCode);
        }
    }

    /**
     * Log the cancellation of a file operation.  Call this when a Job is canceled.
     * @param context
     * @param operationType
     */
    public static void logFileOperationCancelled(Context context, @OpType int operationType) {
        logHistogram(context, COUNT_FILEOP_CANCELED, operationType);
    }

    private static void logInterProviderFileOps(
            Context context,
            String histogram,
            DocumentInfo dst,
            @OpType int operationType) {
        if (operationType == FileOperationService.OPERATION_DELETE) {
            logHistogram(context, histogram, FILEOP_DELETE);
        } else {
            checkArgument(dst != null);
            @Provider int providerType =
                    isSystemProvider(dst.authority) ? PROVIDER_SYSTEM : PROVIDER_EXTERNAL;
            logHistogram(context, histogram, getOpCode(operationType, providerType));
        }
    }

    private static void logIntraProviderFileOps(
            Context context, String authority, @OpType int operationType) {
        // Find the right histogram to log to, then log the operation.
        String histogram = isSystemProvider(authority) ? COUNT_FILEOP_SYSTEM : COUNT_FILEOP_EXTERNAL;
        logHistogram(context, histogram, getOpCode(operationType, PROVIDER_INTRA));
    }

    /**
    /**
     * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
     * Internal method for making a MetricsLogger.count call. Increments the given counter by 1.
     *
     *
@@ -167,7 +348,7 @@ public final class Metrics {
     * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
     * small set of hard-coded roots (ones provided by the system). Other roots are all grouped into
     * a single ROOT_OTHER bucket.
     * a single ROOT_OTHER bucket.
     */
     */
    private static int sanitizeRoot(Uri uri) {
    private static @Root int sanitizeRoot(Uri uri) {
        if (LauncherActivity.isLaunchUri(uri)) {
        if (LauncherActivity.isLaunchUri(uri)) {
            return ROOT_NONE;
            return ROOT_NONE;
        }
        }
@@ -198,7 +379,7 @@ public final class Metrics {
    }
    }


    /** @see #sanitizeRoot(Uri) */
    /** @see #sanitizeRoot(Uri) */
    private static int sanitizeRoot(RootInfo root) {
    private static @Root int sanitizeRoot(RootInfo root) {
        if (root.isRecents()) {
        if (root.isRecents()) {
            // Recents root is special and only identifiable via this method call. Other roots are
            // Recents root is special and only identifiable via this method call. Other roots are
            // identified by URI.
            // identified by URI.
@@ -209,7 +390,7 @@ public final class Metrics {
    }
    }


    /** @see #sanitizeRoot(Uri) */
    /** @see #sanitizeRoot(Uri) */
    private static int sanitizeRoot(ResolveInfo info) {
    private static @Root int sanitizeRoot(ResolveInfo info) {
        // Log all apps under a single bucket in the roots histogram.
        // Log all apps under a single bucket in the roots histogram.
        return ROOT_THIRD_PARTY_APP;
        return ROOT_THIRD_PARTY_APP;
    }
    }
@@ -221,7 +402,7 @@ public final class Metrics {
     * @param mimeType
     * @param mimeType
     * @return
     * @return
     */
     */
    private static int sanitizeMime(String mimeType) {
    private static @Mime int sanitizeMime(String mimeType) {
        if (mimeType == null) {
        if (mimeType == null) {
            return MIME_NONE;
            return MIME_NONE;
        } else if ("*/*".equals(mimeType)) {
        } else if ("*/*".equals(mimeType)) {
@@ -248,4 +429,75 @@ public final class Metrics {
        // Bucket all other types into one bucket.
        // Bucket all other types into one bucket.
        return MIME_OTHER;
        return MIME_OTHER;
    }
    }

    private static boolean isSystemProvider(String authority) {
        switch (authority) {
            case AUTHORITY_MEDIA:
            case AUTHORITY_STORAGE:
            case AUTHORITY_DOWNLOADS:
                return true;
            default:
                return false;
        }
    }

    /**
     * @param operation
     * @param providerType
     * @return An opcode, suitable for use as histogram bucket, for the given operation/provider
     *         combination.
     */
    private static @FileOp int getOpCode(@OpType int operation, @Provider int providerType) {
        switch (operation) {
            case FileOperationService.OPERATION_COPY:
                switch (providerType) {
                    case PROVIDER_INTRA:
                        return FILEOP_COPY_INTRA_PROVIDER;
                    case PROVIDER_SYSTEM:
                        return FILEOP_COPY_SYSTEM_PROVIDER;
                    case PROVIDER_EXTERNAL:
                        return FILEOP_COPY_EXTERNAL_PROVIDER;
                }
            case FileOperationService.OPERATION_MOVE:
                switch (providerType) {
                    case PROVIDER_INTRA:
                        return FILEOP_MOVE_INTRA_PROVIDER;
                    case PROVIDER_SYSTEM:
                        return FILEOP_MOVE_SYSTEM_PROVIDER;
                    case PROVIDER_EXTERNAL:
                        return FILEOP_MOVE_EXTERNAL_PROVIDER;
                }
            case FileOperationService.OPERATION_DELETE:
                return FILEOP_DELETE;
            default:
                Log.w(TAG, "Unrecognized operation type when logging a file operation");
                return FILEOP_OTHER;
        }
    }

    /**
     * Count the given src documents and provide a tally of how many come from the same provider as
     * the dst document (if a dst is provided), how many come from system providers, and how many
     * come from external 3rd-party providers.
     */
    private static ProviderCounts countProviders(
            List<DocumentInfo> srcs, @Nullable DocumentInfo dst) {
        ProviderCounts counts = new ProviderCounts();
        for (DocumentInfo doc: srcs) {
            if (dst != null && doc.authority.equals(dst.authority)) {
                counts.intraProvider++;
            } else if (isSystemProvider(doc.authority)){
                counts.systemProvider++;
            } else {
                counts.externalProvider++;
            }
        }
        return counts;
    }

    private static class ProviderCounts {
        int intraProvider;
        int systemProvider;
        int externalProvider;
    }
}
}
+3 −2
Original line number Original line Diff line number Diff line
@@ -50,6 +50,7 @@ import android.text.format.DateUtils;
import android.util.Log;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.MimeTypeMap;


import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DocumentStack;
@@ -214,10 +215,9 @@ class CopyJob extends Job {
        mBatchSize = calculateSize(mSrcs);
        mBatchSize = calculateSize(mSrcs);


        DocumentInfo srcInfo;
        DocumentInfo srcInfo;
        DocumentInfo dstInfo;
        DocumentInfo dstInfo = stack.peek();
        for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
        for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
            srcInfo = mSrcs.get(i);
            srcInfo = mSrcs.get(i);
            dstInfo = stack.peek();


            // Guard unsupported recursive operation.
            // Guard unsupported recursive operation.
            if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
            if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
@@ -232,6 +232,7 @@ class CopyJob extends Job {


            processDocument(srcInfo, null, dstInfo);
            processDocument(srcInfo, null, dstInfo);
        }
        }
        Metrics.logFileOperation(service, operationType, mSrcs, dstInfo);
    }
    }


    @Override
    @Override
+2 −0
Original line number Original line Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.Context;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.Log;
import android.util.Log;


import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DocumentStack;
@@ -90,6 +91,7 @@ final class DeleteJob extends Job {
                onFileFailed(doc);
                onFileFailed(doc);
            }
            }
        }
        }
        Metrics.logFileOperation(service, operationType, mSrcs, null);
    }
    }


    @Override
    @Override
+3 −0
Original line number Original line Diff line number Diff line
@@ -41,6 +41,7 @@ import android.provider.DocumentsContract;
import android.util.Log;
import android.util.Log;


import com.android.documentsui.FilesActivity;
import com.android.documentsui.FilesActivity;
import com.android.documentsui.Metrics;
import com.android.documentsui.OperationDialogFragment;
import com.android.documentsui.OperationDialogFragment;
import com.android.documentsui.R;
import com.android.documentsui.R;
import com.android.documentsui.Shared;
import com.android.documentsui.Shared;
@@ -114,6 +115,7 @@ abstract public class Job implements Runnable {
            // ensure the service is shut down and notifications
            // ensure the service is shut down and notifications
            // shown/closed.
            // shown/closed.
            Log.e(TAG, "Operation failed due to an exception.", e);
            Log.e(TAG, "Operation failed due to an exception.", e);
            Metrics.logFileOperationErrors(service, operationType, failedFiles);
        } finally {
        } finally {
            listener.onFinished(this);
            listener.onFinished(this);
        }
        }
@@ -150,6 +152,7 @@ abstract public class Job implements Runnable {


    final void cancel() {
    final void cancel() {
        mCanceled = true;
        mCanceled = true;
        Metrics.logFileOperationCancelled(service, operationType);
    }
    }


    final boolean isCanceled() {
    final boolean isCanceled() {