Line data Source code
1 : #include "fd_prometheus.h"
2 :
3 : #include "fd_metrics.h"
4 :
5 : #include "../topo/fd_topo.h"
6 : #include "../../waltz/http/fd_http_server.h"
7 :
8 : struct fd_prom_render {
9 : fd_http_server_t * http;
10 : ulong last_name_hash;
11 : };
12 :
13 : typedef struct fd_prom_render fd_prom_render_t;
14 :
15 : fd_prom_render_t
16 3 : fd_prom_render_create( fd_http_server_t * http ) {
17 3 : return (fd_prom_render_t) {
18 3 : .http = http,
19 3 : .last_name_hash = 0UL
20 3 : };
21 3 : }
22 :
23 : /* Counters are exported with a "_total" suffix per Prometheus naming
24 : conventions. Gauges and histograms keep their bare name (histograms
25 : additionally emit the standard _bucket/_sum/_count series). */
26 :
27 : static char const *
28 : metric_export_name( fd_metrics_meta_t const * metric,
29 : char * buf,
30 432 : ulong buf_sz ) {
31 432 : if( metric->type==FD_METRICS_TYPE_COUNTER ) {
32 387 : FD_TEST( fd_cstr_printf_check( buf, buf_sz, NULL, "%s_total", metric->name ) );
33 387 : return buf;
34 387 : }
35 45 : return metric->name;
36 432 : }
37 :
38 : static void
39 : render_header( fd_prom_render_t * r,
40 294 : fd_metrics_meta_t const * metric ) {
41 : /* Only render header once per metric name */
42 294 : ulong hash = fd_cstr_hash( metric->name );
43 294 : if( r->last_name_hash != hash ) {
44 144 : if( r->last_name_hash ) {
45 141 : fd_http_server_printf( r->http, "\n" );
46 141 : }
47 144 : char name_buf[ 256 ];
48 144 : char const * name = metric_export_name( metric, name_buf, sizeof(name_buf) );
49 144 : fd_http_server_printf( r->http, "# HELP %s %s\n# TYPE %s %s\n", name, metric->desc, name, fd_metrics_meta_type_str( metric ) );
50 144 : r->last_name_hash = hash;
51 144 : }
52 294 : }
53 :
54 : static void
55 : render_link( fd_prom_render_t * r,
56 : fd_metrics_meta_t const * metric,
57 : fd_topo_tile_t const * tile,
58 : fd_topo_link_t const * link,
59 0 : ulong value ) {
60 0 : render_header( r, metric );
61 0 : switch( metric->converter ) {
62 0 : case FD_METRICS_CONVERTER_NANOSECONDS:
63 0 : value = fd_metrics_convert_ticks_to_nanoseconds( value );
64 0 : break;
65 0 : case FD_METRICS_CONVERTER_NONE:
66 0 : break;
67 0 : default:
68 0 : FD_LOG_ERR(( "unknown converter %i", metric->converter ));
69 0 : }
70 0 : char name_buf[ 256 ];
71 0 : char const * name = metric_export_name( metric, name_buf, sizeof(name_buf) );
72 0 : fd_http_server_printf( r->http, "%s{kind=\"%s\",kind_id=\"%lu\",link_kind=\"%s\",link_kind_id=\"%lu\"} %lu\n", name, tile->name, tile->kind_id, link->name, link->kind_id, value );
73 0 : }
74 :
75 : static void
76 : render_histogram( fd_prom_render_t * r,
77 : fd_metrics_meta_t const * metric,
78 6 : fd_topo_tile_t const * tile ) {
79 6 : render_header( r, metric );
80 :
81 6 : fd_histf_t hist[1];
82 6 : if( FD_LIKELY( metric->converter==FD_METRICS_CONVERTER_SECONDS ) )
83 6 : FD_TEST( fd_histf_new( hist, fd_metrics_convert_seconds_to_ticks( metric->histogram.seconds.min ), fd_metrics_convert_seconds_to_ticks ( metric->histogram.seconds.max ) ) );
84 0 : else if( FD_LIKELY( metric->converter==FD_METRICS_CONVERTER_NONE ) )
85 0 : FD_TEST( fd_histf_new( hist, metric->histogram.none.min, metric->histogram.none.max ) );
86 0 : else FD_LOG_ERR(( "unknown converter %i", metric->converter ));
87 :
88 6 : ulong value = 0;
89 6 : char value_str[ 64 ];
90 102 : for( ulong k=0; k<FD_HISTF_BUCKET_CNT; k++ ) {
91 96 : value += *(fd_metrics_tile( tile->metrics ) + metric->offset + k);
92 :
93 96 : char * le; /* le here means "less then or equal" not "left edge" */
94 96 : char le_str[ 64 ];
95 96 : if( FD_UNLIKELY( k==FD_HISTF_BUCKET_CNT-1UL ) ) le = "+Inf";
96 90 : else {
97 90 : ulong edge = fd_histf_right( hist, k );
98 90 : if( FD_LIKELY( metric->converter==FD_METRICS_CONVERTER_SECONDS ) ) {
99 90 : double edgef = fd_metrics_convert_ticks_to_seconds( edge-1 );
100 90 : FD_TEST( fd_cstr_printf_check( le_str, sizeof( le_str ), NULL, "%.17g", edgef ) );
101 90 : } else {
102 0 : FD_TEST( fd_cstr_printf_check( le_str, sizeof( le_str ), NULL, "%lu", edge-1 ) );
103 0 : }
104 90 : le = le_str;
105 90 : }
106 :
107 96 : FD_TEST( fd_cstr_printf_check( value_str, sizeof( value_str ), NULL, "%lu", value ));
108 96 : fd_http_server_printf( r->http, "%s_bucket{kind=\"%s\",kind_id=\"%lu\",le=\"%s\"} %s\n", metric->name, tile->name, tile->kind_id, le, value_str );
109 96 : }
110 :
111 6 : char sum_str[ 64 ];
112 6 : if( FD_LIKELY( metric->converter==FD_METRICS_CONVERTER_SECONDS ) ) {
113 6 : double sumf = fd_metrics_convert_ticks_to_seconds( *(fd_metrics_tile( tile->metrics ) + metric->offset + FD_HISTF_BUCKET_CNT) );
114 6 : FD_TEST( fd_cstr_printf_check( sum_str, sizeof( sum_str ), NULL, "%.17g", sumf ) );
115 6 : } else {
116 0 : FD_TEST( fd_cstr_printf_check( sum_str, sizeof( sum_str ), NULL, "%lu", *(fd_metrics_tile( tile->metrics ) + metric->offset + FD_HISTF_BUCKET_CNT) ));
117 0 : }
118 :
119 6 : fd_http_server_printf( r->http, "%s_sum{kind=\"%s\",kind_id=\"%lu\"} %s\n", metric->name, tile->name, tile->kind_id, sum_str );
120 6 : fd_http_server_printf( r->http, "%s_count{kind=\"%s\",kind_id=\"%lu\"} %s\n", metric->name, tile->name, tile->kind_id, value_str );
121 6 : }
122 :
123 : static void
124 : render_counter( fd_prom_render_t * r,
125 : fd_metrics_meta_t const * metric,
126 288 : fd_topo_tile_t const * tile ) {
127 288 : render_header( r, metric );
128 288 : ulong raw_value = *(fd_metrics_tile( tile->metrics ) + metric->offset);
129 :
130 288 : char name_buf[ 256 ];
131 288 : char const * name = metric_export_name( metric, name_buf, sizeof(name_buf) );
132 288 : fd_http_server_printf( r->http, "%s{kind=\"%s\",kind_id=\"%lu\"", name, tile->name, tile->kind_id );
133 288 : if( metric->enum_name ) {
134 180 : fd_http_server_printf( r->http, ",%s=\"%s\"", metric->enum_name, metric->enum_variant );
135 180 : }
136 288 : switch( metric->converter ) {
137 0 : case FD_METRICS_CONVERTER_NANOSECONDS:
138 0 : fd_http_server_printf( r->http, "} %lu\n", fd_metrics_convert_ticks_to_nanoseconds( raw_value ) );
139 0 : break;
140 0 : case FD_METRICS_CONVERTER_SECONDS:
141 0 : fd_http_server_printf( r->http, "} %e\n", fd_metrics_convert_ticks_to_seconds( raw_value ) );
142 0 : break;
143 288 : case FD_METRICS_CONVERTER_NONE:
144 288 : fd_http_server_printf( r->http, "} %lu\n", raw_value );
145 288 : break;
146 0 : default:
147 0 : FD_LOG_ERR(( "unknown converter %i", metric->converter ));
148 288 : }
149 288 : }
150 :
151 : static void
152 : render_links_in( fd_prom_render_t * r,
153 : fd_topo_t const * topo,
154 : ulong metrics_cnt,
155 0 : fd_metrics_meta_t const * metrics ) {
156 0 : for( ulong i=0UL; i<metrics_cnt; i++ ) {
157 0 : fd_metrics_meta_t const * metric = &metrics[ i ];
158 0 : for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
159 0 : fd_topo_tile_t const * tile = &topo->tiles[ j ];
160 0 : ulong polled_in_idx = 0UL;
161 0 : for( ulong k=0UL; k<tile->in_cnt; k++ ) {
162 0 : if( FD_UNLIKELY( !tile->in_link_poll[ k ] ) ) continue;
163 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ k ] ];
164 0 : ulong value = *(fd_metrics_link_in( tile->metrics, polled_in_idx ) + metric->offset );
165 0 : render_link( r, metric, tile, link, value );
166 0 : polled_in_idx++;
167 0 : }
168 0 : }
169 0 : }
170 0 : }
171 :
172 : static void
173 : render_tile_metric( fd_prom_render_t * r,
174 : fd_topo_tile_t const * tile,
175 294 : fd_metrics_meta_t const * metric ) {
176 294 : if( FD_LIKELY( metric->type==FD_METRICS_TYPE_COUNTER || metric->type==FD_METRICS_TYPE_GAUGE ) ) {
177 288 : render_counter( r, metric, tile );
178 288 : } else if( FD_LIKELY( metric->type==FD_METRICS_TYPE_HISTOGRAM ) ) {
179 6 : render_histogram( r, metric, tile );
180 6 : }
181 294 : }
182 :
183 : static void
184 : render_tile( fd_prom_render_t * r,
185 : fd_topo_t const * topo,
186 : char const * tile_name,
187 : ulong metrics_cnt,
188 0 : fd_metrics_meta_t const * metrics ) {
189 0 : for( ulong i=0UL; i<metrics_cnt; i++ ) {
190 0 : for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
191 : /* FIXME: This is O(n^2) rather than O(n). */
192 0 : if( FD_LIKELY( tile_name!=NULL && 0!=strcmp( topo->tiles[ j ].name, tile_name ) ) ) continue;
193 0 : render_tile_metric( r, topo->tiles+j, metrics+i );
194 0 : }
195 0 : }
196 0 : }
197 :
198 : void
199 : fd_prometheus_render_tile( fd_http_server_t * http,
200 : fd_topo_tile_t const * tile,
201 : fd_metrics_meta_t const * metrics,
202 3 : ulong metrics_cnt ) {
203 3 : fd_prom_render_t r = fd_prom_render_create( http );
204 297 : for( ulong i=0UL; i<metrics_cnt; i++ ) {
205 294 : render_tile_metric( &r, tile, metrics+i );
206 294 : }
207 3 : }
208 :
209 : void
210 : fd_prometheus_render_all( fd_topo_t const * topo,
211 0 : fd_http_server_t * http ) {
212 0 : fd_prom_render_t r = fd_prom_render_create( http );
213 0 : render_tile( &r, topo, NULL, FD_METRICS_ALL_TOTAL, FD_METRICS_ALL );
214 0 : render_links_in( &r, topo, FD_METRICS_ALL_LINK_IN_TOTAL, FD_METRICS_ALL_LINK_IN );
215 0 : for( ulong i=0UL; i<FD_METRICS_TILE_KIND_CNT; i++ ) {
216 0 : render_tile( &r, topo, FD_METRICS_TILE_KIND_NAMES[ i ], FD_METRICS_TILE_KIND_SIZES[ i ], FD_METRICS_TILE_KIND_METRICS[ i ] );
217 0 : }
218 0 : }
|