| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.textclassifier.logging; |
| |
| import android.view.textclassifier.TextClassifier; |
| import android.view.textclassifier.TextClassifierEvent; |
| import android.view.textclassifier.TextLinks; |
| import androidx.collection.ArrayMap; |
| import com.android.textclassifier.TcLog; |
| import com.android.textclassifier.TextClassifierStatsLog; |
| import com.google.common.base.Preconditions; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.UUID; |
| import javax.annotation.Nullable; |
| |
| /** A helper for logging calls to generateLinks. */ |
| public final class GenerateLinksLogger { |
| |
| private static final String LOG_TAG = "GenerateLinksLogger"; |
| |
| private final Random rng; |
| private final int sampleRate; |
| |
| /** |
| * @param sampleRate the rate at which log events are written. (e.g. 100 means there is a 0.01 |
| * chance that a call to logGenerateLinks results in an event being written). To write all |
| * events, pass 1. |
| */ |
| public GenerateLinksLogger(int sampleRate) { |
| this.sampleRate = sampleRate; |
| rng = new Random(); |
| } |
| |
| /** Logs statistics about a call to generateLinks. */ |
| public void logGenerateLinks( |
| CharSequence text, TextLinks links, String callingPackageName, long latencyMs) { |
| Preconditions.checkNotNull(text); |
| Preconditions.checkNotNull(links); |
| Preconditions.checkNotNull(callingPackageName); |
| if (!shouldLog()) { |
| return; |
| } |
| |
| // Always populate the total stats, and per-entity stats for each entity type detected. |
| final LinkifyStats totalStats = new LinkifyStats(); |
| final Map<String, LinkifyStats> perEntityTypeStats = new ArrayMap<>(); |
| for (TextLinks.TextLink link : links.getLinks()) { |
| if (link.getEntityCount() == 0) { |
| continue; |
| } |
| final String entityType = link.getEntity(0); |
| if (entityType == null |
| || TextClassifier.TYPE_OTHER.equals(entityType) |
| || TextClassifier.TYPE_UNKNOWN.equals(entityType)) { |
| continue; |
| } |
| totalStats.countLink(link); |
| perEntityTypeStats.computeIfAbsent(entityType, k -> new LinkifyStats()).countLink(link); |
| } |
| |
| final String callId = UUID.randomUUID().toString(); |
| writeStats(callId, callingPackageName, null, totalStats, text, latencyMs); |
| for (Map.Entry<String, LinkifyStats> entry : perEntityTypeStats.entrySet()) { |
| writeStats(callId, callingPackageName, entry.getKey(), entry.getValue(), text, latencyMs); |
| } |
| } |
| |
| /** |
| * Returns whether this particular event should be logged. |
| * |
| * <p>Sampling is used to reduce the amount of logging data generated. |
| */ |
| private boolean shouldLog() { |
| if (sampleRate <= 1) { |
| return true; |
| } else { |
| return rng.nextInt(sampleRate) == 0; |
| } |
| } |
| |
| /** Writes a log event for the given stats. */ |
| private static void writeStats( |
| String callId, |
| String callingPackageName, |
| @Nullable String entityType, |
| LinkifyStats stats, |
| CharSequence text, |
| long latencyMs) { |
| TextClassifierStatsLog.write( |
| TextClassifierStatsLog.TEXT_LINKIFY_EVENT, |
| callId, |
| TextClassifierEvent.TYPE_LINKS_GENERATED, |
| /*modelName=*/ null, |
| TextClassifierEventLogger.WidgetType.WIDGET_TYPE_UNKNOWN, |
| /*eventIndex=*/ 0, |
| entityType, |
| stats.numLinks, |
| stats.numLinksTextLength, |
| text.length(), |
| latencyMs, |
| callingPackageName); |
| if (TcLog.ENABLE_FULL_LOGGING) { |
| TcLog.v( |
| LOG_TAG, |
| String.format( |
| Locale.US, |
| "%s:%s %d links (%d/%d chars) %dms %s", |
| callId, |
| entityType, |
| stats.numLinks, |
| stats.numLinksTextLength, |
| text.length(), |
| latencyMs, |
| callingPackageName)); |
| } |
| } |
| |
| /** Helper class for storing per-entity type statistics. */ |
| private static final class LinkifyStats { |
| int numLinks; |
| int numLinksTextLength; |
| |
| void countLink(TextLinks.TextLink link) { |
| numLinks += 1; |
| numLinksTextLength += link.getEnd() - link.getStart(); |
| } |
| } |
| } |