1818
1919import com .google .common .annotations .VisibleForTesting ;
2020import io .opencensus .common .Duration ;
21+ import io .opencensus .common .ToLongFunction ;
2122import io .opencensus .implcore .internal .DaemonThreadFactory ;
2223import io .opencensus .implcore .trace .RecordEventsSpanImpl ;
24+ import io .opencensus .metrics .DerivedLongCumulative ;
25+ import io .opencensus .metrics .DerivedLongGauge ;
26+ import io .opencensus .metrics .LabelValue ;
27+ import io .opencensus .metrics .MetricOptions ;
28+ import io .opencensus .metrics .Metrics ;
2329import io .opencensus .trace .export .ExportComponent ;
2430import io .opencensus .trace .export .SpanData ;
2531import io .opencensus .trace .export .SpanExporter ;
3238import java .util .logging .Logger ;
3339import javax .annotation .concurrent .GuardedBy ;
3440
41+ /*>>>
42+ import org.checkerframework.checker.nullness.qual.Nullable;
43+ */
44+
3545/** Implementation of the {@link SpanExporter}. */
3646public final class SpanExporterImpl extends SpanExporter {
3747 private static final Logger logger = Logger .getLogger (ExportComponent .class .getName ());
48+ private static final DerivedLongCumulative droppedSpans =
49+ Metrics .getMetricRegistry ()
50+ .addDerivedLongCumulative (
51+ "oc_worker_spans_dropped" ,
52+ MetricOptions .builder ()
53+ .setDescription ("Number of spans dropped by the exporter thread." )
54+ .setUnit ("1" )
55+ .build ());
56+ private static final DerivedLongCumulative pushedSpans =
57+ Metrics .getMetricRegistry ()
58+ .addDerivedLongCumulative (
59+ "oc_worker_spans_pushed" ,
60+ MetricOptions .builder ()
61+ .setDescription ("Number of spans pushed by the exporter thread to the exporter." )
62+ .setUnit ("1" )
63+ .build ());
64+ private static final DerivedLongGauge referencedSpans =
65+ Metrics .getMetricRegistry ()
66+ .addDerivedLongGauge (
67+ "oc_worker_spans_referenced" ,
68+ MetricOptions .builder ()
69+ .setDescription ("Current number of spans referenced by the exporter thread." )
70+ .setUnit ("1" )
71+ .build ());
3872
3973 private final Worker worker ;
4074 private final Thread workerThread ;
@@ -88,13 +122,64 @@ private SpanExporterImpl(Worker worker) {
88122 new DaemonThreadFactory ("ExportComponent.ServiceExporterThread" ).newThread (worker );
89123 this .workerThread .start ();
90124 this .worker = worker ;
125+ droppedSpans .createTimeSeries (
126+ Collections .<LabelValue >emptyList (), this .worker , new ReportDroppedSpans ());
127+ referencedSpans .createTimeSeries (
128+ Collections .<LabelValue >emptyList (), this .worker , new ReportReferencedSpans ());
129+ pushedSpans .createTimeSeries (
130+ Collections .<LabelValue >emptyList (), this .worker , new ReportPushedSpans ());
131+ }
132+
133+ private static class ReportDroppedSpans implements ToLongFunction </*@Nullable*/ Worker > {
134+ @ Override
135+ public long applyAsLong (/*@Nullable*/ Worker worker ) {
136+ if (worker == null ) {
137+ return 0 ;
138+ }
139+ return worker .getDroppedSpans ();
140+ }
141+ }
142+
143+ private static class ReportReferencedSpans implements ToLongFunction </*@Nullable*/ Worker > {
144+ @ Override
145+ public long applyAsLong (/*@Nullable*/ Worker worker ) {
146+ if (worker == null ) {
147+ return 0 ;
148+ }
149+ return worker .getReferencedSpans ();
150+ }
151+ }
152+
153+ private static class ReportPushedSpans implements ToLongFunction </*@Nullable*/ Worker > {
154+ @ Override
155+ public long applyAsLong (/*@Nullable*/ Worker worker ) {
156+ if (worker == null ) {
157+ return 0 ;
158+ }
159+ return worker .getPushedSpans ();
160+ }
91161 }
92162
93163 @ VisibleForTesting
94164 Thread getServiceExporterThread () {
95165 return workerThread ;
96166 }
97167
168+ @ VisibleForTesting
169+ long getDroppedSpans () {
170+ return worker .getDroppedSpans ();
171+ }
172+
173+ @ VisibleForTesting
174+ long getReferencedSpans () {
175+ return worker .getReferencedSpans ();
176+ }
177+
178+ @ VisibleForTesting
179+ long getPushedSpans () {
180+ return worker .getPushedSpans ();
181+ }
182+
98183 // Worker in a thread that batches multiple span data and calls the registered services to export
99184 // that data.
100185 //
@@ -110,14 +195,29 @@ private static final class Worker implements Runnable {
110195 @ GuardedBy ("monitor" )
111196 private final List <RecordEventsSpanImpl > spans ;
112197
113- private final Map <String , Handler > serviceHandlers = new ConcurrentHashMap <String , Handler >();
198+ @ GuardedBy ("monitor" )
199+ private long referencedSpans = 0 ;
200+
201+ @ GuardedBy ("monitor" )
202+ private long droppedSpans = 0 ;
203+
204+ @ GuardedBy ("monitor" )
205+ private long pushedSpans = 0 ;
206+
207+ private final Map <String , Handler > serviceHandlers = new ConcurrentHashMap <>();
114208 private final int bufferSize ;
209+ private final long maxReferencedSpans ;
115210 private final long scheduleDelayMillis ;
116211
117212 // See SpanExporterImpl#addSpan.
118213 private void addSpan (RecordEventsSpanImpl span ) {
119214 synchronized (monitor ) {
215+ if (referencedSpans == maxReferencedSpans ) {
216+ droppedSpans ++;
217+ return ;
218+ }
120219 this .spans .add (span );
220+ referencedSpans ++;
121221 if (spans .size () >= bufferSize ) {
122222 monitor .notifyAll ();
123223 }
@@ -154,6 +254,11 @@ private void onBatchExport(List<SpanData> spanDataList) {
154254 private Worker (int bufferSize , Duration scheduleDelay ) {
155255 spans = new ArrayList <>(bufferSize );
156256 this .bufferSize = bufferSize ;
257+ // We notify the worker thread when bufferSize elements in the queue, so we will most likely
258+ // have to process more than bufferSize elements but less than 2 * bufferSize in that cycle.
259+ // During the processing time we want to allow the same amount of elements to be queued.
260+ // So we need to have 4 * bufferSize maximum elements referenced as an estimate.
261+ this .maxReferencedSpans = 4L * bufferSize ;
157262 this .scheduleDelayMillis = scheduleDelay .toMillis ();
158263 }
159264
@@ -195,6 +300,24 @@ private void flush() {
195300 exportBatches (spansCopy );
196301 }
197302
303+ private long getDroppedSpans () {
304+ synchronized (monitor ) {
305+ return droppedSpans ;
306+ }
307+ }
308+
309+ private long getReferencedSpans () {
310+ synchronized (monitor ) {
311+ return referencedSpans ;
312+ }
313+ }
314+
315+ private long getPushedSpans () {
316+ synchronized (monitor ) {
317+ return pushedSpans ;
318+ }
319+ }
320+
198321 @ SuppressWarnings ("argument.type.incompatible" )
199322 private void exportBatches (ArrayList <RecordEventsSpanImpl > spanList ) {
200323 ArrayList <SpanData > spanDataList = new ArrayList <>(bufferSize );
@@ -209,12 +332,23 @@ private void exportBatches(ArrayList<RecordEventsSpanImpl> spanList) {
209332 // Cannot clear because the exporter may still have a reference to this list (e.g. async
210333 // scheduled work), so just create a new list.
211334 spanDataList = new ArrayList <>(bufferSize );
335+ // We removed reference for bufferSize Spans.
336+ synchronized (monitor ) {
337+ referencedSpans -= bufferSize ;
338+ pushedSpans += bufferSize ;
339+ }
212340 }
213341 }
214342 // Last incomplete batch, send this as well.
215343 if (!spanDataList .isEmpty ()) {
216344 // Wrap the list with unmodifiableList to ensure exporter does not change the list.
217345 onBatchExport (Collections .unmodifiableList (spanDataList ));
346+ // We removed reference for spanDataList.size() Spans.
347+ synchronized (monitor ) {
348+ referencedSpans -= spanDataList .size ();
349+ pushedSpans += spanDataList .size ();
350+ }
351+ spanDataList .clear ();
218352 }
219353 }
220354 }
0 commit comments