Skip to content

Commit b033c68

Browse files
jemangwaDadanielZ
authored andcommitted
HADOOP-16612. Track Azure Blob File System client-perceived latency
Contributed by Jeetesh Mangwani. This add the ability to track the end-to-end performance of ADLS Gen 2 REST APIs by measuring latency in the Hadoop ABFS driver. The latency information is sent back to the ADLS Gen 2 REST API endpoints in the subsequent requests.
1 parent 9fbfe6c commit b033c68

File tree

18 files changed

+1561
-340
lines changed

18 files changed

+1561
-340
lines changed

hadoop-tools/hadoop-azure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,11 @@
278278
<artifactId>bcpkix-jdk15on</artifactId>
279279
<scope>test</scope>
280280
</dependency>
281+
<dependency>
282+
<groupId>org.assertj</groupId>
283+
<artifactId>assertj-core</artifactId>
284+
<scope>test</scope>
285+
</dependency>
281286
</dependencies>
282287

283288
<profiles>

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ public class AbfsConfiguration{
178178
DefaultValue = DEFAULT_USE_UPN)
179179
private boolean useUpn;
180180

181+
@BooleanConfigurationValidatorAnnotation(ConfigurationKey = FS_AZURE_ABFS_LATENCY_TRACK,
182+
DefaultValue = DEFAULT_ABFS_LATENCY_TRACK)
183+
private boolean trackLatency;
184+
181185
private Map<String, String> storageAccountKeys;
182186

183187
public AbfsConfiguration(final Configuration rawConfig, String accountName)
@@ -471,6 +475,15 @@ public boolean isUpnUsed() {
471475
return this.useUpn;
472476
}
473477

478+
/**
479+
* Whether {@code AbfsClient} should track and send latency info back to storage servers.
480+
*
481+
* @return a boolean indicating whether latency should be tracked.
482+
*/
483+
public boolean shouldTrackLatency() {
484+
return this.trackLatency;
485+
}
486+
474487
public AccessTokenProvider getTokenProvider() throws TokenAccessProviderException {
475488
AuthType authType = getEnum(FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME, AuthType.SharedKey);
476489
if (authType == AuthType.OAuth) {

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java

Lines changed: 514 additions & 330 deletions
Large diffs are not rendered by default.

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/ConfigurationKeys.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ public final class ConfigurationKeys {
114114
public static final String FS_AZURE_ACCOUNT_OAUTH_REFRESH_TOKEN = "fs.azure.account.oauth2.refresh.token";
115115
/** Key for oauth AAD refresh token endpoint: {@value}. */
116116
public static final String FS_AZURE_ACCOUNT_OAUTH_REFRESH_TOKEN_ENDPOINT = "fs.azure.account.oauth2.refresh.token.endpoint";
117+
/** Key for enabling the tracking of ABFS API latency and sending the latency numbers to the ABFS API service */
118+
public static final String FS_AZURE_ABFS_LATENCY_TRACK = "fs.azure.abfs.latency.track";
117119

118120
public static String accountProperty(String property, String account) {
119121
return property + "." + account;

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/FileSystemConfigurations.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public final class FileSystemConfigurations {
6767
public static final boolean DEFAULT_ENABLE_HTTPS = true;
6868

6969
public static final boolean DEFAULT_USE_UPN = false;
70+
public static final boolean DEFAULT_ABFS_LATENCY_TRACK = false;
7071

7172
private FileSystemConfigurations() {}
7273
}

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/HttpHeaderConfigurations.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public final class HttpHeaderConfigurations {
5858
public static final String X_MS_PERMISSIONS = "x-ms-permissions";
5959
public static final String X_MS_UMASK = "x-ms-umask";
6060
public static final String X_MS_NAMESPACE_ENABLED = "x-ms-namespace-enabled";
61+
public static final String X_MS_ABFS_CLIENT_LATENCY = "x-ms-abfs-client-latency";
6162

6263
private HttpHeaderConfigurations() {}
6364
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
* <p>
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* <p>
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.fs.azurebfs.contracts.services;
20+
21+
import org.apache.hadoop.classification.InterfaceStability;
22+
23+
/**
24+
* The AbfsPerfLoggable contract.
25+
*/
26+
@InterfaceStability.Evolving
27+
public interface AbfsPerfLoggable {
28+
/**
29+
* Gets the string to log to the Abfs Logging API.
30+
*
31+
* @return the string that will be logged.
32+
*/
33+
String getLogString();
34+
}

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsClient.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,16 @@ public class AbfsClient implements Closeable {
6060
private final String filesystem;
6161
private final AbfsConfiguration abfsConfiguration;
6262
private final String userAgent;
63+
private final AbfsPerfTracker abfsPerfTracker;
6364

6465
private final AccessTokenProvider tokenProvider;
6566

6667

6768
public AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCredentials,
6869
final AbfsConfiguration abfsConfiguration,
6970
final ExponentialRetryPolicy exponentialRetryPolicy,
70-
final AccessTokenProvider tokenProvider) {
71+
final AccessTokenProvider tokenProvider,
72+
final AbfsPerfTracker abfsPerfTracker) {
7173
this.baseUrl = baseUrl;
7274
this.sharedKeyCredentials = sharedKeyCredentials;
7375
String baseUrlString = baseUrl.toString();
@@ -88,6 +90,7 @@ public AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCredent
8890

8991
this.userAgent = initializeUserAgent(abfsConfiguration, sslProviderName);
9092
this.tokenProvider = tokenProvider;
93+
this.abfsPerfTracker = abfsPerfTracker;
9194
}
9295

9396
@Override
@@ -101,6 +104,10 @@ public String getFileSystem() {
101104
return filesystem;
102105
}
103106

107+
protected AbfsPerfTracker getAbfsPerfTracker() {
108+
return abfsPerfTracker;
109+
}
110+
104111
ExponentialRetryPolicy getRetryPolicy() {
105112
return retryPolicy;
106113
}

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsHttpOperation.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.io.IOException;
2222
import java.io.InputStream;
2323
import java.io.OutputStream;
24+
import java.io.UnsupportedEncodingException;
2425
import java.net.HttpURLConnection;
2526
import java.net.URL;
27+
import java.net.URLEncoder;
2628
import java.util.List;
2729
import java.util.UUID;
2830

@@ -40,12 +42,13 @@
4042

4143
import org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants;
4244
import org.apache.hadoop.fs.azurebfs.constants.HttpHeaderConfigurations;
45+
import org.apache.hadoop.fs.azurebfs.contracts.services.AbfsPerfLoggable;
4346
import org.apache.hadoop.fs.azurebfs.contracts.services.ListResultSchema;
4447

4548
/**
4649
* Represents an HTTP operation.
4750
*/
48-
public class AbfsHttpOperation {
51+
public class AbfsHttpOperation implements AbfsPerfLoggable {
4952
private static final Logger LOG = LoggerFactory.getLogger(AbfsHttpOperation.class);
5053

5154
private static final int CONNECT_TIMEOUT = 30 * 1000;
@@ -161,6 +164,47 @@ public String toString() {
161164
return sb.toString();
162165
}
163166

167+
// Returns a trace message for the ABFS API logging service to consume
168+
public String getLogString() {
169+
String urlStr = null;
170+
171+
try {
172+
urlStr = URLEncoder.encode(url.toString(), "UTF-8");
173+
} catch(UnsupportedEncodingException e) {
174+
urlStr = "https%3A%2F%2Ffailed%2Fto%2Fencode%2Furl";
175+
}
176+
177+
final StringBuilder sb = new StringBuilder();
178+
sb.append("s=")
179+
.append(statusCode)
180+
.append(" e=")
181+
.append(storageErrorCode)
182+
.append(" ci=")
183+
.append(clientRequestId)
184+
.append(" ri=")
185+
.append(requestId);
186+
187+
if (isTraceEnabled) {
188+
sb.append(" ct=")
189+
.append(connectionTimeMs)
190+
.append(" st=")
191+
.append(sendRequestTimeMs)
192+
.append(" rt=")
193+
.append(recvResponseTimeMs);
194+
}
195+
196+
sb.append(" bs=")
197+
.append(bytesSent)
198+
.append(" br=")
199+
.append(bytesReceived)
200+
.append(" m=")
201+
.append(method)
202+
.append(" u=")
203+
.append(urlStr);
204+
205+
return sb.toString();
206+
}
207+
164208
/**
165209
* Initializes a new HTTP request and opens the connection.
166210
*

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsInputStream.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,10 @@ int readRemote(long position, byte[] b, int offset, int length) throws IOExcepti
226226
throw new IllegalArgumentException("requested read length is more than will fit after requested offset in buffer");
227227
}
228228
final AbfsRestOperation op;
229-
try {
229+
AbfsPerfTracker tracker = client.getAbfsPerfTracker();
230+
try (AbfsPerfInfo perfInfo = new AbfsPerfInfo(tracker, "readRemote", "read")) {
230231
op = client.read(path, position, b, offset, length, tolerateOobAppends ? "*" : eTag);
232+
perfInfo.registerResult(op.getResult()).registerSuccess(true);
231233
} catch (AzureBlobFileSystemException ex) {
232234
if (ex instanceof AbfsRestOperationException) {
233235
AbfsRestOperationException ere = (AbfsRestOperationException) ex;

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsOutputStream.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,16 @@ private synchronized void writeCurrentBufferToService() throws IOException {
289289
final Future<Void> job = completionService.submit(new Callable<Void>() {
290290
@Override
291291
public Void call() throws Exception {
292-
client.append(path, offset, bytes, 0,
293-
bytesLength);
294-
byteBufferPool.putBuffer(ByteBuffer.wrap(bytes));
295-
return null;
292+
AbfsPerfTracker tracker = client.getAbfsPerfTracker();
293+
try (AbfsPerfInfo perfInfo = new AbfsPerfInfo(tracker,
294+
"writeCurrentBufferToService", "append")) {
295+
AbfsRestOperation op = client.append(path, offset, bytes, 0,
296+
bytesLength);
297+
perfInfo.registerResult(op.getResult());
298+
byteBufferPool.putBuffer(ByteBuffer.wrap(bytes));
299+
perfInfo.registerSuccess(true);
300+
return null;
301+
}
296302
}
297303
});
298304

@@ -334,8 +340,11 @@ private synchronized void flushWrittenBytesToServiceAsync() throws IOException {
334340

335341
private synchronized void flushWrittenBytesToServiceInternal(final long offset,
336342
final boolean retainUncommitedData, final boolean isClose) throws IOException {
337-
try {
338-
client.flush(path, offset, retainUncommitedData, isClose);
343+
AbfsPerfTracker tracker = client.getAbfsPerfTracker();
344+
try (AbfsPerfInfo perfInfo = new AbfsPerfInfo(tracker,
345+
"flushWrittenBytesToServiceInternal", "flush")) {
346+
AbfsRestOperation op = client.flush(path, offset, retainUncommitedData, isClose);
347+
perfInfo.registerResult(op.getResult()).registerSuccess(true);
339348
} catch (AzureBlobFileSystemException ex) {
340349
if (ex instanceof AbfsRestOperationException) {
341350
if (((AbfsRestOperationException) ex).getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.fs.azurebfs.services;
20+
21+
import java.time.Instant;
22+
23+
import org.apache.hadoop.fs.azurebfs.contracts.services.AbfsPerfLoggable;
24+
25+
/**
26+
* {@code AbfsPerfInfo} holds information on ADLS Gen 2 API performance observed by {@code AbfsClient}. Every
27+
* Abfs request keeps adding its information (success/failure, latency etc) to its {@code AbfsPerfInfo}'s object
28+
* as and when it becomes available. When the request is over, the performance information is recorded while
29+
* the {@code AutoCloseable} {@code AbfsPerfInfo} object is "closed".
30+
*/
31+
public final class AbfsPerfInfo implements AutoCloseable {
32+
33+
// the tracker which will be extracting perf info out of this object.
34+
private AbfsPerfTracker abfsPerfTracker;
35+
36+
// the caller name.
37+
private String callerName;
38+
39+
// the callee name.
40+
private String calleeName;
41+
42+
// time when this tracking started.
43+
private Instant trackingStart;
44+
45+
// time when this tracking ended.
46+
private Instant trackingEnd;
47+
48+
// whether the tracked request was successful.
49+
private boolean success;
50+
51+
// time when the aggregate operation (to which this request belongs) started.
52+
private Instant aggregateStart;
53+
54+
// number of requests in the aggregate operation (to which this request belongs).
55+
private long aggregateCount;
56+
57+
// result of the request.
58+
private AbfsPerfLoggable res;
59+
60+
public AbfsPerfInfo(AbfsPerfTracker abfsPerfTracker, String callerName, String calleeName) {
61+
this.callerName = callerName;
62+
this.calleeName = calleeName;
63+
this.abfsPerfTracker = abfsPerfTracker;
64+
this.success = false;
65+
this.trackingStart = abfsPerfTracker.getLatencyInstant();
66+
}
67+
68+
public AbfsPerfInfo registerSuccess(boolean success) {
69+
this.success = success;
70+
return this;
71+
}
72+
73+
public AbfsPerfInfo registerResult(AbfsPerfLoggable res) {
74+
this.res = res;
75+
return this;
76+
}
77+
78+
public AbfsPerfInfo registerAggregates(Instant aggregateStart, long aggregateCount) {
79+
this.aggregateStart = aggregateStart;
80+
this.aggregateCount = aggregateCount;
81+
return this;
82+
}
83+
84+
public AbfsPerfInfo finishTracking() {
85+
if (this.trackingEnd == null) {
86+
this.trackingEnd = abfsPerfTracker.getLatencyInstant();
87+
}
88+
89+
return this;
90+
}
91+
92+
public AbfsPerfInfo registerCallee(String calleeName) {
93+
this.calleeName = calleeName;
94+
return this;
95+
}
96+
97+
@Override
98+
public void close() {
99+
abfsPerfTracker.trackInfo(this.finishTracking());
100+
}
101+
102+
public String getCallerName() {
103+
return callerName;
104+
};
105+
106+
public String getCalleeName() {
107+
return calleeName;
108+
}
109+
110+
public Instant getTrackingStart() {
111+
return trackingStart;
112+
}
113+
114+
public Instant getTrackingEnd() {
115+
return trackingEnd;
116+
}
117+
118+
public boolean getSuccess() {
119+
return success;
120+
}
121+
122+
public Instant getAggregateStart() {
123+
return aggregateStart;
124+
}
125+
126+
public long getAggregateCount() {
127+
return aggregateCount;
128+
}
129+
130+
public AbfsPerfLoggable getResult() {
131+
return res;
132+
}
133+
}

0 commit comments

Comments
 (0)