1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.textclassifier.common.statsd; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.os.SystemClock; 22 import android.view.textclassifier.TextClassificationSessionId; 23 import androidx.annotation.IntDef; 24 import androidx.annotation.Nullable; 25 import com.android.textclassifier.common.base.TcLog; 26 import com.google.common.base.Preconditions; 27 import com.google.common.base.Supplier; 28 import java.lang.annotation.Retention; 29 import java.util.Locale; 30 import java.util.Random; 31 import java.util.concurrent.Executor; 32 33 /** Logs the TextClassifier API usages. */ 34 public final class TextClassifierApiUsageLogger { 35 private static final String TAG = "ApiUsageLogger"; 36 37 public static final int API_TYPE_SUGGEST_SELECTION = 38 TextClassifierStatsLog.TEXT_CLASSIFIER_API_USAGE_REPORTED__API_TYPE__SUGGEST_SELECTION; 39 public static final int API_TYPE_CLASSIFY_TEXT = 40 TextClassifierStatsLog.TEXT_CLASSIFIER_API_USAGE_REPORTED__API_TYPE__CLASSIFY_TEXT; 41 public static final int API_TYPE_GENERATE_LINKS = 42 TextClassifierStatsLog.TEXT_CLASSIFIER_API_USAGE_REPORTED__API_TYPE__GENERATE_LINKS; 43 public static final int API_TYPE_SUGGEST_CONVERSATION_ACTIONS = 44 TextClassifierStatsLog 45 .TEXT_CLASSIFIER_API_USAGE_REPORTED__API_TYPE__SUGGEST_CONVERSATION_ACTIONS; 46 public static final int API_TYPE_DETECT_LANGUAGES = 47 TextClassifierStatsLog.TEXT_CLASSIFIER_API_USAGE_REPORTED__API_TYPE__DETECT_LANGUAGES; 48 49 /** The type of the API. */ 50 @Retention(SOURCE) 51 @IntDef({ 52 API_TYPE_SUGGEST_SELECTION, 53 API_TYPE_CLASSIFY_TEXT, 54 API_TYPE_GENERATE_LINKS, 55 API_TYPE_SUGGEST_CONVERSATION_ACTIONS, 56 API_TYPE_DETECT_LANGUAGES 57 }) 58 public @interface ApiType {} 59 60 private final Executor executor; 61 62 private final Supplier<Integer> sampleRateSupplier; 63 64 private final Random random; 65 66 /** 67 * @param sampleRateSupplier The rate at which log events are written. (e.g. 100 means there is a 68 * 0.01 chance that a call to logGenerateLinks results in an event being written). To write 69 * all events, pass 1. To disable logging, pass any number < 1. Sampling is used to reduce the 70 * amount of logging data generated. 71 * @param executor that is used to execute the logging work. 72 */ TextClassifierApiUsageLogger(Supplier<Integer> sampleRateSupplier, Executor executor)73 public TextClassifierApiUsageLogger(Supplier<Integer> sampleRateSupplier, Executor executor) { 74 this.executor = Preconditions.checkNotNull(executor); 75 this.sampleRateSupplier = sampleRateSupplier; 76 this.random = new Random(); 77 } 78 createSession( @piType int apiType, @Nullable TextClassificationSessionId sessionId)79 public Session createSession( 80 @ApiType int apiType, @Nullable TextClassificationSessionId sessionId) { 81 return new Session(apiType, sessionId); 82 } 83 84 /** A session to log an API invocation. Creates a new session for each API call. */ 85 public final class Session { 86 @ApiType private final int apiType; 87 @Nullable private final TextClassificationSessionId sessionId; 88 private final long beginElapsedRealTime; 89 Session(@piType int apiType, @Nullable TextClassificationSessionId sessionId)90 private Session(@ApiType int apiType, @Nullable TextClassificationSessionId sessionId) { 91 this.apiType = apiType; 92 this.sessionId = sessionId; 93 beginElapsedRealTime = SystemClock.elapsedRealtime(); 94 } 95 reportSuccess()96 public void reportSuccess() { 97 reportInternal(/* success= */ true); 98 } 99 reportFailure()100 public void reportFailure() { 101 reportInternal(/* success= */ false); 102 } 103 reportInternal(boolean success)104 private void reportInternal(boolean success) { 105 if (!shouldLog()) { 106 return; 107 } 108 final long latencyInMillis = SystemClock.elapsedRealtime() - beginElapsedRealTime; 109 if (TcLog.ENABLE_FULL_LOGGING) { 110 TcLog.v( 111 TAG, 112 String.format( 113 Locale.ENGLISH, 114 "TextClassifierApiUsageLogger: apiType=%d success=%b latencyInMillis=%d", 115 apiType, 116 success, 117 latencyInMillis)); 118 } 119 executor.execute( 120 () -> 121 TextClassifierStatsLog.write( 122 TextClassifierStatsLog.TEXT_CLASSIFIER_API_USAGE_REPORTED, 123 apiType, 124 success 125 ? TextClassifierStatsLog 126 .TEXT_CLASSIFIER_API_USAGE_REPORTED__RESULT_TYPE__SUCCESS 127 : TextClassifierStatsLog 128 .TEXT_CLASSIFIER_API_USAGE_REPORTED__RESULT_TYPE__FAIL, 129 latencyInMillis, 130 sessionId == null ? "" : sessionId.getValue())); 131 } 132 } 133 134 /** Returns whether this particular event should be logged. */ shouldLog()135 private boolean shouldLog() { 136 if (sampleRateSupplier.get() < 1) { 137 return false; 138 } else { 139 return random.nextInt(sampleRateSupplier.get()) == 0; 140 } 141 } 142 } 143