Skip to content

Commit

Permalink
add down propagated data to late BrowserPropagationDataStorage (#1621)
Browse files Browse the repository at this point in the history
* add down propagated data to late BrowserPropagationDataStorage

* remove curly brackets

* adjust test and improve documentation
  • Loading branch information
EddeCCC authored Oct 19, 2023
1 parent 7d1b203 commit aeb572d
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import rocks.inspectit.ocelot.core.tags.TagUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -474,7 +475,8 @@ public void close() {
}

// Write browser propagation data to storage
Map<String, Object> browserPropagationData = getBrowserPropagationData(dataOverwrites);
Map<String, Object> propagationData = getDataAsStream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Map<String, Object> browserPropagationData = getBrowserPropagationData(propagationData);
if(browserPropagationDataStorage != null)
browserPropagationDataStorage.writeData(browserPropagationData);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import rocks.inspectit.ocelot.bootstrap.context.InternalInspectitContext;
import rocks.inspectit.ocelot.core.SpringTestBase;
import rocks.inspectit.ocelot.core.instrumentation.config.model.propagation.PropagationMetaData;
import rocks.inspectit.ocelot.core.instrumentation.context.ContextUtil;
Expand Down Expand Up @@ -49,18 +50,17 @@ void clearDataStorage() {
sessionStorage.clearDataStorages();
}


@Nested
public class WriteBrowserPropagationData {
@Test
void verifyNoDataHasBeenWritten() {
when(propagation.isPropagatedWithBrowser(any())).thenReturn(false);
BrowserPropagationDataStorage dataStorage = sessionStorage.getOrCreateDataStorage(sessionId);
InspectitContextImpl ctx = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctx.readDownPropagationHeaders(headers);
ctx.makeActive();
ctx.setData("keyA", "valueA");

BrowserPropagationDataStorage dataStorage = sessionStorage.getDataStorage(sessionId);
assertThat(dataStorage.readData()).isEmpty();

ctx.close();
Expand All @@ -72,14 +72,13 @@ void verifyNoDataHasBeenWritten() {
void verifyDataHasBeenWritten() {
when(propagation.isPropagatedWithBrowser(anyString())).thenReturn(false);
when(propagation.isPropagatedWithBrowser(eq("keyA"))).thenReturn(true);
BrowserPropagationDataStorage dataStorage = sessionStorage.getOrCreateDataStorage(sessionId);
InspectitContextImpl ctx = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctx.readDownPropagationHeaders(headers);
ctx.makeActive();
ctx.setData("keyA", "valueA");
ctx.setData("keyB", "valueB");


BrowserPropagationDataStorage dataStorage = sessionStorage.getDataStorage(sessionId);
assertThat(dataStorage.readData()).isEmpty();

ctx.close();
Expand All @@ -96,6 +95,7 @@ void verifyDataHasBeenOverwritten() {
Map<String, Object> oldData = new HashMap<>();
oldData.put("keyA", "value0");
dataStorage.writeData(oldData);

InspectitContextImpl ctxA = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctxA.readDownPropagationHeaders(headers);
ctxA.makeActive();
Expand Down Expand Up @@ -123,6 +123,7 @@ void verifyAttributeCountLimit() {
Map<String, Object> dummyMap = IntStream.rangeClosed(1, 128).boxed()
.collect(Collectors.toMap(i -> "key"+i, i -> "value"+i));
dataStorage.writeData(dummyMap);

InspectitContextImpl ctx = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctx.readDownPropagationHeaders(headers);
ctx.makeActive();
Expand All @@ -141,7 +142,6 @@ void verifyAttributeCountLimit() {
@Test
void verifyValidEntries() {
when(propagation.isPropagatedWithBrowser(any())).thenReturn(true);
BrowserPropagationDataStorage dataStorage = sessionStorage.getOrCreateDataStorage(sessionId);
InspectitContextImpl ctx = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctx.readDownPropagationHeaders(headers);
ctx.makeActive();
Expand All @@ -152,11 +152,34 @@ void verifyValidEntries() {
System.out.println(dummyKey.length() + " : " + dummyValue.length());

ctx.setData(dummyKey, dummyValue);
BrowserPropagationDataStorage dataStorage = sessionStorage.getDataStorage(sessionId);
assertThat(dataStorage.readData()).doesNotContainEntry(dummyKey, dummyValue);

ctx.close();
assertThat(dataStorage.readData()).doesNotContainEntry(dummyKey, dummyValue);
}

@Test
void verifyDataHasBeenDownPropagatedToLateDataStorage() {
when(propagation.isPropagatedWithBrowser(any())).thenReturn(true);
when(propagation.isPropagatedDownWithinJVM(any())).thenReturn(true);
InspectitContextImpl ctxA = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctxA.setData("keyA", "valueA");
ctxA.makeActive();

InspectitContextImpl ctxB = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false);
ctxB.setData(InternalInspectitContext.REMOTE_SESSION_ID, sessionId);
ctxB.setData("keyB", "valueB");
ctxB.makeActive();
ctxB.close();

BrowserPropagationDataStorage dataStorage = sessionStorage.getDataStorage(sessionId);
assertThat(dataStorage.readData()).containsEntry("keyA", "valueA");
assertThat(dataStorage.readData()).containsEntry("keyB", "valueB");

ctxA.close();
assertThat(ContextUtil.currentInspectitContext()).isNull();
}
}

@Nested
Expand All @@ -166,7 +189,7 @@ public class ReadBrowserPropagationData {
void verifyNoDownPropagation() {
when(propagation.isPropagatedWithBrowser(any())).thenReturn(true);
when(propagation.isPropagatedDownWithinJVM(any())).thenReturn(false);
when(propagation.isPropagatedDownWithinJVM(eq("remote_session_id"))).thenReturn(true);
when(propagation.isPropagatedDownWithinJVM(eq(InternalInspectitContext.REMOTE_SESSION_ID))).thenReturn(true);
BrowserPropagationDataStorage dataStorage = sessionStorage.getOrCreateDataStorage(sessionId);
Map<String, Object> data = new HashMap<>();
data.put("keyA", "valueA");
Expand Down
30 changes: 22 additions & 8 deletions inspectit-ocelot-documentation/docs/instrumentation/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ For more information, see [Tags-HTTP-Exporter](../tags/tags-exporters.md#http-ex

Up- and down propagation can also be combined: in this case then the data is attached to the control flow, meaning that it will appear as if its value will be passed around with every method call and return.

Also note, that you should only assign Java Objects into data keys and not native data types, like _boolean_.

The second aspect of propagation to consider is the _level_. Does the propagation happen within each Thread separately or is it propagated across threads? Also, what about propagation across JVM borders, e.g. one micro service calling another one via HTTP? In inspectIT Ocelot we provide the following two settings for the propagation level.

* **JVM local:** The data is propagated within the JVM, even across thread borders. The behaviour when data moves from one thread to another is defined through [Special Sensors](instrumentation/special-sensors.md).
Expand Down Expand Up @@ -382,27 +384,39 @@ If multiple conditions are given for the same action invocation, the invocation

#### Execution Order

As we can use data values for input parameters and for conditions, action invocations can depend on another. This means that a defined order on action executions within each phase is required for rules to work as expected.
As we can use data values for input parameters and for conditions, action invocations can depend on another.
This means that a defined order on action executions within each phase is required for rules to work as expected.

As all invocations are specified under the `entry` or the `exit` config options which are YAML dictionaries, the order they are given in the config file does not matter. YAML dictionaries do not maintain or define an order of their entries.
As all invocations are specified under the `entry` or the `exit` config options as well as the
`pre-entry`, `post-entry`, `pre-exit` and `post-exit` config options which are YAML dictionaries,
the order they are given in the config file does not matter. YAML dictionaries do not maintain or define an order
of their entries.

However, inspectIT Ocelot _automatically_ orders the invocations for you correctly.
For each instrumented method the agent first finds all rules which have scopes matching the given method. Afterwards, these rules get combined into one "super"-rule by simply merging the `entry`, `exit` and `metrics` phases.
For each instrumented method the agent first finds all rules which have scopes matching the given method.
Afterward, these rules get combined into one "super"-rule by simply merging the
`entry`, `exit`, `pre-entry`, `post-entry`, `pre-exit` and `post-exit` and `metrics` phases.

Within the `entry` and the `exit` phase, actions are now automatically ordered based on their dependencies.
`pre-entry` and `pre-exit` actions will be executed before their particular phase.
`post-entry` and `post-exit` actions will be executed after their particular phase.

Within the `entry` and the `exit` phase, actions are now automatically ordered based on their dependencies. E.g. if the invocation writing `data_b` uses `data_a` as input, the invocation writing `data_a` is guaranteed to be executed first! Whenever you use a data value as value for a parameter or in a condition, this will be counted as a dependency.
If for example, the invocation writing `data_b` uses `data_a` as input, the invocation writing `data_a` is guaranteed to
be executed first! Whenever you use a data value as value for a parameter or in a condition, this will be counted
as a dependency.

In some rare cases you might want to change this behaviour. E.g. in tracing context you want to store the [down propagated](#data-propagation) `span_id` in `parent_span`, before the current method assigns a new `span_id`. This can easily be realized using the `before` config option for action invocations:
In some rare cases you might want to change this behaviour. E.g. in tracing context you want to store
the [down propagated](#data-propagation) `span_id` in `parent_span`, before the current method assigns
a new `span_id`. This can easily be realized using the `pre-entry` phase for action invocations:

```yaml
#inspectit.instrumentation.rules is omitted here
'r_example_rule':
entry:
pre-entry:
'parent_span':
action: 'a_assign_value'
data-input:
'value': 'span_id'
'before':
'span_id': true
```

### Collecting Metrics
Expand Down
22 changes: 17 additions & 5 deletions inspectit-ocelot-documentation/docs/tags/tags-exporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ One GET-endpoint to expose data to external applications and one PUT-endpoint to
The server is by default started on the port `9000` and data can then be accessed or written by
calling 'http://localhost:9000/inspectit'

#### Production environment
#### Production Environment

The Tags HTTP exporter does not provide any encryption of data and does not perform any authentication.
Thus, this server should not be exposed directly to the public in a production environment.
Expand All @@ -31,7 +31,7 @@ Additionally, make sure that your firewall is not blocking the HTTP-server addre
The server performs authorization with checking, whether the request origin is allowed to access the server.
Additionally, every request has to provide a session-ID to access their own session data.

#### Session identification
#### Session Identification

Data tags will always be stored behind a provided session-ID to ensure data correlation with its browser.
The session-ID will be read from a specific request-header. The _**session-id-header**_-property in the HTTP-exporter allows
Expand All @@ -41,15 +41,27 @@ The default-instrumentation of InspectIT will check the specified _session-id-he
Thus, there is no additional configuration necessary to read session-ID from HTTP-headers.

Behind every session-ID, there is a data storage containing all data tags for this session, as long as they are enabled for browser propagation.
These data storages can only be created, by sending requests to the target application, which the Ocelot-agent is attached to.
A data storage will be created, if an HTTP request to the target application contains a session-ID inside the
_session_id_header_. If a data storage already exists for the specified
session-ID, no new data storage will be created.

You cannot create new data storages for example by pushing data into the HTTP-server by using the API.
If a request to the REST-API contains a session-ID, which does not exist in InspectIT, the API will always return 404.

The HTTP-exporter can only store a specific amount of sessions, which can be configured in the configuration server.
Sessions will be deleted after their _time-to-live_ is expired. Their time-to-live will be reset everytime a request
the HTTP-server receives a successful request.

#### Session limits
#### Non-Remote Session Initialization

It is also possible to create a data storage behind a session-ID inside the inspectIT agent, without
firstly providing it via an HTTP request to the target application.

For this you can use the data key _remote_session_id_ in the configuration server. You can set the data key via
the _a_assign_value_ action in the configuration server. After assigning a new value to the _remote_session_id_ data key,
a new data storage with the specified value as session-ID will be created.

#### Session Limits

There are some limitations for every session to prevent excessive memory consumption.
The length of the session-ID is restricted to a minimum of 16 characters and a maximum of 512 characters.
Expand Down Expand Up @@ -135,7 +147,7 @@ function putTags() {
}
```

### OpenAPI documentation
### OpenAPI Documentation

Below you can see the OpenAPI documentation for the REST-API in YAML-format:

Expand Down

0 comments on commit aeb572d

Please sign in to comment.