diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/InspectitContextImpl.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/InspectitContextImpl.java index babb17039b..fdc5784359 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/InspectitContextImpl.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/InspectitContextImpl.java @@ -17,6 +17,7 @@ import rocks.inspectit.ocelot.core.tags.TagUtils; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -474,7 +475,8 @@ public void close() { } // Write browser propagation data to storage - Map browserPropagationData = getBrowserPropagationData(dataOverwrites); + Map propagationData = getDataAsStream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map browserPropagationData = getBrowserPropagationData(propagationData); if(browserPropagationDataStorage != null) browserPropagationDataStorage.writeData(browserPropagationData); diff --git a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java index 3d715e7288..5a0f46014d 100644 --- a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java +++ b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java @@ -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; @@ -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(); @@ -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(); @@ -96,6 +95,7 @@ void verifyDataHasBeenOverwritten() { Map oldData = new HashMap<>(); oldData.put("keyA", "value0"); dataStorage.writeData(oldData); + InspectitContextImpl ctxA = InspectitContextImpl.createFromCurrent(Collections.emptyMap(), propagation, false); ctxA.readDownPropagationHeaders(headers); ctxA.makeActive(); @@ -123,6 +123,7 @@ void verifyAttributeCountLimit() { Map 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(); @@ -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(); @@ -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 @@ -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 data = new HashMap<>(); data.put("keyA", "valueA"); diff --git a/inspectit-ocelot-documentation/docs/instrumentation/rules.md b/inspectit-ocelot-documentation/docs/instrumentation/rules.md index dd430750c6..0278265e34 100644 --- a/inspectit-ocelot-documentation/docs/instrumentation/rules.md +++ b/inspectit-ocelot-documentation/docs/instrumentation/rules.md @@ -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). @@ -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 diff --git a/inspectit-ocelot-documentation/docs/tags/tags-exporters.md b/inspectit-ocelot-documentation/docs/tags/tags-exporters.md index 6954f1e285..0d87743d6f 100644 --- a/inspectit-ocelot-documentation/docs/tags/tags-exporters.md +++ b/inspectit-ocelot-documentation/docs/tags/tags-exporters.md @@ -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. @@ -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 @@ -41,7 +41,10 @@ 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. @@ -49,7 +52,16 @@ The HTTP-exporter can only store a specific amount of sessions, which can be con 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. @@ -135,7 +147,7 @@ function putTags() { } ``` -### OpenAPI documentation +### OpenAPI Documentation Below you can see the OpenAPI documentation for the REST-API in YAML-format: