blob: 067513f1de67d2cc293f7d9e0e6af03bae7c960f [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.view.textclassifier;
import android.annotation.Nullable;
import android.metrics.LogMaker;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
/**
* A helper for logging calls to generateLinks.
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class GenerateLinksLogger {
private static final String LOG_TAG = "GenerateLinksLogger";
private static final boolean DEBUG_LOG_ENABLED = false;
private static final String ZERO = "0";
private final MetricsLogger mMetricsLogger;
private final Random mRng;
private final int mSampleRate;
/**
* @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) {
mSampleRate = sampleRate;
mRng = new Random(System.nanoTime());
mMetricsLogger = new MetricsLogger();
}
@VisibleForTesting
public GenerateLinksLogger(int sampleRate, MetricsLogger metricsLogger) {
mSampleRate = sampleRate;
mRng = new Random(System.nanoTime());
mMetricsLogger = metricsLogger;
}
/** 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.
*
* Sampling is used to reduce the amount of logging data generated.
**/
private boolean shouldLog() {
if (mSampleRate <= 1) {
return true;
} else {
return mRng.nextInt(mSampleRate) == 0;
}
}
/** Writes a log event for the given stats. */
private void writeStats(String callId, String callingPackageName, @Nullable String entityType,
LinkifyStats stats, CharSequence text, long latencyMs) {
final LogMaker log = new LogMaker(MetricsEvent.TEXT_CLASSIFIER_GENERATE_LINKS)
.setPackageName(callingPackageName)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID, callId)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS, stats.mNumLinks)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH, stats.mNumLinksTextLength)
.addTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH, text.length())
.addTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY, latencyMs);
if (entityType != null) {
log.addTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE, entityType);
}
mMetricsLogger.write(log);
debugLog(log);
}
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
final String callId = Objects.toString(
log.getTaggedData(MetricsEvent.FIELD_LINKIFY_CALL_ID), "");
final String entityType = Objects.toString(
log.getTaggedData(MetricsEvent.FIELD_LINKIFY_ENTITY_TYPE), "ANY_ENTITY");
final int numLinks = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_NUM_LINKS), ZERO));
final int linkLength = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LINK_LENGTH), ZERO));
final int textLength = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_TEXT_LENGTH), ZERO));
final int latencyMs = Integer.parseInt(
Objects.toString(log.getTaggedData(MetricsEvent.FIELD_LINKIFY_LATENCY), ZERO));
Log.d(LOG_TAG,
String.format(Locale.US, "%s:%s %d links (%d/%d chars) %dms %s", callId, entityType,
numLinks, linkLength, textLength, latencyMs, log.getPackageName()));
}
/** Helper class for storing per-entity type statistics. */
private static final class LinkifyStats {
int mNumLinks;
int mNumLinksTextLength;
void countLink(TextLinks.TextLink link) {
mNumLinks += 1;
mNumLinksTextLength += link.getEnd() - link.getStart();
}
}
}