/src/mod_auth_openidc/src/metrics.c
Line | Count | Source |
1 | | /* |
2 | | * Licensed to the Apache Software Foundation (ASF) under one |
3 | | * or more contributor license agreements. See the NOTICE file |
4 | | * distributed with this work for additional information |
5 | | * regarding copyright ownership. The ASF licenses this file |
6 | | * to you under the Apache License, Version 2.0 (the |
7 | | * "License"); you may not use this file except in compliance |
8 | | * with the License. You may obtain a copy of the License at |
9 | | * |
10 | | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | | * |
12 | | * Unless required by applicable law or agreed to in writing, |
13 | | * software distributed under the License is distributed on an |
14 | | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | | * KIND, either express or implied. See the License for the |
16 | | * specific language governing permissions and limitations |
17 | | * under the License. |
18 | | */ |
19 | | |
20 | | /*************************************************************************** |
21 | | * Copyright (C) 2023-2026 ZmartZone Holding BV |
22 | | * All rights reserved. |
23 | | * |
24 | | * DISCLAIMER OF WARRANTIES: |
25 | | * |
26 | | * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT |
27 | | * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, |
28 | | * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, |
29 | | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY |
30 | | * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE |
31 | | * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET |
32 | | * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE |
33 | | * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
34 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
35 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF |
36 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
37 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
38 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
39 | | * |
40 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
41 | | */ |
42 | | |
43 | | // clang-format off |
44 | | |
45 | | #ifndef WIN32 |
46 | | #include <unistd.h> |
47 | | #endif |
48 | | |
49 | | #include "util/util.h" |
50 | | #include "http.h" |
51 | | #include "cache/cache.h" |
52 | | #include "cfg/cfg_int.h" |
53 | | #include "metrics.h" |
54 | | #include <limits.h> |
55 | | #include <apr_atomic.h> |
56 | | #include <apr_shm.h> |
57 | | #include <apr_lib.h> |
58 | | |
59 | | // NB: formatting matters for docs script from here until clang-format on |
60 | | |
61 | | // KEEP THIS: start-of-classes |
62 | | |
63 | | #define OM_CLASS_AUTH_TYPE "authtype" // Request counter, overall and per AuthType: openid-connect, oauth20 and auth-openidc. |
64 | | #define OM_CLASS_AUTHN "authn" // Authentication request creation and response processing. |
65 | | #define OM_CLASS_AUTHZ "authz" // Authorization errors per OIDCUnAutzAction (per Require statement, not overall). |
66 | | #define OM_CLASS_REQUIRE_CLAIM "require.claim" // Match/failure count of Require claim directives (per Require statement, not overall). |
67 | | #define OM_CLASS_CLAIM "claim" // Claims per value |
68 | | #define OM_CLASS_PROVIDER "provider" // Requests to the provider [token, userinfo, metadata] endpoints. |
69 | | #define OM_CLASS_SESSION "session" // Existing session processing. |
70 | | #define OM_CLASS_CACHE "cache" // Cache read/write timings and errors. |
71 | | #define OM_CLASS_LOGOUT "logout" // Front- and back-channel logout processing. |
72 | | #define OM_CLASS_REDIRECT_URI "redirect_uri" // Requests to the Redirect URI, per type. |
73 | | #define OM_CLASS_CONTENT "content" // Requests to the content handler, per type of request: info, metrics, jwks, etc. |
74 | | |
75 | | // KEEP THIS: end-of-classes |
76 | | |
77 | | // NB: order must match the oidc_metrics_timing_type_t enum type in metrics.h |
78 | | |
79 | | const oidc_metrics_timing_info_t _oidc_metrics_timings_info[] = { |
80 | | |
81 | | // KEEP THIS: start-of-timers |
82 | | |
83 | | { OM_CLASS_AUTH_TYPE, "handler", "the overall authn+authz processing time" }, |
84 | | |
85 | | { OM_CLASS_AUTHN, "request", "authentication requests" }, |
86 | | { OM_CLASS_AUTHN, "response", "authentication responses" }, |
87 | | |
88 | | { OM_CLASS_SESSION, "valid", "successfully validated existing sessions" }, |
89 | | |
90 | | { OM_CLASS_PROVIDER, "metadata", "provider discovery document requests" }, |
91 | | { OM_CLASS_PROVIDER, "token", "provider token requests" }, |
92 | | { OM_CLASS_PROVIDER, "refresh", "provider refresh token requests" }, |
93 | | { OM_CLASS_PROVIDER, "userinfo", "provider userinfo requests" }, |
94 | | { OM_CLASS_PROVIDER, "jwks", "provider JWKs document requests" }, |
95 | | { OM_CLASS_PROVIDER, "par", "provider pushed authorization requests" }, |
96 | | |
97 | | { OM_CLASS_CACHE, "read", "cache read requests" }, |
98 | | { OM_CLASS_CACHE, "write", "cache write requests" }, |
99 | | |
100 | | // KEEP THIS: end-of-timers |
101 | | |
102 | | }; |
103 | | |
104 | | // NB: order must match the oidc_metrics_counter_type_t enum type in metrics.h |
105 | | |
106 | | const oidc_metrics_counter_info_t _oidc_metrics_counters_info[] = { |
107 | | #define OIDC_METRICS_COUNTER_INFO(id, class, name, desc) {class, name, desc}, |
108 | | OIDC_METRICS_COUNTERS_LIST(OIDC_METRICS_COUNTER_INFO) |
109 | | #undef OIDC_METRICS_COUNTER_INFO |
110 | | }; |
111 | | |
112 | | // clang-format on |
113 | | |
114 | | /* one entry per oidc_metrics_counter_type_t value */ |
115 | | OIDC_STATIC_ASSERT(sizeof(_oidc_metrics_counters_info) / sizeof(oidc_metrics_counter_info_t) == OM_NUMBER_OF_COUNTERS, |
116 | | counters_info_matches_enum); |
117 | | /* one entry per oidc_metrics_timing_type_t value */ |
118 | | OIDC_STATIC_ASSERT(sizeof(_oidc_metrics_timings_info) / sizeof(oidc_metrics_timing_info_t) == OM_NUMBER_OF_TIMINGS, |
119 | | timings_info_matches_enum); |
120 | | |
121 | | typedef struct oidc_metrics_t { |
122 | | apr_pool_t *pool; |
123 | | apr_hash_t *counters; |
124 | | apr_hash_t *timings; |
125 | | } oidc_metrics_t; |
126 | | |
127 | | // pointer to the shared memory segment that holds the JSON metrics data |
128 | | static apr_shm_t *_oidc_metrics_cache = NULL; |
129 | | // flag to record if we are a parent process or a child process |
130 | | static apr_byte_t _oidc_metrics_is_parent = FALSE; |
131 | | /* Atomic stop flag shared by the flush, cleanup, and child-init paths. */ |
132 | | static volatile apr_uint32_t _oidc_metrics_thread_exit = 0; |
133 | | // mutex to protect the shared memory storage |
134 | | static oidc_cache_mutex_t *_oidc_metrics_global_mutex = NULL; |
135 | | // pointer to the thread that periodically writes the locally gathered metrics to shared memory |
136 | | static apr_thread_t *_oidc_metrics_thread = NULL; |
137 | | // pid that owns _oidc_metrics_thread; used to tell a post-fork stale handle from a same-process double-init |
138 | | static pid_t _oidc_metrics_thread_pid = 0; |
139 | | // local in-memory cached metrics |
140 | | static oidc_metrics_t _oidc_metrics = {NULL, NULL, NULL}; |
141 | | // mutex to protect the local metrics hash table |
142 | | static oidc_cache_mutex_t *_oidc_metrics_process_mutex = NULL; |
143 | | |
144 | | // default shared memory write interval in seconds |
145 | 0 | #define OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT 5000 |
146 | | |
147 | | // maximum length of the string representation of the global JSON metrics data in shared memory |
148 | | // 1024 sample size (compact, long keys, large json_int values, no description), timing + counter |
149 | | // 256 number of individual metrics collected |
150 | | // 4 number of vhosts supported |
151 | 0 | #define OIDC_METRICS_CACHE_JSON_MAX_DEFAULT (1024 * 256 * 4) |
152 | | |
153 | | // NB: "label" is the millisecond le= label of the legacy (deprecated) Prometheus families, |
154 | | // "label_seconds" the seconds le= label of the idiomatic _seconds families |
155 | | typedef struct oidc_metrics_bucket_t { |
156 | | const char *name; |
157 | | const char *label; |
158 | | const char *label_seconds; |
159 | | apr_time_t threshold; |
160 | | } oidc_metrics_bucket_t; |
161 | | |
162 | | // clang-format off |
163 | | |
164 | | static oidc_metrics_bucket_t _oidc_metric_buckets[] = { |
165 | | { "le01", "le=\"0.1\"", "le=\"0.0001\"", 100 }, |
166 | | { "le05", "le=\"0.5\"", "le=\"0.0005\"", 500 }, |
167 | | { "le1", "le=\"1\"", "le=\"0.001\"", apr_time_from_msec(1) }, |
168 | | { "le5", "le=\"5\"", "le=\"0.005\"", apr_time_from_msec(5) }, |
169 | | { "le10", "le=\"10\"", "le=\"0.01\"", apr_time_from_msec(10) }, |
170 | | { "le50", "le=\"50\"", "le=\"0.05\"", apr_time_from_msec(50) }, |
171 | | { "le100", "le=\"100\"", "le=\"0.1\"", apr_time_from_msec(100) }, |
172 | | { "le500", "le=\"500\"", "le=\"0.5\"", apr_time_from_msec(500) }, |
173 | | { "le1000", "le=\"1000\"", "le=\"1\"", apr_time_from_msec(1000) }, |
174 | | { "le5000", "le=\"5000\"", "le=\"5\"", apr_time_from_msec(5000) }, |
175 | | { "le10000", "le=\"10000\"", "le=\"10\"", apr_time_from_msec(10000) }, |
176 | | { "le30000", "le=\"30000\"", "le=\"30\"", apr_time_from_msec(30000) }, |
177 | | { "inf", "le=\"+Inf\"", "le=\"+Inf\"", 0 } |
178 | | }; |
179 | | |
180 | | // clang-format on |
181 | | |
182 | 0 | #define OIDC_METRICS_BUCKET_NUM ((int)(sizeof(_oidc_metric_buckets) / sizeof(oidc_metrics_bucket_t))) |
183 | | |
184 | | // NB: matters for Prometheus formatting |
185 | 0 | #define OIDC_METRICS_SUM "sum" |
186 | 0 | #define OIDC_METRICS_COUNT "count" |
187 | | |
188 | | #define OIDC_METRICS_SPECS "specs" |
189 | | |
190 | 0 | #define OIDC_METRICS_JSON_CLASS_NAME "class" |
191 | 0 | #define OIDC_METRICS_JSON_METRIC_NAME "name" |
192 | 0 | #define OIDC_METRICS_JSON_DESC "desc" |
193 | | |
194 | 0 | #define OIDC_METRICS_TIMINGS "timings" |
195 | 0 | #define OIDC_METRICS_COUNTERS "counters" |
196 | | |
197 | | /* |
198 | | * convert a Jansson number to a string: apr_psprintf does not understand OIDC_JSON_INT_FORMAT |
199 | | * ("%lld"), APR spells a 64-bit integer as APR_INT64_T_FMT, and oidc_json_int_t is at most 64 |
200 | | * bits wide |
201 | | */ |
202 | 0 | static inline char *_json_int2str(apr_pool_t *pool, oidc_json_int_t n) { |
203 | 0 | return apr_psprintf(pool, "%" APR_INT64_T_FMT, (apr_int64_t)n); |
204 | 0 | } |
205 | | |
206 | 0 | #define OIDC_METRICS_INT_MAX OIDC_JSON_INT_MAX |
207 | | |
208 | | /* |
209 | | * check Jansson specific integer/long number overrun |
210 | | */ |
211 | 0 | static inline int _is_overflow(server_rec *s, oidc_json_int_t cur, oidc_json_int_t add) { |
212 | 0 | if (add > (OIDC_METRICS_INT_MAX - cur)) { |
213 | 0 | oidc_swarn(s, |
214 | 0 | "reset metrics since the size (%s) of the integer value would be larger than the " |
215 | 0 | "JSON/libjansson maximum " |
216 | 0 | "(%s)", |
217 | 0 | _json_int2str(s->process->pool, add), _json_int2str(s->process->pool, OIDC_METRICS_INT_MAX)); |
218 | 0 | return 1; |
219 | 0 | } |
220 | 0 | return 0; |
221 | 0 | } |
222 | | |
223 | | // single counter container |
224 | | typedef struct oidc_metrics_counter_t { |
225 | | oidc_json_int_t count; |
226 | | } oidc_metrics_counter_t; |
227 | | |
228 | | // single timing stats container |
229 | | typedef struct oidc_metrics_timing_t { |
230 | | oidc_json_int_t buckets[OIDC_METRICS_BUCKET_NUM]; |
231 | | apr_time_t sum; |
232 | | oidc_json_int_t count; |
233 | | } oidc_metrics_timing_t; |
234 | | |
235 | | /* |
236 | | * Reinitialize the per-process collection tables in a dedicated child pool. |
237 | | * Clearing this pool after a flush reclaims all nested hashes, keys and values |
238 | | * in one operation; the process mutex must be held once request threads exist. |
239 | | */ |
240 | 0 | static apr_byte_t oidc_metrics_local_reset(server_rec *s) { |
241 | 0 | if (_oidc_metrics.pool == NULL) { |
242 | 0 | if (apr_pool_create(&_oidc_metrics.pool, s->process->pool) != APR_SUCCESS) { |
243 | 0 | oidc_serror(s, "apr_pool_create failed: cannot initialize local metrics"); |
244 | 0 | return FALSE; |
245 | 0 | } |
246 | 0 | } else { |
247 | 0 | apr_pool_clear(_oidc_metrics.pool); |
248 | 0 | } |
249 | | |
250 | 0 | _oidc_metrics.counters = apr_hash_make(_oidc_metrics.pool); |
251 | 0 | _oidc_metrics.timings = apr_hash_make(_oidc_metrics.pool); |
252 | 0 | return TRUE; |
253 | 0 | } |
254 | | |
255 | | // context holder for parsing valid classnames |
256 | | typedef struct oidc_metrics_add_classname_ctx_t { |
257 | | apr_pool_t *pool; |
258 | | char **valid_names; |
259 | | } oidc_metrics_add_classname_ctx_t; |
260 | | |
261 | | /* |
262 | | * loop function for parsing valid classnames |
263 | | */ |
264 | 0 | static int oidc_metrics_add_classnames(void *rec, const char *key, const char *value) { |
265 | 0 | oidc_metrics_add_classname_ctx_t *ctx = (oidc_metrics_add_classname_ctx_t *)rec; |
266 | 0 | *ctx->valid_names = apr_psprintf(ctx->pool, "%s%s%s", *ctx->valid_names ? *ctx->valid_names : "", |
267 | 0 | *ctx->valid_names ? " | " : "", value); |
268 | 0 | return 1; |
269 | 0 | } |
270 | | |
271 | | /* |
272 | | * check if the provided value is a valid classname |
273 | | */ |
274 | 0 | apr_byte_t oidc_metrics_is_valid_classname(apr_pool_t *pool, const char *name, char **valid_names) { |
275 | 0 | int i = 0; |
276 | 0 | int n = 0; |
277 | 0 | apr_table_t *names = apr_table_make(pool, 1); |
278 | 0 | oidc_metrics_add_classname_ctx_t ctx = {pool, valid_names}; |
279 | |
|
280 | 0 | n = sizeof(_oidc_metrics_timings_info) / sizeof(oidc_metrics_timing_info_t); |
281 | 0 | for (i = 0; i < n; i++) { |
282 | 0 | apr_table_set(names, _oidc_metrics_timings_info[i].class_name, |
283 | 0 | _oidc_metrics_timings_info[i].class_name); |
284 | 0 | } |
285 | 0 | n = sizeof(_oidc_metrics_counters_info) / sizeof(oidc_metrics_counter_info_t); |
286 | 0 | for (i = 0; i < n; i++) { |
287 | | // NB: "claim" is the only counter class with a name/value option and is special-cased here (rather |
288 | | // than driven from a static list) |
289 | 0 | if (_oidc_strcmp(_oidc_metrics_counters_info[i].class_name, "claim") != 0) |
290 | 0 | apr_table_set(names, _oidc_metrics_counters_info[i].class_name, |
291 | 0 | _oidc_metrics_counters_info[i].class_name); |
292 | 0 | } |
293 | |
|
294 | 0 | *valid_names = NULL; |
295 | 0 | apr_table_do(oidc_metrics_add_classnames, &ctx, names, NULL); |
296 | 0 | *valid_names = apr_psprintf(pool, "%s%s%s", *valid_names ? *valid_names : "", *valid_names ? " | " : "", |
297 | 0 | "claim.id_token.* | claim.userinfo.*"); |
298 | |
|
299 | 0 | return apr_table_get(names, name) ? TRUE |
300 | 0 | : ((_oidc_strstr(name, "claim.id_token.") != NULL) || |
301 | 0 | (_oidc_strstr(name, "claim.userinfo.") != NULL)); |
302 | 0 | } |
303 | | |
304 | | /* |
305 | | * retrieve environment variable integer with default setting |
306 | | */ |
307 | 0 | static inline int _oidc_metrics_get_env_int(const char *name, int dval) { |
308 | 0 | return _oidc_str_to_int(getenv(name), dval); |
309 | 0 | } |
310 | | |
311 | 0 | #define OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR "OIDC_METRICS_CACHE_JSON_MAX" |
312 | | |
313 | | /* |
314 | | * fixed self-health header kept in the shared memory segment in front of the JSON metrics data; |
315 | | * 32-bit members only so the apr_atomic_*32 functions cover every supported APR version |
316 | | */ |
317 | | typedef struct oidc_metrics_shm_hdr_t { |
318 | | // number of flushes/resets that could not be stored because the JSON data outgrew the segment |
319 | | apr_uint32_t flush_errors; |
320 | | // seconds-since-epoch timestamp of the last completed flush cycle of this process' flush thread |
321 | | apr_uint32_t last_flush; |
322 | | } oidc_metrics_shm_hdr_t; |
323 | | |
324 | 0 | #define OIDC_METRICS_SHM_HDR_SIZE (APR_ALIGN_DEFAULT(sizeof(oidc_metrics_shm_hdr_t))) |
325 | | |
326 | 0 | static inline oidc_metrics_shm_hdr_t *_oidc_metrics_shm_hdr(void) { |
327 | 0 | return (oidc_metrics_shm_hdr_t *)apr_shm_baseaddr_get(_oidc_metrics_cache); |
328 | 0 | } |
329 | | |
330 | 0 | static inline char *_oidc_metrics_shm_json(void) { |
331 | 0 | return ((char *)apr_shm_baseaddr_get(_oidc_metrics_cache)) + OIDC_METRICS_SHM_HDR_SIZE; |
332 | 0 | } |
333 | | |
334 | | static apr_size_t _g_oidc_metrics_shm_size = 0; |
335 | | |
336 | | /* |
337 | | * get the maximum size of the serialized JSON metrics data: directive, environment variable or default |
338 | | */ |
339 | 0 | static inline apr_size_t _oidc_metrics_shm_size(server_rec *s) { |
340 | 0 | if (_g_oidc_metrics_shm_size == 0) { |
341 | 0 | const oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); |
342 | 0 | int n = oidc_cfg_metrics_cache_json_max_get(cfg); |
343 | 0 | if (n == OIDC_CONFIG_POS_INT_UNSET) |
344 | 0 | n = _oidc_metrics_get_env_int(OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, |
345 | 0 | OIDC_METRICS_CACHE_JSON_MAX_DEFAULT); |
346 | 0 | if ((n < 1) || (n > 1024 * 256 * 4 * 100)) { |
347 | 0 | oidc_serror(s, "environment value %s out of bounds, fallback to default", |
348 | 0 | OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR); |
349 | 0 | _g_oidc_metrics_shm_size = OIDC_METRICS_CACHE_JSON_MAX_DEFAULT; |
350 | 0 | } else { |
351 | 0 | _g_oidc_metrics_shm_size = n; |
352 | 0 | } |
353 | 0 | } |
354 | 0 | return _g_oidc_metrics_shm_size; |
355 | 0 | } |
356 | | |
357 | | /* |
358 | | * retrieve the (JSON) serialized (global) metrics data from shared memory |
359 | | */ |
360 | 0 | static inline char *_oidc_metrics_storage_get(server_rec *s, apr_pool_t *pool) { |
361 | 0 | const char *p = _oidc_metrics_shm_json(); |
362 | 0 | return p && (*p != 0) ? apr_pstrndup(pool, p, _oidc_metrics_shm_size(s)) : NULL; |
363 | 0 | } |
364 | | |
365 | | /* |
366 | | * store the serialized (global) metrics data in shared memory |
367 | | */ |
368 | 0 | static inline void _oidc_metrics_storage_set(server_rec *s, const char *value) { |
369 | 0 | char *p = _oidc_metrics_shm_json(); |
370 | 0 | if (value) { |
371 | 0 | apr_size_t n = _oidc_strlen(value) + 1; |
372 | 0 | if (n > _oidc_metrics_shm_size(s)) { |
373 | 0 | apr_atomic_inc32(&_oidc_metrics_shm_hdr()->flush_errors); |
374 | 0 | oidc_serror(s, |
375 | 0 | "json value too large: set or increase " OIDCMetricsCacheJsonMax |
376 | 0 | " (or system environment variable %s) to a value " |
377 | 0 | "larger than %" APR_SIZE_T_FMT, |
378 | 0 | OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, _oidc_metrics_shm_size(s)); |
379 | 0 | } else { |
380 | 0 | _oidc_memcpy(p, value, n); |
381 | 0 | } |
382 | 0 | } else { |
383 | 0 | *p = 0; |
384 | 0 | } |
385 | 0 | } |
386 | | |
387 | | /* |
388 | | * parse a string into a JSON object |
389 | | */ |
390 | 0 | static oidc_json_t *oidc_metrics_json_load(apr_pool_t *pool, const char *s_json, char **s_err) { |
391 | 0 | oidc_json_t *json = NULL; |
392 | 0 | if (s_json == NULL) |
393 | 0 | s_json = "{}"; |
394 | 0 | oidc_json_parse(pool, s_json, 0, &json, s_err); |
395 | 0 | return json; |
396 | 0 | } |
397 | | |
398 | | /* |
399 | | * parse a string into a JSON object in a server_rec context |
400 | | */ |
401 | 0 | static oidc_json_t *oidc_metrics_json_parse_s(server_rec *s, apr_pool_t *pool, const char *s_json) { |
402 | 0 | char *s_err = NULL; |
403 | 0 | oidc_json_t *json = oidc_metrics_json_load(pool, s_json, &s_err); |
404 | 0 | if (json == NULL) |
405 | 0 | oidc_serror(s, "JSON parsing failed: %s", s_err); |
406 | 0 | return json; |
407 | 0 | } |
408 | | |
409 | | /* |
410 | | * recursively reset all integer leaf values within a JSON object tree to 0 |
411 | | */ |
412 | 0 | static void oidc_metrics_reset_integer_tree(oidc_json_t *json) { |
413 | 0 | void *iter = oidc_json_object_iter(json); |
414 | 0 | while (iter) { |
415 | 0 | oidc_json_t *value = oidc_json_object_iter_value(iter); |
416 | 0 | if (oidc_json_is_integer(value)) |
417 | 0 | oidc_json_integer_set(value, 0); |
418 | 0 | else if (oidc_json_is_object(value)) |
419 | 0 | oidc_metrics_reset_integer_tree(value); |
420 | 0 | iter = oidc_json_object_iter_next(json, iter); |
421 | 0 | } |
422 | 0 | } |
423 | | |
424 | | /* |
425 | | * reset a single timings entry (buckets, sum, count) to 0 |
426 | | */ |
427 | 0 | static void oidc_metrics_reset_timer(oidc_json_t *j_entry) { |
428 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) |
429 | 0 | oidc_json_object_set_new(j_entry, _oidc_metric_buckets[i].name, oidc_json_integer(0)); |
430 | 0 | oidc_json_object_set_new(j_entry, OIDC_METRICS_SUM, oidc_json_integer(0)); |
431 | 0 | oidc_json_object_set_new(j_entry, OIDC_METRICS_COUNT, oidc_json_integer(0)); |
432 | 0 | } |
433 | | |
434 | | /* |
435 | | * reset all counter and timer entries for a single server in the JSON data |
436 | | */ |
437 | 0 | static void oidc_metrics_reset_server(const oidc_json_t *j_server) { |
438 | 0 | oidc_json_t *j_entries = NULL; |
439 | 0 | void *iter = NULL; |
440 | |
|
441 | 0 | j_entries = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS); |
442 | 0 | if (j_entries) |
443 | 0 | oidc_metrics_reset_integer_tree(j_entries); |
444 | |
|
445 | 0 | j_entries = oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS); |
446 | 0 | iter = oidc_json_object_iter(j_entries); |
447 | 0 | while (iter) { |
448 | 0 | oidc_metrics_reset_timer(oidc_json_object_iter_value(iter)); |
449 | 0 | iter = oidc_json_object_iter_next(j_entries, iter); |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | | /* |
454 | | * reset the serialized (global) metrics data in shared memory |
455 | | */ |
456 | 0 | static inline void oidc_metrics_storage_reset(server_rec *s, apr_pool_t *pool) { |
457 | 0 | const char *s_json = NULL; |
458 | 0 | oidc_json_t *json = NULL; |
459 | 0 | void *iter = NULL; |
460 | | |
461 | | /* get the global stringified JSON metrics */ |
462 | 0 | s_json = _oidc_metrics_storage_get(s, pool); |
463 | | |
464 | | /* parse the metrics string to JSON */ |
465 | 0 | json = oidc_metrics_json_parse_s(s, pool, s_json); |
466 | 0 | if (json == NULL) |
467 | 0 | json = oidc_json_object(); |
468 | |
|
469 | 0 | iter = oidc_json_object_iter(json); |
470 | 0 | while (iter) { |
471 | 0 | oidc_metrics_reset_server(oidc_json_object_iter_value(iter)); |
472 | 0 | iter = oidc_json_object_iter_next(json, iter); |
473 | 0 | } |
474 | | |
475 | | /* serialize the metrics data, preserve order is required for Prometheus */ |
476 | 0 | s_json = oidc_json_encode(pool, json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER); |
477 | | |
478 | | /* free the JSON data */ |
479 | 0 | oidc_json_decref(json); |
480 | | |
481 | | /* store the serialized metrics data in shared memory */ |
482 | 0 | _oidc_metrics_storage_set(s, s_json); |
483 | 0 | } |
484 | | |
485 | | /* |
486 | | * create a new timings entry in the collected JSON data |
487 | | */ |
488 | 0 | static oidc_json_t *oidc_metrics_timings_new(const oidc_metrics_timing_t *timing) { |
489 | 0 | oidc_json_t *entry = oidc_json_object(); |
490 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) |
491 | 0 | oidc_json_object_set_new(entry, _oidc_metric_buckets[i].name, oidc_json_integer(timing->buckets[i])); |
492 | | /* the sum is kept in microseconds so the _seconds Prometheus family is exact; the |
493 | | * (documented) json format converts to milliseconds at presentation time */ |
494 | 0 | oidc_json_object_set_new(entry, OIDC_METRICS_SUM, oidc_json_integer(timing->sum)); |
495 | 0 | oidc_json_object_set_new(entry, OIDC_METRICS_COUNT, oidc_json_integer(timing->count)); |
496 | 0 | return entry; |
497 | 0 | } |
498 | | |
499 | | /* |
500 | | * update an entry in the collected JSON data |
501 | | */ |
502 | 0 | static void oidc_metrics_timings_update(server_rec *s, const oidc_json_t *entry, const oidc_metrics_timing_t *timing) { |
503 | 0 | oidc_json_t *j_member = NULL; |
504 | 0 | oidc_json_int_t n = 0; |
505 | 0 | oidc_json_int_t v = 0; |
506 | |
|
507 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { |
508 | 0 | j_member = oidc_json_object_get(entry, _oidc_metric_buckets[i].name); |
509 | 0 | oidc_json_integer_set(j_member, oidc_json_integer_value(j_member) + timing->buckets[i]); |
510 | 0 | } |
511 | |
|
512 | 0 | j_member = oidc_json_object_get(entry, OIDC_METRICS_SUM); |
513 | 0 | n = oidc_json_integer_value(j_member); |
514 | | |
515 | | /* microseconds, matching oidc_metrics_timings_new */ |
516 | 0 | v = timing->sum; |
517 | 0 | if (_is_overflow(s, n, v)) |
518 | 0 | n = 0; |
519 | |
|
520 | 0 | oidc_json_integer_set(j_member, n + v); |
521 | |
|
522 | 0 | j_member = oidc_json_object_get(entry, OIDC_METRICS_COUNT); |
523 | 0 | n = oidc_json_integer_value(j_member); |
524 | 0 | oidc_json_integer_set(j_member, n + timing->count); |
525 | 0 | } |
526 | | |
527 | 0 | #define OIDC_METRICS_VALUE_DEFAULT "_" |
528 | | |
529 | | /* |
530 | | * value helper to make sure it is not empty |
531 | | */ |
532 | 0 | static inline const char *_metrics_value2key(const char *value) { |
533 | 0 | return (value && _oidc_strcmp(value, "") != 0) ? value : OIDC_METRICS_VALUE_DEFAULT; |
534 | 0 | } |
535 | | |
536 | | /* |
537 | | * create a new counter entry in the collected JSON data |
538 | | */ |
539 | 0 | static oidc_json_t *oidc_metrics_counter_new(apr_hash_t *htable) { |
540 | 0 | oidc_metrics_counter_t *counter = NULL; |
541 | 0 | char *value = NULL; |
542 | 0 | oidc_json_t *j_values = NULL; |
543 | 0 | for (apr_hash_index_t *hi = apr_hash_first(NULL, htable); hi; hi = apr_hash_next(hi)) { |
544 | 0 | apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter); |
545 | 0 | if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) { |
546 | 0 | j_values = oidc_json_integer(counter->count); |
547 | 0 | } else { |
548 | 0 | if (j_values == NULL) |
549 | 0 | j_values = oidc_json_object(); |
550 | 0 | oidc_json_object_set_new(j_values, value, oidc_json_integer(counter->count)); |
551 | 0 | } |
552 | 0 | } |
553 | 0 | return j_values; |
554 | 0 | } |
555 | | |
556 | | /* |
557 | | * update a counter entry in the collected JSON data |
558 | | */ |
559 | 0 | static void oidc_metrics_counter_update(server_rec *s, oidc_json_t *j_counter, apr_hash_t *htable) { |
560 | 0 | oidc_json_int_t v = 0; |
561 | 0 | oidc_metrics_counter_t *counter = NULL; |
562 | 0 | char *value = NULL; |
563 | 0 | oidc_json_t *j_value = NULL; |
564 | 0 | for (apr_hash_index_t *hi = apr_hash_first(NULL, htable); hi; hi = apr_hash_next(hi)) { |
565 | 0 | apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter); |
566 | 0 | if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) { |
567 | 0 | j_value = j_counter; |
568 | 0 | } else { |
569 | 0 | j_value = oidc_json_object_get(j_counter, value); |
570 | 0 | if (j_value == NULL) { |
571 | 0 | oidc_json_object_set_new(j_counter, value, oidc_json_integer(counter->count)); |
572 | 0 | continue; |
573 | 0 | } |
574 | 0 | } |
575 | 0 | v = oidc_json_integer_value(j_value); |
576 | 0 | if (_is_overflow(s, v, counter->count)) |
577 | 0 | v = 0; |
578 | 0 | oidc_json_integer_set(j_value, v + counter->count); |
579 | 0 | } |
580 | 0 | } |
581 | | |
582 | | /* |
583 | | * get or create the vhost entry in the global metrics |
584 | | */ |
585 | 0 | static oidc_json_t *oidc_metrics_server_get(oidc_json_t *json, const char *name) { |
586 | 0 | oidc_json_t *j_server = oidc_json_object_get(json, name); |
587 | 0 | if (j_server == NULL) { |
588 | 0 | j_server = oidc_json_object(); |
589 | 0 | oidc_json_object_set_new(j_server, OIDC_METRICS_COUNTERS, oidc_json_object()); |
590 | 0 | oidc_json_object_set_new(j_server, OIDC_METRICS_TIMINGS, oidc_json_object()); |
591 | 0 | oidc_json_object_set_new(json, name, j_server); |
592 | 0 | } |
593 | 0 | return j_server; |
594 | 0 | } |
595 | | |
596 | | /* |
597 | | * convert an enum type value to its corresponding string |
598 | | */ |
599 | 0 | static inline char *_oidc_metrics_type_name2key(apr_pool_t *pool, unsigned int type, const char *name) { |
600 | 0 | return (name == NULL) ? apr_psprintf(pool, "%u", type) : apr_psprintf(pool, "%u.%s", type, name); |
601 | 0 | } |
602 | | |
603 | | /* |
604 | | * look up the info table entry for the enum type in a "<type>[.<name>]" key: the keys come out of the |
605 | | * JSON in the shared memory segment that this module writes itself, but a corrupted or stale segment |
606 | | * (or a key that does not parse) must not turn into an out-of-bounds index into the info tables, so |
607 | | * the type is checked against the table size right where it indexes it; NULL for a key that does not |
608 | | * name a type this build knows, which the callers skip |
609 | | */ |
610 | 0 | static inline const oidc_metrics_counter_info_t *_oidc_metrics_key2counter_info(const char *key) { |
611 | 0 | unsigned int type = 0; |
612 | 0 | if ((key == NULL) || (sscanf(key, "%u", &type) != 1) || (type >= OM_NUMBER_OF_COUNTERS)) |
613 | 0 | return NULL; |
614 | 0 | return &_oidc_metrics_counters_info[type]; |
615 | 0 | } |
616 | | |
617 | 0 | static inline const oidc_metrics_timing_info_t *_oidc_metrics_key2timing_info(const char *key) { |
618 | 0 | unsigned int type = 0; |
619 | 0 | if ((key == NULL) || (sscanf(key, "%u", &type) != 1) || (type >= OM_NUMBER_OF_TIMINGS)) |
620 | 0 | return NULL; |
621 | 0 | return &_oidc_metrics_timings_info[type]; |
622 | 0 | } |
623 | | |
624 | | /* |
625 | | * update an existing counter under parent[key] or create a new one from counter_hash |
626 | | */ |
627 | | static void oidc_metrics_counter_set_or_update(server_rec *s, oidc_json_t *parent, const char *key, |
628 | 0 | apr_hash_t *counter_hash) { |
629 | 0 | oidc_json_t *j_counter = oidc_json_object_get(parent, key); |
630 | 0 | if (j_counter != NULL) |
631 | 0 | oidc_metrics_counter_update(s, j_counter, counter_hash); |
632 | 0 | else |
633 | 0 | oidc_json_object_set_new(parent, key, oidc_metrics_counter_new(counter_hash)); |
634 | 0 | } |
635 | | |
636 | | /* |
637 | | * merge a single local counter entry into the global counters object; |
638 | | * keys of the form "class.name" are nested as j_counters[class][name] |
639 | | */ |
640 | | static void oidc_metrics_store_counter_entry(server_rec *s, apr_pool_t *pool, oidc_json_t *j_counters, const char *key, |
641 | 0 | apr_hash_t *counter_hash) { |
642 | 0 | const char *class_name = apr_pstrdup(pool, key); |
643 | 0 | char *name = _oidc_strstr(class_name, "."); |
644 | 0 | oidc_json_t *j_names = NULL; |
645 | |
|
646 | 0 | if (name == NULL) { |
647 | 0 | oidc_metrics_counter_set_or_update(s, j_counters, class_name, counter_hash); |
648 | 0 | return; |
649 | 0 | } |
650 | | |
651 | 0 | *name++ = '\0'; |
652 | 0 | j_names = oidc_json_object_get(j_counters, class_name); |
653 | 0 | if (j_names == NULL) { |
654 | 0 | j_names = oidc_json_object(); |
655 | 0 | oidc_json_object_set_new(j_names, name, oidc_metrics_counter_new(counter_hash)); |
656 | 0 | oidc_json_object_set_new(j_counters, class_name, j_names); |
657 | 0 | return; |
658 | 0 | } |
659 | | |
660 | 0 | oidc_metrics_counter_set_or_update(s, j_names, name, counter_hash); |
661 | 0 | } |
662 | | |
663 | | /* |
664 | | * merge all locally collected counters into the global JSON |
665 | | */ |
666 | 0 | static void oidc_metrics_store_counters(server_rec *s, apr_pool_t *pool, oidc_json_t *json) { |
667 | 0 | const char *name = NULL; |
668 | 0 | const char *key = NULL; |
669 | 0 | apr_hash_t *server_hash = NULL; |
670 | 0 | apr_hash_t *counter_hash = NULL; |
671 | 0 | oidc_json_t *j_counters = NULL; |
672 | |
|
673 | 0 | for (apr_hash_index_t *hi1 = apr_hash_first(NULL, _oidc_metrics.counters); hi1; hi1 = apr_hash_next(hi1)) { |
674 | 0 | apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash); |
675 | 0 | j_counters = oidc_json_object_get(oidc_metrics_server_get(json, name), OIDC_METRICS_COUNTERS); |
676 | 0 | for (apr_hash_index_t *hi2 = apr_hash_first(NULL, server_hash); hi2; hi2 = apr_hash_next(hi2)) { |
677 | 0 | apr_hash_this(hi2, (const void **)&key, NULL, (void **)&counter_hash); |
678 | 0 | oidc_metrics_store_counter_entry(s, pool, j_counters, key, counter_hash); |
679 | 0 | } |
680 | 0 | } |
681 | 0 | } |
682 | | |
683 | | /* |
684 | | * merge all locally collected timings into the global JSON |
685 | | */ |
686 | 0 | static void oidc_metrics_store_timings(server_rec *s, oidc_json_t *json) { |
687 | 0 | const char *name = NULL; |
688 | 0 | const char *key = NULL; |
689 | 0 | apr_hash_t *server_hash = NULL; |
690 | 0 | oidc_metrics_timing_t *timing = NULL; |
691 | 0 | oidc_json_t *j_timings = NULL; |
692 | 0 | const oidc_json_t *j_timer = NULL; |
693 | |
|
694 | 0 | for (apr_hash_index_t *hi1 = apr_hash_first(NULL, _oidc_metrics.timings); hi1; hi1 = apr_hash_next(hi1)) { |
695 | 0 | apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash); |
696 | 0 | j_timings = oidc_json_object_get(oidc_metrics_server_get(json, name), OIDC_METRICS_TIMINGS); |
697 | 0 | for (apr_hash_index_t *hi2 = apr_hash_first(NULL, server_hash); hi2; hi2 = apr_hash_next(hi2)) { |
698 | 0 | apr_hash_this(hi2, (const void **)&key, NULL, (void **)&timing); |
699 | 0 | j_timer = oidc_json_object_get(j_timings, key); |
700 | 0 | if (j_timer != NULL) |
701 | 0 | oidc_metrics_timings_update(s, j_timer, timing); |
702 | 0 | else |
703 | 0 | oidc_json_object_set_new(j_timings, key, oidc_metrics_timings_new(timing)); |
704 | 0 | } |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | | /* |
709 | | * flush the locally gathered metrics data into the global data kept in shared memory |
710 | | */ |
711 | 0 | static apr_byte_t oidc_metrics_store(server_rec *s) { |
712 | 0 | const char *s_json = NULL; |
713 | 0 | oidc_json_t *json = NULL; |
714 | 0 | apr_pool_t *pool = NULL; |
715 | |
|
716 | 0 | if ((apr_hash_count(_oidc_metrics.counters) == 0) && (apr_hash_count(_oidc_metrics.timings) == 0)) |
717 | 0 | return TRUE; |
718 | | |
719 | | /* everything below is scratch: the whole document is read, re-serialized and copied |
720 | | * into shared memory on every flush, so it must not come from the process pool, |
721 | | * which is never cleared for the lifetime of the server */ |
722 | 0 | if (apr_pool_create(&pool, s->process->pool) != APR_SUCCESS) { |
723 | 0 | oidc_serror(s, "apr_pool_create failed: cannot flush metrics"); |
724 | 0 | return FALSE; |
725 | 0 | } |
726 | | |
727 | | /* lock the shared memory for other processes */ |
728 | 0 | if (oidc_cache_mutex_lock(pool, s, _oidc_metrics_global_mutex) == FALSE) { |
729 | 0 | apr_pool_destroy(pool); |
730 | 0 | return FALSE; |
731 | 0 | } |
732 | | |
733 | | /* get the global stringified JSON metrics */ |
734 | 0 | s_json = _oidc_metrics_storage_get(s, pool); |
735 | | |
736 | | /* parse the metrics string to JSON */ |
737 | 0 | json = oidc_metrics_json_parse_s(s, pool, s_json); |
738 | 0 | if (json == NULL) |
739 | 0 | json = oidc_json_object(); |
740 | |
|
741 | 0 | oidc_metrics_store_counters(s, pool, json); |
742 | 0 | oidc_metrics_store_timings(s, json); |
743 | | |
744 | | /* serialize the metrics data, preserve order is required for Prometheus */ |
745 | 0 | s_json = oidc_json_encode(pool, json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER); |
746 | | |
747 | | /* free the JSON data */ |
748 | 0 | oidc_json_decref(json); |
749 | | |
750 | | /* store the serialized metrics data in shared memory */ |
751 | 0 | _oidc_metrics_storage_set(s, s_json); |
752 | | |
753 | | /* unlock the shared memory for other processes; the transfer already completed, so an unlock |
754 | | * failure (logged by the mutex layer) still reports the data as consumed and a later cycle |
755 | | * cannot double-count it */ |
756 | 0 | (void)oidc_cache_mutex_unlock(pool, s, _oidc_metrics_global_mutex); |
757 | |
|
758 | 0 | apr_pool_destroy(pool); |
759 | 0 | return TRUE; |
760 | 0 | } |
761 | | |
762 | 0 | #define OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR "OIDC_METRICS_CACHE_STORAGE_INTERVAL" |
763 | | |
764 | | /* |
765 | | * obtain the metrics flush interval: directive, environment variable or default |
766 | | */ |
767 | 0 | static inline apr_interval_time_t _oidc_metrics_interval(server_rec *s) { |
768 | 0 | const oidc_cfg_t *cfg = ap_get_module_config(s->module_config, &auth_openidc_module); |
769 | 0 | int n = oidc_cfg_metrics_cache_storage_interval_get(cfg); |
770 | 0 | if (n == OIDC_CONFIG_POS_INT_UNSET) |
771 | 0 | n = _oidc_metrics_get_env_int(OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR, |
772 | 0 | OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT); |
773 | 0 | return apr_time_from_msec(n); |
774 | 0 | } |
775 | | |
776 | | #define OIDC_METRICS_POLL_INTERVAL 250 |
777 | | |
778 | | /* |
779 | | * thread that periodically writes the local data into the shared memory |
780 | | */ |
781 | 0 | static void *APR_THREAD_FUNC oidc_metrics_thread_run(apr_thread_t *thread, void *data) { |
782 | 0 | server_rec *s = (server_rec *)data; |
783 | | |
784 | | /* sleep for a short random time <0.1s so child processes write-lock on a different frequency */ |
785 | 0 | apr_sleep(apr_time_from_msec(oidc_util_rand_int(100))); |
786 | | |
787 | | /* split the flush interval into POLL_INTERVAL-sized ticks so shutdown is observed quickly; if the |
788 | | * configured interval is shorter than POLL_INTERVAL, use it as the tick directly so we still flush |
789 | | * on schedule rather than busy-looping with n=0 */ |
790 | 0 | apr_interval_time_t interval = _oidc_metrics_interval(s); |
791 | | /* a misconfigured env var parses to 0 (or negative) — fall back to the default to avoid 0/0 below */ |
792 | 0 | if (interval <= 0) |
793 | 0 | interval = apr_time_from_msec(OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT); |
794 | 0 | apr_interval_time_t tick = apr_time_from_msec(OIDC_METRICS_POLL_INTERVAL); |
795 | 0 | if (tick > interval) |
796 | 0 | tick = interval; |
797 | 0 | int n = (int)(interval / tick); |
798 | | |
799 | | /* see if we are asked to exit */ |
800 | 0 | while (apr_atomic_read32(&_oidc_metrics_thread_exit) == 0) { |
801 | | |
802 | | /* break up the sleep interval in short intervals so we can exit timely fashion without confusing Apache |
803 | | * at shutdown */ |
804 | 0 | for (int i = 0; i < n; i++) { |
805 | 0 | apr_sleep(tick); |
806 | 0 | if (apr_atomic_read32(&_oidc_metrics_thread_exit) != 0) |
807 | 0 | break; |
808 | 0 | } |
809 | | |
810 | | // NB: no exit here because we need to write our local metrics into the cache before exiting |
811 | | |
812 | | /* lock the mutex that protects the locally cached metrics */ |
813 | 0 | if (oidc_cache_mutex_lock(s->process->pool, s, _oidc_metrics_process_mutex) == FALSE) |
814 | 0 | continue; |
815 | | |
816 | | /* flush the locally cached metrics into the global shared memory */ |
817 | 0 | if (oidc_metrics_store(s) == TRUE) { |
818 | | /* record that a flush cycle completed, for the metrics self-health output */ |
819 | 0 | apr_atomic_set32(&_oidc_metrics_shm_hdr()->last_flush, |
820 | 0 | (apr_uint32_t)apr_time_sec(apr_time_now())); |
821 | | |
822 | | /* release all allocations made while gathering this flush interval */ |
823 | 0 | oidc_metrics_local_reset(s); |
824 | 0 | } |
825 | | |
826 | | /* unlock the mutex that protects the locally cached metrics */ |
827 | 0 | if (oidc_cache_mutex_unlock(s->process->pool, s, _oidc_metrics_process_mutex) == FALSE) |
828 | 0 | break; |
829 | 0 | } |
830 | | |
831 | | /* NB: don't call apr_thread_exit here because it seems that Apache is cleaning up its own threads */ |
832 | |
|
833 | 0 | return NULL; |
834 | 0 | } |
835 | | |
836 | | /* |
837 | | * server config handlers |
838 | | */ |
839 | | |
840 | | /* |
841 | | * NB: global, yet called for each vhost that has metrics enabled! |
842 | | */ |
843 | 0 | apr_byte_t oidc_metrics_post_config(apr_pool_t *pool, server_rec *s) { |
844 | | |
845 | | /* make sure it gets executed exactly once! */ |
846 | 0 | if (_oidc_metrics_cache != NULL) |
847 | 0 | return TRUE; |
848 | | |
849 | | /* create the shared memory segment that holds the self-health header plus the |
850 | | * stringified JSON formatted metrics data */ |
851 | 0 | if (apr_shm_create(&_oidc_metrics_cache, OIDC_METRICS_SHM_HDR_SIZE + _oidc_metrics_shm_size(s), NULL, |
852 | 0 | s->process->pool) != APR_SUCCESS) |
853 | 0 | return FALSE; |
854 | 0 | if (_oidc_metrics_cache == NULL) |
855 | 0 | return FALSE; |
856 | | |
857 | | /* initialize the shared memory segment to 0 */ |
858 | 0 | char *p = apr_shm_baseaddr_get(_oidc_metrics_cache); |
859 | 0 | _oidc_memset(p, 0, OIDC_METRICS_SHM_HDR_SIZE + _oidc_metrics_shm_size(s)); |
860 | | |
861 | | /* flag this as the parent, for shared memory cleanup purposes and "multiple child-init calls" detection */ |
862 | 0 | _oidc_metrics_is_parent = TRUE; |
863 | | |
864 | | /* create the pool and hashtables that hold local metrics data */ |
865 | 0 | if (oidc_metrics_local_reset(s) == FALSE) |
866 | 0 | return FALSE; |
867 | | |
868 | | /* create and initialize the mutex that guards the shared-memory data */ |
869 | 0 | _oidc_metrics_global_mutex = oidc_cache_mutex_create(s->process->pool, TRUE); |
870 | 0 | if (_oidc_metrics_global_mutex == NULL) |
871 | 0 | return FALSE; |
872 | 0 | if (oidc_cache_mutex_post_config(s->process->pool, s, _oidc_metrics_global_mutex, "metrics-global") == FALSE) |
873 | 0 | return FALSE; |
874 | | |
875 | | /* create and initialize the mutex that guards the local metrics tables */ |
876 | 0 | _oidc_metrics_process_mutex = oidc_cache_mutex_create(s->process->pool, FALSE); |
877 | 0 | if (_oidc_metrics_process_mutex == NULL) |
878 | 0 | return FALSE; |
879 | 0 | if (oidc_cache_mutex_post_config(s->process->pool, s, _oidc_metrics_process_mutex, "metrics-process") == FALSE) |
880 | 0 | return FALSE; |
881 | | |
882 | | /* create the thread only after all state that it accesses has been initialized */ |
883 | 0 | if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS) |
884 | 0 | return FALSE; |
885 | 0 | _oidc_metrics_thread_pid = getpid(); |
886 | |
|
887 | 0 | return TRUE; |
888 | 0 | } |
889 | | |
890 | | /* |
891 | | * NB: global, yet called for each vhost that has metrics enabled! |
892 | | */ |
893 | 0 | apr_status_t oidc_metrics_child_init(apr_pool_t *p, server_rec *s) { |
894 | 0 | apr_status_t rv = APR_SUCCESS; |
895 | | |
896 | | /* make sure this executes only once per child */ |
897 | 0 | if (_oidc_metrics_is_parent == FALSE) |
898 | 0 | return APR_SUCCESS; |
899 | | |
900 | 0 | if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_global_mutex) != APR_SUCCESS) |
901 | 0 | return APR_EGENERAL; |
902 | | |
903 | 0 | if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_process_mutex) != APR_SUCCESS) |
904 | 0 | return APR_EGENERAL; |
905 | | |
906 | | /* Stop an existing same-process flush thread; discard inherited handles after a fork. */ |
907 | 0 | if (_oidc_metrics_thread != NULL && _oidc_metrics_thread_pid == getpid()) { |
908 | 0 | apr_atomic_set32(&_oidc_metrics_thread_exit, 1); |
909 | 0 | apr_thread_join(&rv, _oidc_metrics_thread); |
910 | 0 | apr_atomic_set32(&_oidc_metrics_thread_exit, 0); |
911 | 0 | } |
912 | 0 | _oidc_metrics_thread = NULL; |
913 | 0 | if (oidc_metrics_local_reset(s) == FALSE) |
914 | 0 | return APR_EGENERAL; |
915 | | |
916 | | /* the metrics flush thread is not inherited from the parent, so re-create it in the child */ |
917 | 0 | if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS) |
918 | 0 | return APR_EGENERAL; |
919 | 0 | _oidc_metrics_thread_pid = getpid(); |
920 | | |
921 | | /* flag this is a child */ |
922 | 0 | _oidc_metrics_is_parent = FALSE; |
923 | |
|
924 | 0 | return APR_SUCCESS; |
925 | 0 | } |
926 | | |
927 | | /* |
928 | | * NB: global, yet called for each vhost that has metrics enabled! |
929 | | */ |
930 | 0 | apr_status_t oidc_metrics_cleanup(server_rec *s) { |
931 | 0 | apr_status_t rv = APR_SUCCESS; |
932 | | |
933 | | /* make sure it gets executed exactly once! */ |
934 | 0 | if ((_oidc_metrics_cache == NULL) || (apr_atomic_read32(&_oidc_metrics_thread_exit) != 0) || |
935 | 0 | (_oidc_metrics_thread == NULL)) |
936 | 0 | return APR_SUCCESS; |
937 | | |
938 | | /* signal the collector thread to exit */ |
939 | 0 | apr_atomic_set32(&_oidc_metrics_thread_exit, 1); |
940 | 0 | apr_thread_join(&rv, _oidc_metrics_thread); |
941 | 0 | if (rv != APR_SUCCESS) |
942 | 0 | oidc_serror(s, "apr_thread_join failed"); |
943 | 0 | apr_atomic_set32(&_oidc_metrics_thread_exit, 0); |
944 | 0 | _oidc_metrics_thread = NULL; |
945 | 0 | _oidc_metrics_thread_pid = 0; |
946 | | |
947 | | /* delete the shared memory segment if we are in the parent process */ |
948 | 0 | if (_oidc_metrics_is_parent == TRUE) |
949 | 0 | apr_shm_destroy(_oidc_metrics_cache); |
950 | 0 | _oidc_metrics_cache = NULL; |
951 | | /* the cached segment size lives exactly as long as the segment: the next post_config (a graceful |
952 | | * restart, or the next unit test in the same process) sizes a new one from its own configuration */ |
953 | 0 | _g_oidc_metrics_shm_size = 0; |
954 | | |
955 | | /* delete the process mutex that guards the local metrics data */ |
956 | 0 | if (oidc_cache_mutex_destroy(s, _oidc_metrics_process_mutex) == FALSE) |
957 | 0 | return APR_EGENERAL; |
958 | 0 | _oidc_metrics_process_mutex = NULL; |
959 | | |
960 | | /* delete the process mutex that guards the global shared memory segment */ |
961 | 0 | if (oidc_cache_mutex_destroy(s, _oidc_metrics_global_mutex) == FALSE) |
962 | 0 | return APR_EGENERAL; |
963 | 0 | _oidc_metrics_global_mutex = NULL; |
964 | |
|
965 | 0 | if (_oidc_metrics.pool != NULL) |
966 | 0 | apr_pool_destroy(_oidc_metrics.pool); |
967 | 0 | _oidc_metrics.pool = NULL; |
968 | 0 | _oidc_metrics.counters = NULL; |
969 | 0 | _oidc_metrics.timings = NULL; |
970 | |
|
971 | 0 | return APR_SUCCESS; |
972 | 0 | } |
973 | | |
974 | | /* |
975 | | * sampling |
976 | | */ |
977 | | |
978 | | /* |
979 | | * obtain the local metrics hashtable for the current vhost |
980 | | */ |
981 | 0 | static inline apr_hash_t *_oidc_metrics_server_hash(const request_rec *r, apr_hash_t *table) { |
982 | 0 | apr_hash_t *server_hash = NULL; |
983 | 0 | const char *name = "_default_"; |
984 | | |
985 | | /* obtain the server name */ |
986 | 0 | if (r->server->server_hostname) |
987 | 0 | name = r->server->server_hostname; |
988 | | |
989 | | /* get the entry to the vhost record, or newly create it */ |
990 | 0 | server_hash = apr_hash_get(table, name, APR_HASH_KEY_STRING); |
991 | 0 | if (server_hash == NULL) { |
992 | 0 | server_hash = apr_hash_make(_oidc_metrics.pool); |
993 | 0 | apr_hash_set(table, name, APR_HASH_KEY_STRING, server_hash); |
994 | 0 | } |
995 | |
|
996 | 0 | return server_hash; |
997 | 0 | } |
998 | | |
999 | | /* |
1000 | | * retrieve or create a local timing for the specified type |
1001 | | */ |
1002 | 0 | static inline oidc_metrics_timing_t *_oidc_metrics_timing_get(request_rec *r, unsigned int type) { |
1003 | 0 | oidc_metrics_timing_t *result = NULL; |
1004 | | /* NB: the lookup key is request-scoped; only the copy the hash retains on insert |
1005 | | * below is allocated process-wide, since that pool is never cleared */ |
1006 | 0 | const char *key = _oidc_metrics_type_name2key(r->pool, type, NULL); |
1007 | 0 | apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.timings); |
1008 | | /* get the entry to the specified metric */ |
1009 | 0 | result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING); |
1010 | 0 | if (result == NULL) { |
1011 | 0 | result = apr_pcalloc(_oidc_metrics.pool, sizeof(oidc_metrics_timing_t)); |
1012 | 0 | apr_hash_set(server_hash, apr_pstrdup(_oidc_metrics.pool, key), APR_HASH_KEY_STRING, result); |
1013 | 0 | } |
1014 | 0 | return result; |
1015 | 0 | } |
1016 | | |
1017 | | /* |
1018 | | * retrieve or create a counter from a hashtable of values |
1019 | | */ |
1020 | 0 | static inline oidc_metrics_counter_t *_oidc_metrics_counter_value_get(apr_hash_t *table, const char *value) { |
1021 | | /* get the entry to the specified metric */ |
1022 | 0 | oidc_metrics_counter_t *result = apr_hash_get(table, value, APR_HASH_KEY_STRING); |
1023 | 0 | if (result == NULL) { |
1024 | 0 | result = apr_pcalloc(_oidc_metrics.pool, sizeof(oidc_metrics_counter_t)); |
1025 | 0 | apr_hash_set(table, apr_pstrdup(_oidc_metrics.pool, value), APR_HASH_KEY_STRING, result); |
1026 | 0 | } |
1027 | 0 | return result; |
1028 | 0 | } |
1029 | | |
1030 | | /* |
1031 | | * retrieve or create a local counter for the specified type and name |
1032 | | */ |
1033 | 0 | static inline apr_hash_t *_oidc_metrics_counter_get(request_rec *r, unsigned int type, const char *name) { |
1034 | 0 | apr_hash_t *result = NULL; |
1035 | | /* NB: request-scoped lookup key, process-wide copy only on insert - see above */ |
1036 | 0 | const char *key = _oidc_metrics_type_name2key(r->pool, type, name); |
1037 | 0 | apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.counters); |
1038 | | |
1039 | | /* get the entry to the specified metric */ |
1040 | 0 | result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING); |
1041 | 0 | if (result == NULL) { |
1042 | 0 | result = apr_hash_make(_oidc_metrics.pool); |
1043 | 0 | apr_hash_set(server_hash, apr_pstrdup(_oidc_metrics.pool, key), APR_HASH_KEY_STRING, result); |
1044 | 0 | } |
1045 | |
|
1046 | 0 | return result; |
1047 | 0 | } |
1048 | | |
1049 | | /* |
1050 | | * add/increase a counter metric in the locally cached data |
1051 | | */ |
1052 | 0 | void oidc_metrics_counter_inc(request_rec *r, oidc_metrics_counter_type_t type, const char *name, const char *value) { |
1053 | 0 | oidc_metrics_counter_t *counter = NULL; |
1054 | | |
1055 | | /* lock the local metrics cache hashtable */ |
1056 | 0 | if (oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex) == FALSE) |
1057 | 0 | return; |
1058 | | |
1059 | | /* obtain or create the entry for the specified key */ |
1060 | 0 | counter = _oidc_metrics_counter_value_get(_oidc_metrics_counter_get(r, type, name), _metrics_value2key(value)); |
1061 | | |
1062 | | /* performance */ |
1063 | 0 | if (counter->count <= 0) { |
1064 | | // new counter was created just now or reset earlier |
1065 | 0 | counter->count = 1; |
1066 | 0 | } else { |
1067 | | // increase after checking possible overflow |
1068 | 0 | if (_is_overflow(r->server, counter->count, 1)) |
1069 | 0 | counter->count = 0; |
1070 | 0 | counter->count++; |
1071 | 0 | } |
1072 | | |
1073 | | /* unlock the local metrics cache hashtable */ |
1074 | 0 | (void)oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex); |
1075 | 0 | } |
1076 | | |
1077 | | /* |
1078 | | * zero out a timings entry (count, sum and all buckets) |
1079 | | */ |
1080 | 0 | static inline void _oidc_metrics_timing_clear(oidc_metrics_timing_t *timing) { |
1081 | 0 | timing->count = 0; |
1082 | 0 | timing->sum = 0; |
1083 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) |
1084 | 0 | timing->buckets[i] = 0; |
1085 | 0 | } |
1086 | | |
1087 | | /* |
1088 | | * increment every bucket whose threshold covers elapsed (buckets are ordered; |
1089 | | * threshold == 0 marks the open-ended last bucket) |
1090 | | */ |
1091 | 0 | static inline void _oidc_metrics_timing_buckets_inc(oidc_metrics_timing_t *timing, apr_time_t elapsed) { |
1092 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) { |
1093 | 0 | if ((elapsed <= _oidc_metric_buckets[i].threshold) || (_oidc_metric_buckets[i].threshold == 0)) { |
1094 | 0 | for (int j = i; j < OIDC_METRICS_BUCKET_NUM; j++) |
1095 | 0 | timing->buckets[j]++; |
1096 | 0 | break; |
1097 | 0 | } |
1098 | 0 | } |
1099 | 0 | } |
1100 | | |
1101 | | /* |
1102 | | * add a metrics timing sample to the locally cached data |
1103 | | */ |
1104 | 0 | void oidc_metrics_timing_add(request_rec *r, oidc_metrics_timing_type_t type, apr_time_t elapsed) { |
1105 | 0 | oidc_metrics_timing_t *timing = NULL; |
1106 | |
|
1107 | 0 | if (elapsed < 0) { |
1108 | 0 | oidc_warn(r, "discarding metrics timing [%s.%s]: elapsed (%" APR_TIME_T_FMT ") < 0", |
1109 | 0 | _oidc_metrics_timings_info[type].class_name, _oidc_metrics_timings_info[type].metric_name, |
1110 | 0 | elapsed); |
1111 | 0 | return; |
1112 | 0 | } |
1113 | | |
1114 | | /* lock the local metrics cache hashtable */ |
1115 | 0 | if (oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex) == FALSE) |
1116 | 0 | return; |
1117 | | |
1118 | | /* obtain or create the entry for the specified key */ |
1119 | 0 | timing = _oidc_metrics_timing_get(r, type); |
1120 | | |
1121 | | /* reset on overflow; a freshly-created entry already has count/sum/buckets at zero */ |
1122 | 0 | if ((timing->count > 0) && _is_overflow(r->server, timing->sum, elapsed)) |
1123 | 0 | _oidc_metrics_timing_clear(timing); |
1124 | |
|
1125 | 0 | _oidc_metrics_timing_buckets_inc(timing, elapsed); |
1126 | 0 | timing->sum += elapsed; |
1127 | 0 | timing->count++; |
1128 | | |
1129 | | /* unlock the local metrics cache hashtable */ |
1130 | 0 | (void)oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex); |
1131 | 0 | } |
1132 | | |
1133 | | /* |
1134 | | * representation handlers |
1135 | | */ |
1136 | | |
1137 | | /* |
1138 | | * the "class.metric" name of a counter, from its info table entry |
1139 | | */ |
1140 | 0 | static inline char *_oidc_metrics_counter_info2s(apr_pool_t *pool, const oidc_metrics_counter_info_t *info) { |
1141 | 0 | return apr_psprintf(pool, "%s.%s", info->class_name, info->metric_name); |
1142 | 0 | } |
1143 | | |
1144 | | /* |
1145 | | * the "class.metric" name of a timing, from its info table entry |
1146 | | */ |
1147 | 0 | static inline char *_oidc_metrics_timing_info2s(apr_pool_t *pool, const oidc_metrics_timing_info_t *info) { |
1148 | 0 | return apr_psprintf(pool, "%s.%s", info->class_name, info->metric_name); |
1149 | 0 | } |
1150 | | |
1151 | | /* |
1152 | | * parse a string into a JSON object in the request_rec context |
1153 | | */ |
1154 | 0 | static oidc_json_t *oidc_metrics_json_parse_r(request_rec *r, const char *s_json) { |
1155 | 0 | char *s_err = NULL; |
1156 | 0 | oidc_json_t *json = oidc_metrics_json_load(r->pool, s_json, &s_err); |
1157 | 0 | if (json == NULL) |
1158 | 0 | oidc_error(r, "JSON parsing failed: %s", s_err); |
1159 | 0 | return json; |
1160 | 0 | } |
1161 | | |
1162 | | /* |
1163 | | * JSON with extended descriptions/names |
1164 | | */ |
1165 | 0 | static int oidc_metrics_handle_json(request_rec *r, const char *s_json) { |
1166 | |
|
1167 | 0 | oidc_json_t *json = NULL; |
1168 | 0 | const oidc_json_t *j_server = NULL; |
1169 | 0 | oidc_json_t *j_timings = NULL; |
1170 | 0 | oidc_json_t *j_counters = NULL; |
1171 | 0 | const oidc_json_t *j_timing = NULL; |
1172 | 0 | oidc_json_t *j_counter = NULL; |
1173 | 0 | oidc_json_t *o_json = NULL; |
1174 | 0 | oidc_json_t *o_server = NULL; |
1175 | 0 | oidc_json_t *o_counters = NULL; |
1176 | 0 | oidc_json_t *o_counter = NULL; |
1177 | 0 | oidc_json_t *o_timings = NULL; |
1178 | 0 | oidc_json_t *o_timing = NULL; |
1179 | 0 | const char *s_server = NULL; |
1180 | 0 | const oidc_metrics_counter_info_t *c_info = NULL; |
1181 | 0 | const oidc_metrics_timing_info_t *t_info = NULL; |
1182 | 0 | void *i1 = NULL; |
1183 | 0 | void *i2 = NULL; |
1184 | | |
1185 | | /* parse the metrics string to JSON */ |
1186 | 0 | json = oidc_metrics_json_parse_r(r, s_json); |
1187 | 0 | if (json == NULL) |
1188 | 0 | goto end; |
1189 | | |
1190 | 0 | o_json = oidc_json_object(); |
1191 | |
|
1192 | 0 | i1 = oidc_json_object_iter(json); |
1193 | 0 | while (i1) { |
1194 | 0 | s_server = oidc_json_object_iter_key(i1); |
1195 | 0 | j_server = oidc_json_object_iter_value(i1); |
1196 | |
|
1197 | 0 | o_server = oidc_json_object(); |
1198 | 0 | oidc_json_object_set_new(o_json, s_server, o_server); |
1199 | |
|
1200 | 0 | j_counters = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS); |
1201 | 0 | o_counters = oidc_json_object(); |
1202 | 0 | oidc_json_object_set_new(o_server, OIDC_METRICS_COUNTERS, o_counters); |
1203 | |
|
1204 | 0 | i2 = oidc_json_object_iter(j_counters); |
1205 | 0 | while (i2) { |
1206 | 0 | c_info = _oidc_metrics_key2counter_info(oidc_json_object_iter_key(i2)); |
1207 | 0 | if (c_info == NULL) { |
1208 | | /* not a counter this build knows: skip it rather than index past the info table */ |
1209 | 0 | i2 = oidc_json_object_iter_next(j_counters, i2); |
1210 | 0 | continue; |
1211 | 0 | } |
1212 | 0 | j_counter = oidc_json_object_iter_value(i2); |
1213 | 0 | o_counter = oidc_json_object(); |
1214 | 0 | if (oidc_json_is_integer(j_counter)) |
1215 | 0 | oidc_json_object_set(o_counter, "count", j_counter); |
1216 | 0 | else |
1217 | 0 | oidc_json_object_set_new(o_counter, "values", oidc_json_deep_copy(j_counter)); |
1218 | 0 | oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_CLASS_NAME, |
1219 | 0 | oidc_json_string(c_info->class_name)); |
1220 | 0 | oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_METRIC_NAME, |
1221 | 0 | oidc_json_string(c_info->metric_name)); |
1222 | 0 | oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_DESC, oidc_json_string(c_info->desc)); |
1223 | 0 | oidc_json_object_set_new(o_counters, _oidc_metrics_counter_info2s(r->pool, c_info), o_counter); |
1224 | 0 | i2 = oidc_json_object_iter_next(j_counters, i2); |
1225 | 0 | } |
1226 | |
|
1227 | 0 | j_timings = oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS); |
1228 | 0 | o_timings = oidc_json_object(); |
1229 | 0 | oidc_json_object_set_new(o_server, OIDC_METRICS_TIMINGS, o_timings); |
1230 | |
|
1231 | 0 | i2 = oidc_json_object_iter(j_timings); |
1232 | 0 | while (i2) { |
1233 | 0 | t_info = _oidc_metrics_key2timing_info(oidc_json_object_iter_key(i2)); |
1234 | 0 | if (t_info == NULL) { |
1235 | 0 | i2 = oidc_json_object_iter_next(j_timings, i2); |
1236 | 0 | continue; |
1237 | 0 | } |
1238 | 0 | j_timing = oidc_json_object_iter_value(i2); |
1239 | |
|
1240 | 0 | o_timing = oidc_json_deep_copy(j_timing); |
1241 | | /* the shm sum is kept in microseconds; this format documents milliseconds */ |
1242 | 0 | oidc_json_object_set_new( |
1243 | 0 | o_timing, OIDC_METRICS_SUM, |
1244 | 0 | oidc_json_integer( |
1245 | 0 | oidc_json_integer_value(oidc_json_object_get(j_timing, OIDC_METRICS_SUM)) / 1000)); |
1246 | 0 | oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_CLASS_NAME, |
1247 | 0 | oidc_json_string(t_info->class_name)); |
1248 | 0 | oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_METRIC_NAME, |
1249 | 0 | oidc_json_string(t_info->metric_name)); |
1250 | 0 | oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_DESC, oidc_json_string(t_info->desc)); |
1251 | |
|
1252 | 0 | oidc_json_object_set_new(o_timings, _oidc_metrics_timing_info2s(r->pool, t_info), o_timing); |
1253 | |
|
1254 | 0 | i2 = oidc_json_object_iter_next(j_timings, i2); |
1255 | 0 | } |
1256 | 0 | i1 = oidc_json_object_iter_next(json, i1); |
1257 | 0 | } |
1258 | |
|
1259 | 0 | s_json = oidc_json_encode(r->pool, o_json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER); |
1260 | |
|
1261 | 0 | oidc_json_decref(o_json); |
1262 | 0 | oidc_json_decref(json); |
1263 | |
|
1264 | 0 | end: |
1265 | | |
1266 | | /* return the data to the caller */ |
1267 | 0 | return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK); |
1268 | 0 | } |
1269 | | |
1270 | | /* |
1271 | | * dump the internal shared memory segment |
1272 | | */ |
1273 | 0 | static int oidc_metrics_handle_internal(request_rec *r, const char *s_json) { |
1274 | 0 | if (s_json == NULL) |
1275 | 0 | return HTTP_NOT_FOUND; |
1276 | 0 | return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK); |
1277 | 0 | } |
1278 | | |
1279 | 0 | #define OIDC_METRICS_SERVER_PARAM "server_name" |
1280 | 0 | #define OIDC_METRICS_COUNTER_PARAM "counter" |
1281 | 0 | #define OIDC_METRICS_NAME_PARAM "name" |
1282 | 0 | #define OIDC_METRICS_VALUE_PARAM "value" |
1283 | | |
1284 | | /* |
1285 | | * find the counter entry in j_counters whose type matches "class.metric" (s_metric_param) |
1286 | | */ |
1287 | | static oidc_json_t *oidc_metrics_status_find_counter(request_rec *r, oidc_json_t *j_counters, |
1288 | 0 | const char *s_metric_param) { |
1289 | 0 | const oidc_metrics_counter_info_t *info = NULL; |
1290 | 0 | void *iter = oidc_json_object_iter(j_counters); |
1291 | 0 | while (iter) { |
1292 | 0 | info = _oidc_metrics_key2counter_info(oidc_json_object_iter_key(iter)); |
1293 | 0 | if ((info != NULL) && (_oidc_strcmp(_oidc_metrics_counter_info2s(r->pool, info), s_metric_param) == 0)) |
1294 | 0 | return oidc_json_object_iter_value(iter); |
1295 | 0 | iter = oidc_json_object_iter_next(j_counters, iter); |
1296 | 0 | } |
1297 | 0 | return NULL; |
1298 | 0 | } |
1299 | | |
1300 | | /* |
1301 | | * extract the requested integer value out of a counter entry: |
1302 | | * - integer entry: the counter itself |
1303 | | * - object entry with value-only selector: j_counter[value] |
1304 | | * - object entry with name+value selectors: j_counter[name][value] |
1305 | | */ |
1306 | | static oidc_json_t *oidc_metrics_status_select_value(oidc_json_t *j_counter, const char *s_name_param, |
1307 | 0 | const char *s_value_param) { |
1308 | 0 | const oidc_json_t *j_values = NULL; |
1309 | 0 | if (oidc_json_is_integer(j_counter)) |
1310 | 0 | return j_counter; |
1311 | 0 | if (s_value_param == NULL) |
1312 | 0 | return NULL; |
1313 | 0 | if (s_name_param == NULL) |
1314 | 0 | return oidc_json_object_get(j_counter, s_value_param); |
1315 | 0 | j_values = oidc_json_object_get(j_counter, s_name_param); |
1316 | 0 | if (j_values == NULL) |
1317 | 0 | return NULL; |
1318 | 0 | return oidc_json_object_get(j_values, s_value_param); |
1319 | 0 | } |
1320 | | |
1321 | 0 | #define OIDC_METRICS_STATUS_PING_KEY "status-ping" |
1322 | | |
1323 | | /* |
1324 | | * readiness variant of format=status (no counter selector): verify that the configured cache |
1325 | | * backend completes a write/read round-trip and report how provider metadata is configured; |
1326 | | * returns a 503 with an "ERROR" body when the cache backend is not usable |
1327 | | */ |
1328 | 0 | static int oidc_metrics_status_readiness(request_rec *r) { |
1329 | 0 | oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
1330 | 0 | char *value = NULL; |
1331 | 0 | const char *msg = NULL; |
1332 | 0 | const char *s_provider = NULL; |
1333 | 0 | apr_byte_t ok = FALSE; |
1334 | | |
1335 | | /* write/read round-trip through the configured cache backend */ |
1336 | 0 | if ((oidc_cache_set(r, OIDC_CACHE_SECTION_HEALTH, OIDC_METRICS_STATUS_PING_KEY, "OK", |
1337 | 0 | apr_time_now() + apr_time_from_sec(60)) == TRUE) && |
1338 | 0 | (oidc_cache_get(r, OIDC_CACHE_SECTION_HEALTH, OIDC_METRICS_STATUS_PING_KEY, &value) == TRUE) && |
1339 | 0 | (value != NULL) && (_oidc_strcmp(value, "OK") == 0)) |
1340 | 0 | ok = TRUE; |
1341 | |
|
1342 | 0 | if (oidc_cfg_metadata_dir_get(cfg) != NULL) |
1343 | 0 | s_provider = "metadata_dir"; |
1344 | 0 | else if (oidc_cfg_provider_issuer_get(oidc_cfg_provider_get(cfg)) != NULL) |
1345 | 0 | s_provider = "static"; |
1346 | 0 | else |
1347 | 0 | s_provider = "none"; |
1348 | |
|
1349 | 0 | msg = apr_psprintf(r->pool, "%s\ncache: %s: %s\nprovider: metadata: %s\n", ok ? "OK" : "ERROR", |
1350 | 0 | cfg->cache.impl->name, ok ? "ok" : "fail", s_provider); |
1351 | |
|
1352 | 0 | return oidc_util_http_send(r, msg, _oidc_strlen(msg), "text/plain", ok ? OK : HTTP_SERVICE_UNAVAILABLE); |
1353 | 0 | } |
1354 | | |
1355 | | /* |
1356 | | * return status updates |
1357 | | */ |
1358 | 0 | static int oidc_metrics_handle_status(request_rec *r, const char *s_json) { |
1359 | 0 | const char *msg = "OK\n"; |
1360 | 0 | char *s_metric_param = NULL; |
1361 | 0 | char *s_server_param = NULL; |
1362 | 0 | char *s_name_param = NULL; |
1363 | 0 | char *s_value_param = NULL; |
1364 | 0 | oidc_json_t *json = NULL; |
1365 | 0 | const oidc_json_t *j_server = NULL; |
1366 | 0 | oidc_json_t *j_counters = NULL; |
1367 | 0 | oidc_json_t *j_counter = NULL; |
1368 | 0 | const oidc_json_t *j_value = NULL; |
1369 | |
|
1370 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_SERVER_PARAM, &s_server_param); |
1371 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_COUNTER_PARAM, &s_metric_param); |
1372 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_NAME_PARAM, &s_name_param); |
1373 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_VALUE_PARAM, &s_value_param); |
1374 | |
|
1375 | 0 | if (s_server_param == NULL) |
1376 | 0 | s_server_param = "localhost"; |
1377 | | |
1378 | | /* without a counter selector this is a readiness probe rather than a counter query */ |
1379 | 0 | if (s_metric_param == NULL) |
1380 | 0 | return oidc_metrics_status_readiness(r); |
1381 | | |
1382 | 0 | json = oidc_metrics_json_parse_r(r, s_json); |
1383 | 0 | if (json == NULL) |
1384 | 0 | goto end; |
1385 | | |
1386 | 0 | j_server = oidc_json_object_get(json, s_server_param); |
1387 | 0 | if (j_server == NULL) |
1388 | 0 | goto end; |
1389 | | |
1390 | 0 | j_counters = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS); |
1391 | 0 | if (j_counters == NULL) |
1392 | 0 | goto end; |
1393 | | |
1394 | 0 | j_counter = oidc_metrics_status_find_counter(r, j_counters, s_metric_param); |
1395 | 0 | if (j_counter == NULL) |
1396 | 0 | goto end; |
1397 | | |
1398 | 0 | j_value = oidc_metrics_status_select_value(j_counter, s_name_param, s_value_param); |
1399 | 0 | if (j_value) |
1400 | 0 | msg = apr_psprintf(r->pool, "OK: %s\n", _json_int2str(r->pool, oidc_json_integer_value(j_value))); |
1401 | |
|
1402 | 0 | end: |
1403 | |
|
1404 | 0 | if (json) |
1405 | 0 | oidc_json_decref(json); |
1406 | |
|
1407 | 0 | return oidc_util_http_send(r, msg, _oidc_strlen(msg), "text/plain", OK); |
1408 | 0 | } |
1409 | | |
1410 | | /* |
1411 | | * return the bucket table entry for a JSON bucket key |
1412 | | */ |
1413 | 0 | static const oidc_metrics_bucket_t *oidc_metrics_prometheus_bucket_get(const char *json_name) { |
1414 | 0 | for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) |
1415 | 0 | if (_oidc_strcmp(_oidc_metric_buckets[i].name, json_name) == 0) |
1416 | 0 | return &_oidc_metric_buckets[i]; |
1417 | 0 | return NULL; |
1418 | 0 | } |
1419 | | |
1420 | 0 | #define OIDC_METRICS_PROMETHEUS_PREFIX "oidc" |
1421 | | |
1422 | | /* |
1423 | | * normalize a metric name to something that Prometheus accepts |
1424 | | */ |
1425 | 0 | static const char *oidc_metric_prometheus_normalize_name(apr_pool_t *pool, const char *name) { |
1426 | 0 | char *label = apr_psprintf(pool, "%s", name); |
1427 | 0 | for (size_t i = 0; i < _oidc_strlen(label); i++) |
1428 | 0 | if (apr_isalnum(label[i]) == 0) |
1429 | 0 | label[i] = '_'; |
1430 | 0 | return apr_psprintf(pool, "%s_%s", OIDC_METRICS_PROMETHEUS_PREFIX, label); |
1431 | 0 | } |
1432 | | |
1433 | | /* Escape a Prometheus text-format label value: backslash, quote and LF are special. */ |
1434 | 0 | static const char *oidc_metrics_prometheus_escape_label(apr_pool_t *pool, const char *value) { |
1435 | 0 | apr_size_t len = _oidc_strlen(value); |
1436 | 0 | char *result = apr_palloc(pool, (len * 2) + 1); |
1437 | 0 | char *dst = result; |
1438 | |
|
1439 | 0 | for (const char *src = value; *src != '\0'; src++) { |
1440 | 0 | switch (*src) { |
1441 | 0 | case '\\': |
1442 | 0 | case '"': |
1443 | 0 | *dst++ = '\\'; |
1444 | 0 | *dst++ = *src; |
1445 | 0 | break; |
1446 | 0 | case '\n': |
1447 | 0 | *dst++ = '\\'; |
1448 | 0 | *dst++ = 'n'; |
1449 | 0 | break; |
1450 | 0 | default: |
1451 | 0 | *dst++ = *src; |
1452 | 0 | break; |
1453 | 0 | } |
1454 | 0 | } |
1455 | 0 | *dst = '\0'; |
1456 | 0 | return result; |
1457 | 0 | } |
1458 | | |
1459 | 0 | #define OIDC_METRICS_PROMETHEUS_CONTENT_TYPE "text/plain; version=0.0.4" |
1460 | | |
1461 | 0 | #define OIDC_METRICS_PROMETHEUS_SERVER "server_name" |
1462 | 0 | #define OIDC_METRICS_PROMETHEUS_BUCKET "bucket" |
1463 | 0 | #define OIDC_METRICS_PROMETHEUS_VALUE "value" |
1464 | 0 | #define OIDC_METRICS_PROMETHEUS_NAME "name" |
1465 | | |
1466 | | // loop context for Prometheus output |
1467 | | typedef struct oidc_metric_prometheus_callback_ctx_t { |
1468 | | char *s_result; |
1469 | | apr_pool_t *pool; |
1470 | | } oidc_metric_prometheus_callback_ctx_t; |
1471 | | |
1472 | | /* |
1473 | | * append per-(name, value) label-pair lines for a single key under a server's counter |
1474 | | */ |
1475 | | static char *oidc_metrics_prometheus_counter_named(apr_pool_t *pool, char *s_text, const char *s_start, |
1476 | 0 | const char *s_key, oidc_json_t *j_value) { |
1477 | 0 | void *iter = oidc_json_object_iter(j_value); |
1478 | 0 | while (iter) { |
1479 | 0 | const char *s_value = oidc_json_object_iter_key(iter); |
1480 | 0 | s_text = |
1481 | 0 | apr_psprintf(pool, "%s%s,%s=\"%s\",%s=\"%s\"} %s\n", s_text, s_start, OIDC_METRICS_PROMETHEUS_NAME, |
1482 | 0 | oidc_metrics_prometheus_escape_label(pool, s_key), OIDC_METRICS_PROMETHEUS_VALUE, |
1483 | 0 | oidc_metrics_prometheus_escape_label(pool, s_value), |
1484 | 0 | _json_int2str(pool, oidc_json_integer_value(oidc_json_object_iter_value(iter)))); |
1485 | 0 | iter = oidc_json_object_iter_next(j_value, iter); |
1486 | 0 | } |
1487 | 0 | return s_text; |
1488 | 0 | } |
1489 | | |
1490 | | /* |
1491 | | * append per-key lines for one server's counter dict; integer entries are value-labeled, sub-dict |
1492 | | * entries are delegated to oidc_metrics_prometheus_counter_named for name+value labeling |
1493 | | */ |
1494 | | static char *oidc_metrics_prometheus_counter_keyed(apr_pool_t *pool, char *s_text, const char *s_start, |
1495 | 0 | oidc_json_t *j_counter) { |
1496 | 0 | void *iter = oidc_json_object_iter(j_counter); |
1497 | 0 | while (iter) { |
1498 | 0 | const char *s_key = oidc_json_object_iter_key(iter); |
1499 | 0 | oidc_json_t *j_value = oidc_json_object_iter_value(iter); |
1500 | 0 | if (oidc_json_is_integer(j_value)) |
1501 | 0 | s_text = |
1502 | 0 | apr_psprintf(pool, "%s%s,%s=\"%s\"} %s\n", s_text, s_start, OIDC_METRICS_PROMETHEUS_VALUE, |
1503 | 0 | oidc_metrics_prometheus_escape_label(pool, s_key), |
1504 | 0 | _json_int2str(pool, oidc_json_integer_value(j_value))); |
1505 | 0 | else |
1506 | 0 | s_text = oidc_metrics_prometheus_counter_named(pool, s_text, s_start, s_key, j_value); |
1507 | 0 | iter = oidc_json_object_iter_next(j_counter, iter); |
1508 | 0 | } |
1509 | 0 | return s_text; |
1510 | 0 | } |
1511 | | |
1512 | | /* |
1513 | | * loop function for converting counter metrics to Prometheus output |
1514 | | */ |
1515 | | static int oidc_metrics_prometheus_counters(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key, |
1516 | 0 | oidc_json_t *value) { |
1517 | 0 | oidc_json_t *o_counter = value; |
1518 | 0 | const oidc_metrics_counter_info_t *info = _oidc_metrics_key2counter_info(key); |
1519 | 0 | const char *s_label = NULL; |
1520 | 0 | char *s_text = NULL; |
1521 | |
|
1522 | 0 | if (info == NULL) |
1523 | 0 | return 1; |
1524 | | |
1525 | 0 | s_label = oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_counter_info2s(ctx->pool, info)); |
1526 | 0 | s_text = apr_psprintf(ctx->pool, "# HELP %s The number of %s.\n", s_label, info->desc); |
1527 | 0 | s_text = apr_psprintf(ctx->pool, "%s# TYPE %s counter\n", s_text, s_label); |
1528 | |
|
1529 | 0 | void *iter = oidc_json_object_iter(o_counter); |
1530 | 0 | while (iter) { |
1531 | 0 | const char *s_server = oidc_json_object_iter_key(iter); |
1532 | 0 | oidc_json_t *j_counter = oidc_json_object_iter_value(iter); |
1533 | 0 | const char *s_start = apr_psprintf(ctx->pool, "%s{%s=\"%s\"", s_label, OIDC_METRICS_PROMETHEUS_SERVER, |
1534 | 0 | oidc_metrics_prometheus_escape_label(ctx->pool, s_server)); |
1535 | 0 | if (oidc_json_is_integer(j_counter)) |
1536 | 0 | s_text = apr_psprintf(ctx->pool, "%s%s} %s\n", s_text, s_start, |
1537 | 0 | _json_int2str(ctx->pool, oidc_json_integer_value(j_counter))); |
1538 | 0 | else |
1539 | 0 | s_text = oidc_metrics_prometheus_counter_keyed(ctx->pool, s_text, s_start, j_counter); |
1540 | 0 | iter = oidc_json_object_iter_next(o_counter, iter); |
1541 | 0 | } |
1542 | 0 | ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL); |
1543 | 0 | oidc_json_decref(o_counter); |
1544 | 0 | return 1; |
1545 | 0 | } |
1546 | | |
1547 | | /* |
1548 | | * loop function for converting timing metrics to Prometheus output |
1549 | | */ |
1550 | | |
1551 | | static int oidc_metrics_prometheus_timings(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key, |
1552 | 0 | oidc_json_t *value) { |
1553 | 0 | const char *s_server = NULL; |
1554 | 0 | const char *s_key = NULL; |
1555 | 0 | const oidc_metrics_bucket_t *bucket = NULL; |
1556 | 0 | oidc_json_t *j_timing = NULL; |
1557 | 0 | const oidc_json_t *j_member = NULL; |
1558 | 0 | oidc_json_t *o_timer = value; |
1559 | 0 | oidc_json_int_t v = 0; |
1560 | 0 | const oidc_metrics_timing_info_t *info = _oidc_metrics_key2timing_info(key); |
1561 | 0 | const char *s_label = NULL; |
1562 | 0 | char *s_text = NULL; |
1563 | 0 | char *s_secs = NULL; |
1564 | |
|
1565 | 0 | if (info == NULL) |
1566 | 0 | return 1; |
1567 | | |
1568 | 0 | s_label = oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_timing_info2s(ctx->pool, info)); |
1569 | | /* the unsuffixed millisecond family is deprecated as of 2.4.20.4 in favor of the |
1570 | | * Prometheus-idiomatic _seconds family emitted alongside it below, and will be |
1571 | | * removed in a future release */ |
1572 | 0 | s_text = apr_psprintf(ctx->pool, "# HELP %s A histogram of %s.\n", s_label, info->desc); |
1573 | 0 | s_text = apr_psprintf(ctx->pool, "%s# TYPE %s histogram\n", s_text, s_label); |
1574 | 0 | s_secs = apr_psprintf(ctx->pool, "# HELP %s_seconds A histogram of %s in seconds.\n", s_label, info->desc); |
1575 | 0 | s_secs = apr_psprintf(ctx->pool, "%s# TYPE %s_seconds histogram\n", s_secs, s_label); |
1576 | |
|
1577 | 0 | void *iter1 = oidc_json_object_iter(o_timer); |
1578 | 0 | while (iter1) { |
1579 | 0 | s_server = oidc_json_object_iter_key(iter1); |
1580 | 0 | const char *s_server_escaped = oidc_metrics_prometheus_escape_label(ctx->pool, s_server); |
1581 | 0 | j_timing = oidc_json_object_iter_value(iter1); |
1582 | 0 | void *iter3 = oidc_json_object_iter(j_timing); |
1583 | 0 | while (iter3) { |
1584 | 0 | s_key = oidc_json_object_iter_key(iter3); |
1585 | 0 | j_member = oidc_json_object_iter_value(iter3); |
1586 | 0 | v = oidc_json_integer_value(j_member); |
1587 | 0 | bucket = oidc_metrics_prometheus_bucket_get(s_key); |
1588 | 0 | if (bucket != NULL) { |
1589 | 0 | s_text = apr_psprintf(ctx->pool, "%s%s_%s{%s,%s=\"%s\"} %s\n", s_text, s_label, |
1590 | 0 | OIDC_METRICS_PROMETHEUS_BUCKET, bucket->label, |
1591 | 0 | OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1592 | 0 | _json_int2str(ctx->pool, v)); |
1593 | 0 | s_secs = apr_psprintf(ctx->pool, "%s%s_seconds_%s{%s,%s=\"%s\"} %s\n", s_secs, s_label, |
1594 | 0 | OIDC_METRICS_PROMETHEUS_BUCKET, bucket->label_seconds, |
1595 | 0 | OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1596 | 0 | _json_int2str(ctx->pool, v)); |
1597 | 0 | } else if (_oidc_strcmp(s_key, OIDC_METRICS_SUM) == 0) { |
1598 | | /* the sum is kept in microseconds in shared memory: truncate to |
1599 | | * milliseconds for the legacy family, exact seconds for _seconds */ |
1600 | 0 | s_text = apr_psprintf(ctx->pool, "%s%s_%s{%s=\"%s\"} %s\n", s_text, s_label, s_key, |
1601 | 0 | OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1602 | 0 | _json_int2str(ctx->pool, v / 1000)); |
1603 | 0 | s_secs = apr_psprintf(ctx->pool, "%s%s_seconds_%s{%s=\"%s\"} %.6f\n", s_secs, s_label, |
1604 | 0 | s_key, OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1605 | 0 | (double)v / 1000000.0); |
1606 | 0 | } else { |
1607 | 0 | s_text = apr_psprintf(ctx->pool, "%s%s_%s{%s=\"%s\"} %s\n", s_text, s_label, s_key, |
1608 | 0 | OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1609 | 0 | _json_int2str(ctx->pool, v)); |
1610 | 0 | s_secs = apr_psprintf(ctx->pool, "%s%s_seconds_%s{%s=\"%s\"} %s\n", s_secs, s_label, |
1611 | 0 | s_key, OIDC_METRICS_PROMETHEUS_SERVER, s_server_escaped, |
1612 | 0 | _json_int2str(ctx->pool, v)); |
1613 | 0 | } |
1614 | 0 | iter3 = oidc_json_object_iter_next(j_timing, iter3); |
1615 | 0 | } |
1616 | 0 | iter1 = oidc_json_object_iter_next(o_timer, iter1); |
1617 | 0 | } |
1618 | 0 | ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", s_secs, "\n", NULL); |
1619 | 0 | oidc_json_decref(o_timer); |
1620 | 0 | return 1; |
1621 | 0 | } |
1622 | | |
1623 | | /* |
1624 | | * take a list of metrics from a server indexed list and add it to a type indexed list |
1625 | | */ |
1626 | 0 | static void oidc_metrics_prometheus_convert(apr_hash_t *hash, const char *server, oidc_json_t *list) { |
1627 | 0 | const char *type = NULL; |
1628 | 0 | oidc_json_t *src = NULL; |
1629 | 0 | oidc_json_t *dst = NULL; |
1630 | 0 | void *iter = oidc_json_object_iter(list); |
1631 | 0 | while (iter) { |
1632 | 0 | type = oidc_json_object_iter_key(iter); |
1633 | 0 | src = oidc_json_object_iter_value(iter); |
1634 | 0 | dst = (oidc_json_t *)apr_hash_get(hash, type, APR_HASH_KEY_STRING); |
1635 | 0 | if (dst) { |
1636 | 0 | oidc_json_object_set(dst, server, src); |
1637 | 0 | } else { |
1638 | 0 | dst = oidc_json_object(); |
1639 | 0 | oidc_json_object_set(dst, server, src); |
1640 | 0 | apr_hash_set(hash, type, APR_HASH_KEY_STRING, dst); |
1641 | 0 | } |
1642 | 0 | iter = oidc_json_object_iter_next(list, iter); |
1643 | 0 | } |
1644 | 0 | } |
1645 | | |
1646 | | /* |
1647 | | * generate output in Prometheus formatting |
1648 | | */ |
1649 | 0 | static int oidc_metrics_handle_prometheus(request_rec *r, const char *s_json) { |
1650 | 0 | oidc_json_t *json = NULL; |
1651 | 0 | const oidc_json_t *j_server = NULL; |
1652 | 0 | const char *s_server = NULL; |
1653 | 0 | apr_hash_t *t_counters = apr_hash_make(r->pool); |
1654 | 0 | apr_hash_t *t_timings = apr_hash_make(r->pool); |
1655 | 0 | apr_hash_index_t *hi = NULL; |
1656 | 0 | const char *name = NULL; |
1657 | 0 | void *value = NULL; |
1658 | | |
1659 | | /* start the output with the build info and the metrics subsystem's own health */ |
1660 | 0 | char *s_self = apr_psprintf( |
1661 | 0 | r->pool, |
1662 | 0 | "# HELP %s_build_info A metric with a constant '1' value labeled by the module build version.\n" |
1663 | 0 | "# TYPE %s_build_info gauge\n" |
1664 | 0 | "%s_build_info{version=\"%s\"} 1\n\n" |
1665 | 0 | "# HELP %s_metrics_flush_errors The number of metrics flushes dropped because the JSON data outgrew the " |
1666 | 0 | "shared memory segment.\n" |
1667 | 0 | "# TYPE %s_metrics_flush_errors counter\n" |
1668 | 0 | "%s_metrics_flush_errors %u\n\n" |
1669 | 0 | "# HELP %s_metrics_last_flush_timestamp_seconds The Unix time of the last completed metrics flush cycle in " |
1670 | 0 | "this server process.\n" |
1671 | 0 | "# TYPE %s_metrics_last_flush_timestamp_seconds gauge\n" |
1672 | 0 | "%s_metrics_last_flush_timestamp_seconds %u\n\n", |
1673 | 0 | OIDC_METRICS_PROMETHEUS_PREFIX, OIDC_METRICS_PROMETHEUS_PREFIX, OIDC_METRICS_PROMETHEUS_PREFIX, |
1674 | 0 | oidc_metrics_prometheus_escape_label(r->pool, NAMEVERSION), OIDC_METRICS_PROMETHEUS_PREFIX, |
1675 | 0 | OIDC_METRICS_PROMETHEUS_PREFIX, OIDC_METRICS_PROMETHEUS_PREFIX, |
1676 | 0 | apr_atomic_read32(&_oidc_metrics_shm_hdr()->flush_errors), OIDC_METRICS_PROMETHEUS_PREFIX, |
1677 | 0 | OIDC_METRICS_PROMETHEUS_PREFIX, OIDC_METRICS_PROMETHEUS_PREFIX, |
1678 | 0 | apr_atomic_read32(&_oidc_metrics_shm_hdr()->last_flush)); |
1679 | |
|
1680 | 0 | oidc_metric_prometheus_callback_ctx_t ctx = {s_self, r->pool}; |
1681 | 0 | void *iter = NULL; |
1682 | | |
1683 | | /* parse the metrics string to JSON */ |
1684 | 0 | json = oidc_metrics_json_parse_r(r, s_json); |
1685 | 0 | if (json == NULL) |
1686 | 0 | return OK; |
1687 | | |
1688 | 0 | iter = oidc_json_object_iter(json); |
1689 | 0 | while (iter) { |
1690 | 0 | s_server = oidc_json_object_iter_key(iter); |
1691 | 0 | j_server = oidc_json_object_iter_value(iter); |
1692 | 0 | oidc_metrics_prometheus_convert(t_counters, s_server, |
1693 | 0 | oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS)); |
1694 | 0 | oidc_metrics_prometheus_convert(t_timings, s_server, |
1695 | 0 | oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS)); |
1696 | 0 | iter = oidc_json_object_iter_next(json, iter); |
1697 | 0 | } |
1698 | |
|
1699 | 0 | for (hi = apr_hash_first(r->pool, t_counters); hi; hi = apr_hash_next(hi)) { |
1700 | 0 | apr_hash_this(hi, (const void **)&name, NULL, &value); |
1701 | 0 | oidc_metrics_prometheus_counters(&ctx, name, value); |
1702 | 0 | } |
1703 | |
|
1704 | 0 | for (hi = apr_hash_first(r->pool, t_timings); hi; hi = apr_hash_next(hi)) { |
1705 | 0 | apr_hash_this(hi, (const void **)&name, NULL, &value); |
1706 | 0 | oidc_metrics_prometheus_timings(&ctx, name, value); |
1707 | 0 | } |
1708 | |
|
1709 | 0 | oidc_json_decref(json); |
1710 | |
|
1711 | 0 | return oidc_util_http_send(r, ctx.s_result, _oidc_strlen(ctx.s_result), OIDC_METRICS_PROMETHEUS_CONTENT_TYPE, |
1712 | 0 | OK); |
1713 | 0 | } |
1714 | | |
1715 | | /* |
1716 | | * definitions for handler callbacks |
1717 | | */ |
1718 | | |
1719 | | typedef int (*oidc_metrics_handler_function_t)(request_rec *, const char *); |
1720 | | |
1721 | | // holder for output function callback context |
1722 | | typedef struct oidc_metrics_handler_t { |
1723 | | const char *format; |
1724 | | oidc_metrics_handler_function_t callback; |
1725 | | int reset; |
1726 | | } oidc_metrics_content_handler_t; |
1727 | | |
1728 | | // output handlers |
1729 | | // NB: no format resets the collected metrics by default (json did before 2.4.20.4); |
1730 | | // a caller that wants the pre-2.4.20.4 behavior passes reset=true explicitly |
1731 | | const oidc_metrics_content_handler_t _oidc_metrics_handlers[] = { |
1732 | | // first is default |
1733 | | {"prometheus", oidc_metrics_handle_prometheus, 0}, |
1734 | | {"json", oidc_metrics_handle_json, 0}, |
1735 | | {"internal", oidc_metrics_handle_internal, 0}, |
1736 | | {"status", oidc_metrics_handle_status, 0}, |
1737 | | }; |
1738 | | |
1739 | 0 | #define OIDC_CONTENT_HANDLER_MAX ((int)(sizeof(_oidc_metrics_handlers) / sizeof(oidc_metrics_content_handler_t))) |
1740 | | |
1741 | 0 | #define OIDC_METRICS_RESET_PARAM "reset" |
1742 | | |
1743 | | /* |
1744 | | * see if we are going to reset the cache after this |
1745 | | */ |
1746 | 0 | static int oidc_metric_reset(request_rec *r, int dvalue) { |
1747 | 0 | char *s_reset = NULL; |
1748 | 0 | int value = 0; |
1749 | |
|
1750 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_RESET_PARAM, &s_reset); |
1751 | |
|
1752 | 0 | if (s_reset == NULL) |
1753 | 0 | return dvalue; |
1754 | | |
1755 | 0 | if (_oidc_strnatcasecmp(s_reset, "true") == 0) |
1756 | 0 | value = 1; |
1757 | 0 | else if (_oidc_strnatcasecmp(s_reset, "false") == 0) |
1758 | 0 | value = 0; |
1759 | |
|
1760 | 0 | return value; |
1761 | 0 | } |
1762 | | |
1763 | 0 | #define OIDC_METRICS_FORMAT_PARAM "format" |
1764 | | |
1765 | | /* |
1766 | | * find the format handler |
1767 | | */ |
1768 | 0 | const oidc_metrics_content_handler_t *oidc_metrics_find_handler(request_rec *r) { |
1769 | 0 | const oidc_metrics_content_handler_t *handler = NULL; |
1770 | 0 | char *s_format = NULL; |
1771 | | |
1772 | | /* get the specified format */ |
1773 | 0 | oidc_util_url_parameter_get(r, OIDC_METRICS_FORMAT_PARAM, &s_format); |
1774 | |
|
1775 | 0 | if (s_format == NULL) |
1776 | 0 | return &_oidc_metrics_handlers[0]; |
1777 | | |
1778 | 0 | for (int i = 0; i < OIDC_CONTENT_HANDLER_MAX; i++) { |
1779 | 0 | if (_oidc_strcmp(s_format, _oidc_metrics_handlers[i].format) == 0) { |
1780 | 0 | handler = &_oidc_metrics_handlers[i]; |
1781 | 0 | break; |
1782 | 0 | } |
1783 | 0 | } |
1784 | |
|
1785 | 0 | if (handler == NULL) |
1786 | 0 | oidc_warn(r, "could not find a metrics handler for format: %s", s_format); |
1787 | |
|
1788 | 0 | return handler; |
1789 | 0 | } |
1790 | | |
1791 | | /* |
1792 | | * return the metrics to the caller and flush the storage |
1793 | | */ |
1794 | 0 | int oidc_metrics_handle_request(request_rec *r) { |
1795 | 0 | const char *s_json = NULL; |
1796 | 0 | const oidc_metrics_content_handler_t *handler = NULL; |
1797 | | |
1798 | | /* get the content handler for the format */ |
1799 | 0 | handler = oidc_metrics_find_handler(r); |
1800 | 0 | if (handler == NULL) |
1801 | 0 | return HTTP_NOT_FOUND; |
1802 | | |
1803 | | /* lock the global shared memory */ |
1804 | 0 | if (oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_global_mutex) == FALSE) |
1805 | 0 | return HTTP_INTERNAL_SERVER_ERROR; |
1806 | | |
1807 | | /* retrieve the JSON formatted metrics as a string; NB: on the request pool, since |
1808 | | * it is handed to the content handler below, after the mutex has been released */ |
1809 | 0 | s_json = _oidc_metrics_storage_get(r->server, r->pool); |
1810 | | |
1811 | | /* now that the metrics have been consumed, clear the shared memory segment */ |
1812 | 0 | if (oidc_metric_reset(r, handler->reset)) |
1813 | 0 | oidc_metrics_storage_reset(r->server, r->pool); |
1814 | | |
1815 | | /* unlock the global shared memory */ |
1816 | 0 | if (oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_global_mutex) == FALSE) |
1817 | 0 | return HTTP_INTERNAL_SERVER_ERROR; |
1818 | | |
1819 | | /* handle the specified format */ |
1820 | 0 | return handler->callback(r, s_json); |
1821 | 0 | } |