Coverage Report

Created: 2026-09-01 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 "metrics.h"
53
#include <limits.h>
54
#include <apr_atomic.h>
55
#include <apr_shm.h>
56
#include <apr_lib.h>
57
58
// NB: formatting matters for docs script from here until clang-format on
59
60
// KEEP THIS: start-of-classes
61
62
#define OM_CLASS_AUTH_TYPE     "authtype"       // Request counter, overall and per AuthType: openid-connect, oauth20 and auth-openidc.
63
#define OM_CLASS_AUTHN         "authn"          // Authentication request creation and response processing.
64
#define OM_CLASS_AUTHZ         "authz"          // Authorization errors per OIDCUnAutzAction (per Require statement, not overall).
65
#define OM_CLASS_REQUIRE_CLAIM "require.claim"  // Match/failure count of Require claim directives (per Require statement, not overall).
66
#define OM_CLASS_CLAIM         "claim"          // Claims per value
67
#define OM_CLASS_PROVIDER      "provider"       // Requests to the provider [token, userinfo, metadata] endpoints.
68
#define OM_CLASS_SESSION       "session"        // Existing session processing.
69
#define OM_CLASS_CACHE         "cache"          // Cache read/write timings and errors.
70
#define OM_CLASS_LOGOUT        "logout"         // Front- and back-channel logout processing.
71
#define OM_CLASS_REDIRECT_URI  "redirect_uri"   // Requests to the Redirect URI, per type.
72
#define OM_CLASS_CONTENT       "content"        // Requests to the content handler, per type of request: info, metrics, jwks, etc.
73
74
// KEEP THIS: end-of-classes
75
76
// NB: order must match the oidc_metrics_timing_type_t enum type in metrics.h
77
78
const oidc_metrics_timing_info_t _oidc_metrics_timings_info[] = {
79
80
  // KEEP THIS: start-of-timers
81
82
  { OM_CLASS_AUTH_TYPE, "handler", "the overall authn+authz processing time" },
83
84
  { OM_CLASS_AUTHN,    "request",  "authentication requests" },
85
  { OM_CLASS_AUTHN,    "response", "authentication responses" },
86
87
  { OM_CLASS_SESSION,  "valid",    "successfully validated existing sessions" },
88
89
  { OM_CLASS_PROVIDER, "metadata", "provider discovery document requests" },
90
  { OM_CLASS_PROVIDER, "token",    "provider token requests" },
91
  { OM_CLASS_PROVIDER, "refresh",  "provider refresh token requests" },
92
  { OM_CLASS_PROVIDER, "userinfo", "provider userinfo requests" },
93
  { OM_CLASS_PROVIDER, "jwks",     "provider JWKs document requests" },
94
  { OM_CLASS_PROVIDER, "par",      "provider pushed authorization requests" },
95
96
  { OM_CLASS_CACHE,    "read",     "cache read requests" },
97
  { OM_CLASS_CACHE,    "write",    "cache write requests" },
98
99
  // KEEP THIS: end-of-timers
100
101
};
102
103
// NB: order must match the oidc_metrics_counter_type_t enum type in metrics.h
104
105
const oidc_metrics_counter_info_t _oidc_metrics_counters_info[] = {
106
#define OIDC_METRICS_COUNTER_INFO(id, class, name, desc) {class, name, desc},
107
    OIDC_METRICS_COUNTERS_LIST(OIDC_METRICS_COUNTER_INFO)
108
#undef OIDC_METRICS_COUNTER_INFO
109
};
110
111
// clang-format on
112
113
/* one entry per oidc_metrics_counter_type_t value */
114
OIDC_STATIC_ASSERT(sizeof(_oidc_metrics_counters_info) / sizeof(oidc_metrics_counter_info_t) == OM_NUMBER_OF_COUNTERS,
115
       counters_info_matches_enum);
116
117
typedef struct oidc_metrics_t {
118
  apr_hash_t *counters;
119
  apr_hash_t *timings;
120
} oidc_metrics_t;
121
122
// pointer to the shared memory segment that holds the JSON metrics data
123
static apr_shm_t *_oidc_metrics_cache = NULL;
124
// flag to record if we are a parent process or a child process
125
static apr_byte_t _oidc_metrics_is_parent = FALSE;
126
/* Atomic stop flag shared by the flush, cleanup, and child-init paths. */
127
static volatile apr_uint32_t _oidc_metrics_thread_exit = 0;
128
// mutex to protect the shared memory storage
129
static oidc_cache_mutex_t *_oidc_metrics_global_mutex = NULL;
130
// pointer to the thread that periodically writes the locally gathered metrics to shared memory
131
static apr_thread_t *_oidc_metrics_thread = NULL;
132
// pid that owns _oidc_metrics_thread; used to tell a post-fork stale handle from a same-process double-init
133
static pid_t _oidc_metrics_thread_pid = 0;
134
// local in-memory cached metrics
135
static oidc_metrics_t _oidc_metrics = {NULL, NULL};
136
// mutex to protect the local metrics hash table
137
static oidc_cache_mutex_t *_oidc_metrics_process_mutex = NULL;
138
139
// default shared memory write interval in seconds
140
#define OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT 5000
141
142
// maximum length of the string representation of the global JSON metrics data in shared memory
143
//   1024 sample size (compact, long keys, large json_int values, no description), timing + counter
144
//   256 number of individual metrics collected
145
//     4 number of vhosts supported
146
0
#define OIDC_METRICS_CACHE_JSON_MAX_DEFAULT (1024 * 256 * 4)
147
148
typedef struct oidc_metrics_bucket_t {
149
  const char *name;
150
  const char *label;
151
  apr_time_t threshold;
152
} oidc_metrics_bucket_t;
153
154
// clang-format off
155
156
static oidc_metrics_bucket_t _oidc_metric_buckets[] = {
157
  { "le01", "le=\"0.1\"", 100 },
158
  { "le05", "le=\"0.5\"", 500 },
159
  { "le1", "le=\"1\"", apr_time_from_msec(1) },
160
  { "le5", "le=\"5\"", apr_time_from_msec(5) },
161
  { "le10", "le=\"10\"", apr_time_from_msec(10) },
162
  { "le50", "le=\"50\"", apr_time_from_msec(50) },
163
  { "le100", "le=\"100\"",  apr_time_from_msec(100) },
164
  { "le500", "le=\"500\"",  apr_time_from_msec(500) },
165
  { "le1000", "le=\"1000\"", apr_time_from_msec(1000) },
166
    { "le5000", "le=\"5000\"", apr_time_from_msec(5000) },
167
    { "inf", "le=\"+Inf\"", 0 }
168
};
169
170
// clang-format on
171
172
0
#define OIDC_METRICS_BUCKET_NUM ((int)(sizeof(_oidc_metric_buckets) / sizeof(oidc_metrics_bucket_t)))
173
174
// NB: matters for Prometheus formatting
175
0
#define OIDC_METRICS_SUM "sum"
176
0
#define OIDC_METRICS_COUNT "count"
177
178
#define OIDC_METRICS_SPECS "specs"
179
180
0
#define OIDC_METRICS_JSON_CLASS_NAME "class"
181
0
#define OIDC_METRICS_JSON_METRIC_NAME "name"
182
0
#define OIDC_METRICS_JSON_DESC "desc"
183
184
0
#define OIDC_METRICS_TIMINGS "timings"
185
0
#define OIDC_METRICS_COUNTERS "counters"
186
187
/*
188
 * convert a Jansson number to a string: OIDC_JSON_INT_FORMAT does not work with apr_psprintf !?
189
 */
190
0
static inline char *_json_int2str(apr_pool_t *pool, oidc_json_int_t n) {
191
0
  char s[255];
192
0
  snprintf(s, 255, "%" OIDC_JSON_INT_FORMAT, n);
193
0
  return apr_pstrdup(pool, s);
194
0
}
195
196
0
#define OIDC_METRICS_INT_MAX OIDC_JSON_INT_MAX
197
198
/*
199
 * check Jansson specific integer/long number overrun
200
 */
201
0
static inline int _is_overflow(server_rec *s, oidc_json_int_t cur, oidc_json_int_t add) {
202
0
  if (add > (OIDC_METRICS_INT_MAX - cur)) {
203
0
    oidc_swarn(s,
204
0
         "reset metrics since the size (%s) of the integer value would be larger than the "
205
0
         "JSON/libjansson maximum "
206
0
         "(%s)",
207
0
         _json_int2str(s->process->pool, add), _json_int2str(s->process->pool, OIDC_METRICS_INT_MAX));
208
0
    return 1;
209
0
  }
210
0
  return 0;
211
0
}
212
213
// single counter container
214
typedef struct oidc_metrics_counter_t {
215
  oidc_json_int_t count;
216
} oidc_metrics_counter_t;
217
218
// single timing stats container
219
typedef struct oidc_metrics_timing_t {
220
  oidc_json_int_t buckets[OIDC_METRICS_BUCKET_NUM];
221
  apr_time_t sum;
222
  oidc_json_int_t count;
223
} oidc_metrics_timing_t;
224
225
// context holder for parsing valid classnames
226
typedef struct oidc_metrics_add_classname_ctx_t {
227
  apr_pool_t *pool;
228
  char **valid_names;
229
} oidc_metrics_add_classname_ctx_t;
230
231
/*
232
 * loop function for parsing valid classnames
233
 */
234
0
static int oidc_metrics_add_classnames(void *rec, const char *key, const char *value) {
235
0
  oidc_metrics_add_classname_ctx_t *ctx = (oidc_metrics_add_classname_ctx_t *)rec;
236
0
  *ctx->valid_names = apr_psprintf(ctx->pool, "%s%s%s", *ctx->valid_names ? *ctx->valid_names : "",
237
0
           *ctx->valid_names ? " | " : "", value);
238
0
  return 1;
239
0
}
240
241
/*
242
 * check if the provided value is a valid classname
243
 */
244
0
apr_byte_t oidc_metrics_is_valid_classname(apr_pool_t *pool, const char *name, char **valid_names) {
245
0
  int i = 0;
246
0
  int n = 0;
247
0
  apr_table_t *names = apr_table_make(pool, 1);
248
0
  oidc_metrics_add_classname_ctx_t ctx = {pool, valid_names};
249
250
0
  n = sizeof(_oidc_metrics_timings_info) / sizeof(oidc_metrics_timing_info_t);
251
0
  for (i = 0; i < n; i++) {
252
0
    apr_table_set(names, _oidc_metrics_timings_info[i].class_name,
253
0
            _oidc_metrics_timings_info[i].class_name);
254
0
  }
255
0
  n = sizeof(_oidc_metrics_counters_info) / sizeof(oidc_metrics_counter_info_t);
256
0
  for (i = 0; i < n; i++) {
257
    // NB: "claim" is the only counter class with a name/value option and is special-cased here (rather
258
    // than driven from a static list)
259
0
    if (_oidc_strcmp(_oidc_metrics_counters_info[i].class_name, "claim") != 0)
260
0
      apr_table_set(names, _oidc_metrics_counters_info[i].class_name,
261
0
              _oidc_metrics_counters_info[i].class_name);
262
0
  }
263
264
0
  *valid_names = NULL;
265
0
  apr_table_do(oidc_metrics_add_classnames, &ctx, names, NULL);
266
0
  *valid_names = apr_psprintf(pool, "%s%s%s", *valid_names ? *valid_names : "", *valid_names ? " | " : "",
267
0
            "claim.id_token.* | claim.userinfo.*");
268
269
0
  return apr_table_get(names, name) ? TRUE
270
0
            : ((_oidc_strstr(name, "claim.id_token.") != NULL) ||
271
0
               (_oidc_strstr(name, "claim.userinfo.") != NULL));
272
0
}
273
274
/*
275
 * retrieve environment variable integer with default setting
276
 */
277
0
static inline int _oidc_metrics_get_env_int(const char *name, int dval) {
278
0
  return _oidc_str_to_int(getenv(name), dval);
279
0
}
280
281
0
#define OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR "OIDC_METRICS_CACHE_JSON_MAX"
282
283
static apr_size_t _g_oidc_metrics_shm_size = 0;
284
285
/*
286
 * get the size of the to-be-allocated shared memory segment
287
 */
288
0
static inline apr_size_t _oidc_metrics_shm_size(server_rec *s) {
289
0
  if (_g_oidc_metrics_shm_size == 0) {
290
0
    int n =
291
0
        _oidc_metrics_get_env_int(OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, OIDC_METRICS_CACHE_JSON_MAX_DEFAULT);
292
0
    if ((n < 1) || (n > 1024 * 256 * 4 * 100)) {
293
0
      oidc_serror(s, "environment value %s out of bounds, fallback to default",
294
0
            OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR);
295
0
      _g_oidc_metrics_shm_size = OIDC_METRICS_CACHE_JSON_MAX_DEFAULT;
296
0
    } else {
297
0
      _g_oidc_metrics_shm_size = n;
298
0
    }
299
0
  }
300
0
  return _g_oidc_metrics_shm_size;
301
0
}
302
303
/*
304
 * retrieve the (JSON) serialized (global) metrics data from shared memory
305
 */
306
0
static inline char *_oidc_metrics_storage_get(server_rec *s, apr_pool_t *pool) {
307
0
  const char *p = (char *)apr_shm_baseaddr_get(_oidc_metrics_cache);
308
0
  return p && (*p != 0) ? apr_pstrndup(pool, p, _oidc_metrics_shm_size(s)) : NULL;
309
0
}
310
311
/*
312
 * store the serialized (global) metrics data in shared memory
313
 */
314
0
static inline void _oidc_metrics_storage_set(server_rec *s, const char *value) {
315
0
  char *p = apr_shm_baseaddr_get(_oidc_metrics_cache);
316
0
  if (value) {
317
0
    apr_size_t n = _oidc_strlen(value) + 1;
318
0
    if (n > _oidc_metrics_shm_size(s))
319
0
      oidc_serror(s,
320
0
            "json value too large: set or increase system environment variable %s to a value "
321
0
            "larger than %" APR_SIZE_T_FMT,
322
0
            OIDC_METRICS_CACHE_JSON_MAX_ENV_VAR, _oidc_metrics_shm_size(s));
323
0
    else
324
0
      _oidc_memcpy(p, value, n);
325
0
  } else {
326
0
    *p = 0;
327
0
  }
328
0
}
329
330
/*
331
 * parse a string into a JSON object
332
 */
333
0
static oidc_json_t *oidc_metrics_json_load(apr_pool_t *pool, const char *s_json, char **s_err) {
334
0
  oidc_json_t *json = NULL;
335
0
  if (s_json == NULL)
336
0
    s_json = "{}";
337
0
  oidc_json_parse(pool, s_json, 0, &json, s_err);
338
0
  return json;
339
0
}
340
341
/*
342
 * parse a string into a JSON object in a server_rec context
343
 */
344
0
static oidc_json_t *oidc_metrics_json_parse_s(server_rec *s, apr_pool_t *pool, const char *s_json) {
345
0
  char *s_err = NULL;
346
0
  oidc_json_t *json = oidc_metrics_json_load(pool, s_json, &s_err);
347
0
  if (json == NULL)
348
0
    oidc_serror(s, "JSON parsing failed: %s", s_err);
349
0
  return json;
350
0
}
351
352
/*
353
 * recursively reset all integer leaf values within a JSON object tree to 0
354
 */
355
0
static void oidc_metrics_reset_integer_tree(oidc_json_t *json) {
356
0
  void *iter = oidc_json_object_iter(json);
357
0
  while (iter) {
358
0
    oidc_json_t *value = oidc_json_object_iter_value(iter);
359
0
    if (oidc_json_is_integer(value))
360
0
      oidc_json_integer_set(value, 0);
361
0
    else if (oidc_json_is_object(value))
362
0
      oidc_metrics_reset_integer_tree(value);
363
0
    iter = oidc_json_object_iter_next(json, iter);
364
0
  }
365
0
}
366
367
/*
368
 * reset a single timings entry (buckets, sum, count) to 0
369
 */
370
0
static void oidc_metrics_reset_timer(oidc_json_t *j_entry) {
371
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++)
372
0
    oidc_json_object_set_new(j_entry, _oidc_metric_buckets[i].name, oidc_json_integer(0));
373
0
  oidc_json_object_set_new(j_entry, OIDC_METRICS_SUM, oidc_json_integer(0));
374
0
  oidc_json_object_set_new(j_entry, OIDC_METRICS_COUNT, oidc_json_integer(0));
375
0
}
376
377
/*
378
 * reset all counter and timer entries for a single server in the JSON data
379
 */
380
0
static void oidc_metrics_reset_server(const oidc_json_t *j_server) {
381
0
  oidc_json_t *j_entries = NULL;
382
0
  void *iter = NULL;
383
384
0
  j_entries = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS);
385
0
  if (j_entries)
386
0
    oidc_metrics_reset_integer_tree(j_entries);
387
388
0
  j_entries = oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS);
389
0
  iter = oidc_json_object_iter(j_entries);
390
0
  while (iter) {
391
0
    oidc_metrics_reset_timer(oidc_json_object_iter_value(iter));
392
0
    iter = oidc_json_object_iter_next(j_entries, iter);
393
0
  }
394
0
}
395
396
/*
397
 * reset the serialized (global) metrics data in shared memory
398
 */
399
0
static inline void oidc_metrics_storage_reset(server_rec *s, apr_pool_t *pool) {
400
0
  const char *s_json = NULL;
401
0
  oidc_json_t *json = NULL;
402
0
  void *iter = NULL;
403
404
  /* get the global stringified JSON metrics */
405
0
  s_json = _oidc_metrics_storage_get(s, pool);
406
407
  /* parse the metrics string to JSON */
408
0
  json = oidc_metrics_json_parse_s(s, pool, s_json);
409
0
  if (json == NULL)
410
0
    json = oidc_json_object();
411
412
0
  iter = oidc_json_object_iter(json);
413
0
  while (iter) {
414
0
    oidc_metrics_reset_server(oidc_json_object_iter_value(iter));
415
0
    iter = oidc_json_object_iter_next(json, iter);
416
0
  }
417
418
  /* serialize the metrics data, preserve order is required for Prometheus */
419
0
  s_json = oidc_json_encode(pool, json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER);
420
421
  /* free the JSON data */
422
0
  oidc_json_decref(json);
423
424
  /* store the serialized metrics data in shared memory */
425
0
  _oidc_metrics_storage_set(s, s_json);
426
0
}
427
428
/*
429
 * create a new timings entry in the collected JSON data
430
 */
431
0
static oidc_json_t *oidc_metrics_timings_new(const oidc_metrics_timing_t *timing) {
432
0
  oidc_json_t *entry = oidc_json_object();
433
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++)
434
0
    oidc_json_object_set_new(entry, _oidc_metric_buckets[i].name, oidc_json_integer(timing->buckets[i]));
435
0
  oidc_json_object_set_new(entry, OIDC_METRICS_SUM, oidc_json_integer(apr_time_as_msec(timing->sum)));
436
0
  oidc_json_object_set_new(entry, OIDC_METRICS_COUNT, oidc_json_integer(timing->count));
437
0
  return entry;
438
0
}
439
440
/*
441
 * update an entry in the collected JSON data
442
 */
443
0
static void oidc_metrics_timings_update(server_rec *s, const oidc_json_t *entry, const oidc_metrics_timing_t *timing) {
444
0
  oidc_json_t *j_member = NULL;
445
0
  oidc_json_int_t n = 0;
446
0
  oidc_json_int_t v = 0;
447
448
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) {
449
0
    j_member = oidc_json_object_get(entry, _oidc_metric_buckets[i].name);
450
0
    oidc_json_integer_set(j_member, oidc_json_integer_value(j_member) + timing->buckets[i]);
451
0
  }
452
453
0
  j_member = oidc_json_object_get(entry, OIDC_METRICS_SUM);
454
0
  n = oidc_json_integer_value(j_member);
455
456
0
  v = apr_time_as_msec(timing->sum);
457
0
  if (_is_overflow(s, n, v))
458
0
    n = 0;
459
460
0
  oidc_json_integer_set(j_member, n + v);
461
462
0
  j_member = oidc_json_object_get(entry, OIDC_METRICS_COUNT);
463
0
  n = oidc_json_integer_value(j_member);
464
0
  oidc_json_integer_set(j_member, n + timing->count);
465
0
}
466
467
0
#define OIDC_METRICS_VALUE_DEFAULT "_"
468
469
/*
470
 * value helper to make sure it is not empty
471
 */
472
0
static inline const char *_metrics_value2key(const char *value) {
473
0
  return (value && _oidc_strcmp(value, "") != 0) ? value : OIDC_METRICS_VALUE_DEFAULT;
474
0
}
475
476
/*
477
 * create a new counter entry in the collected JSON data
478
 */
479
0
static oidc_json_t *oidc_metrics_counter_new(apr_hash_t *htable) {
480
0
  oidc_metrics_counter_t *counter = NULL;
481
0
  char *value = NULL;
482
0
  oidc_json_t *j_values = NULL;
483
0
  for (apr_hash_index_t *hi = apr_hash_first(NULL, htable); hi; hi = apr_hash_next(hi)) {
484
0
    apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter);
485
0
    if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) {
486
0
      j_values = oidc_json_integer(counter->count);
487
0
    } else {
488
0
      if (j_values == NULL)
489
0
        j_values = oidc_json_object();
490
0
      oidc_json_object_set_new(j_values, value, oidc_json_integer(counter->count));
491
0
    }
492
0
  }
493
0
  return j_values;
494
0
}
495
496
/*
497
 * update a counter entry in the collected JSON data
498
 */
499
0
static void oidc_metrics_counter_update(server_rec *s, oidc_json_t *j_counter, apr_hash_t *htable) {
500
0
  oidc_json_int_t v = 0;
501
0
  oidc_metrics_counter_t *counter = NULL;
502
0
  char *value = NULL;
503
0
  oidc_json_t *j_value = NULL;
504
0
  for (apr_hash_index_t *hi = apr_hash_first(NULL, htable); hi; hi = apr_hash_next(hi)) {
505
0
    apr_hash_this(hi, (const void **)&value, NULL, (void **)&counter);
506
0
    if (_oidc_strcmp(value, OIDC_METRICS_VALUE_DEFAULT) == 0) {
507
0
      j_value = j_counter;
508
0
    } else {
509
0
      j_value = oidc_json_object_get(j_counter, value);
510
0
      if (j_value == NULL) {
511
0
        oidc_json_object_set_new(j_counter, value, oidc_json_integer(counter->count));
512
0
        continue;
513
0
      }
514
0
    }
515
0
    v = oidc_json_integer_value(j_value);
516
0
    if (_is_overflow(s, v, counter->count))
517
0
      v = 0;
518
0
    oidc_json_integer_set(j_value, v + counter->count);
519
0
  }
520
0
}
521
522
/*
523
 * get or create the vhost entry in the global metrics
524
 */
525
0
static oidc_json_t *oidc_metrics_server_get(oidc_json_t *json, const char *name) {
526
0
  oidc_json_t *j_server = oidc_json_object_get(json, name);
527
0
  if (j_server == NULL) {
528
0
    j_server = oidc_json_object();
529
0
    oidc_json_object_set_new(j_server, OIDC_METRICS_COUNTERS, oidc_json_object());
530
0
    oidc_json_object_set_new(j_server, OIDC_METRICS_TIMINGS, oidc_json_object());
531
0
    oidc_json_object_set_new(json, name, j_server);
532
0
  }
533
0
  return j_server;
534
0
}
535
536
/*
537
 * convert an enum type value to its corresponding string
538
 */
539
0
static inline char *_oidc_metrics_type_name2key(apr_pool_t *pool, unsigned int type, const char *name) {
540
0
  return (name == NULL) ? apr_psprintf(pool, "%u", type) : apr_psprintf(pool, "%u.%s", type, name);
541
0
}
542
543
/*
544
 * convert a string key type to an enum type
545
 */
546
0
static inline unsigned int _oidc_metrics_key2type(const char *key) {
547
0
  unsigned int type = 0;
548
0
  sscanf(key, "%u", &type);
549
0
  return type;
550
0
}
551
552
/*
553
 * update an existing counter under parent[key] or create a new one from counter_hash
554
 */
555
static void oidc_metrics_counter_set_or_update(server_rec *s, oidc_json_t *parent, const char *key,
556
0
                 apr_hash_t *counter_hash) {
557
0
  oidc_json_t *j_counter = oidc_json_object_get(parent, key);
558
0
  if (j_counter != NULL)
559
0
    oidc_metrics_counter_update(s, j_counter, counter_hash);
560
0
  else
561
0
    oidc_json_object_set_new(parent, key, oidc_metrics_counter_new(counter_hash));
562
0
}
563
564
/*
565
 * merge a single local counter entry into the global counters object;
566
 * keys of the form "class.name" are nested as j_counters[class][name]
567
 */
568
static void oidc_metrics_store_counter_entry(server_rec *s, apr_pool_t *pool, oidc_json_t *j_counters, const char *key,
569
0
               apr_hash_t *counter_hash) {
570
0
  const char *class_name = apr_pstrdup(pool, key);
571
0
  char *name = _oidc_strstr(class_name, ".");
572
0
  oidc_json_t *j_names = NULL;
573
574
0
  if (name == NULL) {
575
0
    oidc_metrics_counter_set_or_update(s, j_counters, class_name, counter_hash);
576
0
    return;
577
0
  }
578
579
0
  *name++ = '\0';
580
0
  j_names = oidc_json_object_get(j_counters, class_name);
581
0
  if (j_names == NULL) {
582
0
    j_names = oidc_json_object();
583
0
    oidc_json_object_set_new(j_names, name, oidc_metrics_counter_new(counter_hash));
584
0
    oidc_json_object_set_new(j_counters, class_name, j_names);
585
0
    return;
586
0
  }
587
588
0
  oidc_metrics_counter_set_or_update(s, j_names, name, counter_hash);
589
0
}
590
591
/*
592
 * merge all locally collected counters into the global JSON
593
 */
594
0
static void oidc_metrics_store_counters(server_rec *s, apr_pool_t *pool, oidc_json_t *json) {
595
0
  const char *name = NULL;
596
0
  const char *key = NULL;
597
0
  apr_hash_t *server_hash = NULL;
598
0
  apr_hash_t *counter_hash = NULL;
599
0
  oidc_json_t *j_counters = NULL;
600
601
0
  for (apr_hash_index_t *hi1 = apr_hash_first(NULL, _oidc_metrics.counters); hi1; hi1 = apr_hash_next(hi1)) {
602
0
    apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash);
603
0
    j_counters = oidc_json_object_get(oidc_metrics_server_get(json, name), OIDC_METRICS_COUNTERS);
604
0
    for (apr_hash_index_t *hi2 = apr_hash_first(NULL, server_hash); hi2; hi2 = apr_hash_next(hi2)) {
605
0
      apr_hash_this(hi2, (const void **)&key, NULL, (void **)&counter_hash);
606
0
      oidc_metrics_store_counter_entry(s, pool, j_counters, key, counter_hash);
607
0
    }
608
0
  }
609
0
}
610
611
/*
612
 * merge all locally collected timings into the global JSON
613
 */
614
0
static void oidc_metrics_store_timings(server_rec *s, oidc_json_t *json) {
615
0
  const char *name = NULL;
616
0
  const char *key = NULL;
617
0
  apr_hash_t *server_hash = NULL;
618
0
  oidc_metrics_timing_t *timing = NULL;
619
0
  oidc_json_t *j_timings = NULL;
620
0
  const oidc_json_t *j_timer = NULL;
621
622
0
  for (apr_hash_index_t *hi1 = apr_hash_first(NULL, _oidc_metrics.timings); hi1; hi1 = apr_hash_next(hi1)) {
623
0
    apr_hash_this(hi1, (const void **)&name, NULL, (void **)&server_hash);
624
0
    j_timings = oidc_json_object_get(oidc_metrics_server_get(json, name), OIDC_METRICS_TIMINGS);
625
0
    for (apr_hash_index_t *hi2 = apr_hash_first(NULL, server_hash); hi2; hi2 = apr_hash_next(hi2)) {
626
0
      apr_hash_this(hi2, (const void **)&key, NULL, (void **)&timing);
627
0
      j_timer = oidc_json_object_get(j_timings, key);
628
0
      if (j_timer != NULL)
629
0
        oidc_metrics_timings_update(s, j_timer, timing);
630
0
      else
631
0
        oidc_json_object_set_new(j_timings, key, oidc_metrics_timings_new(timing));
632
0
    }
633
0
  }
634
0
}
635
636
/*
637
 * flush the locally gathered metrics data into the global data kept in shared memory
638
 */
639
0
static void oidc_metrics_store(server_rec *s) {
640
0
  const char *s_json = NULL;
641
0
  oidc_json_t *json = NULL;
642
0
  apr_pool_t *pool = NULL;
643
644
0
  if ((apr_hash_count(_oidc_metrics.counters) == 0) && (apr_hash_count(_oidc_metrics.timings) == 0))
645
0
    return;
646
647
  /* everything below is scratch: the whole document is read, re-serialized and copied
648
   * into shared memory on every flush, so it must not come from the process pool,
649
   * which is never cleared for the lifetime of the server */
650
0
  if (apr_pool_create(&pool, s->process->pool) != APR_SUCCESS) {
651
0
    oidc_serror(s, "apr_pool_create failed: cannot flush metrics");
652
0
    return;
653
0
  }
654
655
  /* lock the shared memory for other processes */
656
0
  oidc_cache_mutex_lock(pool, s, _oidc_metrics_global_mutex);
657
658
  /* get the global stringified JSON metrics */
659
0
  s_json = _oidc_metrics_storage_get(s, pool);
660
661
  /* parse the metrics string to JSON */
662
0
  json = oidc_metrics_json_parse_s(s, pool, s_json);
663
0
  if (json == NULL)
664
0
    json = oidc_json_object();
665
666
0
  oidc_metrics_store_counters(s, pool, json);
667
0
  oidc_metrics_store_timings(s, json);
668
669
  /* serialize the metrics data, preserve order is required for Prometheus */
670
0
  s_json = oidc_json_encode(pool, json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER);
671
672
  /* free the JSON data */
673
0
  oidc_json_decref(json);
674
675
  /* store the serialized metrics data in shared memory */
676
0
  _oidc_metrics_storage_set(s, s_json);
677
678
  /* unlock the shared memory for other processes */
679
0
  oidc_cache_mutex_unlock(pool, s, _oidc_metrics_global_mutex);
680
681
0
  apr_pool_destroy(pool);
682
0
}
683
684
#define OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR "OIDC_METRICS_CACHE_STORAGE_INTERVAL"
685
686
/*
687
 * obtain the metrics flush interval from the environment variables
688
 */
689
0
static inline apr_interval_time_t _oidc_metrics_interval(void) {
690
0
  return apr_time_from_msec(_oidc_metrics_get_env_int(OIDC_METRICS_CACHE_STORAGE_INTERVAL_ENV_VAR,
691
0
                  OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT));
692
0
}
693
694
#define OIDC_METRICS_POLL_INTERVAL 250
695
696
/*
697
 * thread that periodically writes the local data into the shared memory
698
 */
699
0
static void *APR_THREAD_FUNC oidc_metrics_thread_run(apr_thread_t *thread, void *data) {
700
0
  server_rec *s = (server_rec *)data;
701
702
  /* sleep for a short random time <0.1s so child processes write-lock on a different frequency */
703
0
  apr_sleep(apr_time_from_msec(oidc_util_rand_int(100)));
704
705
  /* split the flush interval into POLL_INTERVAL-sized ticks so shutdown is observed quickly; if the
706
   * configured interval is shorter than POLL_INTERVAL, use it as the tick directly so we still flush
707
   * on schedule rather than busy-looping with n=0 */
708
0
  apr_interval_time_t interval = _oidc_metrics_interval();
709
  /* a misconfigured env var parses to 0 (or negative) — fall back to the default to avoid 0/0 below */
710
0
  if (interval <= 0)
711
0
    interval = apr_time_from_msec(OIDC_METRICS_CACHE_STORAGE_INTERVAL_DEFAULT);
712
0
  apr_interval_time_t tick = apr_time_from_msec(OIDC_METRICS_POLL_INTERVAL);
713
0
  if (tick > interval)
714
0
    tick = interval;
715
0
  int n = (int)(interval / tick);
716
717
  /* see if we are asked to exit */
718
0
  while (apr_atomic_read32(&_oidc_metrics_thread_exit) == 0) {
719
720
    /* break up the sleep interval in short intervals so we can exit timely fashion without confusing Apache
721
     * at shutdown */
722
0
    for (int i = 0; i < n; i++) {
723
0
      apr_sleep(tick);
724
0
      if (apr_atomic_read32(&_oidc_metrics_thread_exit) != 0)
725
0
        break;
726
0
    }
727
728
    // NB: no exit here because we need to write our local metrics into the cache before exiting
729
730
    /* lock the mutex that protects the locally cached metrics */
731
0
    oidc_cache_mutex_lock(s->process->pool, s, _oidc_metrics_process_mutex);
732
733
    /* flush the locally cached metrics into the global shared memory */
734
0
    oidc_metrics_store(s);
735
736
    /* reset the local hashtables */
737
0
    oidc_util_apr_hash_clear(_oidc_metrics.counters);
738
0
    oidc_util_apr_hash_clear(_oidc_metrics.timings);
739
740
    /* unlock the mutex that protects the locally cached metrics */
741
0
    oidc_cache_mutex_unlock(s->process->pool, s, _oidc_metrics_process_mutex);
742
0
  }
743
744
  /* NB: don't call apr_thread_exit here because it seems that Apache is cleaning up its own threads */
745
746
0
  return NULL;
747
0
}
748
749
/*
750
 * server config handlers
751
 */
752
753
/*
754
 * NB: global, yet called for each vhost that has metrics enabled!
755
 */
756
0
apr_byte_t oidc_metrics_post_config(apr_pool_t *pool, server_rec *s) {
757
758
  /* make sure it gets executed exactly once! */
759
0
  if (_oidc_metrics_cache != NULL)
760
0
    return TRUE;
761
762
  /* create the shared memory segment that holds the stringified JSON formatted metrics data */
763
0
  if (apr_shm_create(&_oidc_metrics_cache, _oidc_metrics_shm_size(s), NULL, s->process->pool) != APR_SUCCESS)
764
0
    return FALSE;
765
0
  if (_oidc_metrics_cache == NULL)
766
0
    return FALSE;
767
768
  /* initialize the shared memory segment to 0 */
769
0
  char *p = apr_shm_baseaddr_get(_oidc_metrics_cache);
770
0
  _oidc_memset(p, 0, _oidc_metrics_shm_size(s));
771
772
  /* flag this as the parent, for shared memory cleanup purposes and "multiple child-init calls" detection */
773
0
  _oidc_metrics_is_parent = TRUE;
774
775
  /* create the thread that will periodically flush the local metrics data to shared memory */
776
0
  if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS)
777
0
    return FALSE;
778
0
  _oidc_metrics_thread_pid = getpid();
779
780
  /* create the hashtable that holds local metrics data */
781
0
  _oidc_metrics.counters = apr_hash_make(s->process->pool);
782
0
  _oidc_metrics.timings = apr_hash_make(s->process->pool);
783
784
  /* create and initialize the mutex that guards _oidc_metrics_hash */
785
0
  _oidc_metrics_global_mutex = oidc_cache_mutex_create(s->process->pool, TRUE);
786
0
  if (_oidc_metrics_global_mutex == NULL)
787
0
    return FALSE;
788
0
  if (oidc_cache_mutex_post_config(s->process->pool, s, _oidc_metrics_global_mutex, "metrics-global") == FALSE)
789
0
    return FALSE;
790
791
  /* create and initialize the mutex that guards the shared memory */
792
0
  _oidc_metrics_process_mutex = oidc_cache_mutex_create(s->process->pool, FALSE);
793
0
  if (_oidc_metrics_process_mutex == NULL)
794
0
    return FALSE;
795
0
  if (oidc_cache_mutex_post_config(s->process->pool, s, _oidc_metrics_process_mutex, "metrics-process") == FALSE)
796
0
    return FALSE;
797
798
0
  return TRUE;
799
0
}
800
801
/*
802
 * NB: global, yet called for each vhost that has metrics enabled!
803
 */
804
0
apr_status_t oidc_metrics_child_init(apr_pool_t *p, server_rec *s) {
805
0
  apr_status_t rv = APR_SUCCESS;
806
807
  /* make sure this executes only once per child */
808
0
  if (_oidc_metrics_is_parent == FALSE)
809
0
    return APR_SUCCESS;
810
811
0
  if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_global_mutex) != APR_SUCCESS)
812
0
    return APR_EGENERAL;
813
814
0
  if (oidc_cache_mutex_child_init(p, s, _oidc_metrics_process_mutex) != APR_SUCCESS)
815
0
    return APR_EGENERAL;
816
817
  /* Stop an existing same-process flush thread; discard inherited handles after a fork. */
818
0
  if (_oidc_metrics_thread != NULL && _oidc_metrics_thread_pid == getpid()) {
819
0
    apr_atomic_set32(&_oidc_metrics_thread_exit, 1);
820
0
    apr_thread_join(&rv, _oidc_metrics_thread);
821
0
    apr_atomic_set32(&_oidc_metrics_thread_exit, 0);
822
0
  }
823
0
  _oidc_metrics_thread = NULL;
824
825
  /* the metrics flush thread is not inherited from the parent, so re-create it in the child */
826
0
  if (apr_thread_create(&_oidc_metrics_thread, NULL, oidc_metrics_thread_run, s, s->process->pool) != APR_SUCCESS)
827
0
    return APR_EGENERAL;
828
0
  _oidc_metrics_thread_pid = getpid();
829
830
  /* flag this is a child */
831
0
  _oidc_metrics_is_parent = FALSE;
832
833
0
  return APR_SUCCESS;
834
0
}
835
836
/*
837
 * NB: global, yet called for each vhost that has metrics enabled!
838
 */
839
0
apr_status_t oidc_metrics_cleanup(server_rec *s) {
840
0
  apr_status_t rv = APR_SUCCESS;
841
842
  /* make sure it gets executed exactly once! */
843
0
  if ((_oidc_metrics_cache == NULL) || (apr_atomic_read32(&_oidc_metrics_thread_exit) != 0) ||
844
0
      (_oidc_metrics_thread == NULL))
845
0
    return APR_SUCCESS;
846
847
  /* signal the collector thread to exit */
848
0
  apr_atomic_set32(&_oidc_metrics_thread_exit, 1);
849
0
  apr_thread_join(&rv, _oidc_metrics_thread);
850
0
  if (rv != APR_SUCCESS)
851
0
    oidc_serror(s, "apr_thread_join failed");
852
0
  apr_atomic_set32(&_oidc_metrics_thread_exit, 0);
853
0
  _oidc_metrics_thread = NULL;
854
0
  _oidc_metrics_thread_pid = 0;
855
856
  /* delete the shared memory segment if we are in the parent process */
857
0
  if (_oidc_metrics_is_parent == TRUE)
858
0
    apr_shm_destroy(_oidc_metrics_cache);
859
0
  _oidc_metrics_cache = NULL;
860
861
  /* delete the process mutex that guards the local metrics data */
862
0
  if (oidc_cache_mutex_destroy(s, _oidc_metrics_process_mutex) == FALSE)
863
0
    return APR_EGENERAL;
864
0
  _oidc_metrics_process_mutex = NULL;
865
866
  /* delete the process mutex that guards the global shared memory segment */
867
0
  if (oidc_cache_mutex_destroy(s, _oidc_metrics_global_mutex) == FALSE)
868
0
    return APR_EGENERAL;
869
0
  _oidc_metrics_global_mutex = NULL;
870
871
0
  return APR_SUCCESS;
872
0
}
873
874
/*
875
 * sampling
876
 */
877
878
/*
879
 * obtain the local metrics hashtable for the current vhost
880
 */
881
0
static inline apr_hash_t *_oidc_metrics_server_hash(request_rec *r, apr_hash_t *table) {
882
0
  apr_hash_t *server_hash = NULL;
883
0
  const char *name = "_default_";
884
885
  /* obtain the server name */
886
0
  if (r->server->server_hostname)
887
0
    name = r->server->server_hostname;
888
889
  /* get the entry to the vhost record, or newly create it */
890
0
  server_hash = apr_hash_get(table, name, APR_HASH_KEY_STRING);
891
0
  if (server_hash == NULL) {
892
    // NB: process pool!
893
0
    server_hash = apr_hash_make(r->server->process->pool);
894
0
    apr_hash_set(table, name, APR_HASH_KEY_STRING, server_hash);
895
0
  }
896
897
0
  return server_hash;
898
0
}
899
900
/*
901
 * retrieve or create a local timing for the specified type
902
 */
903
0
static inline oidc_metrics_timing_t *_oidc_metrics_timing_get(request_rec *r, unsigned int type) {
904
0
  oidc_metrics_timing_t *result = NULL;
905
  /* NB: the lookup key is request-scoped; only the copy the hash retains on insert
906
   * below is allocated process-wide, since that pool is never cleared */
907
0
  const char *key = _oidc_metrics_type_name2key(r->pool, type, NULL);
908
0
  apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.timings);
909
  /* get the entry to the specified metric */
910
0
  result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING);
911
0
  if (result == NULL) {
912
    /* allocate the timing structure in the process pool */
913
0
    result = apr_pcalloc(r->server->process->pool, sizeof(oidc_metrics_timing_t));
914
0
    apr_hash_set(server_hash, apr_pstrdup(r->server->process->pool, key), APR_HASH_KEY_STRING, result);
915
0
  }
916
0
  return result;
917
0
}
918
919
/*
920
 * retrieve or create a counter from a hashtable of values
921
 */
922
static inline oidc_metrics_counter_t *_oidc_metrics_counter_value_get(request_rec *r, apr_hash_t *table,
923
0
                      const char *value) {
924
  /* get the entry to the specified metric */
925
0
  oidc_metrics_counter_t *result = apr_hash_get(table, value, APR_HASH_KEY_STRING);
926
0
  if (result == NULL) {
927
0
    result = apr_pcalloc(r->server->process->pool, sizeof(oidc_metrics_counter_t));
928
0
    apr_hash_set(table, apr_pstrdup(r->server->process->pool, value), APR_HASH_KEY_STRING, result);
929
0
  }
930
0
  return result;
931
0
}
932
933
/*
934
 * retrieve or create a local counter for the specified type and name
935
 */
936
0
static inline apr_hash_t *_oidc_metrics_counter_get(request_rec *r, unsigned int type, const char *name) {
937
0
  apr_hash_t *result = NULL;
938
  /* NB: request-scoped lookup key, process-wide copy only on insert - see above */
939
0
  const char *key = _oidc_metrics_type_name2key(r->pool, type, name);
940
0
  apr_hash_t *server_hash = _oidc_metrics_server_hash(r, _oidc_metrics.counters);
941
942
  /* get the entry to the specified metric */
943
0
  result = apr_hash_get(server_hash, key, APR_HASH_KEY_STRING);
944
0
  if (result == NULL) {
945
    /* allocate the values hashtable in the process pool */
946
0
    result = apr_hash_make(r->server->process->pool);
947
0
    apr_hash_set(server_hash, apr_pstrdup(r->server->process->pool, key), APR_HASH_KEY_STRING, result);
948
0
  }
949
950
0
  return result;
951
0
}
952
953
/*
954
 * add/increase a counter metric in the locally cached data
955
 */
956
0
void oidc_metrics_counter_inc(request_rec *r, oidc_metrics_counter_type_t type, const char *name, const char *value) {
957
0
  oidc_metrics_counter_t *counter = NULL;
958
959
  /* lock the local metrics cache hashtable */
960
0
  oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex);
961
962
  /* obtain or create the entry for the specified key */
963
0
  counter =
964
0
      _oidc_metrics_counter_value_get(r, _oidc_metrics_counter_get(r, type, name), _metrics_value2key(value));
965
966
  /* performance */
967
0
  if (counter->count <= 0) {
968
    // new counter was created just now or reset earlier
969
0
    counter->count = 1;
970
0
  } else {
971
    // increase after checking possible overflow
972
0
    if (_is_overflow(r->server, counter->count, 1))
973
0
      counter->count = 0;
974
0
    counter->count++;
975
0
  }
976
977
  /* unlock the local metrics cache hashtable */
978
0
  oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex);
979
0
}
980
981
/*
982
 * zero out a timings entry (count, sum and all buckets)
983
 */
984
0
static inline void _oidc_metrics_timing_clear(oidc_metrics_timing_t *timing) {
985
0
  timing->count = 0;
986
0
  timing->sum = 0;
987
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++)
988
0
    timing->buckets[i] = 0;
989
0
}
990
991
/*
992
 * increment every bucket whose threshold covers elapsed (buckets are ordered;
993
 * threshold == 0 marks the open-ended last bucket)
994
 */
995
0
static inline void _oidc_metrics_timing_buckets_inc(oidc_metrics_timing_t *timing, apr_time_t elapsed) {
996
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) {
997
0
    if ((elapsed < _oidc_metric_buckets[i].threshold) || (_oidc_metric_buckets[i].threshold == 0)) {
998
0
      for (int j = i; j < OIDC_METRICS_BUCKET_NUM; j++)
999
0
        timing->buckets[j]++;
1000
0
      break;
1001
0
    }
1002
0
  }
1003
0
}
1004
1005
/*
1006
 * add a metrics timing sample to the locally cached data
1007
 */
1008
0
void oidc_metrics_timing_add(request_rec *r, oidc_metrics_timing_type_t type, apr_time_t elapsed) {
1009
0
  oidc_metrics_timing_t *timing = NULL;
1010
1011
0
  if (elapsed < 0) {
1012
0
    oidc_warn(r, "discarding metrics timing [%s.%s]: elapsed (%" APR_TIME_T_FMT ") < 0",
1013
0
        _oidc_metrics_timings_info[type].class_name, _oidc_metrics_timings_info[type].metric_name,
1014
0
        elapsed);
1015
0
    return;
1016
0
  }
1017
1018
  /* lock the local metrics cache hashtable */
1019
0
  oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_process_mutex);
1020
1021
  /* obtain or create the entry for the specified key */
1022
0
  timing = _oidc_metrics_timing_get(r, type);
1023
1024
  /* reset on overflow; a freshly-created entry already has count/sum/buckets at zero */
1025
0
  if ((timing->count > 0) && _is_overflow(r->server, timing->sum, elapsed))
1026
0
    _oidc_metrics_timing_clear(timing);
1027
1028
0
  _oidc_metrics_timing_buckets_inc(timing, elapsed);
1029
0
  timing->sum += elapsed;
1030
0
  timing->count++;
1031
1032
  /* unlock the local metrics cache hashtable */
1033
0
  oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_process_mutex);
1034
0
}
1035
1036
/*
1037
 * representation handlers
1038
 */
1039
1040
/*
1041
 * convert in integer counter enum type to its corresponding string name
1042
 */
1043
0
static inline char *_oidc_metrics_counter_type2s(apr_pool_t *pool, unsigned int type) {
1044
0
  return apr_psprintf(pool, "%s.%s", _oidc_metrics_counters_info[type].class_name,
1045
0
          _oidc_metrics_counters_info[type].metric_name);
1046
0
}
1047
1048
/*
1049
 * convert in integer timings enum type to its corresponding string name
1050
 */
1051
0
static inline char *_oidc_metrics_timing_type2s(apr_pool_t *pool, unsigned int type) {
1052
0
  return apr_psprintf(pool, "%s.%s", _oidc_metrics_timings_info[type].class_name,
1053
0
          _oidc_metrics_timings_info[type].metric_name);
1054
0
}
1055
1056
/*
1057
 * parse a string into a JSON object in the request_rec context
1058
 */
1059
0
static oidc_json_t *oidc_metrics_json_parse_r(request_rec *r, const char *s_json) {
1060
0
  char *s_err = NULL;
1061
0
  oidc_json_t *json = oidc_metrics_json_load(r->pool, s_json, &s_err);
1062
0
  if (json == NULL)
1063
0
    oidc_error(r, "JSON parsing failed: %s", s_err);
1064
0
  return json;
1065
0
}
1066
1067
/*
1068
 * JSON with extended descriptions/names
1069
 */
1070
0
static int oidc_metrics_handle_json(request_rec *r, const char *s_json) {
1071
1072
0
  oidc_json_t *json = NULL;
1073
0
  const oidc_json_t *j_server = NULL;
1074
0
  oidc_json_t *j_timings = NULL;
1075
0
  oidc_json_t *j_counters = NULL;
1076
0
  const oidc_json_t *j_timing = NULL;
1077
0
  oidc_json_t *j_counter = NULL;
1078
0
  oidc_json_t *o_json = NULL;
1079
0
  oidc_json_t *o_server = NULL;
1080
0
  oidc_json_t *o_counters = NULL;
1081
0
  oidc_json_t *o_counter = NULL;
1082
0
  oidc_json_t *o_timings = NULL;
1083
0
  oidc_json_t *o_timing = NULL;
1084
0
  const char *s_server = NULL;
1085
0
  unsigned int type = 0;
1086
0
  void *i1 = NULL;
1087
0
  void *i2 = NULL;
1088
1089
  /* parse the metrics string to JSON */
1090
0
  json = oidc_metrics_json_parse_r(r, s_json);
1091
0
  if (json == NULL)
1092
0
    goto end;
1093
1094
0
  o_json = oidc_json_object();
1095
1096
0
  i1 = oidc_json_object_iter(json);
1097
0
  while (i1) {
1098
0
    s_server = oidc_json_object_iter_key(i1);
1099
0
    j_server = oidc_json_object_iter_value(i1);
1100
1101
0
    o_server = oidc_json_object();
1102
0
    oidc_json_object_set_new(o_json, s_server, o_server);
1103
1104
0
    j_counters = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS);
1105
0
    o_counters = oidc_json_object();
1106
0
    oidc_json_object_set_new(o_server, OIDC_METRICS_COUNTERS, o_counters);
1107
1108
0
    i2 = oidc_json_object_iter(j_counters);
1109
0
    while (i2) {
1110
0
      type = _oidc_metrics_key2type(oidc_json_object_iter_key(i2));
1111
0
      j_counter = oidc_json_object_iter_value(i2);
1112
0
      o_counter = oidc_json_object();
1113
0
      if (oidc_json_is_integer(j_counter))
1114
0
        oidc_json_object_set(o_counter, "count", j_counter);
1115
0
      else
1116
0
        oidc_json_object_set_new(o_counter, "values", oidc_json_deep_copy(j_counter));
1117
0
      oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_CLASS_NAME,
1118
0
             oidc_json_string(_oidc_metrics_counters_info[type].class_name));
1119
0
      oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_METRIC_NAME,
1120
0
             oidc_json_string(_oidc_metrics_counters_info[type].metric_name));
1121
0
      oidc_json_object_set_new(o_counter, OIDC_METRICS_JSON_DESC,
1122
0
             oidc_json_string(_oidc_metrics_counters_info[type].desc));
1123
0
      oidc_json_object_set_new(o_counters, _oidc_metrics_counter_type2s(r->pool, type), o_counter);
1124
0
      i2 = oidc_json_object_iter_next(j_counters, i2);
1125
0
    }
1126
1127
0
    j_timings = oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS);
1128
0
    o_timings = oidc_json_object();
1129
0
    oidc_json_object_set_new(o_server, OIDC_METRICS_TIMINGS, o_timings);
1130
1131
0
    i2 = oidc_json_object_iter(j_timings);
1132
0
    while (i2) {
1133
0
      type = _oidc_metrics_key2type(oidc_json_object_iter_key(i2));
1134
0
      j_timing = oidc_json_object_iter_value(i2);
1135
1136
0
      o_timing = oidc_json_deep_copy(j_timing);
1137
0
      oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_CLASS_NAME,
1138
0
             oidc_json_string(_oidc_metrics_timings_info[type].class_name));
1139
0
      oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_METRIC_NAME,
1140
0
             oidc_json_string(_oidc_metrics_timings_info[type].metric_name));
1141
0
      oidc_json_object_set_new(o_timing, OIDC_METRICS_JSON_DESC,
1142
0
             oidc_json_string(_oidc_metrics_timings_info[type].desc));
1143
1144
0
      oidc_json_object_set_new(o_timings, _oidc_metrics_timing_type2s(r->pool, type), o_timing);
1145
1146
0
      i2 = oidc_json_object_iter_next(j_timings, i2);
1147
0
    }
1148
0
    i1 = oidc_json_object_iter_next(json, i1);
1149
0
  }
1150
1151
0
  s_json = oidc_json_encode(r->pool, o_json, OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER);
1152
1153
0
  oidc_json_decref(o_json);
1154
0
  oidc_json_decref(json);
1155
1156
0
end:
1157
1158
  /* return the data to the caller */
1159
0
  return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK);
1160
0
}
1161
1162
/*
1163
 * dump the internal shared memory segment
1164
 */
1165
0
static int oidc_metrics_handle_internal(request_rec *r, const char *s_json) {
1166
0
  if (s_json == NULL)
1167
0
    return HTTP_NOT_FOUND;
1168
0
  return oidc_util_http_send(r, s_json, _oidc_strlen(s_json), OIDC_HTTP_CONTENT_TYPE_JSON, OK);
1169
0
}
1170
1171
0
#define OIDC_METRICS_SERVER_PARAM "server_name"
1172
0
#define OIDC_METRICS_COUNTER_PARAM "counter"
1173
0
#define OIDC_METRICS_NAME_PARAM "name"
1174
0
#define OIDC_METRICS_VALUE_PARAM "value"
1175
1176
/*
1177
 * find the counter entry in j_counters whose type matches "class.metric" (s_metric_param)
1178
 */
1179
static oidc_json_t *oidc_metrics_status_find_counter(request_rec *r, oidc_json_t *j_counters,
1180
0
                 const char *s_metric_param) {
1181
0
  void *iter = oidc_json_object_iter(j_counters);
1182
0
  while (iter) {
1183
0
    unsigned int type = _oidc_metrics_key2type(oidc_json_object_iter_key(iter));
1184
0
    if (_oidc_strcmp(_oidc_metrics_counter_type2s(r->pool, type), s_metric_param) == 0)
1185
0
      return oidc_json_object_iter_value(iter);
1186
0
    iter = oidc_json_object_iter_next(j_counters, iter);
1187
0
  }
1188
0
  return NULL;
1189
0
}
1190
1191
/*
1192
 * extract the requested integer value out of a counter entry:
1193
 *   - integer entry: the counter itself
1194
 *   - object entry with value-only selector: j_counter[value]
1195
 *   - object entry with name+value selectors: j_counter[name][value]
1196
 */
1197
static oidc_json_t *oidc_metrics_status_select_value(oidc_json_t *j_counter, const char *s_name_param,
1198
0
                 const char *s_value_param) {
1199
0
  const oidc_json_t *j_values = NULL;
1200
0
  if (oidc_json_is_integer(j_counter))
1201
0
    return j_counter;
1202
0
  if (s_value_param == NULL)
1203
0
    return NULL;
1204
0
  if (s_name_param == NULL)
1205
0
    return oidc_json_object_get(j_counter, s_value_param);
1206
0
  j_values = oidc_json_object_get(j_counter, s_name_param);
1207
0
  if (j_values == NULL)
1208
0
    return NULL;
1209
0
  return oidc_json_object_get(j_values, s_value_param);
1210
0
}
1211
1212
/*
1213
 * return status updates
1214
 */
1215
0
static int oidc_metrics_handle_status(request_rec *r, const char *s_json) {
1216
0
  const char *msg = "OK\n";
1217
0
  char *s_metric_param = NULL;
1218
0
  char *s_server_param = NULL;
1219
0
  char *s_name_param = NULL;
1220
0
  char *s_value_param = NULL;
1221
0
  oidc_json_t *json = NULL;
1222
0
  const oidc_json_t *j_server = NULL;
1223
0
  oidc_json_t *j_counters = NULL;
1224
0
  oidc_json_t *j_counter = NULL;
1225
0
  const oidc_json_t *j_value = NULL;
1226
1227
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_SERVER_PARAM, &s_server_param);
1228
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_COUNTER_PARAM, &s_metric_param);
1229
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_NAME_PARAM, &s_name_param);
1230
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_VALUE_PARAM, &s_value_param);
1231
1232
0
  if (s_server_param == NULL)
1233
0
    s_server_param = "localhost";
1234
1235
0
  if (s_metric_param == NULL)
1236
0
    goto end;
1237
1238
0
  json = oidc_metrics_json_parse_r(r, s_json);
1239
0
  if (json == NULL)
1240
0
    goto end;
1241
1242
0
  j_server = oidc_json_object_get(json, s_server_param);
1243
0
  if (j_server == NULL)
1244
0
    goto end;
1245
1246
0
  j_counters = oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS);
1247
0
  if (j_counters == NULL)
1248
0
    goto end;
1249
1250
0
  j_counter = oidc_metrics_status_find_counter(r, j_counters, s_metric_param);
1251
0
  if (j_counter == NULL)
1252
0
    goto end;
1253
1254
0
  j_value = oidc_metrics_status_select_value(j_counter, s_name_param, s_value_param);
1255
0
  if (j_value)
1256
0
    msg = apr_psprintf(r->pool, "OK: %s\n", _json_int2str(r->pool, oidc_json_integer_value(j_value)));
1257
1258
0
end:
1259
1260
0
  if (json)
1261
0
    oidc_json_decref(json);
1262
1263
0
  return oidc_util_http_send(r, msg, _oidc_strlen(msg), "text/plain", OK);
1264
0
}
1265
1266
/*
1267
 * return the Prometheus label name for a bucket
1268
 */
1269
0
static const char *oidc_metrics_prometheus_bucket_label(const char *json_name) {
1270
0
  const char *name = NULL;
1271
0
  for (int i = 0; i < OIDC_METRICS_BUCKET_NUM; i++) {
1272
0
    if (_oidc_strcmp(_oidc_metric_buckets[i].name, json_name) == 0) {
1273
0
      name = _oidc_metric_buckets[i].label;
1274
0
      break;
1275
0
    }
1276
0
  }
1277
0
  return name;
1278
0
}
1279
1280
0
#define OIDC_METRICS_PROMETHEUS_PREFIX "oidc"
1281
1282
/*
1283
 * normalize a metric name to something that Prometheus accepts
1284
 */
1285
0
static const char *oidc_metric_prometheus_normalize_name(apr_pool_t *pool, const char *name) {
1286
0
  char *label = apr_psprintf(pool, "%s", name);
1287
0
  for (size_t i = 0; i < _oidc_strlen(label); i++)
1288
0
    if (apr_isalnum(label[i]) == 0)
1289
0
      label[i] = '_';
1290
0
  return apr_psprintf(pool, "%s_%s", OIDC_METRICS_PROMETHEUS_PREFIX, label);
1291
0
}
1292
1293
0
#define OIDC_METRICS_PROMETHEUS_CONTENT_TYPE "text/plain; version=0.0.4"
1294
1295
0
#define OIDC_METRICS_PROMETHEUS_SERVER "server_name"
1296
0
#define OIDC_METRICS_PROMETHEUS_BUCKET "bucket"
1297
0
#define OIDC_METRICS_PROMETHEUS_VALUE "value"
1298
0
#define OIDC_METRICS_PROMETHEUS_NAME "name"
1299
1300
// loop context for Prometheus output
1301
typedef struct oidc_metric_prometheus_callback_ctx_t {
1302
  char *s_result;
1303
  apr_pool_t *pool;
1304
} oidc_metric_prometheus_callback_ctx_t;
1305
1306
/*
1307
 * append per-(name, value) label-pair lines for a single key under a server's counter
1308
 */
1309
static char *oidc_metrics_prometheus_counter_named(apr_pool_t *pool, char *s_text, const char *s_start,
1310
0
               const char *s_key, oidc_json_t *j_value) {
1311
0
  void *iter = oidc_json_object_iter(j_value);
1312
0
  while (iter) {
1313
0
    const char *s_value = oidc_json_object_iter_key(iter);
1314
0
    s_text = apr_psprintf(pool, "%s%s,%s=\"%s\",%s=\"%s\"} %s\n", s_text, s_start,
1315
0
              OIDC_METRICS_PROMETHEUS_NAME, s_key, OIDC_METRICS_PROMETHEUS_VALUE, s_value,
1316
0
              _json_int2str(pool, oidc_json_integer_value(oidc_json_object_iter_value(iter))));
1317
0
    iter = oidc_json_object_iter_next(j_value, iter);
1318
0
  }
1319
0
  return s_text;
1320
0
}
1321
1322
/*
1323
 * append per-key lines for one server's counter dict; integer entries are value-labeled, sub-dict
1324
 * entries are delegated to oidc_metrics_prometheus_counter_named for name+value labeling
1325
 */
1326
static char *oidc_metrics_prometheus_counter_keyed(apr_pool_t *pool, char *s_text, const char *s_start,
1327
0
               oidc_json_t *j_counter) {
1328
0
  void *iter = oidc_json_object_iter(j_counter);
1329
0
  while (iter) {
1330
0
    const char *s_key = oidc_json_object_iter_key(iter);
1331
0
    oidc_json_t *j_value = oidc_json_object_iter_value(iter);
1332
0
    if (oidc_json_is_integer(j_value))
1333
0
      s_text =
1334
0
          apr_psprintf(pool, "%s%s,%s=\"%s\"} %s\n", s_text, s_start, OIDC_METRICS_PROMETHEUS_VALUE,
1335
0
           s_key, _json_int2str(pool, oidc_json_integer_value(j_value)));
1336
0
    else
1337
0
      s_text = oidc_metrics_prometheus_counter_named(pool, s_text, s_start, s_key, j_value);
1338
0
    iter = oidc_json_object_iter_next(j_counter, iter);
1339
0
  }
1340
0
  return s_text;
1341
0
}
1342
1343
/*
1344
 * loop function for converting counter metrics to Prometheus output
1345
 */
1346
static int oidc_metrics_prometheus_counters(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key,
1347
0
              oidc_json_t *value) {
1348
0
  oidc_json_t *o_counter = value;
1349
0
  unsigned int type = _oidc_metrics_key2type(key);
1350
0
  const char *s_label =
1351
0
      oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_counter_type2s(ctx->pool, type));
1352
0
  char *s_text =
1353
0
      apr_psprintf(ctx->pool, "# HELP %s The number of %s.\n", s_label, _oidc_metrics_counters_info[type].desc);
1354
0
  s_text = apr_psprintf(ctx->pool, "%s# TYPE %s counter\n", s_text, s_label);
1355
1356
0
  void *iter = oidc_json_object_iter(o_counter);
1357
0
  while (iter) {
1358
0
    const char *s_server = oidc_json_object_iter_key(iter);
1359
0
    oidc_json_t *j_counter = oidc_json_object_iter_value(iter);
1360
0
    const char *s_start =
1361
0
        apr_psprintf(ctx->pool, "%s{%s=\"%s\"", s_label, OIDC_METRICS_PROMETHEUS_SERVER, s_server);
1362
0
    if (oidc_json_is_integer(j_counter))
1363
0
      s_text = apr_psprintf(ctx->pool, "%s%s} %s\n", s_text, s_start,
1364
0
                _json_int2str(ctx->pool, oidc_json_integer_value(j_counter)));
1365
0
    else
1366
0
      s_text = oidc_metrics_prometheus_counter_keyed(ctx->pool, s_text, s_start, j_counter);
1367
0
    iter = oidc_json_object_iter_next(o_counter, iter);
1368
0
  }
1369
0
  ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL);
1370
0
  oidc_json_decref(o_counter);
1371
0
  return 1;
1372
0
}
1373
1374
/*
1375
 * loop function for converting timing metrics to Prometheus output
1376
 */
1377
1378
static int oidc_metrics_prometheus_timings(oidc_metric_prometheus_callback_ctx_t *ctx, const char *key,
1379
0
             oidc_json_t *value) {
1380
0
  const char *s_server = NULL;
1381
0
  const char *s_key = NULL;
1382
0
  const char *s_bucket = NULL;
1383
0
  oidc_json_t *j_timing = NULL;
1384
0
  const oidc_json_t *j_member = NULL;
1385
0
  oidc_json_t *o_timer = value;
1386
0
  unsigned int type = _oidc_metrics_key2type(key);
1387
0
  const char *s_label =
1388
0
      oidc_metric_prometheus_normalize_name(ctx->pool, _oidc_metrics_timing_type2s(ctx->pool, type));
1389
0
  char *s_text =
1390
0
      apr_psprintf(ctx->pool, "# HELP %s A histogram of %s.\n", s_label, _oidc_metrics_timings_info[type].desc);
1391
0
  s_text = apr_psprintf(ctx->pool, "%s# TYPE %s histogram\n", s_text, s_label);
1392
1393
0
  void *iter1 = oidc_json_object_iter(o_timer);
1394
0
  while (iter1) {
1395
0
    s_server = oidc_json_object_iter_key(iter1);
1396
0
    j_timing = oidc_json_object_iter_value(iter1);
1397
0
    void *iter3 = oidc_json_object_iter(j_timing);
1398
0
    while (iter3) {
1399
0
      s_key = oidc_json_object_iter_key(iter3);
1400
0
      j_member = oidc_json_object_iter_value(iter3);
1401
0
      s_bucket = oidc_metrics_prometheus_bucket_label(s_key);
1402
0
      if (s_bucket)
1403
0
        s_text = apr_psprintf(ctx->pool, "%s%s_%s{%s,", s_text, s_label,
1404
0
                  OIDC_METRICS_PROMETHEUS_BUCKET, s_bucket);
1405
0
      else
1406
0
        s_text = apr_psprintf(ctx->pool, "%s%s_%s{", s_text, s_label, s_key);
1407
1408
0
      s_text = apr_psprintf(ctx->pool, "%s%s=\"%s\"} %s\n", s_text, OIDC_METRICS_PROMETHEUS_SERVER,
1409
0
                s_server, _json_int2str(ctx->pool, oidc_json_integer_value(j_member)));
1410
0
      iter3 = oidc_json_object_iter_next(j_timing, iter3);
1411
0
    }
1412
0
    iter1 = oidc_json_object_iter_next(o_timer, iter1);
1413
0
  }
1414
0
  ctx->s_result = apr_pstrcat(ctx->pool, ctx->s_result, s_text, "\n", NULL);
1415
0
  oidc_json_decref(o_timer);
1416
0
  return 1;
1417
0
}
1418
1419
/*
1420
 * take a list of metrics from a server indexed list and add it to a type indexed list
1421
 */
1422
0
static void oidc_metrics_prometheus_convert(apr_hash_t *hash, const char *server, oidc_json_t *list) {
1423
0
  const char *type = NULL;
1424
0
  oidc_json_t *src = NULL;
1425
0
  oidc_json_t *dst = NULL;
1426
0
  void *iter = oidc_json_object_iter(list);
1427
0
  while (iter) {
1428
0
    type = oidc_json_object_iter_key(iter);
1429
0
    src = oidc_json_object_iter_value(iter);
1430
0
    dst = (oidc_json_t *)apr_hash_get(hash, type, APR_HASH_KEY_STRING);
1431
0
    if (dst) {
1432
0
      oidc_json_object_set(dst, server, src);
1433
0
    } else {
1434
0
      dst = oidc_json_object();
1435
0
      oidc_json_object_set(dst, server, src);
1436
0
      apr_hash_set(hash, type, APR_HASH_KEY_STRING, dst);
1437
0
    }
1438
0
    iter = oidc_json_object_iter_next(list, iter);
1439
0
  }
1440
0
}
1441
1442
/*
1443
 * generate output in Prometheus formatting
1444
 */
1445
0
static int oidc_metrics_handle_prometheus(request_rec *r, const char *s_json) {
1446
0
  oidc_json_t *json = NULL;
1447
0
  const oidc_json_t *j_server = NULL;
1448
0
  const char *s_server = NULL;
1449
0
  apr_hash_t *t_counters = apr_hash_make(r->pool);
1450
0
  apr_hash_t *t_timings = apr_hash_make(r->pool);
1451
0
  apr_hash_index_t *hi = NULL;
1452
0
  const char *name = NULL;
1453
0
  void *value = NULL;
1454
1455
0
  oidc_metric_prometheus_callback_ctx_t ctx = {"", r->pool};
1456
0
  void *iter = NULL;
1457
1458
  /* parse the metrics string to JSON */
1459
0
  json = oidc_metrics_json_parse_r(r, s_json);
1460
0
  if (json == NULL)
1461
0
    return OK;
1462
1463
0
  iter = oidc_json_object_iter(json);
1464
0
  while (iter) {
1465
0
    s_server = oidc_json_object_iter_key(iter);
1466
0
    j_server = oidc_json_object_iter_value(iter);
1467
0
    oidc_metrics_prometheus_convert(t_counters, s_server,
1468
0
            oidc_json_object_get(j_server, OIDC_METRICS_COUNTERS));
1469
0
    oidc_metrics_prometheus_convert(t_timings, s_server,
1470
0
            oidc_json_object_get(j_server, OIDC_METRICS_TIMINGS));
1471
0
    iter = oidc_json_object_iter_next(json, iter);
1472
0
  }
1473
1474
0
  for (hi = apr_hash_first(r->pool, t_counters); hi; hi = apr_hash_next(hi)) {
1475
0
    apr_hash_this(hi, (const void **)&name, NULL, &value);
1476
0
    oidc_metrics_prometheus_counters(&ctx, name, value);
1477
0
  }
1478
1479
0
  for (hi = apr_hash_first(r->pool, t_timings); hi; hi = apr_hash_next(hi)) {
1480
0
    apr_hash_this(hi, (const void **)&name, NULL, &value);
1481
0
    oidc_metrics_prometheus_timings(&ctx, name, value);
1482
0
  }
1483
1484
0
  oidc_json_decref(json);
1485
1486
0
  return oidc_util_http_send(r, ctx.s_result, _oidc_strlen(ctx.s_result), OIDC_METRICS_PROMETHEUS_CONTENT_TYPE,
1487
0
           OK);
1488
0
}
1489
1490
/*
1491
 * definitions for handler callbacks
1492
 */
1493
1494
typedef int (*oidc_metrics_handler_function_t)(request_rec *, const char *);
1495
1496
// holder for output function callback context
1497
typedef struct oidc_metrics_handler_t {
1498
  const char *format;
1499
  oidc_metrics_handler_function_t callback;
1500
  int reset;
1501
} oidc_metrics_content_handler_t;
1502
1503
// output handlers
1504
const oidc_metrics_content_handler_t _oidc_metrics_handlers[] = {
1505
    // first is default
1506
    {"prometheus", oidc_metrics_handle_prometheus, 0},
1507
    {"json", oidc_metrics_handle_json, 1},
1508
    {"internal", oidc_metrics_handle_internal, 0},
1509
    {"status", oidc_metrics_handle_status, 0},
1510
};
1511
1512
0
#define OIDC_CONTENT_HANDLER_MAX ((int)(sizeof(_oidc_metrics_handlers) / sizeof(oidc_metrics_content_handler_t)))
1513
1514
0
#define OIDC_METRICS_RESET_PARAM "reset"
1515
1516
/*
1517
 * see if we are going to reset the cache after this
1518
 */
1519
0
static int oidc_metric_reset(request_rec *r, int dvalue) {
1520
0
  char *s_reset = NULL;
1521
0
  int value = 0;
1522
1523
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_RESET_PARAM, &s_reset);
1524
1525
0
  if (s_reset == NULL)
1526
0
    return dvalue;
1527
1528
0
  if (_oidc_strnatcasecmp(s_reset, "true") == 0)
1529
0
    value = 1;
1530
0
  else if (_oidc_strnatcasecmp(s_reset, "false") == 0)
1531
0
    value = 0;
1532
1533
0
  return value;
1534
0
}
1535
1536
0
#define OIDC_METRICS_FORMAT_PARAM "format"
1537
1538
/*
1539
 * find the format handler
1540
 */
1541
0
const oidc_metrics_content_handler_t *oidc_metrics_find_handler(request_rec *r) {
1542
0
  const oidc_metrics_content_handler_t *handler = NULL;
1543
0
  char *s_format = NULL;
1544
1545
  /* get the specified format */
1546
0
  oidc_util_url_parameter_get(r, OIDC_METRICS_FORMAT_PARAM, &s_format);
1547
1548
0
  if (s_format == NULL)
1549
0
    return &_oidc_metrics_handlers[0];
1550
1551
0
  for (int i = 0; i < OIDC_CONTENT_HANDLER_MAX; i++) {
1552
0
    if (_oidc_strcmp(s_format, _oidc_metrics_handlers[i].format) == 0) {
1553
0
      handler = &_oidc_metrics_handlers[i];
1554
0
      break;
1555
0
    }
1556
0
  }
1557
1558
0
  if (handler == NULL)
1559
0
    oidc_warn(r, "could not find a metrics handler for format: %s", s_format);
1560
1561
0
  return handler;
1562
0
}
1563
1564
/*
1565
 * return the metrics to the caller and flush the storage
1566
 */
1567
0
int oidc_metrics_handle_request(request_rec *r) {
1568
0
  const char *s_json = NULL;
1569
0
  const oidc_metrics_content_handler_t *handler = NULL;
1570
1571
  /* get the content handler for the format */
1572
0
  handler = oidc_metrics_find_handler(r);
1573
0
  if (handler == NULL)
1574
0
    return HTTP_NOT_FOUND;
1575
1576
  /* lock the global shared memory */
1577
0
  oidc_cache_mutex_lock(r->pool, r->server, _oidc_metrics_global_mutex);
1578
1579
  /* retrieve the JSON formatted metrics as a string; NB: on the request pool, since
1580
   * it is handed to the content handler below, after the mutex has been released */
1581
0
  s_json = _oidc_metrics_storage_get(r->server, r->pool);
1582
1583
  /* now that the metrics have been consumed, clear the shared memory segment */
1584
0
  if (oidc_metric_reset(r, handler->reset))
1585
0
    oidc_metrics_storage_reset(r->server, r->pool);
1586
1587
  /* unlock the global shared memory */
1588
0
  oidc_cache_mutex_unlock(r->pool, r->server, _oidc_metrics_global_mutex);
1589
1590
  /* handle the specified format */
1591
0
  return handler->callback(r, s_json);
1592
0
}