1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- use std:: sync:: OnceLock ;
16- use std:: time:: Duration ;
17-
18- use anyhow:: Context ;
19- use metrics_exporter_otel:: OpenTelemetryRecorder ;
20- use metrics_exporter_prometheus:: {
21- Matcher , PrometheusBuilder , PrometheusHandle , PrometheusRecorder ,
22- } ;
2315use metrics_util:: layers:: FanoutBuilder ;
24- use opentelemetry:: metrics:: MeterProvider ;
25- use opentelemetry_otlp:: { MetricExporter , Protocol as OtlpWireProtocol , WithExportConfig } ;
26- use opentelemetry_sdk:: metrics:: { SdkMeterProvider , Temporality } ;
27-
28- use crate :: config:: { OtlpExporterConfig , OtlpProtocol , quickwit_resource} ;
29-
30- static PROMETHEUS_HANDLE : OnceLock < PrometheusHandle > = OnceLock :: new ( ) ;
16+ use opentelemetry_sdk:: metrics:: SdkMeterProvider ;
3117
32- impl OtlpProtocol {
33- pub ( crate ) fn metric_exporter (
34- & self ,
35- temporality : Temporality ,
36- ) -> anyhow:: Result < MetricExporter > {
37- match self {
38- OtlpProtocol :: Grpc => MetricExporter :: builder ( )
39- . with_tonic ( )
40- . with_temporality ( temporality)
41- . build ( ) ,
42- OtlpProtocol :: HttpProtobuf => MetricExporter :: builder ( )
43- . with_http ( )
44- . with_temporality ( temporality)
45- . with_protocol ( OtlpWireProtocol :: HttpBinary )
46- . build ( ) ,
47- OtlpProtocol :: HttpJson => MetricExporter :: builder ( )
48- . with_http ( )
49- . with_temporality ( temporality)
50- . with_protocol ( OtlpWireProtocol :: HttpJson )
51- . build ( ) ,
52- }
53- . context ( "failed to initialize OTLP metrics exporter" )
54- }
55- }
18+ use crate :: otlp:: OtlpExporterConfig ;
5619
5720/// Sets up the global metrics recorder.
5821pub ( crate ) fn init_metrics_provider (
5922 service_version : & str ,
6023 otlp_config : & OtlpExporterConfig ,
6124) -> anyhow:: Result < Option < SdkMeterProvider > > {
62- let prometheus_recorder = build_prometheus_recorder ( ) ?;
25+ let prometheus_recorder = crate :: prometheus :: metrics :: build_recorder ( ) ?;
6326
6427 let ( recorder, meter_provider) = if otlp_config. is_enabled ( ) {
65- let ( otlp_recorder, meter_provider) = build_otlp_recorder ( service_version, otlp_config) ?;
28+ let ( otlp_recorder, meter_provider) =
29+ crate :: otlp:: metrics:: build_recorder ( service_version, otlp_config) ?;
6630 let recorder = FanoutBuilder :: default ( )
6731 . add_recorder ( prometheus_recorder)
6832 . add_recorder ( otlp_recorder)
@@ -81,95 +45,3 @@ pub(crate) fn init_metrics_provider(
8145
8246 Ok ( meter_provider)
8347}
84-
85- fn build_prometheus_recorder ( ) -> anyhow:: Result < PrometheusRecorder > {
86- let mut prometheus_builder = PrometheusBuilder :: new ( ) ;
87- for ( name, buckets) in quickwit_metrics:: histogram_buckets ( ) {
88- prometheus_builder = prometheus_builder
89- . set_buckets_for_metric ( Matcher :: Full ( name. to_string ( ) ) , & buckets)
90- . with_context ( || {
91- format ! ( "failed to configure Prometheus histogram buckets for `{name}`" )
92- } ) ?;
93- }
94- let prometheus_recorder = prometheus_builder. build_recorder ( ) ;
95- let prometheus_handle = prometheus_recorder. handle ( ) ;
96- PROMETHEUS_HANDLE
97- . set ( prometheus_handle. clone ( ) )
98- . map_err ( |_| anyhow:: anyhow!( "Prometheus metrics renderer is already installed" ) ) ?;
99- spawn_prometheus_upkeep ( prometheus_handle) . map_err ( anyhow:: Error :: msg) ?;
100- Ok ( prometheus_recorder)
101- }
102-
103- pub fn metrics_text_payload ( ) -> Result < String , String > {
104- let handle = PROMETHEUS_HANDLE
105- . get ( )
106- . ok_or_else ( || "Prometheus metrics rendering is not installed yet" . to_string ( ) ) ?;
107- Ok ( handle. render ( ) )
108- }
109-
110- fn spawn_prometheus_upkeep ( handle : PrometheusHandle ) -> Result < ( ) , String > {
111- // Quickwit serves the existing `/metrics` route itself, so we build only the
112- // Prometheus recorder instead of using the exporter's HTTP listener. That lower-level
113- // API does not spawn the upkeep task that periodically drains histogram buffers.
114- std:: thread:: Builder :: new ( )
115- . name ( "telemetry-exporter-prometheus-upkeep" . to_string ( ) )
116- . spawn ( move || {
117- loop {
118- std:: thread:: sleep ( Duration :: from_secs ( 5 ) ) ;
119- handle. run_upkeep ( ) ;
120- }
121- } )
122- . map ( |_| ( ) )
123- . map_err ( |error| format ! ( "failed to spawn Prometheus metrics upkeep thread: {error}" ) )
124- }
125-
126- fn build_otlp_recorder (
127- service_version : & str ,
128- otlp_config : & OtlpExporterConfig ,
129- ) -> anyhow:: Result < ( OpenTelemetryRecorder , SdkMeterProvider ) > {
130- let metrics_protocol = otlp_config. metrics_protocol ( ) ?;
131- let temporality = otlp_config. metrics_temporality ( ) ?;
132- let metric_exporter = metrics_protocol. metric_exporter ( temporality) ?;
133- let metrics_provider = SdkMeterProvider :: builder ( )
134- . with_resource ( quickwit_resource ( service_version) )
135- . with_periodic_exporter ( metric_exporter)
136- . build ( ) ;
137- let meter = metrics_provider. meter ( "quickwit" ) ;
138-
139- let recorder = OpenTelemetryRecorder :: new ( meter) ;
140- for ( name, buckets) in quickwit_metrics:: histogram_buckets ( ) {
141- recorder. set_histogram_bounds ( & metrics:: KeyName :: from ( name) , buckets) ;
142- }
143- Ok ( ( recorder, metrics_provider) )
144- }
145-
146- #[ cfg( test) ]
147- mod tests {
148- use metrics:: with_local_recorder;
149- use metrics_exporter_prometheus:: PrometheusBuilder ;
150- use quickwit_metrics:: { gauge, labels} ;
151-
152- use super :: * ;
153-
154- #[ test]
155- fn metrics_text_payload_renders_prometheus_handle ( ) {
156- let recorder = PrometheusBuilder :: new ( ) . build_recorder ( ) ;
157- PROMETHEUS_HANDLE
158- . set ( recorder. handle ( ) )
159- . expect ( "Prometheus handle should be set once" ) ;
160-
161- with_local_recorder ( & recorder, || {
162- let info_metric = gauge ! (
163- name: "prometheus_payload_info" ,
164- description: "prometheus payload info" ,
165- subsystem: "" ,
166- ) ;
167- quickwit_metrics:: describe_metrics ( ) ;
168- gauge ! ( parent: info_metric, labels: [ labels!( "version" => "test" ) ] ) . set ( 1.0 ) ;
169- } ) ;
170-
171- let payload = metrics_text_payload ( ) . expect ( "Prometheus payload should render" ) ;
172- assert ! ( payload. contains( "# HELP quickwit_prometheus_payload_info" ) ) ;
173- assert ! ( payload. contains( r#"quickwit_prometheus_payload_info{version="test"} 1"# ) ) ;
174- }
175- }
0 commit comments