]>
Commit | Line | Data |
---|---|---|
1e59de90 TL |
1 | # OStreamExporter Design |
2 | ||
3 | In strongly typed languages typically there will be 2 separate `Exporter` | |
4 | interfaces, one that accepts spans from a tracer (SpanExporter) and one that | |
5 | accepts metrics (MetricsExporter) | |
6 | ||
7 | The exporter SHOULD be called with a checkpoint of finished (possibly | |
8 | dimensionally reduced) export records. Most configuration decisions have been | |
9 | made before the exporter is invoked, including which instruments are enabled, | |
10 | which concrete aggregator types to use, and which dimensions to aggregate by. | |
11 | ||
12 | ## Use Cases | |
13 | ||
14 | Monitoring and alerting systems commonly use the data provided through metric | |
15 | events or tracers, after applying various aggregations and converting into | |
16 | various exposition format. After getting the data, the systems need to be able | |
17 | to see the data. The OStreamExporter will be used here to print data through an | |
18 | ostream, this is seen as a simple exporter where the user doesn’t have the | |
19 | burden of implementing or setting up a protocol dependent exporter. | |
20 | ||
21 | The OStreamExporter will also be used as a debugging tool for the Metrics | |
22 | API/SDK and Tracing API/SDK which are currently work in progress projects. This | |
23 | exporter will allow contributors to easily diagnose problems when working on the | |
24 | project. | |
25 | ||
26 | ## Push vs Pull Exporter | |
27 | ||
28 | There are two different versions of exporters: Push and Pull. A Push Exporter | |
29 | pushes the data outwards towards a system, in the case of the OStreamExporter it | |
30 | sends its data into an ostream. A Pull Exporter exposes data to some endpoint | |
31 | for another system to grab the data. | |
32 | ||
33 | The OStreamExporter will only be implementing a Push Exporter framework. | |
34 | ||
35 | ## Design Tenets | |
36 | ||
37 | * Reliability | |
38 | * The Exporter should be reliable; data exported should always be accounted | |
39 | for. The data will either all be successfully exported to the destination | |
40 | server, or in the case of failure, the data is dropped. `Export` will always | |
41 | return failure or success to notify the user of the result. | |
42 | * Thread Safety | |
43 | * The OStreamExporter can be called simultaneously, however we do not handle | |
44 | this in the Exporter. Synchronization should be done at a lower level. | |
45 | * Scalability | |
46 | * The Exporter must be able to operate on sizeable systems with predictable | |
47 | overhead growth. A key requirement of this is that the library does not | |
48 | consume unbounded memory resource. | |
49 | * Security | |
50 | * OStreamExporter should only be used for development and testing purpose, | |
51 | where security and privacy is less a concern as it doesn't communicate to | |
52 | external systems. | |
53 | ||
54 | ## SpanExporter | |
55 | ||
56 | `Span Exporter` defines the interface that protocol-specific exporters must | |
57 | implement so that they can be plugged into OpenTelemetry SDK and support sending | |
58 | of telemetry data. | |
59 | ||
60 | The goal of the interface is to minimize burden of implementation for | |
61 | protocol-dependent telemetry exporters. The protocol exporter is expected to be | |
62 | primarily a simple telemetry data encoder and transmitter. | |
63 | ||
64 | The SpanExporter is called through the SpanProcessor, which passes finished | |
65 | spans to the configured SpanExporter, as soon as they are finished. The | |
66 | SpanProcessor also shutdown the exporter by the Shutdown function within the | |
67 | SpanProcessor. | |
68 | ||
69 | <!-- [//]: # ![SDK Data Path](./images/SpanDataPath.png) --> | |
70 | ||
71 | The specification states: exporter must support two functions: Export and | |
72 | Shutdown. | |
73 | ||
74 | ### SpanExporter.Export(span of recordables) | |
75 | ||
76 | Exports a batch of telemetry data. Protocol exporters that will implement this | |
77 | function are typically expected to serialize and transmit the data to the | |
78 | destination. | |
79 | ||
80 | Export() must not block indefinitely. We can rely on printing to an ostream is | |
81 | reasonably performant and doesn't block. | |
82 | ||
83 | The specification states: Any retry logic that is required by the exporter is | |
84 | the responsibility of the exporter. The default SDK SHOULD NOT implement retry | |
85 | logic, as the required logic is likely to depend heavily on the specific | |
86 | protocol and backend the spans are being sent to. | |
87 | ||
88 | ### SpanExporter.Shutdown() | |
89 | ||
90 | Shuts down the exporter. Called when SDK is shut down. This is an opportunity | |
91 | for exporter to do any cleanup required. | |
92 | ||
93 | `Shutdown` should be called only once for each `Exporter` instance. After the | |
94 | call to `Shutdown` subsequent calls to `Export` are not allowed and should | |
95 | return a `Failure` result. | |
96 | ||
97 | `Shutdown` should not block indefinitely (e.g. if it attempts to flush the data | |
98 | and the destination is unavailable). Language library authors can decide if they | |
99 | want to make the shutdown timeout configurable. | |
100 | ||
101 | In the OStreamExporter there is no cleanup to be done, so there is no need to | |
102 | use the timeout within the `Shutdown` function as it will never be blocking. | |
103 | ||
104 | ```cpp | |
105 | class StreamSpanExporter final : public sdktrace::SpanExporter | |
106 | { | |
107 | ||
108 | private: | |
109 | bool isShutdown = false; | |
110 | ||
111 | public: | |
112 | /* | |
113 | This function should never be called concurrently. | |
114 | */ | |
115 | sdktrace::ExportResult Export( | |
116 | const nostd::span<std::unique_ptr<sdktrace::Recordable>> &spans) noexcept | |
117 | { | |
118 | ||
119 | if(isShutdown) | |
120 | { | |
121 | return sdktrace::ExportResult::kFailure; | |
122 | } | |
123 | ||
124 | for (auto &recordable : spans) | |
125 | { | |
126 | auto span = std::unique_ptr<sdktrace::SpanData>( | |
127 | static_cast<sdktrace::SpanData *>(recordable.release())); | |
128 | ||
129 | if (span != nullptr) | |
130 | { | |
131 | char trace_id[32] = {0}; | |
132 | char span_id[16] = {0}; | |
133 | char parent_span_id[16] = {0}; | |
134 | ||
135 | span->GetTraceId().ToLowerBase16(trace_id); | |
136 | span->GetSpanId().ToLowerBase16(span_id); | |
137 | span->GetParentSpanId().ToLowerBase16(parent_span_id); | |
138 | ||
139 | std::cout << "{" | |
140 | << "\n name : " << span->GetName() | |
141 | << "\n trace_id : " << std::string(trace_id, 32) | |
142 | << "\n span_id : " << std::string(span_id, 16) | |
143 | << "\n parent_span_id: " << std::string(parent_span_id, 16) | |
144 | << "\n start : " << span->GetStartTime().time_since_epoch().count() | |
145 | << "\n duration : " << span->GetDuration().count() | |
146 | << "\n description : " << span->GetDescription() | |
147 | << "\n status : " << span->GetStatus() | |
148 | << "\n attributes : " << span->GetAttributes() << "\n}" | |
149 | << "\n"; | |
150 | } | |
151 | ||
152 | } | |
153 | ||
154 | return sdktrace::ExportResult::kSuccess; | |
155 | } | |
156 | ||
157 | bool Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds::max()) noexcept | |
158 | { | |
159 | isShutdown = true; | |
160 | return true; | |
161 | } | |
162 | }; | |
163 | ``` | |
164 | ||
165 | ## MetricsExporter | |
166 | ||
167 | The MetricsExporter has the same requirements as the SpanExporter. The exporter | |
168 | will go through the different metric instruments and send the value stored in | |
169 | their aggregators to an ostream, for simplicity only Counter is shown here, but | |
170 | all aggregators will be implemented. Counter, Gauge, MinMaxSumCount, Sketch, | |
171 | Histogram and Exact Aggregators will be supported. | |
172 | ||
173 | Exports a batch of telemetry data. Protocol exporters that will implement this | |
174 | function are typically expected to serialize and transmit the data to the | |
175 | destination. | |
176 | ||
177 | <!-- [//]: # ![SDK Data Path](./images/DataPath.png) --> | |
178 | ||
179 | ### MetricsExporter.Export(batch of Records) | |
180 | ||
181 | Export() must not block indefinitely. We can rely on printing to an ostream is | |
182 | reasonably performant and doesn't block. | |
183 | ||
184 | The specification states: Any retry logic that is required by the exporter is | |
185 | the responsibility of the exporter. The default SDK SHOULD NOT implement retry | |
186 | logic, as the required logic is likely to depend heavily on the specific | |
187 | protocol and backend the spans are being sent to. | |
188 | ||
189 | The MetricsExporter is called through the Controller in the SDK data path. The | |
190 | exporter will either be called on a regular interval in the case of a push | |
191 | controller or through manual calls in the case of a pull controller. | |
192 | ||
193 | ### MetricsExporter.Shutdown() | |
194 | ||
195 | Shutdown() is currently not required for the OStreamMetricsExporter. | |
196 | ||
197 | ```cpp | |
198 | class StreamMetricsExporter final : public sdkmeter::MetricsExporter | |
199 | { | |
200 | ||
201 | private: | |
202 | bool isShutdown = false; | |
203 | ||
204 | public: | |
205 | sdkmeter::ExportResult Export( | |
206 | const Collection<Record> batch) noexcept | |
207 | { | |
208 | ||
209 | for (auto &metric : batch) | |
210 | { | |
211 | ||
212 | if (metric != nullptr) | |
213 | { | |
214 | ||
215 | if(metric.AggregationType == CounterAggregator) { | |
216 | std::cout << "{" | |
217 | << "\n name : " << metric->GetName() | |
218 | << "\n labels : " << metric->GetLabels() | |
219 | << "\n sum : " << metric->Value[0] << "\n}" | |
220 | } | |
221 | else if(metric.AggregationType == SketchAggregator) { | |
222 | // Similarly print data | |
223 | } | |
224 | // Other Aggreagators will also be handeled, | |
225 | // Counter, Gauge, MinMaxSumCount, Sketch, Histogram, | |
226 | // and Exact Aggreagtors | |
227 | } | |
228 | ||
229 | } | |
230 | ||
231 | return sdkmeter::ExportResult::kSuccess; | |
232 | } | |
233 | ||
234 | }; | |
235 | ``` | |
236 | ||
237 | ## Test Strategy / Plan | |
238 | ||
239 | In this project, we will follow the TDD rules, and write enough functional unit | |
240 | tests before implementing production code. We will design exhaustive test cases | |
241 | for normal and abnormal inputs, and tests for edge cases. | |
242 | ||
243 | In terms of test framework, as is described in the [Metrics API/SDK design | |
244 | document](https://quip-amazon.com/UBXyAuqRzkIj/Metrics-APISDK-C-Design-Document-External), | |
245 | the OStreamExporter will use [Googletest](https://github.com/google/googletest) | |
246 | framework because it provides test coverage reports, and it also integrate code | |
247 | coverage tools such as [codecov.io](http://codecov.io/) in the project. There | |
248 | are already many reference tests such as MockExporter tests written in | |
249 | GoogleTest, making it a clear choice to stick with it as the testing framework. | |
250 | A required coverage target of 90% will help to ensure that our code is fully | |
251 | tested. | |
252 | ||
253 | ## Future Features | |
254 | ||
255 | * Serialize data to another format (json) | |
256 | ||
257 | ## Contributors | |
258 | ||
259 | * Hudson Humphries |