• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.telemetry;
6 
7 import static java.nio.charset.StandardCharsets.UTF_8;
8 
9 import android.os.Build;
10 import android.util.Log;
11 
12 import androidx.annotation.RequiresApi;
13 import androidx.annotation.VisibleForTesting;
14 
15 import org.chromium.net.impl.CronetLogger;
16 
17 import java.nio.ByteBuffer;
18 import java.security.MessageDigest;
19 import java.security.NoSuchAlgorithmException;
20 import java.util.concurrent.atomic.AtomicInteger;
21 
22 /** Logger for logging cronet's telemetry */
23 @RequiresApi(Build.VERSION_CODES.R)
24 public class CronetLoggerImpl extends CronetLogger {
25     private static final String TAG = CronetLoggerImpl.class.getSimpleName();
26 
27     private static final MessageDigest MD5_MESSAGE_DIGEST;
28 
29     static {
30         MessageDigest messageDigest;
31         try {
32             messageDigest = MessageDigest.getInstance("MD5");
33         } catch (NoSuchAlgorithmException e) {
34             Log.d(TAG, "Error while instantiating messageDigest", e);
35             messageDigest = null;
36         }
37         MD5_MESSAGE_DIGEST = messageDigest;
38     }
39 
40     private final AtomicInteger mSamplesRateLimited = new AtomicInteger();
41     private final RateLimiter mRateLimiter;
42 
CronetLoggerImpl(int sampleRatePerSecond)43     public CronetLoggerImpl(int sampleRatePerSecond) {
44         this(new RateLimiter(sampleRatePerSecond));
45     }
46 
47     @VisibleForTesting
CronetLoggerImpl(RateLimiter rateLimiter)48     public CronetLoggerImpl(RateLimiter rateLimiter) {
49         super();
50         this.mRateLimiter = rateLimiter;
51     }
52 
53     @Override
logCronetEngineCreation( int cronetEngineId, CronetEngineBuilderInfo builder, CronetVersion version, CronetSource source)54     public void logCronetEngineCreation(
55             int cronetEngineId,
56             CronetEngineBuilderInfo builder,
57             CronetVersion version,
58             CronetSource source) {
59         if (builder == null || version == null || source == null) {
60             return;
61         }
62 
63         writeCronetEngineCreation(cronetEngineId, builder, version, source);
64     }
65 
66     @Override
logCronetTrafficInfo(int cronetEngineId, CronetTrafficInfo trafficInfo)67     public void logCronetTrafficInfo(int cronetEngineId, CronetTrafficInfo trafficInfo) {
68         if (trafficInfo == null) {
69             return;
70         }
71 
72         if (!mRateLimiter.tryAcquire()) {
73             mSamplesRateLimited.incrementAndGet();
74             return;
75         }
76 
77         writeCronetTrafficReported(cronetEngineId, trafficInfo, mSamplesRateLimited.getAndSet(0));
78     }
79 
80     @SuppressWarnings("CatchingUnchecked")
writeCronetEngineCreation( long cronetEngineId, CronetEngineBuilderInfo builder, CronetVersion version, CronetSource source)81     public void writeCronetEngineCreation(
82             long cronetEngineId,
83             CronetEngineBuilderInfo builder,
84             CronetVersion version,
85             CronetSource source) {
86         try {
87             // Parse experimental Options
88             ExperimentalOptions experimentalOptions =
89                     new ExperimentalOptions(builder.getExperimentalOptions());
90 
91             CronetStatsLog.write(
92                     CronetStatsLog.CRONET_ENGINE_CREATED,
93                     cronetEngineId,
94                     version.getMajorVersion(),
95                     version.getMinorVersion(),
96                     version.getBuildVersion(),
97                     version.getPatchVersion(),
98                     convertToProtoCronetSource(source),
99                     builder.isBrotliEnabled(),
100                     builder.isHttp2Enabled(),
101                     convertToProtoHttpCacheMode(builder.getHttpCacheMode()),
102                     builder.isPublicKeyPinningBypassForLocalTrustAnchorsEnabled(),
103                     builder.isQuicEnabled(),
104                     builder.isNetworkQualityEstimatorEnabled(),
105                     builder.getThreadPriority(),
106                     // QUIC options
107                     experimentalOptions.getConnectionOptionsOption(),
108                     experimentalOptions.getStoreServerConfigsInPropertiesOption().getValue(),
109                     experimentalOptions.getMaxServerConfigsStoredInPropertiesOption(),
110                     experimentalOptions.getIdleConnectionTimeoutSecondsOption(),
111                     experimentalOptions.getGoawaySessionsOnIpChangeOption().getValue(),
112                     experimentalOptions.getCloseSessionsOnIpChangeOption().getValue(),
113                     experimentalOptions.getMigrateSessionsOnNetworkChangeV2Option().getValue(),
114                     experimentalOptions.getMigrateSessionsEarlyV2().getValue(),
115                     experimentalOptions.getDisableBidirectionalStreamsOption().getValue(),
116                     experimentalOptions.getMaxTimeBeforeCryptoHandshakeSecondsOption(),
117                     experimentalOptions.getMaxIdleTimeBeforeCryptoHandshakeSecondsOption(),
118                     experimentalOptions.getEnableSocketRecvOptimizationOption().getValue(),
119                     // AsyncDNS
120                     experimentalOptions.getAsyncDnsEnableOption().getValue(),
121                     // StaleDNS
122                     experimentalOptions.getStaleDnsEnableOption().getValue(),
123                     experimentalOptions.getStaleDnsDelayMillisOption(),
124                     experimentalOptions.getStaleDnsMaxExpiredTimeMillisOption(),
125                     experimentalOptions.getStaleDnsMaxStaleUsesOption(),
126                     experimentalOptions.getStaleDnsAllowOtherNetworkOption().getValue(),
127                     experimentalOptions.getStaleDnsPersistToDiskOption().getValue(),
128                     experimentalOptions.getStaleDnsPersistDelayMillisOption(),
129                     experimentalOptions.getStaleDnsUseStaleOnNameNotResolvedOption().getValue(),
130                     experimentalOptions.getDisableIpv6OnWifiOption().getValue(),
131                     /* cronet_initialization_ref= */ -1);
132         } catch (Exception e) { // catching all exceptions since we don't want to crash the client
133             Log.d(
134                     TAG,
135                     String.format(
136                             "Failed to log CronetEngine:%s creation: %s",
137                             cronetEngineId, e.getMessage()));
138         }
139     }
140 
141     @SuppressWarnings("CatchingUnchecked")
142     @VisibleForTesting
writeCronetTrafficReported( long cronetEngineId, CronetTrafficInfo trafficInfo, int samplesRateLimitedCount)143     public void writeCronetTrafficReported(
144             long cronetEngineId, CronetTrafficInfo trafficInfo, int samplesRateLimitedCount) {
145         try {
146             CronetStatsLog.write(
147                     CronetStatsLog.CRONET_TRAFFIC_REPORTED,
148                     cronetEngineId,
149                     SizeBuckets.calcRequestHeadersSizeBucket(
150                             trafficInfo.getRequestHeaderSizeInBytes()),
151                     SizeBuckets.calcRequestBodySizeBucket(trafficInfo.getRequestBodySizeInBytes()),
152                     SizeBuckets.calcResponseHeadersSizeBucket(
153                             trafficInfo.getResponseHeaderSizeInBytes()),
154                     SizeBuckets.calcResponseBodySizeBucket(
155                             trafficInfo.getResponseBodySizeInBytes()),
156                     trafficInfo.getResponseStatusCode(),
157                     hashNegotiatedProtocol(trafficInfo.getNegotiatedProtocol()),
158                     (int) trafficInfo.getHeadersLatency().toMillis(),
159                     (int) trafficInfo.getTotalLatency().toMillis(),
160                     trafficInfo.wasConnectionMigrationAttempted(),
161                     trafficInfo.didConnectionMigrationSucceed(),
162                     samplesRateLimitedCount,
163                     /* terminal_state= */ CronetStatsLog
164                             .CRONET_TRAFFIC_REPORTED__TERMINAL_STATE__STATE_UNKNOWN,
165                     /* user_callback_exception_count= */ -1,
166                     /* total_idle_time_millis= */ -1,
167                     /* total_user_executor_execute_latency_millis= */ -1,
168                     /* read_count= */ -1,
169                     /* on_upload_read_count= */ -1,
170                     /* is_bidi_stream= */ CronetStatsLog
171                             .CRONET_TRAFFIC_REPORTED__IS_BIDI_STREAM__OPTIONAL_BOOLEAN_UNSET);
172         } catch (Exception e) {
173             // using addAndGet because another thread might have modified samplesRateLimited's value
174             mSamplesRateLimited.addAndGet(samplesRateLimitedCount);
175             Log.d(
176                     TAG,
177                     String.format(
178                             "Failed to log cronet traffic sample for CronetEngine %s: %s",
179                             cronetEngineId, e.getMessage()));
180         }
181     }
182 
convertToProtoCronetSource(CronetSource source)183     private static int convertToProtoCronetSource(CronetSource source) {
184         switch (source) {
185             case CRONET_SOURCE_STATICALLY_LINKED:
186                 return CronetStatsLog
187                         .CRONET_ENGINE_CREATED__SOURCE__CRONET_SOURCE_STATICALLY_LINKED;
188             case CRONET_SOURCE_PLAY_SERVICES:
189                 return CronetStatsLog.CRONET_ENGINE_CREATED__SOURCE__CRONET_SOURCE_GMSCORE_DYNAMITE;
190             case CRONET_SOURCE_FALLBACK:
191                 return CronetStatsLog.CRONET_ENGINE_CREATED__SOURCE__CRONET_SOURCE_FALLBACK;
192             case CRONET_SOURCE_UNSPECIFIED:
193                 return CronetStatsLog.CRONET_ENGINE_CREATED__SOURCE__CRONET_SOURCE_UNSPECIFIED;
194             default:
195                 return CronetStatsLog.CRONET_ENGINE_CREATED__SOURCE__CRONET_SOURCE_UNSPECIFIED;
196         }
197     }
198 
convertToProtoHttpCacheMode(int httpCacheMode)199     private static int convertToProtoHttpCacheMode(int httpCacheMode) {
200         switch (httpCacheMode) {
201             case 0:
202                 return CronetStatsLog.CRONET_ENGINE_CREATED__HTTP_CACHE_MODE__HTTP_CACHE_DISABLED;
203             case 1:
204                 return CronetStatsLog.CRONET_ENGINE_CREATED__HTTP_CACHE_MODE__HTTP_CACHE_DISK;
205             case 2:
206                 return CronetStatsLog
207                         .CRONET_ENGINE_CREATED__HTTP_CACHE_MODE__HTTP_CACHE_DISK_NO_HTTP;
208             case 3:
209                 return CronetStatsLog.CRONET_ENGINE_CREATED__HTTP_CACHE_MODE__HTTP_CACHE_IN_MEMORY;
210             default:
211                 throw new IllegalArgumentException("Expected httpCacheMode to range from 0 to 3");
212         }
213     }
214 
hashNegotiatedProtocol(String protocol)215     private static long hashNegotiatedProtocol(String protocol) {
216         if (MD5_MESSAGE_DIGEST == null || protocol == null || protocol.isEmpty()) {
217             return 0L;
218         }
219 
220         byte[] md = MD5_MESSAGE_DIGEST.digest(protocol.getBytes(UTF_8));
221         return ByteBuffer.wrap(md).getLong();
222     }
223 }
224