33
33
import java .util .LinkedHashMap ;
34
34
import java .util .List ;
35
35
import java .util .Map ;
36
+ import java .util .Objects ;
36
37
import java .util .Optional ;
37
38
import java .util .Properties ;
38
39
import java .util .UUID ;
40
+ import java .util .function .Consumer ;
41
+ import java .util .stream .Collectors ;
39
42
import org .apache .spark .ExceptionFailure ;
40
43
import org .apache .spark .SparkConf ;
41
44
import org .apache .spark .TaskFailedReason ;
@@ -74,6 +77,7 @@ public abstract class AbstractDatadogSparkListener extends SparkListener {
74
77
private final int MAX_COLLECTION_SIZE = 5000 ;
75
78
private final int MAX_ACCUMULATOR_SIZE = 50000 ;
76
79
private final String RUNTIME_TAGS_PREFIX = "spark.datadog.tags." ;
80
+ private static final String AGENT_OL_ENDPOINT = "openlineage/api/v1/lineage" ;
77
81
78
82
private final SparkConf sparkConf ;
79
83
private final String sparkVersion ;
@@ -123,6 +127,7 @@ public abstract class AbstractDatadogSparkListener extends SparkListener {
123
127
private long availableExecutorTime = 0 ;
124
128
125
129
private volatile boolean applicationEnded = false ;
130
+ private SparkListener openLineageSparkListener = null ;
126
131
127
132
public AbstractDatadogSparkListener (SparkConf sparkConf , String appId , String sparkVersion ) {
128
133
tracer = AgentTracer .get ();
@@ -151,8 +156,50 @@ public AbstractDatadogSparkListener(SparkConf sparkConf, String appId, String sp
151
156
finishApplication (System .currentTimeMillis (), null , 0 , null );
152
157
}
153
158
}));
159
+ initApplicationSpanIfNotInitialized ();
160
+ loadOlSparkListener ();
161
+ }
162
+
163
+ static void setupSparkConf (SparkConf sparkConf ) {
164
+ sparkConf .set ("spark.openlineage.transport.type" , "composite" );
165
+ sparkConf .set ("spark.openlineage.transport.continueOnFailure" , "true" );
166
+ sparkConf .set ("spark.openlineage.transport.transports.agent.type" , "http" );
167
+ sparkConf .set ("spark.openlineage.transport.transports.agent.url" , getAgentHttpUrl ());
168
+ sparkConf .set ("spark.openlineage.transport.transports.agent.endpoint" , AGENT_OL_ENDPOINT );
169
+ sparkConf .set ("spark.openlineage.transport.transports.agent.compression" , "gzip" );
170
+ }
154
171
155
- log .info ("Created datadog spark listener: {}" , this .getClass ().getSimpleName ());
172
+ void setupTrace (SparkConf sc ) {
173
+ sc .set (
174
+ "spark.openlineage.run.tags" ,
175
+ "_dd.trace_id:"
176
+ + applicationSpan .context ().getTraceId ().toString ()
177
+ + ";_dd.intake.emit_spans:false" );
178
+ }
179
+
180
+ void loadOlSparkListener () {
181
+ String className = "io.openlineage.spark.agent.OpenLineageSparkListener" ;
182
+ Optional <Class > clazz = loadClass (className );
183
+ if (!clazz .isPresent ()) {
184
+ log .info ("OpenLineage integration is not present on the classpath" );
185
+ return ;
186
+ }
187
+ try {
188
+ setupSparkConf (sparkConf );
189
+ sparkConf .set (
190
+ "spark.openlineage.run.tags" ,
191
+ "_dd.trace_id:"
192
+ + applicationSpan .context ().getTraceId ().toString ()
193
+ + ";_dd.ol_intake.emit_spans:false" );
194
+
195
+ openLineageSparkListener =
196
+ (SparkListener )
197
+ clazz .get ().getDeclaredConstructor (SparkConf .class ).newInstance (sparkConf );
198
+ log .info (
199
+ "Created OL spark listener: {}" , openLineageSparkListener .getClass ().getSimpleName ());
200
+ } catch (Exception e ) {
201
+ log .warn ("Failed to instantiate OL Spark Listener: {}" , e .toString ());
202
+ }
156
203
}
157
204
158
205
/** Resource name of the spark job. Provide an implementation based on a specific scala version */
@@ -176,6 +223,8 @@ public AbstractDatadogSparkListener(SparkConf sparkConf, String appId, String sp
176
223
@ Override
177
224
public synchronized void onApplicationStart (SparkListenerApplicationStart applicationStart ) {
178
225
this .applicationStart = applicationStart ;
226
+ initApplicationSpanIfNotInitialized ();
227
+ notifyOl (this .openLineageSparkListener ::onApplicationStart , applicationStart );
179
228
}
180
229
181
230
private void initApplicationSpanIfNotInitialized () {
@@ -233,6 +282,7 @@ public void onApplicationEnd(SparkListenerApplicationEnd applicationEnd) {
233
282
log .info (
234
283
"Received spark application end event, finish trace on this event: {}" ,
235
284
finishTraceOnApplicationEnd );
285
+ notifyOl (this .openLineageSparkListener ::onApplicationEnd , applicationEnd );
236
286
237
287
if (finishTraceOnApplicationEnd ) {
238
288
finishApplication (applicationEnd .time (), null , 0 , null );
@@ -426,6 +476,7 @@ public synchronized void onJobStart(SparkListenerJobStart jobStart) {
426
476
stageToJob .put (stageId , jobStart .jobId ());
427
477
}
428
478
jobSpans .put (jobStart .jobId (), jobSpan );
479
+ notifyOl (this .openLineageSparkListener ::onJobStart , jobStart );
429
480
}
430
481
431
482
@ Override
@@ -456,6 +507,7 @@ public synchronized void onJobEnd(SparkListenerJobEnd jobEnd) {
456
507
if (metrics != null ) {
457
508
metrics .setSpanMetrics (jobSpan );
458
509
}
510
+ notifyOl (this .openLineageSparkListener ::onJobEnd , jobEnd );
459
511
460
512
jobSpan .finish (jobEnd .time () * 1000 );
461
513
}
@@ -624,6 +676,8 @@ public void onTaskEnd(SparkListenerTaskEnd taskEnd) {
624
676
625
677
Properties props = stageProperties .get (stageSpanKey );
626
678
sendTaskSpan (stageSpan , taskEnd , props );
679
+
680
+ notifyOl (this .openLineageSparkListener ::onTaskEnd , taskEnd );
627
681
}
628
682
629
683
private void sendTaskSpan (
@@ -705,6 +759,15 @@ public void onOtherEvent(SparkListenerEvent event) {
705
759
updateAdaptiveSQLPlan (event );
706
760
}
707
761
762
+ private <T extends SparkListenerEvent > void notifyOl (Consumer <T > ol , T event ) {
763
+ if (this .openLineageSparkListener != null ) {
764
+ log .debug ("Notifying with event `{}`" , event .getClass ().getCanonicalName ());
765
+ ol .accept (event );
766
+ } else {
767
+ log .debug ("OpenLineageSparkListener is null" );
768
+ }
769
+ }
770
+
708
771
private static final Class <?> adaptiveExecutionUpdateClass ;
709
772
private static final MethodHandle adaptiveExecutionIdMethod ;
710
773
private static final MethodHandle adaptiveSparkPlanMethod ;
@@ -765,6 +828,7 @@ private synchronized void onSQLExecutionEnd(SparkListenerSQLExecutionEnd sqlEnd)
765
828
if (metrics != null ) {
766
829
metrics .setSpanMetrics (span );
767
830
}
831
+ notifyOl (this .openLineageSparkListener ::onOtherEvent , sqlEnd );
768
832
769
833
span .finish (sqlEnd .time () * 1000 );
770
834
}
@@ -923,7 +987,7 @@ private void setDataJobsSamplingPriority(AgentSpan span) {
923
987
924
988
private AgentTracer .SpanBuilder buildSparkSpan (String spanName , Properties properties ) {
925
989
AgentTracer .SpanBuilder builder =
926
- tracer .buildSpan (spanName ).withSpanType ("spark" ).withTag ("app_id" , appId );
990
+ tracer .buildSpan ("spark" , spanName ).withSpanType ("spark" ).withTag ("app_id" , appId );
927
991
928
992
if (databricksServiceName != null ) {
929
993
builder .withServiceName (databricksServiceName );
@@ -1260,9 +1324,71 @@ private static String getDatabricksRunName(SparkConf conf) {
1260
1324
return null ;
1261
1325
}
1262
1326
1327
+ private static String getAgentHttpUrl () {
1328
+ StringBuilder sb =
1329
+ new StringBuilder ("http://" )
1330
+ .append (Config .get ().getAgentHost ())
1331
+ .append (":" )
1332
+ .append (Config .get ().getAgentPort ());
1333
+ return sb .toString ();
1334
+ }
1335
+
1263
1336
@ SuppressForbidden // called at most once per spark application
1264
1337
private static String removeUuidFromEndOfString (String input ) {
1265
1338
return input .replaceAll (
1266
1339
"_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" , "" );
1267
1340
}
1341
+
1342
+ private Optional <Class > loadClass (String className ) {
1343
+ Class clazz = null ;
1344
+ List <ClassLoader > availableClassloaders =
1345
+ Thread .getAllStackTraces ().keySet ().stream ()
1346
+ .map (Thread ::getContextClassLoader )
1347
+ .filter (Objects ::nonNull )
1348
+ .collect (Collectors .toList ());
1349
+ try {
1350
+ clazz = Class .forName (className );
1351
+ } catch (Exception e ) {
1352
+ log .debug ("Failed to load {} via Class.forName: {}" , className , e .toString ());
1353
+ for (ClassLoader classLoader : availableClassloaders ) {
1354
+ try {
1355
+ clazz = classLoader .loadClass (className );
1356
+ log .debug ("Loaded {} via classLoader: {}" , className , classLoader );
1357
+ break ;
1358
+ } catch (Exception ex ) {
1359
+ log .debug (
1360
+ "Failed to load {} via loadClass via ClassLoader {} - {}" ,
1361
+ className ,
1362
+ classLoader ,
1363
+ ex .toString ());
1364
+ }
1365
+ try {
1366
+ clazz = classLoader .getParent ().loadClass (className );
1367
+ log .debug (
1368
+ "Loaded {} via parent classLoader: {} for CL {}" ,
1369
+ className ,
1370
+ classLoader .getParent (),
1371
+ classLoader );
1372
+ break ;
1373
+ } catch (Exception ex ) {
1374
+ log .debug (
1375
+ "Failed to load {} via loadClass via parent ClassLoader {} - {}" ,
1376
+ className ,
1377
+ classLoader .getParent (),
1378
+ ex .toString ());
1379
+ }
1380
+ }
1381
+ }
1382
+ if (clazz == null ) {
1383
+ try {
1384
+ clazz = ClassLoader .getSystemClassLoader ().loadClass (className );
1385
+ log .debug (
1386
+ "Loaded {} via system classLoader: {}" , className , ClassLoader .getSystemClassLoader ());
1387
+ } catch (Exception ex ) {
1388
+ log .debug (
1389
+ "Failed to load {} via loadClass via SystemClassLoader {}" , className , ex .toString ());
1390
+ }
1391
+ }
1392
+ return Optional .ofNullable (clazz );
1393
+ }
1268
1394
}
0 commit comments