• 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 android.view.textclassifier.TextClassificationContext;
20 import android.view.textclassifier.TextClassificationSessionId;
21 import android.view.textclassifier.TextClassifier;
22 import android.view.textclassifier.TextLinks;
23 import androidx.collection.ArrayMap;
24 import com.android.textclassifier.common.base.TcLog;
25 import com.android.textclassifier.common.logging.ResultIdUtils.ModelInfo;
26 import com.android.textclassifier.common.logging.TextClassifierEvent;
27 import com.google.common.base.Optional;
28 import com.google.common.base.Preconditions;
29 import com.google.common.collect.ImmutableList;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Random;
33 import javax.annotation.Nullable;
34 
35 /** A helper for logging calls to generateLinks. */
36 public final class GenerateLinksLogger {
37 
38   private static final String LOG_TAG = "GenerateLinksLogger";
39 
40   private final Random random;
41   private final int sampleRate;
42 
43   /**
44    * @param sampleRate the rate at which log events are written. (e.g. 100 means there is a 0.01
45    *     chance that a call to logGenerateLinks results in an event being written). To write all
46    *     events, pass 1.
47    */
GenerateLinksLogger(int sampleRate)48   public GenerateLinksLogger(int sampleRate) {
49     this.sampleRate = sampleRate;
50     random = new Random();
51   }
52 
53   /** Logs statistics about a call to generateLinks. */
logGenerateLinks( @ullable TextClassificationSessionId sessionId, @Nullable TextClassificationContext textClassificationContext, CharSequence text, TextLinks links, String callingPackageName, long latencyMs, Optional<ModelInfo> annotatorModel, Optional<ModelInfo> langIdModel)54   public void logGenerateLinks(
55       @Nullable TextClassificationSessionId sessionId,
56       @Nullable TextClassificationContext textClassificationContext,
57       CharSequence text,
58       TextLinks links,
59       String callingPackageName,
60       long latencyMs,
61       Optional<ModelInfo> annotatorModel,
62       Optional<ModelInfo> langIdModel) {
63     Preconditions.checkNotNull(text);
64     Preconditions.checkNotNull(links);
65     Preconditions.checkNotNull(callingPackageName);
66     if (!shouldLog()) {
67       return;
68     }
69 
70     // Always populate the total stats, and per-entity stats for each entity type detected.
71     final LinkifyStats totalStats = new LinkifyStats();
72     final Map<String, LinkifyStats> perEntityTypeStats = new ArrayMap<>();
73     for (TextLinks.TextLink link : links.getLinks()) {
74       if (link.getEntityCount() == 0) {
75         continue;
76       }
77       final String entityType = link.getEntity(0);
78       if (entityType == null
79           || TextClassifier.TYPE_OTHER.equals(entityType)
80           || TextClassifier.TYPE_UNKNOWN.equals(entityType)) {
81         continue;
82       }
83       totalStats.countLink(link);
84       perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link);
85     }
86     int widgetType = TextClassifierStatsLog.TEXT_SELECTION_EVENT__WIDGET_TYPE__WIDGET_TYPE_UNKNOWN;
87     if (textClassificationContext != null) {
88       widgetType = WidgetTypeConverter.toLoggingValue(textClassificationContext.getWidgetType());
89     }
90 
91     final String sessionIdStr = sessionId == null ? null : sessionId.getValue();
92     writeStats(
93         sessionIdStr,
94         callingPackageName,
95         null,
96         totalStats,
97         text,
98         widgetType,
99         latencyMs,
100         annotatorModel,
101         langIdModel);
102     // Sort the entity types to ensure the logging order is deterministic.
103     ImmutableList<String> sortedEntityTypes =
104         ImmutableList.sortedCopyOf(perEntityTypeStats.keySet());
105     for (String entityType : sortedEntityTypes) {
106       writeStats(
107           sessionIdStr,
108           callingPackageName,
109           entityType,
110           perEntityTypeStats.get(entityType),
111           text,
112           widgetType,
113           latencyMs,
114           annotatorModel,
115           langIdModel);
116     }
117   }
118 
119   /**
120    * Returns whether this particular event should be logged.
121    *
122    * <p>Sampling is used to reduce the amount of logging data generated.
123    */
shouldLog()124   private boolean shouldLog() {
125     if (sampleRate <= 1) {
126       return true;
127     } else {
128       return random.nextInt(sampleRate) == 0;
129     }
130   }
131 
132   /** Writes a log event for the given stats. */
writeStats( @ullable String sessionId, String callingPackageName, @Nullable String entityType, LinkifyStats stats, CharSequence text, int widgetType, long latencyMs, Optional<ModelInfo> annotatorModel, Optional<ModelInfo> langIdModel)133   private static void writeStats(
134       @Nullable String sessionId,
135       String callingPackageName,
136       @Nullable String entityType,
137       LinkifyStats stats,
138       CharSequence text,
139       int widgetType,
140       long latencyMs,
141       Optional<ModelInfo> annotatorModel,
142       Optional<ModelInfo> langIdModel) {
143     String annotatorModelName = annotatorModel.transform(ModelInfo::toModelName).or("");
144     String langIdModelName = langIdModel.transform(ModelInfo::toModelName).or("");
145     TextClassifierStatsLog.write(
146         TextClassifierStatsLog.TEXT_LINKIFY_EVENT,
147         sessionId,
148         TextClassifierEvent.TYPE_LINKS_GENERATED,
149         annotatorModelName,
150         widgetType,
151         /* eventIndex */ 0,
152         entityType,
153         stats.numLinks,
154         stats.numLinksTextLength,
155         text.length(),
156         latencyMs,
157         callingPackageName,
158         langIdModelName);
159 
160     if (TcLog.ENABLE_FULL_LOGGING) {
161       TcLog.v(
162           LOG_TAG,
163           String.format(
164               Locale.US,
165               "%s:%s %d links (%d/%d chars) %dms %s annotator=%s langid=%s",
166               sessionId,
167               entityType,
168               stats.numLinks,
169               stats.numLinksTextLength,
170               text.length(),
171               latencyMs,
172               callingPackageName,
173               annotatorModelName,
174               langIdModelName));
175     }
176   }
177 
178   /** Helper class for storing per-entity type statistics. */
179   private static final class LinkifyStats {
180     int numLinks;
181     int numLinksTextLength;
182 
countLink(TextLinks.TextLink link)183     void countLink(TextLinks.TextLink link) {
184       numLinks += 1;
185       numLinksTextLength += link.getEnd() - link.getStart();
186     }
187   }
188 }
189