org.glowroot.agent.central.CentralCollector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glowroot-agent-it-harness Show documentation
Show all versions of glowroot-agent-it-harness Show documentation
Glowroot Agent Integration Test Harness
/*
* Copyright 2015-2018 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.agent.central;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Strings;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Lists;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.io.grpc.stub.StreamObserver;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;
import org.glowroot.agent.central.CentralConnection.GrpcCall;
import org.glowroot.agent.collector.Collector;
import org.glowroot.agent.config.ConfigService;
import org.glowroot.agent.live.LiveJvmServiceImpl;
import org.glowroot.agent.live.LiveTraceRepositoryImpl;
import org.glowroot.agent.live.LiveWeavingServiceImpl;
import org.glowroot.agent.shaded.org.glowroot.common.util.OnlyUsedByTests;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.AggregateOuterClass.Aggregate;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceGrpc;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceGrpc.CollectorServiceStub;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.AggregateResponseMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.AggregateStreamHeader;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.AggregateStreamMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.EmptyMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.Environment;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValue;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValueMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValueResponseMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.InitMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.InitResponse;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.LogEvent;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.LogEvent.Level;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.LogMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.OverallAggregate;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.TraceStreamCounts;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.TraceStreamHeader;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.TraceStreamMessage;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.TraceStreamMessage.Queries;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.CollectorServiceOuterClass.TransactionAggregate;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.ProfileOuterClass.Profile;
import org.glowroot.agent.shaded.org.glowroot.wire.api.model.TraceOuterClass.Trace;
public class CentralCollector implements Collector {
private static final Logger logger = LoggerFactory.getLogger(CentralCollector.class);
// log startup messages using logger name "org.glowroot"
private static final Logger startupLogger = LoggerFactory.getLogger("org.glowroot");
private final String agentId;
private final String collectorAddress;
private final ConfigService configService;
private final CentralConnection centralConnection;
private final CollectorServiceStub collectorServiceStub;
private final DownstreamServiceObserver downstreamServiceObserver;
private final SharedQueryTextLimiter sharedQueryTextLimiter = new SharedQueryTextLimiter();
private volatile @MonotonicNonNull Environment environment;
private volatile int nextAggregateDelayMillis;
public CentralCollector(Map properties, String collectorAddress,
@Nullable String collectorAuthority, File confDir, @Nullable File sharedConfDir,
LiveJvmServiceImpl liveJvmService, LiveWeavingServiceImpl liveWeavingService,
LiveTraceRepositoryImpl liveTraceRepository, AgentConfigUpdater agentConfigUpdater,
ConfigService configService) throws Exception {
String agentId = properties.get("glowroot.agent.id");
if (Strings.isNullOrEmpty(agentId)) {
agentId = escapeHostName(InetAddress.getLocalHost().getHostName());
} else if (agentId.endsWith("::")) {
agentId += escapeHostName(InetAddress.getLocalHost().getHostName());
} else if (!agentId.contains("::")) {
// check for 0.9.x agent rollup id
String v09AgentRollupId = properties.get("glowroot.agent.rollup.id");
if (!Strings.isNullOrEmpty(v09AgentRollupId)) {
agentId = convertFromV09AgentRollupId(v09AgentRollupId) + agentId;
}
}
this.agentId = agentId;
this.collectorAddress = collectorAddress;
this.configService = configService;
startupLogger.info("agent id: {}", agentId);
AtomicBoolean inConnectionFailure = new AtomicBoolean();
centralConnection = new CentralConnection(collectorAddress, collectorAuthority, confDir,
sharedConfDir, inConnectionFailure);
collectorServiceStub = CollectorServiceGrpc.newStub(centralConnection.getChannel())
.withCompression("gzip");
downstreamServiceObserver = new DownstreamServiceObserver(centralConnection,
agentConfigUpdater, liveJvmService, liveWeavingService, liveTraceRepository,
agentId, inConnectionFailure, sharedQueryTextLimiter);
}
@Override
public void init(File confDir, @Nullable File sharedConfDir, final Environment environment,
AgentConfig agentConfig, final AgentConfigUpdater agentConfigUpdater) {
final InitMessage initMessage = InitMessage.newBuilder()
.setAgentId(agentId)
.setEnvironment(environment)
.setAgentConfig(agentConfig)
.build();
centralConnection.callInit(new GrpcCall() {
@Override
public void call(StreamObserver responseObserver) {
collectorServiceStub.collectInit(initMessage, responseObserver);
}
@Override
void doWithResponse(final InitResponse response) {
CentralCollector.this.environment = environment;
// don't need to suppress sending this log message to the central collector because
// startup logger info messages are never sent to the central collector
startupLogger.info("connected to the central collector {}, version {}",
collectorAddress, response.getGlowrootCentralVersion());
if (response.hasAgentConfig()) {
try {
agentConfigUpdater.update(response.getAgentConfig());
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
downstreamServiceObserver.connectAsync();
}
});
}
// collecting even when no aggregates since collection triggers transaction-based alerts
@Override
public void collectAggregates(AggregateReader aggregateReader) {
centralConnection.callWithAFewRetries(nextAggregateDelayMillis,
new CollectAggregatesGrpcCall(aggregateReader));
}
@Override
public void collectGaugeValues(List gaugeValues) {
final GaugeValueMessage gaugeValueMessage = GaugeValueMessage.newBuilder()
.setAgentId(agentId)
.addAllGaugeValues(gaugeValues)
.setPostV09(true)
.build();
centralConnection.callWithAFewRetries(new GrpcCall() {
@Override
public void call(StreamObserver responseObserver) {
collectorServiceStub.collectGaugeValues(gaugeValueMessage, responseObserver);
}
@Override
public void doWithResponse(GaugeValueResponseMessage response) {
if (response.getResendInit() && environment != null) {
final InitMessage initMessage = InitMessage.newBuilder()
.setAgentId(agentId)
.setEnvironment(environment)
.setAgentConfig(configService.getAgentConfig())
.build();
// only once, since resendInit will continue to be sent back until it succeeds
centralConnection.callOnce(new GrpcCall() {
@Override
void call(StreamObserver responseObserver) {
collectorServiceStub.collectInit(initMessage, responseObserver);
}
});
}
}
});
}
@Override
public void collectTrace(TraceReader traceReader) {
if (traceReader.partial()) {
// do not retry partial transactions since they are live and reading from the trace
// reader will not be idempotent, so could lead to confusing results
centralConnection.callOnce(new CollectTraceGrpcCall(traceReader));
} else {
centralConnection.callWithAFewRetries(new CollectTraceGrpcCall(traceReader));
}
}
@Override
public void log(LogEvent logEvent) {
if (centralConnection.suppressLogCollector()) {
return;
}
if (logEvent.getLoggerName().equals("org.glowroot") && logEvent.getLevel() == Level.INFO) {
// never send startup logger info messages to the central collector
return;
}
final LogMessage logMessage = LogMessage.newBuilder()
.setAgentId(agentId)
.setLogEvent(logEvent)
.setPostV09(true)
.build();
centralConnection.callWithAFewRetries(new GrpcCall() {
@Override
public void call(StreamObserver responseObserver) {
collectorServiceStub.log(logMessage, responseObserver);
}
});
}
@OnlyUsedByTests
public void close() throws InterruptedException {
downstreamServiceObserver.close();
centralConnection.close();
}
@OnlyUsedByTests
public void awaitClose() throws InterruptedException {
centralConnection.awaitClose();
}
@VisibleForTesting
static String escapeHostName(String hostName) {
hostName = hostName.replace("\\", "\\\\");
if (hostName.startsWith(":")) {
hostName = "\\" + hostName;
}
while (hostName.contains("::")) {
hostName = hostName.replace("::", ":\\:");
}
return hostName;
}
private static String convertFromV09AgentRollupId(String agentRollupId) {
// old agent rollup id supported spaces around separator
return agentRollupId.replaceAll(" */ *", "::").trim() + "::";
}
private class CollectAggregatesGrpcCall extends GrpcCall {
private final AggregateReader aggregateReader;
private final List fullTextSha1s = Lists.newArrayList();
private CollectAggregatesGrpcCall(AggregateReader aggregateReader) {
this.aggregateReader = aggregateReader;
}
@Override
public void call(StreamObserver responseObserver) {
final StreamObserver requestObserver =
collectorServiceStub.collectAggregateStream(responseObserver);
requestObserver.onNext(AggregateStreamMessage.newBuilder()
.setStreamHeader(AggregateStreamHeader.newBuilder()
.setAgentId(agentId)
.setCaptureTime(aggregateReader.captureTime())
.setPostV09(true))
.build());
// need to clear in case this is a retry
fullTextSha1s.clear();
try {
aggregateReader.accept(new AggregateVisitorImpl(requestObserver));
} catch (Throwable t) {
logger.error(t.getMessage(), t);
requestObserver.onError(t);
return;
}
requestObserver.onCompleted();
}
@Override
public void doWithResponse(AggregateResponseMessage response) {
// Math.min is just for safety
nextAggregateDelayMillis = Math.min(response.getNextDelayMillis(), 30000);
for (String fullTextSha1 : fullTextSha1s) {
sharedQueryTextLimiter.onSuccessfullySentToCentralCollector(fullTextSha1);
}
}
private class AggregateVisitorImpl implements AggregateVisitor {
private final StreamObserver requestObserver;
private AggregateVisitorImpl(StreamObserver requestObserver) {
this.requestObserver = requestObserver;
}
@Override
public void visitOverallAggregate(String transactionType,
List sharedQueryTexts, Aggregate overallAggregate) {
for (String sharedQueryText : sharedQueryTexts) {
Aggregate.SharedQueryText aggregateSharedQueryText = sharedQueryTextLimiter
.buildAggregateSharedQueryText(sharedQueryText, fullTextSha1s);
requestObserver.onNext(AggregateStreamMessage.newBuilder()
.setSharedQueryText(aggregateSharedQueryText)
.build());
}
requestObserver.onNext(AggregateStreamMessage.newBuilder()
.setOverallAggregate(OverallAggregate.newBuilder()
.setTransactionType(transactionType)
.setAggregate(overallAggregate))
.build());
}
@Override
public void visitTransactionAggregate(String transactionType,
String transactionName, List sharedQueryTexts,
Aggregate transactionAggregate) {
for (String sharedQueryText : sharedQueryTexts) {
requestObserver.onNext(AggregateStreamMessage.newBuilder()
.setSharedQueryText(sharedQueryTextLimiter
.buildAggregateSharedQueryText(sharedQueryText, fullTextSha1s))
.build());
}
requestObserver.onNext(AggregateStreamMessage.newBuilder()
.setTransactionAggregate(TransactionAggregate.newBuilder()
.setTransactionType(transactionType)
.setTransactionName(transactionName)
.setAggregate(transactionAggregate))
.build());
}
}
}
private class CollectTraceGrpcCall extends GrpcCall {
private final TraceReader traceReader;
private final List fullTextSha1s = Lists.newArrayList();
private CollectTraceGrpcCall(TraceReader traceReader) {
this.traceReader = traceReader;
}
@Override
public void call(StreamObserver responseObserver) {
StreamObserver requestObserver =
collectorServiceStub.collectTraceStream(responseObserver);
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setStreamHeader(TraceStreamHeader.newBuilder()
.setAgentId(agentId)
.setTraceId(traceReader.traceId())
.setUpdate(traceReader.update())
.setPostV09(true))
.build());
// need to clear in case this is a retry
fullTextSha1s.clear();
TraceVisitorImpl traceVisitor = new TraceVisitorImpl(requestObserver, fullTextSha1s);
try {
traceReader.accept(traceVisitor);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
requestObserver.onError(t);
return;
}
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setStreamCounts(TraceStreamCounts.newBuilder()
.setEntryCount(traceVisitor.entryCount)
.setSharedQueryTextCount(traceVisitor.sharedQueryTextCount))
.build());
requestObserver.onCompleted();
}
@Override
public void doWithResponse(EmptyMessage response) {
for (String fullTextSha1 : fullTextSha1s) {
sharedQueryTextLimiter.onSuccessfullySentToCentralCollector(fullTextSha1);
}
}
}
private class TraceVisitorImpl implements TraceVisitor {
private final StreamObserver requestObserver;
private final List fullTextSha1s;
private int entryCount;
private int sharedQueryTextCount;
private TraceVisitorImpl(StreamObserver requestObserver,
List fullTextSha1s) {
this.requestObserver = requestObserver;
this.fullTextSha1s = fullTextSha1s;
}
@Override
public void visitEntry(Trace.Entry entry) {
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setEntry(entry)
.build());
entryCount++;
}
@Override
public void visitQueries(List queries) {
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setQueries(Queries.newBuilder()
.addAllQuery(queries))
.build());
}
@Override
public void visitSharedQueryTexts(List sharedQueryTexts) {
for (String sharedQueryText : sharedQueryTexts) {
Trace.SharedQueryText traceSharedQueryText = sharedQueryTextLimiter
.buildTraceSharedQueryText(sharedQueryText, fullTextSha1s);
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setSharedQueryText(traceSharedQueryText)
.build());
}
sharedQueryTextCount = sharedQueryTexts.size();
}
@Override
public void visitMainThreadProfile(Profile profile) {
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setMainThreadProfile(profile)
.build());
}
@Override
public void visitAuxThreadProfile(Profile profile) {
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setAuxThreadProfile(profile)
.build());
}
@Override
public void visitHeader(Trace.Header header) {
requestObserver.onNext(TraceStreamMessage.newBuilder()
.setHeader(header)
.build());
}
}
}