Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HeartBeat Feature Implementation #631

Merged
merged 26 commits into from
Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bb3576b
Initial heartbeat feature commit
dhaval24 Mar 27, 2018
8553cbd
adding required interfaces and main class to spin up Metric thread
dhaval24 Mar 27, 2018
1a0fa4e
some refactoring, implementing initialize for HeartBeatProvider, addi…
dhaval24 Mar 28, 2018
edef438
some refactoring adding properties collection for webapps
dhaval24 Mar 29, 2018
aca4383
fix unlocking
dhaval24 Mar 29, 2018
607b656
removing unnecessary variable creation, initial test cases added
dhaval24 Mar 30, 2018
f147ff9
adding more tests
dhaval24 Mar 30, 2018
3430d26
added support for heartbeat module to be added as default, refactored…
dhaval24 Mar 30, 2018
efa1ff2
added more test cases, some refactoring in tests, added javadocs
dhaval24 Mar 31, 2018
6bd5570
Merge branch 'master' into HeartBeat
dhaval24 Mar 31, 2018
baba3b9
updating test to remove try catch and adding throws
dhaval24 Mar 31, 2018
883fd72
updated changelog, added somemore java docs, renamed method to be mor…
dhaval24 Mar 31, 2018
912bae6
fix threadpool shutdown timing, add missing shutdown for a pool, chan…
dhaval24 Apr 1, 2018
51f0258
fixing proper locking
dhaval24 Apr 2, 2018
704a11f
removing caching of environment variable to prevent hotswap data read…
dhaval24 Apr 4, 2018
fd168aa
Heartbeat updates to accomodate autoconfig in SpringBoot
dhaval24 Apr 10, 2018
4c8d54e
Optimizig disabling heartbeat to improve performance, renaming isEnab…
dhaval24 Apr 12, 2018
6b8fe30
done some refactoring and polishing, addressed review comments
dhaval24 Apr 13, 2018
c31ff35
fix a broken test and polish
dhaval24 Apr 14, 2018
d844011
some behavior change in setProperties, polish docs, addressed remaini…
dhaval24 Apr 14, 2018
c8cff0e
addressing leftover
dhaval24 Apr 16, 2018
63a6217
done some refactoring, removed extra logging and polish
dhaval24 Apr 17, 2018
1e4ad3e
Renaming, removing volatile
dhaval24 Apr 17, 2018
d33177c
mockito for tests, removed redundant lock, add containsKey, public co…
dhaval24 Apr 17, 2018
757f0d1
refactor to use factoryMethod for single thread executor, moved shutd…
dhaval24 Apr 18, 2018
7531583
some minor changes and reverting gradle files changed
dhaval24 Apr 20, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
* <h1>Base Heartbeat Property Provider</h1>
*
* <p>
* This class is a concrete implementation of {@link com.microsoft.applicationinsights.internal.heartbeat.HeartBeatDefaultPayloadProviderInterface}
* This class is a concrete implementation of {@link HeartBeatPayloadProviderInterface}
* It enables setting SDK Metadata to heartbeat payload.
* </p>
*
* @author Dhaval Doshi
* @since 03-30-2018
*/
public class BaseDefaultHeartbeatPropertyProvider implements HeartBeatDefaultPayloadProviderInterface {
public class DefaultHeartbeatPropertyProvider implements HeartBeatPayloadProviderInterface {

/**
* Collection holding default properties for this default provider.
Expand All @@ -38,9 +38,17 @@ public class BaseDefaultHeartbeatPropertyProvider implements HeartBeatDefaultPay
/**
* Name of this provider.
*/
private final String name = "Base";
private final String name = "Default";

public BaseDefaultHeartbeatPropertyProvider() {
private final String JRE_VERSION = "jreVersion";

private final String SDK_VERSION = "sdkVersion";

private final String OS_VERSION = "osVersion";

private final String PROCESS_SESSION_ID = "processSessionId";

public DefaultHeartbeatPropertyProvider() {
defaultFields = new HashSet<>();
initializeDefaultFields(defaultFields);
}
Expand All @@ -51,7 +59,7 @@ public String getName() {
}

@Override
public boolean isKeyWord(String keyword) {
public boolean isKeyword(String keyword) {
return defaultFields.contains(keyword);
}

Expand All @@ -60,27 +68,26 @@ public Callable<Boolean> setDefaultPayload(final List<String> disableFields,
final HeartBeatProviderInterface provider) {
return new Callable<Boolean>() {

// using volatile here to avoid caching in threads.
volatile boolean hasSetValues = false;
volatile Set<String> enabledProperties = MiscUtils.except(defaultFields, disableFields);
@Override
public Boolean call() {
boolean hasSetValues = false;
for (String fieldName : enabledProperties) {
try {
switch (fieldName) {
case "jdkVersion":
provider.addHeartBeatProperty(fieldName, getJdkVersion(), true);
case JRE_VERSION:
provider.addHeartBeatProperty(fieldName, getJreVersion(), true);
hasSetValues = true;
break;
case "sdk-version":
case SDK_VERSION:
provider.addHeartBeatProperty(fieldName, getSdkVersion(), true);
hasSetValues = true;
break;
case "osType":
provider.addHeartBeatProperty(fieldName, getOsType(), true);
case OS_VERSION:
provider.addHeartBeatProperty(fieldName, getOsVersion(), true);
hasSetValues = true;
break;
case "processSessionId":
case PROCESS_SESSION_ID:
provider.addHeartBeatProperty(fieldName, getProcessSessionId(), true);
hasSetValues = true;
break;
Expand Down Expand Up @@ -109,17 +116,17 @@ private void initializeDefaultFields(Set<String> defaultFields) {
if (defaultFields == null) {
defaultFields = new HashSet<>();
}
defaultFields.add("jdkVersion");
defaultFields.add("sdk-version");
defaultFields.add("osType");
defaultFields.add("processSessionId");
defaultFields.add(JRE_VERSION);
defaultFields.add(SDK_VERSION);
defaultFields.add(OS_VERSION);
defaultFields.add(PROCESS_SESSION_ID);
}

/**
* Gets the JDK version being used by the application
* @return String representing JDK Version
*/
private String getJdkVersion() {
private String getJreVersion() {
return System.getProperty("java.version");
}

Expand All @@ -145,7 +152,7 @@ private String getSdkVersion() {
* Gets the OS version on which application is running.
* @return String representing OS version
*/
private String getOsType() {
private String getOsVersion() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want the OS version? If so, you should explore the difference between os.name and os.version (i.e. os.name does not contain version information).

I also found a few articles stating that os.name doesn't have the correct value when on Windows 10. Perhaps there is a different way to find this information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can amend it, if there is a better way though I am not aware of one. This is meta data which is not very very vital though.

return System.getProperty("os.name");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ public void setHeartBeatEnabled(boolean heartBeatEnabled) {

@Override
public void initialize(TelemetryConfiguration configuration) {
if (!isInitialized) {
if (!isInitialized && isHeartBeatEnabled()) {
synchronized (lock) {
if (!isInitialized) {
if (!isInitialized && isHeartBeatEnabled()) {
this.heartBeatProviderInterface.initialize(configuration);
InternalLogger.INSTANCE.info("heartbeat is enabled");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: Heartbeat is initialized instead of enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed this variable in the latest comment. I agree it was misleading.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go one step further:
Why initialize the provider if it's disabled? I would refactor so the responsibility of "being enabled" lives in this class. If this module is disabled, we don't need any of its downstream dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I should probably add this check. It is usless to perform operation of initialization if the module is disabled. I think this should checked in conjucation with isHeartBeatEnabled. I will do the updates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this change will work. isHeartbeatEnabled() uses the heartbeatProviderInterface, but the provider doesn't get the config values until it's initialized. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have already discussed this. This is addressed

isInitialized = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
* This interface is used to set the default payload of a provider and defines implementation for
* some helper methods to assist it.
*
* The default concrete implementations are {@link com.microsoft.applicationinsights.internal.heartbeat.BaseDefaultHeartbeatPropertyProvider}
* and {@link com.microsoft.applicationinsights.internal.heartbeat.WebAppsDefaultHeartbeatProvider}
* The default concrete implementations are {@link DefaultHeartbeatPropertyProvider}
* and {@link WebAppsHeartbeatProvider}
* </p>
*
* @author Dhaval Doshi
* @since 03-30-2018
*/
public interface HeartBeatDefaultPayloadProviderInterface {
public interface HeartBeatPayloadProviderInterface {

/**
* Returns the name of the heartbeat provider.
Expand All @@ -30,7 +30,7 @@ public interface HeartBeatDefaultPayloadProviderInterface {
* @param keyword string to test
* @return true if input string is reserved keyword
*/
boolean isKeyWord(String keyword);
boolean isKeyword(String keyword);

/**
* Returns a callable which can be executed to set the payload based on the parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.internal.logger.InternalLogger;
import com.microsoft.applicationinsights.internal.shutdown.Stoppable;
import com.microsoft.applicationinsights.internal.util.ThreadPoolUtils;
import com.microsoft.applicationinsights.telemetry.MetricTelemetry;
import com.microsoft.applicationinsights.telemetry.Telemetry;
import java.util.ArrayList;
Expand All @@ -15,6 +16,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand Down Expand Up @@ -69,12 +71,12 @@ public class HeartBeatProvider implements HeartBeatProviderInterface, Stoppable
/**
* ThreadPool used for adding properties to concurrent dictionary
*/
private ExecutorService executorService = Executors.newCachedThreadPool();
private ExecutorService propertyUpdateService;

/**
* Threadpool used to send data heartbeat telemetry
*/
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
private ScheduledExecutorService heartBeatSenderService;

/**
* Heartbeat enabled state
Expand All @@ -88,6 +90,8 @@ public HeartBeatProvider() {
this.heartbeatProperties = new ConcurrentHashMap<>();
this.isEnabled = true;
this.heartbeatsSent = new AtomicLong(0);
this.propertyUpdateService = Executors.newCachedThreadPool(ThreadPoolUtils.createDaemonThreadFactory(HeartBeatProvider.class));
Copy link
Contributor

@littleaj littleaj Apr 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes these two thread pools indistinguishable. You may need another method in ThreadPool utils to specify a custom name. #Resolved

this.heartBeatSenderService = Executors.newScheduledThreadPool(1, ThreadPoolUtils.createDaemonThreadFactory(HeartBeatProvider.class));
}

@Override
Expand All @@ -111,12 +115,10 @@ public void initialize(TelemetryConfiguration configuration) {

//Submit task to set properties to dictionary using separate thread. we do not wait for the
//results to come out as some I/O bound properties may take time.
executorService.submit(HeartbeatDefaultPayload.populateDefaultPayload(getExcludedHeartBeatProperties(),
propertyUpdateService.submit(HeartbeatDefaultPayload.populateDefaultPayload(getExcludedHeartBeatProperties(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which properties are I/O bound? I wasn't able to find any. We may be able to remove the thread pool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no properties which are IO bound today. But later on there will be when we will query ARM to get VM ID. I would rather keep it.

getExcludedHeartBeatPropertyProviders(), this));



scheduledExecutorService.scheduleAtFixedRate(heartBeatPulse(), interval, interval, TimeUnit.SECONDS);
heartBeatSenderService.scheduleAtFixedRate(heartBeatPulse(), interval, interval, TimeUnit.SECONDS);
}
}

Expand All @@ -133,10 +135,10 @@ public boolean addHeartBeatProperty(String propertyName, String propertyValue,
payload.setPayloadValue(propertyValue);
heartbeatProperties.put(propertyName, payload);
isAdded = true;
InternalLogger.INSTANCE.trace("added heartbeat property");
InternalLogger.INSTANCE.trace("added heartbeat property %s - %s", propertyName, propertyValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyphen is an interesting choice. Usually you see %s:%s or %s=%s to indicate key=value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nit: I think should be fine :)

}
else {
throw new Exception("heartbeat property cannot be added twice");
throw new Exception("heartbeat property cannot be added twice. Please use setHeartBeatProperty instead to modify the value");
Copy link
Contributor

@littleaj littleaj Apr 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a more specific Exception subclass. #Resolved

Copy link
Contributor Author

@dhaval24 dhaval24 Apr 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice to have but I don't think this should block the PR. #Resolved

}
} catch (Exception e) {
InternalLogger.INSTANCE.warn("Failed to add the property %s value %s, stack trace is : %s," ,
Expand All @@ -156,17 +158,21 @@ public boolean setHeartBeatProperty(String propertyName, String propertyValue,
boolean setResult = false;
if (!StringUtils.isEmpty(propertyName)) {
try {
if (heartbeatProperties.containsKey(propertyName) && !HeartbeatDefaultPayload.isDefaultKeyWord(propertyName)) {

if (!heartbeatProperties.containsKey(propertyName)) {
setResult = false;
throw new Exception("heartbeat property cannot be set without adding it first");
} else if (HeartbeatDefaultPayload.isDefaultKeyword(propertyName)) {
setResult = false;
throw new Exception("heartbeat beat property specified is a reserved property");
}
else {
HeartBeatPropertyPayload payload = new HeartBeatPropertyPayload();
payload.setHealthy(isHealthy);
payload.setPayloadValue(propertyValue);
heartbeatProperties.put(propertyName, payload);
setResult = true;
}
else {
throw new Exception("heartbeat property cannot be set without adding it first, or heartbeat"
+ "beat property specified is a reserved property");
}
}
catch (Exception e) {
InternalLogger.INSTANCE.warn("failed to set heartbeat property name %s, value %s, "
Expand Down Expand Up @@ -228,11 +234,11 @@ public void setExcludedHeartBeatProperties(List<String> excludedHeartBeatPropert

@Override
public void stop(long timeout, TimeUnit timeUnit) {
executorService.shutdown();
scheduledExecutorService.shutdown();
propertyUpdateService.shutdown();
heartBeatSenderService.shutdown();
try {
executorService.awaitTermination(timeout, timeUnit);
scheduledExecutorService.awaitTermination(timeout, timeUnit);
propertyUpdateService.awaitTermination(timeout, timeUnit);
heartBeatSenderService.awaitTermination(timeout, timeUnit);
}
catch (InterruptedException e) {
InternalLogger.INSTANCE.warn("unable to successfully terminate heartbeat module, "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@

public class HeartbeatDefaultPayload {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think this class is the Payload, but really its delegating things to PayloadProviders. Consider renaming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is infact a container of default heartbeat providers and I think should be fine.


private static final List<HeartBeatDefaultPayloadProviderInterface> defaultPayloadProviders =
private static final List<HeartBeatPayloadProviderInterface> defaultPayloadProviders =
new ArrayList<>();

static {
defaultPayloadProviders.add(new BaseDefaultHeartbeatPropertyProvider());
defaultPayloadProviders.add(new WebAppsDefaultHeartbeatProvider());
defaultPayloadProviders.add(new DefaultHeartbeatPropertyProvider());
defaultPayloadProviders.add(new WebAppsHeartbeatProvider());
}

public static boolean isDefaultKeyWord(String keyword) {
for (HeartBeatDefaultPayloadProviderInterface payloadProvider : defaultPayloadProviders) {
if (payloadProvider.isKeyWord(keyword)) {
public static boolean isDefaultKeyword(String keyword) {
for (HeartBeatPayloadProviderInterface payloadProvider : defaultPayloadProviders) {
if (payloadProvider.isKeyword(keyword)) {
return true;
}
}
return false;
}

public static boolean addDefaultPayLoadProvider(HeartBeatDefaultPayloadProviderInterface payloadProviderInterface) {
public static boolean addDefaultPayLoadProvider(HeartBeatPayloadProviderInterface payloadProviderInterface) {
if (payloadProviderInterface != null) {
defaultPayloadProviders.add(payloadProviderInterface);
return true;
Expand All @@ -38,7 +38,7 @@ public static Callable<Boolean> populateDefaultPayload(final List<String> disabl
volatile boolean populatedFields = false;
@Override
public Boolean call() throws Exception {
for (HeartBeatDefaultPayloadProviderInterface payloadProvider : defaultPayloadProviders) {
for (HeartBeatPayloadProviderInterface payloadProvider : defaultPayloadProviders) {
if (disabledProviders != null && disabledProviders.contains(payloadProvider.getName())) {

// skip any azure specific modules here
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
public class MiscUtils {

/**
* Returns a list which contains Items present in list2 but not in the set.
* Returns a list which contains items present in list2 but not in the set.
* @param set
* @param list2
* @return
Expand Down
Loading