1 /* 2 * Copyright (C) 2017 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 android.view.textclassifier; 18 19 import android.annotation.Nullable; 20 import android.metrics.LogMaker; 21 import android.util.ArrayMap; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.internal.logging.MetricsLogger; 25 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 26 import com.android.internal.util.Preconditions; 27 28 import java.util.Locale; 29 import java.util.Map; 30 import java.util.Objects; 31 import java.util.Random; 32 import java.util.UUID; 33 34 /** 35 * A helper for logging calls to generateLinks. 36 * @hide 37 */ 38 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 39 public final class GenerateLinksLogger { 40 41 private static final String LOG_TAG = "GenerateLinksLogger"; 42 private static final String ZERO = "0"; 43 44 private final MetricsLogger mMetricsLogger; 45 private final Random mRng; 46 private final int mSampleRate; 47 48 /** 49 * @param sampleRate the rate at which log events are written. (e.g. 100 means there is a 0.01 50 * chance that a call to logGenerateLinks results in an event being written). 51 * To write all events, pass 1. 52 */ GenerateLinksLogger(int sampleRate)53 public GenerateLinksLogger(int sampleRate) { 54 mSampleRate = sampleRate; 55 mRng = new Random(System.nanoTime()); 56 mMetricsLogger = new MetricsLogger(); 57 } 58 59 @VisibleForTesting GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger)60 public GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger) { 61 mSampleRate = sampleRate; 62 mRng = new Random(System.nanoTime()); 63 mMetricsLogger = metricsLogger; 64 } 65 66 /** Logs statistics about a call to generateLinks. */ logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName, long latencyMs)67 public void logGenerateLinks(CharSequence text, TextLinks links, String callingPackageName, 68 long latencyMs) { 69 Preconditions.checkNotNull(text); 70 Preconditions.checkNotNull(links); 71 Preconditions.checkNotNull(callingPackageName); 72 if (!shouldLog()) { 73 return; 74 } 75 76 // Always populate the total stats, and per-entity stats for each entity type detected. 77 final LinkifyStats totalStats = new LinkifyStats(); 78 final Map<String, LinkifyStats> perEntityTypeStats = new ArrayMap<>(); 79 for (TextLinks.TextLink link : links.getLinks()) { 80 if (link.getEntityCount() == 0) continue; 81 final String entityType = link.getEntity(0); 82 if (entityType == null 83 || TextClassifier.TYPE_OTHER.equals(entityType) 84 || TextClassifier.TYPE_UNKNOWN.equals(entityType)) { 85 continue; 86 } 87 totalStats.countLink(link); 88 perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link); 89 } 90 91 final String callId = UUID.randomUUID().toString(); 92 writeStats(callId, callingPackageName, null, totalStats, text, latencyMs); 93 for (Map.Entry<String, LinkifyStats> entry : perEntityTypeStats.entrySet()) { 94 writeStats(callId, callingPackageName, entry.getKey(), entry.getValue(), text, 95 latencyMs); 96 } 97 } 98 99 /** 100 * Returns whether this particular event should be logged. 101 * 102 * Sampling is used to reduce the amount of logging data generated. 103 **/ shouldLog()104 private boolean shouldLog() { 105 if (mSampleRate <= 1) { 106 return true; 107 } else { 108 return mRng.nextInt(mSampleRate) == 0; 109 } 110 } 111 112 /** Writes a log event for the given stats. */ writeStats(String callId, String callingPackageName, @Nullable String entityType, LinkifyStats stats, CharSequence text, long latencyMs)113 private void writeStats(String callId, String callingPackageName, @Nullable String entityType, 114 LinkifyStats stats, CharSequence text, long latencyMs) { 115 final LogMaker log = new LogMaker(MetricsEvent.TEXT_CLASSIFIER_GENERATE_LINKS) 116 .setPackageName(callingPackageName) 117 .addTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID, callId) 118 .addTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS, stats.mNumLinks) 119 .addTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH, stats.mNumLinksTextLength) 120 .addTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH, text.length()) 121 .addTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY, latencyMs); 122 if (entityType != null) { 123 log.addTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE, entityType); 124 } 125 mMetricsLogger.write(log); 126 debugLog(log); 127 } 128 debugLog(LogMaker log)129 private static void debugLog(LogMaker log) { 130 if (!Log.ENABLE_FULL_LOGGING) { 131 return; 132 } 133 final String callId = Objects.toString( 134 log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), ""); 135 final String entityType = Objects.toString( 136 log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "ANY_ENTITY"); 137 final int numLinks = Integer.parseInt( 138 Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS), ZERO)); 139 final int linkLength = Integer.parseInt( 140 Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH), ZERO)); 141 final int textLength = Integer.parseInt( 142 Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH), ZERO)); 143 final int latencyMs = Integer.parseInt( 144 Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO)); 145 146 Log.v(LOG_TAG, 147 String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType, 148 numLinks, linkLength, textLength, latencyMs, log.getPackageName())); 149 } 150 151 /** Helper class for storing per-entity type statistics. */ 152 private static final class LinkifyStats { 153 int mNumLinks; 154 int mNumLinksTextLength; 155 countLink(TextLinks.TextLink link)156 void countLink(TextLinks.TextLink link) { 157 mNumLinks += 1; 158 mNumLinksTextLength += link.getEnd() - link.getStart(); 159 } 160 } 161 } 162