diff --git a/documentation/user/en/operate/reference/metrics.md b/documentation/user/en/operate/reference/metrics.md
index 8510a39e3..945d4aff4 100644
--- a/documentation/user/en/operate/reference/metrics.md
+++ b/documentation/user/en/operate/reference/metrics.md
@@ -1,95 +1,440 @@
### Metrics
- Labels used in metrics
-
- :
-
- Transaction resolution:
-
- File type:
-
- Logical file name:
-
- Transaction stage:
-
- File type:
-
- Logical file name:
-
- Entity type:
-
- File type:
-
- Logical file name:
-
- :
-
- Name of the service that was called:
-
- Name of the procedure that was called:
-
- Initiator of the call (client or server):
-
- State of the response (OK, ERROR, CANCELED):
-
- Record type:
-
- File type:
-
- Logical file name:
-
- File type:
-
- Logical file name:
-
- Transaction resolution:
-
- Entity type:
-
- Entity type:
-
- Prefetched vs. non-prefetched query:
-
- File type:
-
- Logical file name:
-
- File type:
-
- Logical file name:
- #### io.evitadb.api.grpc
+ Labels used in metrics
+
+ - entityType
+ - **Entity type**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - taskName
+ - **N/A**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - resolution
+ - **Transaction resolution**: N/A
+ - entityType
+ - **Entity type**: N/A
+ - prefetched
+ - **Prefetched vs. non-prefetched query**: N/A
+ - stage
+ - **Transaction stage**: N/A
+ - initiator
+ - **Initiator of the call (client or server)**: N/A
+ - procedureName
+ - **Name of the procedure that was called**: N/A
+ - responseState
+ - **State of the response (OK, ERROR, CANCELED)**: N/A
+ - serviceName
+ - **Name of the service that was called**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - recordType
+ - **Record type**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+ - taskName
+ - **N/A**: N/A
+ - entityType
+ - **Entity type**: N/A
+ - resolution
+ - **Transaction resolution**: N/A
+ - fileType
+ - **File type**: N/A
+ - name
+ - **Logical file name**: N/A
+
+
+
+#### API / gRPC
+ - `io_evitadb_api_grpc_procedure_called_duration_milliseconds` (HISTOGRAM)
+ - gRPC procedure called duration
+
+ Labels: initiator, procedureName, responseState, serviceName
+
+ - `io_evitadb_api_grpc_procedure_called_total` (COUNTER)
+ - gRPC procedure called total
+
+ Labels: initiator, procedureName, responseState, serviceName
+
-#### io.evitadb.cache
+#### Cache
+ - `io_evitadb_cache_anteroom_record_statistics_updated_records` (GAUGE)
+ - **Number of records waiting in anteroom**: N/A
+ - `io_evitadb_cache_anteroom_wasted_total` (COUNTER)
+ - Anteroom wasted total
-#### io.evitadb.query
+#### Query
+ - `io_evitadb_query_entity_enrich_duration_milliseconds` (HISTOGRAM)
+ - Entity enrichment duration in milliseconds
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_enrich_records` (COUNTER)
+ - **Records enriched total**: N/A
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_enrich_size_bytes` (HISTOGRAM)
+ - **Enrichment size in bytes**: N/A
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_enrich_total` (COUNTER)
+ - Entity enriched
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_fetch_duration_milliseconds` (HISTOGRAM)
+ - Entity fetch duration in milliseconds
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_fetch_records` (COUNTER)
+ - **Records fetched total**: N/A
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_fetch_size_bytes` (HISTOGRAM)
+ - **Fetched size in bytes**: N/A
+
+ Labels: entityType
+
+ - `io_evitadb_query_entity_fetch_total` (COUNTER)
+ - Entity fetched
+
+ Labels: entityType
+
+ - `io_evitadb_query_finished_duration_milliseconds` (HISTOGRAM)
+ - Query duration in milliseconds
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_estimated` (HISTOGRAM)
+ - **Estimated complexity info**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_execution_duration_milliseconds` (HISTOGRAM)
+ - **Query execution duration in milliseconds**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_fetched` (HISTOGRAM)
+ - **Records fetched total**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_fetched_size_bytes` (HISTOGRAM)
+ - **Fetched size in bytes**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_found` (HISTOGRAM)
+ - **Records found total**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_plan_duration_milliseconds` (HISTOGRAM)
+ - **Query planning duration in milliseconds**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_real` (HISTOGRAM)
+ - **Filter complexity**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_returned` (HISTOGRAM)
+ - **Records returned total**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_scanned` (HISTOGRAM)
+ - **Records scanned total**: N/A
+
+ Labels: entityType, prefetched
+
+ - `io_evitadb_query_finished_total` (COUNTER)
+ - Query finished
+
+ Labels: entityType, prefetched
+
-#### io.evitadb.session
+#### Session
+ - `io_evitadb_session_closed_active_sessions` (GAUGE)
+ - **Number of still active sessions**: N/A
+ - `io_evitadb_session_closed_duration_milliseconds` (HISTOGRAM)
+ - Session lifespan duration in milliseconds
+ - `io_evitadb_session_closed_mutations` (HISTOGRAM)
+ - **Number of mutation calls performed in session**: N/A
+ - `io_evitadb_session_closed_oldest_session_timestamp_seconds` (GAUGE)
+ - **Oldest session timestamp**: N/A
+ - `io_evitadb_session_closed_queries` (HISTOGRAM)
+ - **Number of queries performed in session**: N/A
+ - `io_evitadb_session_closed_total` (COUNTER)
+ - Sessions closed
+ - `io_evitadb_session_killed_total` (COUNTER)
+ - Sessions killed
+ - `io_evitadb_session_opened_total` (COUNTER)
+ - Sessions opened
-#### io.evitadb.storage
+#### Storage
+ - `io_evitadb_storage_catalog_statistics_entity_collections` (GAUGE)
+ - **Entity collection count**: N/A
+ - `io_evitadb_storage_catalog_statistics_occupied_disk_space_bytes` (GAUGE)
+ - **Total occupied disk space in Bytes**: N/A
+ - `io_evitadb_storage_catalog_statistics_oldest_catalog_version_timestamp_seconds` (GAUGE)
+ - **Timestamp of the oldest catalog version available in seconds**: N/A
+ - `io_evitadb_storage_data_file_compact_duration_milliseconds` (HISTOGRAM)
+ - Duration of OffsetIndex compaction.
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_data_file_compact_total` (COUNTER)
+ - OffsetIndex compaction.
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_evita_dbcomposition_changed_catalogs` (GAUGE)
+ - **Catalog count**: N/A
+ - `io_evitadb_storage_evita_dbcomposition_changed_corrupted_catalogs` (GAUGE)
+ - **Corrupted catalog count**: N/A
+ - `io_evitadb_storage_observable_output_change_occupied_memory_bytes` (GAUGE)
+ - **Memory occupied by opened output buffers in Bytes**: N/A
+ - `io_evitadb_storage_observable_output_change_opened_buffers` (GAUGE)
+ - **Number of opened output buffers**: N/A
+ - `io_evitadb_storage_observable_output_change_total` (COUNTER)
+ - ObservableOutput buffer count changes.
+ - `io_evitadb_storage_offset_index_flush_active_disk_size_bytes` (GAUGE)
+ - **Active part of disk size in Bytes**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_active_records` (GAUGE)
+ - **Number of active records**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_disk_size_bytes` (GAUGE)
+ - **Disk size in Bytes**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_duration_milliseconds` (HISTOGRAM)
+ - Duration of OffsetIndex flush to disk.
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_estimated_memory_size_bytes` (GAUGE)
+ - **Estimated memory size in Bytes**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_max_record_size` (GAUGE)
+ - **Biggest record Bytes**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_oldest_record_timestamp_seconds` (GAUGE)
+ - **Oldest record kept in memory timestamp in seconds**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_flush_total` (COUNTER)
+ - OffsetIndex flushes to disk.
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_history_kept_oldest_record_timestamp_seconds` (GAUGE)
+ - **Oldest record kept in memory timestamp in seconds**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_non_flushed_record_size_bytes` (GAUGE)
+ - **Size of records pending flush in Bytes**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_non_flushed_records` (GAUGE)
+ - **Number of records pending flush**: N/A
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_offset_index_record_type_count_changed_records` (GAUGE)
+ - **Number of records**: N/A
+
+ Labels: fileType, name, recordType
+
+ - `io_evitadb_storage_read_only_handle_closed_total` (COUNTER)
+ - Closed file read handles.
+
+ Labels: fileType, name
+
+ - `io_evitadb_storage_read_only_handle_opened_total` (COUNTER)
+ - Opened file read handles.
+
+ Labels: fileType, name
+
-#### io.evitadb.system
+#### System
+ - `io_evitadb_system_background_task_finished_total` (COUNTER)
+ - Background tasks finished
+
+ Labels: taskName
+
+ - `io_evitadb_system_background_task_started_total` (COUNTER)
+ - Background tasks started
+
+ Labels: taskName
+
+ - `io_evitadb_system_evita_started_cache_anteroom_record_limit` (GAUGE)
+ - **Maximal number of records in cache anteroom**: N/A
+ - `io_evitadb_system_evita_started_cache_reevaluation_seconds` (GAUGE)
+ - **Cache reevaluation interval in seconds**: N/A
+ - `io_evitadb_system_evita_started_cache_size_in_bytes` (GAUGE)
+ - **Maximal size of cache in Bytes**: N/A
+ - `io_evitadb_system_evita_started_compaction_file_size_threshold_bytes` (GAUGE)
+ - **Minimal file size threshold to start compaction in Bytes**: N/A
+ - `io_evitadb_system_evita_started_compaction_minimal_active_record_share_percent` (GAUGE)
+ - **Minimal share of active records in the file to start compaction in %**: N/A
+ - `io_evitadb_system_evita_started_max_threads` (GAUGE)
+ - **Maximal number of background threads**: N/A
+ - `io_evitadb_system_evita_started_max_threads_queue_size` (GAUGE)
+ - **Maximal queue size for background threads**: N/A
+ - `io_evitadb_system_evita_started_read_only_handles_limit` (GAUGE)
+ - **Maximal count of opened read-only handles**: N/A
+ - `io_evitadb_system_evita_started_session_max_inactive_age_seconds` (GAUGE)
+ - **Maximal session inactivity age in seconds**: N/A
+ - `io_evitadb_system_evita_started_short_tasks_timeout_seconds` (GAUGE)
+ - **Short running tasks timeout in seconds**: N/A
+ - `io_evitadb_system_evita_started_total` (COUNTER)
+ - Evita started total
+ - `io_evitadb_system_evita_started_transaction_max_queue_size` (GAUGE)
+ - **Maximal count of commited transactions in queue**: N/A
+ - `io_evitadb_system_evita_started_transaction_memory_buffer_limit_size_bytes` (GAUGE)
+ - **Size of off-heap memory buffer for transactions in Bytes**: N/A
+ - `io_evitadb_system_evita_started_transaction_memory_regions` (GAUGE)
+ - **Number of off-heap memory regions for transactions**: N/A
+ - `io_evitadb_system_evita_started_wal_max_file_count_kept` (GAUGE)
+ - **Maximal write-ahead log file count to keep**: N/A
+ - `io_evitadb_system_evita_started_wal_max_file_size_bytes` (GAUGE)
+ - **Maximal write-ahead log file size in Bytes**: N/A
-#### io.evitadb.transaction
+#### Transaction
+ - `io.evitadb.transaction.WalStatistics.oldestWalEntryTimestampSeconds` (GAUGE)
+ - **Oldest WAL entry timestamp**: N/A
+ - `io.evitadb.transaction.WalStatistics.oldestWalEntryTimestampSeconds` (GAUGE)
+ - **Oldest WAL entry timestamp**: N/A
+ - `io_evitadb_transaction_catalog_goes_live_duration_milliseconds` (HISTOGRAM)
+ - Catalog transition to live state duration
+ - `io_evitadb_transaction_catalog_goes_live_total` (COUNTER)
+ - Catalog goes live invocation count
+ - `io_evitadb_transaction_isolated_wal_file_closed_total` (COUNTER)
+ - Closed files for isolated WAL storage.
+ - `io_evitadb_transaction_isolated_wal_file_opened_total` (COUNTER)
+ - Opened files for isolated WAL storage.
+ - `io_evitadb_transaction_new_catalog_version_propagated_collapsed_transactions` (COUNTER)
+ - **Transactions propagated to live view.**: N/A
+ - `io_evitadb_transaction_new_catalog_version_propagated_duration_milliseconds` (HISTOGRAM)
+ - New catalog version propagation duration in milliseconds
+ - `io_evitadb_transaction_new_catalog_version_propagated_total` (COUNTER)
+ - Catalog versions propagated
+ - `io_evitadb_transaction_off_heap_memory_allocation_change_allocated_memory_bytes` (GAUGE)
+ - **Allocated memory bytes**: N/A
+ - `io_evitadb_transaction_off_heap_memory_allocation_change_used_memory_bytes` (GAUGE)
+ - **Used memory bytes**: N/A
+ - `io_evitadb_transaction_transaction_accepted_duration_milliseconds` (HISTOGRAM)
+ - Conflict resolution duration in milliseconds
+
+ Labels: resolution
+
+ - `io_evitadb_transaction_transaction_accepted_total` (COUNTER)
+ - Transactions accepted
+
+ Labels: resolution
+
+ - `io_evitadb_transaction_transaction_appended_to_wal_appended_atomic_mutations` (COUNTER)
+ - **Atomic mutations appended.**: N/A
+ - `io_evitadb_transaction_transaction_appended_to_wal_appended_wal_bytes` (COUNTER)
+ - **Size of the written WAL in Bytes.**: N/A
+ - `io_evitadb_transaction_transaction_appended_to_wal_duration_milliseconds` (HISTOGRAM)
+ - Appending transaction to shared WAL duration in milliseconds
+ - `io_evitadb_transaction_transaction_appended_to_wal_total` (COUNTER)
+ - Transactions appended to WAL
+ - `io_evitadb_transaction_transaction_finished_duration_milliseconds` (HISTOGRAM)
+ - Transaction lifespan duration in milliseconds
+
+ Labels: resolution
+
+ - `io_evitadb_transaction_transaction_finished_oldest_transaction_timestamp_seconds` (GAUGE)
+ - **Oldest transaction timestamp**: N/A
+
+ Labels: resolution
+
+ - `io_evitadb_transaction_transaction_finished_total` (COUNTER)
+ - Transactions finished
+
+ Labels: resolution
+
+ - `io_evitadb_transaction_transaction_incorporated_to_trunk_collapsed_transactions` (COUNTER)
+ - **Transactions incorporated into shared data structures.**: N/A
+ - `io_evitadb_transaction_transaction_incorporated_to_trunk_incorporation_duration_milliseconds` (HISTOGRAM)
+ - Incorporation duration in milliseconds
+ - `io_evitadb_transaction_transaction_incorporated_to_trunk_processed_atomic_mutations` (COUNTER)
+ - **Atomic mutations processed.**: N/A
+ - `io_evitadb_transaction_transaction_incorporated_to_trunk_processed_local_mutations` (COUNTER)
+ - **Local mutations processed.**: N/A
+ - `io_evitadb_transaction_transaction_processed_lag_milliseconds` (HISTOGRAM)
+ - **Transaction lag between being committed and finally visible to all**: N/A
+ - `io_evitadb_transaction_transaction_queued_duration_milliseconds` (HISTOGRAM)
+ - Transaction waiting time in queue.
+
+ Labels: stage
+
+ - `io_evitadb_transaction_transaction_started_total` (COUNTER)
+ - Transactions initiated
+ - `io_evitadb_transaction_wal_cache_size_changed_locations_cached` (GAUGE)
+ - **Total cached locations in WAL file**: N/A
+ - `io_evitadb_transaction_wal_rotation_duration_milliseconds` (HISTOGRAM)
+ - WAL rotation duration in milliseconds
+ - `io_evitadb_transaction_wal_rotation_total` (COUNTER)
+ - WAL rotations
diff --git a/evita_external_api/evita_external_api_observability/src/main/java/io/evitadb/externalApi/observability/metric/MetricHandler.java b/evita_external_api/evita_external_api_observability/src/main/java/io/evitadb/externalApi/observability/metric/MetricHandler.java
index 60f0091a4..01f2d2734 100644
--- a/evita_external_api/evita_external_api_observability/src/main/java/io/evitadb/externalApi/observability/metric/MetricHandler.java
+++ b/evita_external_api/evita_external_api_observability/src/main/java/io/evitadb/externalApi/observability/metric/MetricHandler.java
@@ -129,38 +129,6 @@ public class MetricHandler {
private final ObservabilityConfig observabilityConfig;
- /**
- * Converts the getter method into the exporter of the metric label.
- *
- * @param getter getter method
- * @return exporter of the metric label
- */
- @Nonnull
- private static MetricLabelExporter convertGetterToMetricLabelExporter(@Nonnull Method getter) {
- final String propertyName = ReflectionLookup.getPropertyNameFromMethodName(getter.getName());
- final ExportMetricLabel exportMetricLabel = getter.getAnnotation(ExportMetricLabel.class);
- return new MetricLabelExporter(
- of(exportMetricLabel.value()).filter(it -> !it.isBlank()).orElse(propertyName),
- recordedEvent -> recordedEvent.getString(propertyName)
- );
- }
-
- /**
- * Converts the field into the exporter of the metric label.
- *
- * @param field the field to export data from
- * @return exporter of the metric label
- */
- @Nonnull
- private static MetricLabelExporter convertFieldToMetricLabelExporter(@Nonnull Field field) {
- final ExportMetricLabel exportMetricLabel = field.getAnnotation(ExportMetricLabel.class);
- final String fieldName = field.getName();
- return new MetricLabelExporter(
- of(exportMetricLabel.value()).filter(it -> !it.isBlank()).orElse(fieldName),
- recordedEvent -> recordedEvent.getString(fieldName)
- );
- }
-
/**
* Composes the name of the metric from the event class, export metric and field name.
*
@@ -170,7 +138,7 @@ private static MetricLabelExporter convertFieldToMetricLabelExporter(@Nonnull Fi
* @return composed name of the metric
*/
@Nonnull
- private static String composeMetricName(
+ public static String composeMetricName(
@Nonnull Class extends CustomMetricsExecutionEvent> eventClass,
@Nonnull ExportMetric exportMetric,
@Nonnull String fieldName
@@ -192,7 +160,7 @@ private static String composeMetricName(
* @return composed name of the metric
*/
@Nonnull
- private static String composeMetricName(
+ public static String composeMetricName(
@Nonnull Class extends CustomMetricsExecutionEvent> eventClass,
@Nonnull String metricName
) {
@@ -203,6 +171,38 @@ private static String composeMetricName(
);
}
+ /**
+ * Converts the getter method into the exporter of the metric label.
+ *
+ * @param getter getter method
+ * @return exporter of the metric label
+ */
+ @Nonnull
+ private static MetricLabelExporter convertGetterToMetricLabelExporter(@Nonnull Method getter) {
+ final String propertyName = ReflectionLookup.getPropertyNameFromMethodName(getter.getName());
+ final ExportMetricLabel exportMetricLabel = getter.getAnnotation(ExportMetricLabel.class);
+ return new MetricLabelExporter(
+ of(exportMetricLabel.value()).filter(it -> !it.isBlank()).orElse(propertyName),
+ recordedEvent -> recordedEvent.getString(propertyName)
+ );
+ }
+
+ /**
+ * Converts the field into the exporter of the metric label.
+ *
+ * @param field the field to export data from
+ * @return exporter of the metric label
+ */
+ @Nonnull
+ private static MetricLabelExporter convertFieldToMetricLabelExporter(@Nonnull Field field) {
+ final ExportMetricLabel exportMetricLabel = field.getAnnotation(ExportMetricLabel.class);
+ final String fieldName = field.getName();
+ return new MetricLabelExporter(
+ of(exportMetricLabel.value()).filter(it -> !it.isBlank()).orElse(fieldName),
+ recordedEvent -> recordedEvent.getString(fieldName)
+ );
+ }
+
/**
* Stores the lambda into the reference and chains it with the previous lambda if there is one.
*
@@ -308,7 +308,7 @@ private static Metric buildAndRegisterMetric(@Nonnull LoggedMetric metric, @Nonn
}
},
() -> builder.classicExponentialUpperBounds(1, 2.0, 14)
- .unit(new Unit("milliseconds")));
+ .unit(new Unit("milliseconds")));
yield builder
.labelNames(metric.labels())
.help(metric.helpMessage())
diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JfrDocumentation.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JfrDocumentation.java
index 1114d78f8..ab0531434 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JfrDocumentation.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JfrDocumentation.java
@@ -23,8 +23,15 @@
package io.evitadb.documentation;
+import io.evitadb.api.configuration.metric.MetricType;
+import io.evitadb.api.observability.annotation.ExportDurationMetric;
+import io.evitadb.api.observability.annotation.ExportInvocationMetric;
+import io.evitadb.api.observability.annotation.ExportMetric;
+import io.evitadb.api.observability.annotation.ExportMetricLabel;
import io.evitadb.core.metric.event.CustomMetricsExecutionEvent;
+import io.evitadb.dataType.data.ReflectionCachingBehaviour;
import io.evitadb.externalApi.observability.metric.EvitaJfrEventRegistry;
+import io.evitadb.externalApi.observability.metric.MetricHandler;
import io.evitadb.test.EvitaTestSupport;
import io.evitadb.utils.Assert;
import io.evitadb.utils.ReflectionLookup;
@@ -33,9 +40,11 @@
import jdk.jfr.Label;
import org.junit.jupiter.api.Test;
+import javax.annotation.Nonnull;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
+import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
@@ -43,7 +52,12 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
/**
* This class generates referential documentation for Java Flight Recorder (JFR) events and metrics generated from them.
@@ -51,10 +65,17 @@
* @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2024
*/
public class JfrDocumentation implements EvitaTestSupport {
+ private static final String PATH_PREFIX = "evita_engine/src/main/java/";
private static final String JFR_REFERENCE_DOCUMENTATION = "documentation/user/en/operate/reference/jfr-events.md";
+ private static final String METRICS_DOCUMENTATION = "documentation/user/en/operate/reference/metrics.md";
@Test
void updateJfrReferenceDocumentation() throws IOException {
+ generateJfrReferenceDocumentation();
+ generateMetricsDocumentation();
+ }
+
+ private void generateJfrReferenceDocumentation() throws IOException {
final ReflectionLookup lookup = ReflectionLookup.NO_CACHE_INSTANCE;
final Path jfrReferencePath = getRootDirectory().resolve(JFR_REFERENCE_DOCUMENTATION);
try (Writer writer = new FileWriter(jfrReferencePath.toFile(), StandardCharsets.UTF_8, false)) {
@@ -86,7 +107,7 @@ void updateJfrReferenceDocumentation() throws IOException {
.forEach(
event -> {
try {
- writer.write(" " + lookup.getClassAnnotation(event, Label.class).value() + " (" + event.getName() + ")\n");
+ writer.write(" " + PATH_PREFIX + event.getName().replace('.', '/') + ".java " + lookup.getClassAnnotation(event, Label.class).value() + "\n");
writer.write(" " + lookup.getClassAnnotation(event, Description.class).value() + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
@@ -101,4 +122,153 @@ void updateJfrReferenceDocumentation() throws IOException {
}
}
+ private void generateMetricsDocumentation() throws IOException {
+ final ReflectionLookup lookup = new ReflectionLookup(ReflectionCachingBehaviour.CACHE);
+ final Path jfrReferencePath = getRootDirectory().resolve(METRICS_DOCUMENTATION);
+ try (Writer writer = new FileWriter(jfrReferencePath.toFile(), StandardCharsets.UTF_8, false)) {
+ writer.write("### Metrics\n\n");
+
+ final List labels = EvitaJfrEventRegistry.getEventClasses()
+ .stream()
+ .flatMap(it -> {
+ final Map> fields = lookup.getFields(it, ExportMetricLabel.class);
+ return fields.entrySet()
+ .stream()
+ .map(
+ field -> {
+ final ExportMetricLabel annotation = field.getValue().get(0);
+ final String label = ofNullable(field.getKey().getAnnotation(Label.class)).map(Label::value).orElse("N/A");
+ final String description = ofNullable(field.getKey().getAnnotation(Description.class)).map(Description::value).orElse("N/A");
+ return new MetricLabel(
+ ofNullable(annotation.value()).filter(value -> !value.isBlank()).orElse(field.getKey().getName()),
+ "**" + label + "**: " + description
+ );
+ })
+ .distinct()
+ .sorted(Comparator.comparing(MetricLabel::name));
+ })
+ .toList();
+
+ writer.write("\n");
+ writer.write(" Labels used in metrics
\n");
+ writer.write(" \n");
+ for (MetricLabel label : labels) {
+ writer.write(" - " + label.name() + "
\n");
+ writer.write(" - " + label.description() + "
\n");
+ }
+ writer.write("
\n");
+ writer.write("\n\n");
+
+ final Map>> groupedEvents = EvitaJfrEventRegistry.getEventClasses()
+ .stream()
+ .collect(
+ Collectors.groupingBy(
+ it -> {
+ final Category classAnnotation = lookup.getClassAnnotation(it, Category.class);
+ Assert.isPremiseValid(classAnnotation != null, "Event class " + it.getName() + " is missing @Category annotation");
+ final String[] groups = classAnnotation.value();
+ return Arrays.stream(groups).skip(1).collect(Collectors.joining(" / "));
+ }
+ )
+ );
+ groupedEvents.entrySet()
+ .stream()
+ .sorted(Entry.comparingByKey())
+ .forEach(
+ group -> {
+ try {
+ writer.write("#### " + group.getKey() + "\n\n");
+ writer.write("\n");
+ group.getValue()
+ .stream()
+ .flatMap(
+ it -> Stream.of(
+ ofNullable(lookup.getClassAnnotation(it, ExportInvocationMetric.class)).stream().map(ann -> toInvocationMetric(it, ann, lookup)),
+ ofNullable(lookup.getClassAnnotation(it, ExportDurationMetric.class)).stream().map(ann -> toDurationMetric(it, ann, lookup)),
+ lookup.getFields(it, ExportMetric.class).entrySet().stream().map(
+ field -> {
+ final ExportMetric annotation = field.getValue().get(0);
+ final String label = ofNullable(field.getKey().getAnnotation(Label.class)).map(Label::value).orElse("N/A");
+ final String description = ofNullable(field.getKey().getAnnotation(Description.class)).map(Description::value).orElse("N/A");
+ return new Metric(
+ MetricHandler.composeMetricName(it, annotation, of(annotation.metricName()).filter(metricName -> !metricName.isBlank()).orElse(field.getKey().getName())),
+ annotation.metricType().name(),
+ "**" + label + "**: " + description,
+ getLabels(it, lookup)
+ );
+ }
+ )
+ ).flatMap(Function.identity())
+ )
+ .sorted(Comparator.comparing(Metric::name))
+ .forEach(
+ metric -> {
+ try {
+ writer.write(" - `" + metric.name() + "` (" + metric.metricType() + ")
\n");
+ writer.write(" - ");
+ writer.write(metric.description());
+ if (metric.labels().length > 0) {
+ writer.write("\n\n Labels: ");
+ writer.write(Arrays.stream(metric.labels())
+ .map(label -> "" + label + "").collect(Collectors.joining(", ")));
+ writer.write("\n");
+ }
+ writer.write("
\n");
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ writer.write("
\n\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ );
+ }
+ }
+
+ @Nonnull
+ private static Metric toInvocationMetric(@Nonnull Class extends CustomMetricsExecutionEvent> eventClass, @Nonnull ExportInvocationMetric invocationMetric, @Nonnull ReflectionLookup lookup) {
+ return new Metric(
+ MetricHandler.composeMetricName(eventClass, invocationMetric.value()),
+ MetricType.COUNTER.name(),
+ invocationMetric.label(),
+ getLabels(eventClass, lookup)
+ );
+ }
+
+ @Nonnull
+ private static Metric toDurationMetric(@Nonnull Class extends CustomMetricsExecutionEvent> eventClass, @Nonnull ExportDurationMetric durationMetric, @Nonnull ReflectionLookup lookup) {
+ return new Metric(
+ MetricHandler.composeMetricName(eventClass, durationMetric.value()),
+ MetricType.HISTOGRAM.name(),
+ durationMetric.label(),
+ getLabels(eventClass, lookup)
+ );
+ }
+
+ @Nonnull
+ private static String[] getLabels(@Nonnull Class> eventClass, @Nonnull ReflectionLookup lookup) {
+ return lookup.getFields(eventClass, ExportMetricLabel.class)
+ .entrySet()
+ .stream()
+ .map(it -> it.getValue().stream().map(ExportMetricLabel::value).filter(value -> !value.isBlank()).findFirst().orElse(it.getKey().getName()))
+ .distinct()
+ .sorted()
+ .toArray(String[]::new);
+ }
+
+ private record Metric(
+ @Nonnull String name,
+ @Nonnull String metricType,
+ @Nonnull String description,
+ @Nonnull String[] labels
+ ) {}
+
+ private record MetricLabel(
+ @Nonnull String name,
+ @Nonnull String description
+ ) {}
+
}