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 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 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 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 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 + ) {} + }