/*
* Copyright 2015-2017 the original author or authors.
*
* 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 org.glowroot.central;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import io.grpc.stub.StreamObserver;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.immutables.serial.Serial;
import org.immutables.value.Value;
import org.infinispan.util.function.SerializableFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.glowroot.central.repo.AgentDao;
import org.glowroot.central.repo.AgentDao.AgentConfigUpdate;
import org.glowroot.central.repo.ConfigDao;
import org.glowroot.central.util.ClusterManager;
import org.glowroot.central.util.DistributedExecutionMap;
import org.glowroot.common.live.ImmutableEntries;
import org.glowroot.common.live.LiveJvmService.AgentNotConnectedException;
import org.glowroot.common.live.LiveJvmService.AgentUnsupportedOperationException;
import org.glowroot.common.live.LiveJvmService.DirectoryDoesNotExistException;
import org.glowroot.common.live.LiveJvmService.UnavailableDueToRunningInIbmJvmException;
import org.glowroot.common.live.LiveJvmService.UnavailableDueToRunningInJreException;
import org.glowroot.common.live.LiveTraceRepository.Entries;
import org.glowroot.wire.api.model.DownstreamServiceGrpc.DownstreamServiceImplBase;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AgentConfigUpdateRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AgentResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AgentResponse.MessageCase;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AuxThreadProfileRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AuxThreadProfileResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AvailableDiskSpaceRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.AvailableDiskSpaceResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.Capabilities;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.CapabilitiesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.CentralRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.EntriesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.EntriesResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.FullTraceRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.FullTraceResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.GcRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.GlobalMeta;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.GlobalMetaRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeaderRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapDumpFileInfo;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapDumpRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapDumpResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapHistogram;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapHistogramRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HeapHistogramResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.HelloAck;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.JstackRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.JstackResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanDump;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanDumpRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanDumpRequest.MBeanDumpKind;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanMeta;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MBeanMetaRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MainThreadProfileRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MainThreadProfileResponse;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MatchingClassNamesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MatchingMBeanObjectNamesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MatchingMethodNamesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MethodSignature;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.MethodSignaturesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.PreloadClasspathCacheRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.ReweaveRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.SystemPropertiesRequest;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.ThreadDump;
import org.glowroot.wire.api.model.DownstreamServiceOuterClass.ThreadDumpRequest;
import org.glowroot.wire.api.model.ProfileOuterClass.Profile;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
class DownstreamServiceImpl extends DownstreamServiceImplBase {
private static final Logger logger = LoggerFactory.getLogger(DownstreamServiceImpl.class);
// log startup messages using logger name "org.glowroot"
private static final Logger startupLogger = LoggerFactory.getLogger("org.glowroot");
private final DistributedExecutionMap<String, ConnectedAgent> connectedAgents;
private final AgentDao agentDao;
private final ConfigDao configDao;
DownstreamServiceImpl(AgentDao agentDao, ConfigDao configDao, ClusterManager clusterManager) {
this.agentDao = agentDao;
this.configDao = configDao;
connectedAgents = clusterManager.createDistributedExecutionMap("connectedAgents");
}
@Override
public StreamObserver<AgentResponse> connect(StreamObserver<CentralRequest> requestObserver) {
return new ConnectedAgent(requestObserver);
}
void updateAgentConfigIfConnectedAndNeeded(String agentId) throws Exception {
connectedAgents.execute(agentId, ConnectedAgent::updateAgentConfigIfConnectedAndNeeded);
}
boolean isAvailable(String agentId) throws Exception {
java.util.Optional<Boolean> optional =
connectedAgents.execute(agentId, ConnectedAgent::isAvailable);
return optional.isPresent();
}
ThreadDump threadDump(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::threadDump);
return responseWrapper.getThreadDumpResponse().getThreadDump();
}
String jstack(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::jstack);
JstackResponse response = responseWrapper.getJstackResponse();
if (response.getUnavailableDueToRunningInJre()) {
throw new UnavailableDueToRunningInJreException();
}
if (response.getUnavailableDueToRunningInIbmJvm()) {
throw new UnavailableDueToRunningInIbmJvmException();
}
return response.getJstack();
}
long availableDiskSpaceBytes(String agentId, String directory) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.availableDiskSpaceBytes(directory));
AvailableDiskSpaceResponse response = responseWrapper.getAvailableDiskSpaceResponse();
if (response.getDirectoryDoesNotExist()) {
throw new DirectoryDoesNotExistException();
}
return response.getAvailableBytes();
}
HeapDumpFileInfo heapDump(String agentId, String directory) throws Exception {
AgentResponse responseWrapper =
runOnCluster(agentId, connectedAgent -> connectedAgent.heapDump(directory));
HeapDumpResponse response = responseWrapper.getHeapDumpResponse();
if (response.getDirectoryDoesNotExist()) {
throw new DirectoryDoesNotExistException();
}
return response.getHeapDumpFileInfo();
}
HeapHistogram heapHistogram(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::heapHistogram);
HeapHistogramResponse response = responseWrapper.getHeapHistogramResponse();
if (response.getUnavailableDueToRunningInJre()) {
throw new UnavailableDueToRunningInJreException();
}
if (response.getUnavailableDueToRunningInIbmJvm()) {
throw new UnavailableDueToRunningInIbmJvmException();
}
return response.getHeapHistogram();
}
void gc(String agentId) throws Exception {
runOnCluster(agentId, ConnectedAgent::gc);
}
MBeanDump mbeanDump(String agentId, MBeanDumpKind mbeanDumpKind, List<String> objectNames)
throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.mbeanDump(mbeanDumpKind, objectNames));
return responseWrapper.getMbeanDumpResponse().getMbeanDump();
}
List<String> matchingMBeanObjectNames(String agentId, String partialObjectName, int limit)
throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, connectedAgent -> connectedAgent
.matchingMBeanObjectNames(partialObjectName, limit));
return responseWrapper.getMatchingMbeanObjectNamesResponse().getObjectNameList();
}
MBeanMeta mbeanMeta(String agentId, String objectName) throws Exception {
AgentResponse responseWrapper =
runOnCluster(agentId, connectedAgent -> connectedAgent.mbeanMeta(objectName));
return responseWrapper.getMbeanMetaResponse().getMbeanMeta();
}
Map<String, String> systemProperties(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::systemProperties);
return responseWrapper.getSystemPropertiesResponse().getSystemPropertiesMap();
}
Capabilities capabilities(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::capabilities);
return responseWrapper.getCapabilitiesResponse().getCapabilities();
}
GlobalMeta globalMeta(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::globalMeta);
return responseWrapper.getGlobalMetaResponse().getGlobalMeta();
}
void preloadClasspathCache(String agentId) throws Exception {
runOnCluster(agentId, ConnectedAgent::preloadClasspathCache);
}
List<String> matchingClassNames(String agentId, String partialClassName, int limit)
throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.matchingClassNames(partialClassName, limit));
return responseWrapper.getMatchingClassNamesResponse().getClassNameList();
}
List<String> matchingMethodNames(String agentId, String className, String partialMethodName,
int limit) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, connectedAgent -> connectedAgent
.matchingMethodNames(className, partialMethodName, limit));
return responseWrapper.getMatchingMethodNamesResponse().getMethodNameList();
}
List<MethodSignature> methodSignatures(String agentId, String className, String methodName)
throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.methodSignatures(className, methodName));
return responseWrapper.getMethodSignaturesResponse().getMethodSignatureList();
}
int reweave(String agentId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId, ConnectedAgent::reweave);
return responseWrapper.getReweaveResponse().getClassUpdateCount();
}
@Nullable
Trace.Header getHeader(String agentId, String traceId) throws Exception {
AgentResponse responseWrapper =
runOnCluster(agentId, connectedAgent -> connectedAgent.getHeader(traceId));
return responseWrapper.getHeaderResponse().getHeader();
}
@Nullable
Entries getEntries(String agentId, String traceId) throws Exception {
AgentResponse responseWrapper =
runOnCluster(agentId, connectedAgent -> connectedAgent.getEntries(traceId));
EntriesResponse response = responseWrapper.getEntriesResponse();
List<Trace.Entry> entries = response.getEntryList();
if (entries.isEmpty()) {
return null;
} else {
return ImmutableEntries.builder()
.addAllEntries(entries)
.addAllSharedQueryTexts(response.getSharedQueryTextList())
.build();
}
}
@Nullable
Profile getMainThreadProfile(String agentId, String traceId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.getMainThreadProfile(traceId));
MainThreadProfileResponse response = responseWrapper.getMainThreadProfileResponse();
if (response.hasProfile()) {
return response.getProfile();
} else {
return null;
}
}
@Nullable
Profile getAuxThreadProfile(String agentId, String traceId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.getAuxThreadProfile(traceId));
AuxThreadProfileResponse response = responseWrapper.getAuxThreadProfileResponse();
if (response.hasProfile()) {
return response.getProfile();
} else {
return null;
}
}
@Nullable
Trace getFullTrace(String agentId, String traceId) throws Exception {
AgentResponse responseWrapper = runOnCluster(agentId,
connectedAgent -> connectedAgent.getFullTrace(traceId));
FullTraceResponse response = responseWrapper.getFullTraceResponse();
if (response.hasTrace()) {
return response.getTrace();
} else {
return null;
}
}
private AgentResponse runOnCluster(String agentId,
SerializableFunction<ConnectedAgent, AgentResult> task) throws Exception {
java.util.Optional<AgentResult> result = connectedAgents.execute(agentId, task);
if (result.isPresent()) {
return getResponseWrapper(result.get());
} else {
throw new AgentNotConnectedException();
}
}
private static AgentResponse getResponseWrapper(AgentResult result) throws Exception {
if (result.interrupted()) {
throw new InterruptedException();
}
if (result.timeout()) {
throw new TimeoutException();
}
AgentResponse response = result.value().get();
if (response.getMessageCase() == MessageCase.UNKNOWN_REQUEST_RESPONSE) {
throw new AgentUnsupportedOperationException();
}
if (response.getMessageCase() == MessageCase.EXCEPTION_RESPONSE) {
throw new AgentException();
}
return response;
}
private class ConnectedAgent implements StreamObserver<AgentResponse> {
private final AtomicLong nextRequestId = new AtomicLong(1);
// expiration in the unlikely case that response is never returned from agent
private final com.google.common.cache.Cache<Long, ResponseHolder> responseHolders =
CacheBuilder.newBuilder()
.expireAfterWrite(1, HOURS)
.build();
private volatile @MonotonicNonNull String agentId;
private final StreamObserver<CentralRequest> requestObserver;
private ConnectedAgent(StreamObserver<CentralRequest> requestObserver) {
this.requestObserver = requestObserver;
}
@Override
public void onNext(AgentResponse value) {
if (value.getMessageCase() == MessageCase.HELLO) {
agentId = value.getHello().getAgentId();
connectedAgents.put(agentId, ConnectedAgent.this);
synchronized (requestObserver) {
requestObserver.onNext(CentralRequest.newBuilder()
.setHelloAck(HelloAck.getDefaultInstance())
.build());
}
startupLogger.info("downstream connection (re-)established with agent: {}",
getDisplayForLogging(agentId));
return;
}
if (agentId == null) {
logger.error("first message from agent to downstream service must be HELLO");
return;
}
long requestId = value.getRequestId();
ResponseHolder responseHolder = responseHolders.getIfPresent(requestId);
responseHolders.invalidate(requestId);
if (responseHolder == null) {
logger.error("no response holder for request id: {}", requestId);
return;
}
try {
// this shouldn't timeout since it is the other side of the exchange that is waiting
responseHolder.response.exchange(value, 1, MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("{} - {}", getDisplayForLogging(agentId), e.getMessage(), e);
} catch (TimeoutException e) {
logger.error("{} - {}", getDisplayForLogging(agentId), e.getMessage(), e);
}
}
@Override
public void onError(Throwable t) {
logger.debug("{} - {}", t.getMessage(), t);
if (agentId != null) {
startupLogger.info("downstream connection lost with agent: {}",
getDisplayForLogging(agentId));
connectedAgents.remove(agentId, ConnectedAgent.this);
}
}
@Override
public void onCompleted() {
synchronized (requestObserver) {
requestObserver.onCompleted();
}
if (agentId != null) {
connectedAgents.remove(agentId, ConnectedAgent.this);
}
}
// dummy return value, just needs to be serializable
private boolean updateAgentConfigIfConnectedAndNeeded() {
checkNotNull(agentId);
AgentConfigUpdate agentConfigUpdate;
try {
agentConfigUpdate = configDao.readForUpdate(agentId);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
if (agentConfigUpdate == null) {
return false;
}
sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setAgentConfigUpdateRequest(AgentConfigUpdateRequest.newBuilder()
.setAgentConfig(agentConfigUpdate.config()))
.build());
try {
configDao.markUpdated(agentId, agentConfigUpdate.configUpdateToken());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
return false;
}
private boolean isAvailable() {
return true;
}
private AgentResult threadDump() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setThreadDumpRequest(ThreadDumpRequest.getDefaultInstance())
.build());
}
private AgentResult jstack() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setJstackRequest(JstackRequest.getDefaultInstance())
.build());
}
private AgentResult availableDiskSpaceBytes(String directory) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setAvailableDiskSpaceRequest(AvailableDiskSpaceRequest.newBuilder()
.setDirectory(directory))
.build());
}
private AgentResult heapDump(String directory) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setHeapDumpRequest(HeapDumpRequest.newBuilder()
.setDirectory(directory))
.build());
}
private AgentResult heapHistogram() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setHeapHistogramRequest(HeapHistogramRequest.newBuilder())
.build());
}
private AgentResult gc() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setGcRequest(GcRequest.getDefaultInstance())
.build());
}
private AgentResult mbeanDump(MBeanDumpKind mbeanDumpKind, List<String> objectNames) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMbeanDumpRequest(MBeanDumpRequest.newBuilder()
.setKind(mbeanDumpKind)
.addAllObjectName(objectNames))
.build());
}
private AgentResult matchingMBeanObjectNames(String partialObjectName, int limit) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMatchingMbeanObjectNamesRequest(MatchingMBeanObjectNamesRequest.newBuilder()
.setPartialObjectName(partialObjectName)
.setLimit(limit))
.build());
}
private AgentResult mbeanMeta(String objectName) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMbeanMetaRequest(MBeanMetaRequest.newBuilder()
.setObjectName(objectName))
.build());
}
private AgentResult systemProperties() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setSystemPropertiesRequest(SystemPropertiesRequest.getDefaultInstance())
.build());
}
private AgentResult capabilities() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setCapabilitiesRequest(CapabilitiesRequest.getDefaultInstance())
.build());
}
private AgentResult globalMeta() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setGlobalMetaRequest(GlobalMetaRequest.getDefaultInstance())
.build());
}
private AgentResult preloadClasspathCache() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setPreloadClasspathCacheRequest(
PreloadClasspathCacheRequest.getDefaultInstance())
.build());
}
private AgentResult matchingClassNames(String partialClassName, int limit) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMatchingClassNamesRequest(MatchingClassNamesRequest.newBuilder()
.setPartialClassName(partialClassName)
.setLimit(limit))
.build());
}
private AgentResult matchingMethodNames(String className, String partialMethodName,
int limit) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMatchingMethodNamesRequest(MatchingMethodNamesRequest.newBuilder()
.setClassName(className)
.setPartialMethodName(partialMethodName)
.setLimit(limit))
.build());
}
private AgentResult methodSignatures(String className, String methodName) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMethodSignaturesRequest(MethodSignaturesRequest.newBuilder()
.setClassName(className)
.setMethodName(methodName))
.build());
}
private AgentResult reweave() {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setReweaveRequest(ReweaveRequest.getDefaultInstance())
.build());
}
private AgentResult getHeader(String traceId) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setHeaderRequest(HeaderRequest.newBuilder()
.setTraceId(traceId))
.build());
}
private AgentResult getEntries(String traceId) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setEntriesRequest(EntriesRequest.newBuilder()
.setTraceId(traceId))
.build());
}
private AgentResult getMainThreadProfile(String traceId) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setMainThreadProfileRequest(MainThreadProfileRequest.newBuilder()
.setTraceId(traceId))
.build());
}
private AgentResult getAuxThreadProfile(String traceId) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setAuxThreadProfileRequest(AuxThreadProfileRequest.newBuilder()
.setTraceId(traceId))
.build());
}
private AgentResult getFullTrace(String traceId) {
return sendRequest(CentralRequest.newBuilder()
.setRequestId(nextRequestId.getAndIncrement())
.setFullTraceRequest(FullTraceRequest.newBuilder()
.setTraceId(traceId))
.build());
}
private AgentResult sendRequest(CentralRequest request) {
ResponseHolder responseHolder = new ResponseHolder();
responseHolders.put(request.getRequestId(), responseHolder);
// synchronization required since individual StreamObservers are not thread-safe
synchronized (requestObserver) {
requestObserver.onNext(request);
}
// timeout is in case agent never responds
// passing AgentResponse.getDefaultInstance() is just dummy (non-null) value
try {
AgentResponse response = responseHolder.response
.exchange(AgentResponse.getDefaultInstance(), 1, MINUTES);
return ImmutableAgentResult.builder()
.value(response)
.build();
} catch (InterruptedException e) {
return ImmutableAgentResult.builder()
.interrupted(true)
.build();
} catch (TimeoutException e) {
return ImmutableAgentResult.builder()
.timeout(true)
.build();
}
}
private String getDisplayForLogging(String agentRollupId) {
try {
return agentDao.readAgentRollupDisplay(agentRollupId);
} catch (Exception e) {
logger.error("{} - {}", agentRollupId, e.getMessage(), e);
return "id:" + agentRollupId;
}
}
}
@Value.Immutable
@Serial.Structural
interface AgentResult extends Serializable {
Optional<AgentResponse> value();
@Value.Default
default boolean timeout() {
return false;
}
@Value.Default
default boolean interrupted() {
return false;
}
}
@Value.Immutable
@Serial.Structural
interface UpdateAgentConfigResult extends Serializable {
@Value.Default
default boolean timeout() {
return false;
}
@Value.Default
default boolean interrupted() {
return false;
}
// exception occurred on central side (e.g. Cassandra exception)
@Value.Default
default boolean exception() {
return false;
}
}
private static class ResponseHolder {
private final Exchanger<AgentResponse> response = new Exchanger<>();
}
@SuppressWarnings("serial")
private static class AgentException extends Exception {}
}