4545import org .slf4j .LoggerFactory ;
4646
4747import java .io .File ;
48- import java .net .InetAddress ;
48+ import org .opensearch .dataprepper .model .host .HostContext ;
49+
50+ import java .nio .charset .StandardCharsets ;
51+ import java .security .MessageDigest ;
4952import java .time .Clock ;
5053import java .time .Duration ;
5154import java .time .Instant ;
@@ -73,7 +76,6 @@ public class OTelApmServiceMapProcessor extends AbstractProcessor<Record<Event>,
7376
7477 private static final String SPANS_DB_SIZE = "spansDbSize" ;
7578 private static final String SPANS_DB_COUNT = "spansDbCount" ;
76- private static final String HOST_ID = resolveHostId ();
7779
7880 private static final Logger LOG = LoggerFactory .getLogger (OTelApmServiceMapProcessor .class );
7981 private static final String EVENT_TYPE_OTEL_APM_SERVICE_MAP = "SERVICE_MAP" ;
@@ -95,7 +97,9 @@ public class OTelApmServiceMapProcessor extends AbstractProcessor<Record<Event>,
9597 private static Clock clock ;
9698
9799 private final int thisProcessorId ;
100+ private final String hostId ;
98101 private final List <String > groupByAttributes ;
102+ private final MetricTimestampSource metricTimestampSource ;
99103 private final EventFactory eventFactory ;
100104
101105 @ DataPrepperPluginConstructor
@@ -110,7 +114,8 @@ public OTelApmServiceMapProcessor(
110114 pipelineDescription .getNumberOfProcessWorkers (),
111115 eventFactory ,
112116 pluginMetrics ,
113- config .getGroupByAttributes ());
117+ config .getGroupByAttributes (),
118+ config .getMetricTimestampSource ());
114119 }
115120
116121 OTelApmServiceMapProcessor (final Duration windowDuration ,
@@ -119,7 +124,8 @@ public OTelApmServiceMapProcessor(
119124 final int processWorkers ,
120125 final EventFactory eventFactory ,
121126 final PluginMetrics pluginMetrics ) {
122- this (windowDuration , databasePath , clock , processWorkers , eventFactory , pluginMetrics , Collections .emptyList ());
127+ this (windowDuration , databasePath , clock , processWorkers , eventFactory , pluginMetrics ,
128+ Collections .emptyList (), MetricTimestampSource .SPAN_END_TIME );
123129 }
124130
125131 OTelApmServiceMapProcessor (final Duration windowDuration ,
@@ -129,9 +135,23 @@ public OTelApmServiceMapProcessor(
129135 final EventFactory eventFactory ,
130136 final PluginMetrics pluginMetrics ,
131137 final List <String > groupByAttributes ) {
138+ this (windowDuration , databasePath , clock , processWorkers , eventFactory , pluginMetrics ,
139+ groupByAttributes , MetricTimestampSource .SPAN_END_TIME );
140+ }
141+
142+ OTelApmServiceMapProcessor (final Duration windowDuration ,
143+ final File databasePath ,
144+ final Clock clock ,
145+ final int processWorkers ,
146+ final EventFactory eventFactory ,
147+ final PluginMetrics pluginMetrics ,
148+ final List <String > groupByAttributes ,
149+ final MetricTimestampSource metricTimestampSource ) {
132150 super (pluginMetrics );
133151
152+ this .hostId = resolveHostId ();
134153 this .groupByAttributes = groupByAttributes != null ? Collections .unmodifiableList (groupByAttributes ) : Collections .emptyList ();
154+ this .metricTimestampSource = metricTimestampSource != null ? metricTimestampSource : MetricTimestampSource .ARRIVAL_TIME ;
135155
136156 this .eventFactory = eventFactory ;
137157 OTelApmServiceMapProcessor .clock = clock ;
@@ -448,19 +468,23 @@ private Map<String, String> extractGroupByAttributes(final Span span) {
448468 }
449469
450470 /**
451- * Get anchor timestamp from span's endTime , truncated to the specified unit.
452- * Sum metrics use SECONDS to minimize collisions while keeping stable time series .
453- * Histogram metrics use MINUTES to aggregate more samples for richer bucket distributions .
471+ * Get anchor timestamp for metrics , truncated to the specified unit.
472+ * When metric_timestamp_source is ARRIVAL_TIME, uses fallbackTime (clock.instant()) .
473+ * When metric_timestamp_source is SPAN_END_TIME, uses the span's endTime field .
454474 *
455475 * @param spanStateData The span to extract timestamp from
456- * @param fallbackTime Current system time to use if span endTime is null
476+ * @param fallbackTime Current system time to use as arrival time or if span endTime is null
457477 * @param truncationUnit The ChronoUnit to truncate the timestamp to
458478 * @return Instant truncated to the specified boundary
459479 */
460480 private Instant getAnchorTimestampFromSpan (final SpanStateData spanStateData , final Instant fallbackTime ,
461481 final ChronoUnit truncationUnit ) {
462- Instant timestamp = fallbackTime ; // Default to current system time
482+ if (metricTimestampSource == MetricTimestampSource .ARRIVAL_TIME ) {
483+ return fallbackTime .truncatedTo (truncationUnit );
484+ }
463485
486+ // SPAN_END_TIME mode: parse span's endTime, fall back to system time
487+ Instant timestamp = fallbackTime ;
464488 final String endTime = spanStateData .getEndTime ();
465489 try {
466490 if (endTime != null && !endTime .isEmpty ()) {
@@ -476,19 +500,17 @@ private Instant getAnchorTimestampFromSpan(final SpanStateData spanStateData, fi
476500
477501 /**
478502 * Resolve a stable host identifier for this Data Prepper instance.
479- * Uses a truncated SHA-256 hash of the hostname to ensure uniqueness
480- * without revealing the actual hostname in emitted metrics.
481- * Falls back to a random UUID if hostname resolution fails.
503+ * Uses a truncated SHA-256 hash of the hostname (from {@link HostContext})
504+ * to ensure uniqueness without revealing the actual hostname in emitted metrics.
482505 */
483- private static String resolveHostId () {
506+ private String resolveHostId () {
484507 try {
485- final String hostname = InetAddress . getLocalHost (). getHostName ();
486- final java . security . MessageDigest digest = java . security . MessageDigest .getInstance ("SHA-256" );
487- final byte [] hash = digest .digest (hostname .getBytes (java . nio . charset . StandardCharsets .UTF_8 ));
508+ final String hostname = HostContext . getHostname ();
509+ final MessageDigest digest = MessageDigest .getInstance ("SHA-256" );
510+ final byte [] hash = digest .digest (hostname .getBytes (StandardCharsets .UTF_8 ));
488511 return Hex .encodeHexString (hash ).substring (0 , 16 );
489- } catch (Exception e ) {
490- LOG .warn ("Failed to resolve hostname for trace_processor_host_id, using random UUID: {}" , e .getMessage ());
491- return java .util .UUID .randomUUID ().toString ();
512+ } catch (final java .security .NoSuchAlgorithmException e ) {
513+ throw new RuntimeException ("SHA-256 algorithm not available" , e );
492514 }
493515 }
494516
@@ -777,7 +799,7 @@ private Collection<Record<Event>> generateNodeOperationDetailEvents(final ThreeW
777799 final Instant histAnchor = getAnchorTimestampFromSpan (clientSpan , currentTime , ChronoUnit .MINUTES );
778800
779801 final NodeOperationDetail nodeOperationDetail = new NodeOperationDetail (
780- sourceNode , targetNode , sourceOp , targetOp , sumAnchor );
802+ sourceNode , targetNode , sourceOp , targetOp , histAnchor );
781803
782804 final EventMetadata eventMetadata = new DefaultEventMetadata .Builder ()
783805 .withEventType (EVENT_TYPE_OTEL_APM_SERVICE_MAP ).build ();
@@ -792,7 +814,7 @@ private Collection<Record<Event>> generateNodeOperationDetailEvents(final ThreeW
792814 if (decoration .getParentServerOperationName () != null ) {
793815 ApmServiceMapMetricsUtil .generateMetricsForClientSpan (
794816 clientSpan , decoration , currentTime , sumStateByKey , histogramStateByKey ,
795- sumAnchor , histAnchor , HOST_ID );
817+ sumAnchor , histAnchor , hostId );
796818 }
797819 }
798820 }
@@ -805,7 +827,7 @@ private Collection<Record<Event>> generateNodeOperationDetailEvents(final ThreeW
805827 final Instant histAnchor = getAnchorTimestampFromSpan (serverSpan , currentTime , ChronoUnit .MINUTES );
806828 ApmServiceMapMetricsUtil .generateMetricsForServerSpan (
807829 serverSpan , currentTime , sumStateByKey , histogramStateByKey ,
808- sumAnchor , histAnchor , HOST_ID );
830+ sumAnchor , histAnchor , hostId );
809831
810832 final ServerSpanDecoration decoration = traceData .getDecorations ().getServerDecoration (serverSpan .getSpanId ());
811833
@@ -819,7 +841,7 @@ private Collection<Record<Event>> generateNodeOperationDetailEvents(final ThreeW
819841 final Operation sourceOp = new Operation (serverSpan .getOperationName ());
820842
821843 final NodeOperationDetail nodeOperationDetail = new NodeOperationDetail (
822- sourceNode , null , sourceOp , null , sumAnchor );
844+ sourceNode , null , sourceOp , null , histAnchor );
823845
824846 final EventMetadata eventMetadata = new DefaultEventMetadata .Builder ()
825847 .withEventType (EVENT_TYPE_OTEL_APM_SERVICE_MAP ).build ();
0 commit comments