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