package org.skywalking.apm.agent.core.client;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.skywalking.apm.agent.core.boot.ServiceManager;
import org.skywalking.apm.agent.core.conf.Config;
import org.skywalking.apm.agent.core.queue.TraceSegmentProcessQueue;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;
import org.skywalking.apm.trace.SegmentsMessage;
import org.skywalking.apm.trace.TraceSegment;
import java.io.IOException;
import java.util.List;
import java.util.Random;
/**
* The <code>CollectorClient</code> runs as an independency thread.
* It retrieves cached {@link TraceSegment} from {@link TraceSegmentProcessQueue},
* and send to collector by HTTP-RESTFUL-SERVICE: POST /skywalking/trace/segment
*
* @author wusheng
*/
public class CollectorClient implements Runnable {
private static final ILog logger = LogManager.getLogger(CollectorClient.class);
private static long SLEEP_TIME_MILLIS = 500;
private String[] serverList;
private volatile int selectedServer = -1;
public CollectorClient() {
serverList = Config.Collector.SERVERS.split(",");
Random r = new Random();
if (serverList.length > 0) {
selectedServer = r.nextInt(serverList.length);
}
}
@Override
public void run() {
while (true) {
try {
long sleepTime = -1;
TraceSegmentProcessQueue segmentProcessQueue = ServiceManager.INSTANCE.findService(TraceSegmentProcessQueue.class);
List<TraceSegment> cachedTraceSegments = segmentProcessQueue.getCachedTraceSegments();
if (cachedTraceSegments.size() > 0) {
SegmentsMessage message = null;
int count = 0;
for (TraceSegment segment : cachedTraceSegments) {
if (message == null) {
message = new SegmentsMessage();
}
message.append(segment);
if (count == Config.Collector.BATCH_SIZE) {
sendToCollector(message);
message = null;
}
}
sendToCollector(message);
} else {
sleepTime = SLEEP_TIME_MILLIS;
}
if (sleepTime > 0) {
try2Sleep(sleepTime);
}
} catch (Throwable t) {
logger.error(t, "Send trace segments to collector failure.");
}
}
}
/**
* Send the given {@link SegmentsMessage} to collector.
*
* @param message to be send.
*/
private void sendToCollector(SegmentsMessage message) throws RESTResponseStatusError, IOException {
if (message == null) {
return;
}
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
String messageJson = gson.toJson(message);
CloseableHttpClient httpClient = HttpClients.custom().build();
try {
HttpPost httpPost = ready2Send(messageJson);
if (httpPost != null) {
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (200 != statusCode) {
findBackupServer();
throw new RESTResponseStatusError(statusCode);
}
}
} catch (IOException e) {
findBackupServer();
throw e;
} finally {
httpClient.close();
}
}
/**
* Prepare the given message for HTTP Post service.
*
* @param messageJson to send
* @return {@link HttpPost}, when is ready to send. otherwise, null.
*/
private HttpPost ready2Send(String messageJson) {
if (selectedServer == -1) {
//no available server
return null;
}
HttpPost post = new HttpPost("http://" + serverList[selectedServer] + Config.Collector.SERVICE_NAME);
StringEntity entity = new StringEntity(messageJson, ContentType.APPLICATION_JSON);
post.setEntity(entity);
return post;
}
/**
* Choose the next server in {@link #serverList}, by moving {@link #selectedServer}.
*/
private void findBackupServer() {
selectedServer++;
if (selectedServer == serverList.length) {
selectedServer = 0;
}
}
/**
* Try to sleep, and ignore the {@link InterruptedException}
*
* @param millis the length of time to sleep in milliseconds
*/
private void try2Sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
}
}