Skip to content
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* ApplicationInsights-Java
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the ""Software""), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

package com.microsoft.applicationinsights.internal.quickpulse;

import com.microsoft.applicationinsights.internal.util.LocalStringsUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Created by gupele on 12/14/2016.
*/
final class DefaultQuickPulseCoordinator implements QuickPulseCoordinator, Runnable {

private static final Logger logger = LoggerFactory.getLogger(DefaultQuickPulseCoordinator.class);
private String qpsServiceRedirectedEndpoint;
private long qpsServicePollingIntervalHintMillis;

private volatile boolean stopped = false;
private volatile boolean pingMode = true;

private final QuickPulsePingSender pingSender;
private final QuickPulseDataFetcher dataFetcher;
private final QuickPulseDataSender dataSender;

private final long waitBetweenPingsInMS;
private final long waitBetweenPostsInMS;
private final long waitOnErrorInMS;

public DefaultQuickPulseCoordinator(QuickPulseCoordinatorInitData initData) {
dataSender = initData.dataSender;
pingSender = initData.pingSender;
dataFetcher = initData.dataFetcher;

waitBetweenPingsInMS = initData.waitBetweenPingsInMS;
waitBetweenPostsInMS = initData.waitBetweenPostsInMS;
waitOnErrorInMS = initData.waitBetweenPingsInMS;
qpsServiceRedirectedEndpoint = null;
qpsServicePollingIntervalHintMillis = -1;
}

@Override
public void run() {
try {
while (!stopped) {
long sleepInMS;
if (pingMode) {
sleepInMS = ping();
} else {
sleepInMS = sendData();
}
Thread.sleep(sleepInMS);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ThreadDeath td) {
throw td;
} catch (Throwable t) {
// chomp
}
}

private long sendData() {
dataFetcher.prepareQuickPulseDataForSend(qpsServiceRedirectedEndpoint);
final QuickPulseHeaderInfo currentQuickPulseHeaderInfo = dataSender.getQuickPulseHeaderInfo();

this.handleReceivedHeaders(currentQuickPulseHeaderInfo);

switch (currentQuickPulseHeaderInfo.getQuickPulseStatus()) {
case ERROR:
pingMode = true;
return waitOnErrorInMS;

case QP_IS_OFF:
pingMode = true;
return qpsServicePollingIntervalHintMillis > 0 ? qpsServicePollingIntervalHintMillis : waitBetweenPingsInMS;

case QP_IS_ON:
return waitBetweenPostsInMS;

default:
logger.error( "Critical error while sending QP data: unknown status, aborting");
QuickPulseDataCollector.INSTANCE.disable();
stopped = true;
return 0;
}
}

private long ping() {
QuickPulseHeaderInfo pingResult = pingSender.ping(qpsServiceRedirectedEndpoint);
this.handleReceivedHeaders(pingResult);
switch (pingResult.getQuickPulseStatus()) {
case ERROR:
return waitOnErrorInMS;

case QP_IS_ON:
pingMode = false;
dataSender.startSending();
return waitBetweenPostsInMS;
case QP_IS_OFF:
return qpsServicePollingIntervalHintMillis > 0 ? qpsServicePollingIntervalHintMillis : waitBetweenPingsInMS;

default:
logger.error( "Critical error while ping QP: unknown status, aborting");
QuickPulseDataCollector.INSTANCE.disable();
stopped = true;
return 0;
}
}

private void handleReceivedHeaders(QuickPulseHeaderInfo currentQuickPulseHeaderInfo) {
String redirectLink = currentQuickPulseHeaderInfo.getQpsServiceEndpointRedirect();
if (!LocalStringsUtils.isNullOrEmpty(redirectLink)) {
qpsServiceRedirectedEndpoint = redirectLink;
}

long newPollingInterval = currentQuickPulseHeaderInfo.getQpsServicePollingInterval();
if (newPollingInterval > 0) {
qpsServicePollingIntervalHintMillis = newPollingInterval;
}
}

public void stop() {
stopped = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* ApplicationInsights-Java
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the ""Software""), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

package com.microsoft.applicationinsights.internal.quickpulse;

import java.net.URISyntaxException;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;

import com.google.common.annotations.VisibleForTesting;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.internal.util.LocalStringsUtils;
import com.microsoft.applicationinsights.internal.util.PropertyHelper;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Created by gupele on 12/12/2016.
*/
final class DefaultQuickPulseDataFetcher implements QuickPulseDataFetcher {

private static final Logger logger = LoggerFactory.getLogger(DefaultQuickPulseDataFetcher.class);

private final ArrayBlockingQueue<HttpPost> sendQueue;
private final TelemetryConfiguration config;
private final String ikey;
private final QuickPulseNetworkHelper networkHelper = new QuickPulseNetworkHelper();
private String postPrefix;
private final String sdkVersion;

public DefaultQuickPulseDataFetcher(ArrayBlockingQueue<HttpPost> sendQueue, TelemetryConfiguration config, String machineName,
String instanceName, String roleName, String quickPulseId) {
this(sendQueue, config, null, machineName, instanceName, roleName, quickPulseId);
}

@Deprecated
public DefaultQuickPulseDataFetcher(final ArrayBlockingQueue<HttpPost> sendQueue, final String ikey, final String machineName, final String instanceName, final String quickPulseId) {
this(sendQueue, null, ikey, machineName, instanceName, quickPulseId);
}

private DefaultQuickPulseDataFetcher(ArrayBlockingQueue<HttpPost> sendQueue, TelemetryConfiguration config, String ikey, String machineName,
String instanceName, String roleName, String quickPulseId) {
this.sendQueue = sendQueue;
this.config = config;
this.ikey = ikey;
sdkVersion = getCurrentSdkVersion();
final StringBuilder sb = new StringBuilder();

if (!LocalStringsUtils.isNullOrEmpty(roleName)) {
roleName = "\"" + roleName + "\"";
}

sb.append("[{");
formatDocuments(sb);
sb.append("\"Instance\": \"").append(instanceName).append("\",");
sb.append("\"InstrumentationKey\": \"").append(ikey).append("\",");
sb.append("\"InvariantVersion\": ").append(QuickPulse.QP_INVARIANT_VERSION).append(",");
sb.append("\"MachineName\": \"").append(machineName).append("\",");
sb.append("\"RoleName\": ").append(roleName).append(",");
sb.append("\"StreamId\": \"").append(quickPulseId).append("\",");
postPrefix = sb.toString();
if (logger.isTraceEnabled()) {
try {
logger.trace("{} using endpoint {}", DefaultQuickPulseDataFetcher.class.getSimpleName(), QuickPulseNetworkHelper.getQuickPulseEndpoint(config));
} catch (URISyntaxException use) {
logger.error("{} using invalid endpoint: {}", DefaultQuickPulsePingSender.class.getSimpleName(), use.getMessage());
}
}
}

/**
* Get SDK Version from properties
* @return current SDK version
*/
/* Visible for testing */ String getCurrentSdkVersion() {
return PropertyHelper.getQualifiedSdkVersionString();
}

@Override
public void prepareQuickPulseDataForSend(String redirectedEndpoint) {
try {
QuickPulseDataCollector.FinalCounters counters = QuickPulseDataCollector.INSTANCE.getAndRestart();

final Date currentDate = new Date();
final String endpointPrefix = LocalStringsUtils.isNullOrEmpty(redirectedEndpoint) ? QuickPulseNetworkHelper.getQuickPulseEndpoint(config) : redirectedEndpoint;
final HttpPost request = networkHelper.buildRequest(currentDate, this.getEndpointUrl(endpointPrefix));

final ByteArrayEntity postEntity = buildPostEntity(counters);

request.setEntity(postEntity);

if (!sendQueue.offer(request)) {
logger.trace("Quick Pulse send queue is full");
}
} catch (ThreadDeath td) {
throw td;
} catch (Throwable e) {
try {
logger.error("Quick Pulse failed to prepare data for send", e);
} catch (ThreadDeath td) {
throw td;
} catch (Throwable t2) {
// chomp
}
}
}

@VisibleForTesting
String getEndpointUrl(String endpointPrefix) {
return endpointPrefix + "/QuickPulseService.svc/post?ikey=" + getInstrumentationKey();
}

private String getInstrumentationKey() {
if (config != null) {
return config.getInstrumentationKey();
} else {
return ikey;
}
}

private ByteArrayEntity buildPostEntity(QuickPulseDataCollector.FinalCounters counters) {
StringBuilder sb = new StringBuilder(postPrefix);
formatMetrics(counters, sb);
sb.append("\"Timestamp\": \"\\/Date(");
long ms = System.currentTimeMillis();
sb.append(ms);
sb.append(")\\/\",");
sb.append("\"Version\": \"");
sb.append(sdkVersion);
sb.append("\"}]");
return new ByteArrayEntity(sb.toString().getBytes());
}

private void formatDocuments(StringBuilder sb) {
sb.append("\"Documents\": [] ,");
}

private void formatSingleMetric(StringBuilder sb, String metricName, double metricValue, int metricWeight, Boolean includeComma) {
String comma = includeComma ? "," : "";
sb.append(String.format("{\"Name\": \"%s\",\"Value\": %s,\"Weight\": %s}%s", metricName, metricValue, metricWeight, comma));
}

private void formatSingleMetric(StringBuilder sb, String metricName, long metricValue, int metricWeight, Boolean includeComma) {
String comma = includeComma ? "," : "";
sb.append(String.format("{\"Name\": \"%s\",\"Value\": %s,\"Weight\": %s}%s", metricName, metricValue, metricWeight, comma));
}

private void formatMetrics(QuickPulseDataCollector.FinalCounters counters, StringBuilder sb) {
sb.append("\"Metrics\":[");
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Requests\\/Sec", counters.requests, 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Request Duration", counters.requestsDuration, (int)counters.requests, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Requests Failed\\/Sec", counters.unsuccessfulRequests, 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Requests Succeeded\\/Sec", (counters.requests - counters.unsuccessfulRequests), 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Dependency Calls\\/Sec", counters.rdds, 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Dependency Call Duration", counters.rddsDuration, (int)counters.rdds, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Dependency Calls Failed\\/Sec", counters.unsuccessfulRdds, 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Dependency Calls Succeeded\\/Sec", counters.rdds - counters.unsuccessfulRdds, 1, true);
formatSingleMetric(sb, "\\\\ApplicationInsights\\\\Exceptions\\/Sec", counters.exceptions, 1, true);
formatSingleMetric(sb, "\\\\Memory\\\\Committed Bytes", counters.memoryCommitted, 1, true);
formatSingleMetric(sb, "\\\\Processor(_Total)\\\\% Processor Time", counters.cpuUsage, 1, false);
sb.append("],");
}
}
Loading