• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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