Skip to content

Commit

Permalink
Closes #1221 - Implement flag for disabling the instrumentation (#1222)
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusoe authored Sep 30, 2021
1 parent 26b8b03 commit 3b7216d
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*.log
*.zip
*.swp
*.DS_STORE
.DS_STORE

### STS ###
.apt_generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
@NoArgsConstructor
public class InstrumentationSettings {

/**
* Master switch for the instrumentation. Defines whether the agent is instrumenting classes. In case this is
* disabled, classes will not be instrumented, thus, any tracing and metric recording will NOT be possible.
* Only the instrumentation is affected by this flag.
* If this is set to false, it has the same effect as if no rules were defined.
*/
private boolean enabled = true;

/**
* The configuration of internal parameters regarding the instrumentation process.
*/
Expand Down Expand Up @@ -59,7 +67,6 @@ public class InstrumentationSettings {
@NotNull
private Map<@NotBlank String, @Valid GenericActionSettings> actions = Collections.emptyMap();


/**
* The configuration of the defined scopes. The map's key represents an unique id for the related instrumentation scope.
*/
Expand Down Expand Up @@ -93,13 +100,13 @@ public class InstrumentationSettings {
*/
public void performValidation(InspectitConfig container, ViolationBuilder vios) {
Set<String> declaredMetrics = container.getMetrics().getDefinitions().keySet();
rules.forEach((name, r) ->
r.performValidation(this, declaredMetrics, vios.atProperty("rules").atProperty(name)));
rules.forEach((name, r) -> r.performValidation(this, declaredMetrics, vios.atProperty("rules")
.atProperty(name)));

HashSet<String> verified = new HashSet<>();
// Verifies that scopes that are defined as Exclude also exist
scopes.forEach((name, s) ->
s.performValidation(name, this, vios.atProperty("scopes").atProperty(name), verified));
scopes.forEach((name, s) -> s.performValidation(name, this, vios.atProperty("scopes")
.atProperty(name), verified));
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
inspectit:
# settings regarding the instrumentation performed on the target application
instrumentation:
# Master switch for the instrumentation. Defines whether the agent is instrumenting classes. In case this is
# disabled, classes will not be instrumented, thus, any tracing and metric recording will NOT be possible.
# Only the instrumentation is affected by this flag.
# If this is set to false, it has the same effect as if no rules were defined.
enabled: true

# settings for special sensors
special:
# enables or disables the instrumentation to ensure context propagation across java.util.concurrent.Executor instances
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ public class InstrumentationConfigurationResolver {
@Autowired
private PropagationMetaDataResolver propagationMetaDataResolver;


/**
* Holds the currently active instrumentation configuration.
*/
Expand All @@ -79,14 +78,14 @@ private void init() {
* for the given class.
*
* @param clazz the class for which the configuration shal lbe queried
*
* @return the configuration or {@link ClassInstrumentationConfiguration#NO_INSTRUMENTATION} if this class should not be instrumented
*/
public ClassInstrumentationConfiguration getClassInstrumentationConfiguration(Class<?> clazz) {
val config = currentConfig;
InstrumentationConfiguration config = currentConfig;
try {
if (isIgnoredClass(clazz, config)) {
if (!config.getSource().isEnabled() || isIgnoredClass(clazz, config)) {
return ClassInstrumentationConfiguration.NO_INSTRUMENTATION;

} else {
TypeDescription description = TypeDescription.ForLoadedType.of(clazz);
Set<SpecialSensor> activeSensors = specialSensors.stream()
Expand All @@ -110,6 +109,7 @@ public ClassInstrumentationConfiguration getClassInstrumentationConfiguration(Cl
* Finds out for each method of the given class which rules apply and builds a {@link MethodHookConfiguration} for each instrumented method.
*
* @param clazz the class to check
*
* @return a map mapping hook configurations to the methods which they should be applied on.
*/
public Map<MethodDescription, MethodHookConfiguration> getHookConfigurations(Class<?> clazz) {
Expand All @@ -125,16 +125,16 @@ public Map<MethodDescription, MethodHookConfiguration> getHookConfigurations(Cla
Map<MethodDescription, MethodHookConfiguration> result = new HashMap<>();
for (MethodDescription method : type.getDeclaredMethods()) {
Set<InstrumentationRule> rulesMatchingOnMethod = narrowedRules.stream()
.filter(rule -> rule.getScopes().stream()
.filter(rule -> rule.getScopes()
.stream()
.anyMatch(scope -> scope.getMethodMatcher().matches(method)))
.collect(Collectors.toSet());
if (!rulesMatchingOnMethod.isEmpty()) {
try {
Set<InstrumentationRule> matchedAndIncludedRules = resolveIncludes(config, rulesMatchingOnMethod);
result.put(method, hookResolver.buildHookConfiguration(config, matchedAndIncludedRules));
} catch (Exception e) {
log.error("Could not build hook for {} of class {}",
CoreUtils.getSignature(method), clazz.getName(), e);
log.error("Could not build hook for {} of class {}", CoreUtils.getSignature(method), clazz.getName(), e);
}
}
}
Expand All @@ -147,14 +147,14 @@ public Map<MethodDescription, MethodHookConfiguration> getHookConfigurations(Cla
}
return Collections.emptyMap();


}

/**
* For a given collection of rules, a set containing these rules and all rules included (transitively) by them are returned.
*
* @param config the configuration which is used to resolve rule names to rules
* @param rules the initial collection of rules
*
* @return the set of the initial rules plus their includes
*/
@VisibleForTesting
Expand All @@ -181,16 +181,16 @@ private void addWithIncludes(InstrumentationRule rule, InstrumentationConfigurat
*
* @param typeDescription the class which are the rules targeting
* @param config the configuration which is used as basis for the rules
*
* @return Returns a set containing rules with scopes targeting only the given type.
*/
private Set<InstrumentationRule> getNarrowedRulesFor(TypeDescription typeDescription, InstrumentationConfiguration config) {
return config.getRules().stream()
.map(rule -> Pair.of(
rule,
rule.getScopes()
.stream()
.filter(s -> s.getTypeMatcher().matches(typeDescription))
.collect(Collectors.toSet())))
return config.getRules()
.stream()
.map(rule -> Pair.of(rule, rule.getScopes()
.stream()
.filter(s -> s.getTypeMatcher().matches(typeDescription))
.collect(Collectors.toSet())))
.filter(p -> !p.getRight().isEmpty())
.map(p -> p.getLeft().toBuilder().clearScopes().scopes(p.getRight()).build())
.collect(Collectors.toSet());
Expand Down Expand Up @@ -225,6 +225,7 @@ private InstrumentationConfiguration resolveConfiguration(InspectitConfig config
*
* @param clazz the class to check
* @param config configuration to check for
*
* @return true, if the class is ignored (=it should not be instrumented)
*/
@VisibleForTesting
Expand Down Expand Up @@ -254,15 +255,21 @@ boolean isIgnoredClass(Class<?> clazz, InstrumentationConfiguration config) {

String name = clazz.getName();

boolean isIgnored = config.getSource().getIgnoredPackages().entrySet().stream()
boolean isIgnored = config.getSource()
.getIgnoredPackages()
.entrySet()
.stream()
.filter(Map.Entry::getValue)
.anyMatch(e -> name.startsWith(e.getKey()));
if (isIgnored) {
return true;
}

if (clazz.getClassLoader() == null) {
boolean isIgnoredOnBootstrap = config.getSource().getIgnoredBootstrapPackages().entrySet().stream()
boolean isIgnoredOnBootstrap = config.getSource()
.getIgnoredBootstrapPackages()
.entrySet()
.stream()
.filter(Map.Entry::getValue)
.anyMatch(e -> name.startsWith(e.getKey()));
if (isIgnoredOnBootstrap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ void setupInstrumentationMock() {
lenient().when(settings.getIgnoredBootstrapPackages()).thenReturn(Collections.emptyMap());
lenient().when(settings.getIgnoredPackages()).thenReturn(Collections.emptyMap());
lenient().when(settings.isExcludeLambdas()).thenReturn(true);
lenient().when(settings.isEnabled()).thenReturn(true);

config = InstrumentationConfiguration.builder().source(settings).build();
}
Expand Down Expand Up @@ -114,6 +115,20 @@ public void narrowingRule() throws IllegalAccessException {
.containsExactly(ElementMatchers.nameEndsWithIgnoreCase("object"), ElementMatchers.any());
}

@Test
public void disabledInstrumentation() throws IllegalAccessException {
lenient().when(settings.isEnabled()).thenReturn(false);

InstrumentationScope scope = new InstrumentationScope(ElementMatchers.any(), ElementMatchers.any());
InstrumentationRule rule = InstrumentationRule.builder().name("name").scope(scope).build();
config = InstrumentationConfiguration.builder().source(settings).rule(rule).build();
FieldUtils.writeDeclaredField(resolver, "currentConfig", config, true);

ClassInstrumentationConfiguration result = resolver.getClassInstrumentationConfiguration(Object.class);

assertThat(result).isNotNull();
assertThat(result).isSameAs(ClassInstrumentationConfiguration.NO_INSTRUMENTATION);
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ id: Breaking Changes
title: Breaking Changes
---

## Breaking changes in 1.12.2

There are no breaking changes for version 1.12.2.

## Breaking changes in 1.12.1

There are no breaking changes for version 1.12.1.
Expand All @@ -11,12 +15,10 @@ There are no breaking changes for version 1.12.1.

There are no breaking changes for version 1.12.0.


## Breaking changes in 1.11.1

There are no breaking changes for version 1.11.1.


## Breaking changes in 1.11.0

There are no breaking changes for version 1.11.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,37 @@ Scopes are then used by [rules](instrumentation/rules.md). While scopes define w
For the definition of rules, [actions](instrumentation/rules.md#actions) are a key concept.
Long story short: actions allow you to specify _Java snippets in your configuration_ which will be executed to extract any data you want. This can be performance data such as the response time or any kind of business data, e.g. the shopping cart size.

> All instrumentation settings can be changed without restarting the application! They can even be changed while a previous instrumentation is still in progress. In this case the inspectIT Ocelot agent will automatically switch to the new instrumentation as soon as the configuration is loaded.
:::tip
All instrumentation settings can be changed without restarting the application!
They can even be changed while a previous instrumentation is **still in progress**. In this case the inspectIT Ocelot agent will automatically switch to the new instrumentation as soon as the configuration is loaded.
:::

## Disabling the Instrumentation

Since version `1.12.2`, the agent provides the option to disable its instrumentation process.
When disabled, the agent will **revert** any instrumentations done and restore the original state of all instrumented classes.
This would be like having no single rule (also none in the agent's default configuration) defined.
This results in no more runtime data (traces and metrics) being recorded from executed methods.

```yaml
inspectit:
instrumentation:
enabled: true
```
:::note
Metrics which are not based on instrumentations like e.g. JVM internals (thread statistics, memory statistics, ...) are **not affected** by this option.
:::
## Naming Convention
Differentiating between rules, actions and scopes as well as differentiating between keys and values
in the configuration can be tricky from time to time.
Therefore, to increase readability of your configuration files the following naming convention is recommended:
* Scope names always start with "s_", e.g. `s_my_scope`.
* Action names always start with "a_", e.g. `a_my_action`.
* Rule names always start with "r_", e.g. `r_my_rule`.
* Scope names always start with `s_`, e.g. `s_my_scope`.
* Action names always start with `a_`, e.g. `a_my_action`.
* Rule names always start with `r_`, e.g. `r_my_rule`.
* Context variables names should start with `d_`, e.g. `d_transaction_name`.
* Fields which are defined by the user should always be put in single quotations marks, e.g. `input: 'my_input'`. This rule also applies to keys which
can be entirely defined by the user, for example when defining the name of a custom action or attribute names.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ id: process
title: Instrumentation Process
---

The approach inspectIT Ocelot takes for instrumenting is fundamentally different from the approach of most other JVM instrumentation agents. InspectIT Ocelot does *not* instrument classes when they are loaded, the instrumentation is performed purely asynchronous in the background.
The approach inspectIT Ocelot takes for instrumenting is fundamentally different from the approach of most other JVM instrumentation agents.
InspectIT Ocelot does *not* instrument classes when they are loaded, the instrumentation is performed purely asynchronous in the background.

In this background task inspectIT Ocelot essentially looks at every loaded class and performs an instrumentation if required by the active configuration. Hereby, the agent manages the classes he has to analyze in a queue. This queue is processed in batches to ensure that no CPU resources are blocked if they are required by the instrumented application. The batching is configurable using the `internal` settings:

Expand Down

0 comments on commit 3b7216d

Please sign in to comment.