Skip to content

Commit cd71c72

Browse files
committed
Admin API: stream stats
1 parent 36a93a3 commit cd71c72

File tree

3 files changed

+70
-29
lines changed

3 files changed

+70
-29
lines changed

pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/NonPersistentTopics.java

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Collections;
2828
import java.util.List;
2929
import java.util.concurrent.CompletableFuture;
30+
import java.util.concurrent.CompletionException;
3031
import java.util.concurrent.ExecutionException;
3132
import java.util.concurrent.TimeUnit;
3233
import java.util.concurrent.TimeoutException;
@@ -47,6 +48,7 @@
4748
import javax.ws.rs.core.MediaType;
4849
import javax.ws.rs.core.Response;
4950
import javax.ws.rs.core.Response.Status;
51+
import javax.ws.rs.core.StreamingOutput;
5052
import org.apache.commons.lang3.StringUtils;
5153
import org.apache.pulsar.broker.PulsarServerException;
5254
import org.apache.pulsar.broker.service.Topic;
@@ -60,6 +62,7 @@
6062
import org.apache.pulsar.common.policies.data.stats.NonPersistentPartitionedTopicStatsImpl;
6163
import org.apache.pulsar.common.policies.data.stats.NonPersistentTopicStatsImpl;
6264
import org.apache.pulsar.common.util.FutureUtil;
65+
import org.apache.pulsar.common.util.ObjectMapperFactory;
6366
import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap;
6467
import org.slf4j.Logger;
6568
import org.slf4j.LoggerFactory;
@@ -111,8 +114,7 @@ public void getPartitionedMetadata(
111114
@ApiResponse(code = 412, message = "Topic name is not valid"),
112115
@ApiResponse(code = 500, message = "Internal server error"),
113116
})
114-
public void getInternalStats(
115-
@Suspended final AsyncResponse asyncResponse,
117+
public StreamingOutput getInternalStats(
116118
@ApiParam(value = "Specify the tenant", required = true)
117119
@PathParam("tenant") String tenant,
118120
@ApiParam(value = "Specify the namespace", required = true)
@@ -123,21 +125,28 @@ public void getInternalStats(
123125
@QueryParam("authoritative") @DefaultValue("false") boolean authoritative,
124126
@QueryParam("metadata") @DefaultValue("false") boolean metadata) {
125127
validateTopicName(tenant, namespace, encodedTopic);
126-
validateTopicOwnershipAsync(topicName, authoritative)
127-
.thenCompose(__ -> validateTopicOperationAsync(topicName, TopicOperation.GET_STATS))
128-
.thenCompose(__ -> {
129-
Topic topic = getTopicReference(topicName);
130-
boolean includeMetadata = metadata && hasSuperUserAccess();
131-
return topic.getInternalStats(includeMetadata);
132-
})
133-
.thenAccept(asyncResponse::resume)
134-
.exceptionally(ex -> {
135-
if (isNot307And404Exception(ex)) {
136-
log.error("[{}] Failed to get internal stats for topic {}", clientAppId(), topicName, ex);
137-
}
138-
resumeAsyncResponseExceptionally(asyncResponse, ex);
139-
return null;
140-
});
128+
return output -> {
129+
validateTopicOwnershipAsync(topicName, authoritative)
130+
.thenCompose(__ -> validateTopicOperationAsync(topicName, TopicOperation.GET_STATS))
131+
.thenCompose(__ -> {
132+
Topic topic = getTopicReference(topicName);
133+
boolean includeMetadata = metadata && hasSuperUserAccess();
134+
return topic.getInternalStats(includeMetadata);
135+
})
136+
.thenAccept(stats -> {
137+
try {
138+
ObjectMapperFactory.getMapper().getObjectMapper().writeValue(output, stats);
139+
} catch (Throwable e) {
140+
throw new CompletionException(e);
141+
}
142+
})
143+
.exceptionally(ex -> {
144+
if (isNot307And404Exception(ex)) {
145+
log.error("[{}] Failed to get internal stats for topic {}", clientAppId(), topicName, ex);
146+
}
147+
throw translateToWebApplicationException(ex);
148+
});
149+
};
141150
}
142151

143152
@PUT

pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919
package org.apache.pulsar.broker.admin.v2;
2020

2121
import static org.apache.pulsar.common.util.Codec.decode;
22+
import static org.apache.pulsar.common.util.FutureUtil.unwrapCompletionException;
2223
import com.fasterxml.jackson.core.JsonProcessingException;
2324
import io.swagger.annotations.Api;
2425
import io.swagger.annotations.ApiOperation;
2526
import io.swagger.annotations.ApiParam;
2627
import io.swagger.annotations.ApiResponse;
2728
import io.swagger.annotations.ApiResponses;
29+
import java.io.IOException;
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.Optional;
3133
import java.util.Set;
34+
import java.util.concurrent.CompletionException;
35+
import javax.servlet.http.HttpServletRequest;
3236
import javax.ws.rs.DELETE;
3337
import javax.ws.rs.DefaultValue;
3438
import javax.ws.rs.Encoded;
@@ -42,8 +46,10 @@
4246
import javax.ws.rs.WebApplicationException;
4347
import javax.ws.rs.container.AsyncResponse;
4448
import javax.ws.rs.container.Suspended;
49+
import javax.ws.rs.core.Context;
4550
import javax.ws.rs.core.MediaType;
4651
import javax.ws.rs.core.Response;
52+
import javax.ws.rs.core.StreamingOutput;
4753
import org.apache.bookkeeper.mledger.Position;
4854
import org.apache.bookkeeper.mledger.impl.PositionImpl;
4955
import org.apache.pulsar.broker.admin.impl.PersistentTopicsBase;
@@ -83,6 +89,7 @@
8389
import org.apache.pulsar.common.policies.data.stats.PartitionedTopicStatsImpl;
8490
import org.apache.pulsar.common.util.Codec;
8591
import org.apache.pulsar.common.util.FutureUtil;
92+
import org.apache.pulsar.common.util.ObjectMapperFactory;
8693
import org.apache.pulsar.metadata.api.MetadataStoreException;
8794
import org.slf4j.Logger;
8895
import org.slf4j.LoggerFactory;
@@ -1120,7 +1127,7 @@ public void deleteTopic(
11201127
internalDeleteTopicAsync(authoritative, force)
11211128
.thenAccept(__ -> asyncResponse.resume(Response.noContent().build()))
11221129
.exceptionally(ex -> {
1123-
Throwable t = FutureUtil.unwrapCompletionException(ex);
1130+
Throwable t = unwrapCompletionException(ex);
11241131
if (!force && (t instanceof BrokerServiceException.TopicBusyException)) {
11251132
ex = new RestException(Response.Status.PRECONDITION_FAILED,
11261133
t.getMessage());
@@ -1215,6 +1222,8 @@ public void getStats(
12151222
});
12161223
}
12171224

1225+
@Context HttpServletRequest servletRequest;
1226+
12181227
@GET
12191228
@Path("{tenant}/{namespace}/{topic}/internalStats")
12201229
@ApiOperation(value = "Get the internal stats for the topic.", response = PersistentTopicInternalStats.class)
@@ -1226,8 +1235,7 @@ public void getStats(
12261235
@ApiResponse(code = 412, message = "Topic name is not valid"),
12271236
@ApiResponse(code = 500, message = "Internal server error"),
12281237
@ApiResponse(code = 503, message = "Failed to validate global cluster configuration") })
1229-
public void getInternalStats(
1230-
@Suspended final AsyncResponse asyncResponse,
1238+
public StreamingOutput getInternalStats(
12311239
@ApiParam(value = "Specify the tenant", required = true)
12321240
@PathParam("tenant") String tenant,
12331241
@ApiParam(value = "Specify the namespace", required = true)
@@ -1238,15 +1246,22 @@ public void getInternalStats(
12381246
@QueryParam("authoritative") @DefaultValue("false") boolean authoritative,
12391247
@QueryParam("metadata") @DefaultValue("false") boolean metadata) {
12401248
validateTopicName(tenant, namespace, encodedTopic);
1241-
internalGetInternalStatsAsync(authoritative, metadata)
1242-
.thenAccept(asyncResponse::resume)
1243-
.exceptionally(ex -> {
1244-
if (isNot307And404Exception(ex)) {
1245-
log.error("[{}] Failed to get internal stats for topic {}", clientAppId(), topicName, ex);
1246-
}
1247-
resumeAsyncResponseExceptionally(asyncResponse, ex);
1248-
return null;
1249-
});
1249+
return output -> {
1250+
internalGetInternalStatsAsync(authoritative, metadata)
1251+
.thenAccept(stats -> {
1252+
try {
1253+
ObjectMapperFactory.getMapper().getObjectMapper().writeValue(output, stats);
1254+
} catch (IOException error) {
1255+
throw new CompletionException(error);
1256+
}
1257+
})
1258+
.exceptionally(ex -> {
1259+
if (isNot307And404Exception(ex)) {
1260+
log.error("[{}] Failed to get internal stats for topic {}", clientAppId(), topicName, ex);
1261+
}
1262+
throw translateToWebApplicationException(ex);
1263+
});
1264+
};
12501265
}
12511266

12521267
@GET

pulsar-broker/src/main/java/org/apache/pulsar/broker/web/PulsarWebResource.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,23 @@ public <T> T sync(Supplier<CompletableFuture<T>> supplier) {
13141314
}
13151315
}
13161316

1317+
protected static WebApplicationException translateToWebApplicationException(Throwable exception) {
1318+
Throwable realCause = FutureUtil.unwrapCompletionException(exception);
1319+
if (realCause instanceof WebApplicationException) {
1320+
return (WebApplicationException) realCause;
1321+
} else if (realCause instanceof BrokerServiceException.NotAllowedException) {
1322+
return new RestException(Status.CONFLICT, realCause);
1323+
} else if (realCause instanceof MetadataStoreException.NotFoundException) {
1324+
return new RestException(Status.NOT_FOUND, realCause);
1325+
} else if (realCause instanceof MetadataStoreException.BadVersionException) {
1326+
return new RestException(Status.CONFLICT, "Concurrent modification");
1327+
} else if (realCause instanceof PulsarAdminException) {
1328+
return new RestException(((PulsarAdminException) realCause));
1329+
} else {
1330+
return new RestException(realCause);
1331+
}
1332+
}
1333+
13171334
protected static void resumeAsyncResponseExceptionally(AsyncResponse asyncResponse, Throwable exception) {
13181335
Throwable realCause = FutureUtil.unwrapCompletionException(exception);
13191336
if (realCause instanceof WebApplicationException) {

0 commit comments

Comments
 (0)