330 lines
11 KiB
C
Raw Normal View History

2025-01-22 17:22:38 +01:00
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Public apis related to metric collection and reporting
*/
/* lws_metrics public part */
typedef uint64_t u_mt_t;
enum {
LWSMTFL_REPORT_OUTLIERS = (1 << 0),
/**< track outliers and report them internally */
LWSMTFL_REPORT_OOB = (1 << 1),
/**< report events as they happen */
LWSMTFL_REPORT_INACTIVITY_AT_PERIODIC = (1 << 2),
/**< explicitly externally report no activity at periodic cb, by
* default no events in the period is just not reported */
LWSMTFL_REPORT_MEAN = (1 << 3),
/**< average/min/max is meaningful, else only sum is meaningful */
LWSMTFL_REPORT_ONLY_GO = (1 << 4),
/**< no-go pieces invalid */
LWSMTFL_REPORT_DUTY_WALLCLOCK_US = (1 << 5),
/**< aggregate compares to wallclock us for duty cycle */
LWSMTFL_REPORT_HIST = (1 << 6),
/**< our type is histogram (otherwise, sum / mean aggregation) */
};
/*
* lws_metrics_tag allows your object to accumulate OpenMetrics-style
* descriptive tags before accounting for it with a metrics object at the end.
*
* Tags should represent low entropy information that is likely to repeat
* identically, so, eg, http method name, not eg, latency in us which is
* unlikely to be seen the same twice.
*
* Tags are just a list of name=value pairs, used for qualifying the final
* metrics entry with decorations in additional dimensions. For example,
* rather than keep individual metrics on methods, scheme, mountpoint, result
* code, you can keep metrics on http transactions only, and qualify the
* transaction metrics entries with tags that can be queried on the metrics
* backend to get the finer-grained information.
*
* http_srv{code="404",mount="/",method="GET",scheme="http"} 3
*
* For OpenMetrics the tags are converted to a { list } and appended to the base
* metrics name before using with actual metrics objects, the same set of tags
* on different transactions resolve to the same qualification string.
*/
typedef struct lws_metrics_tag {
lws_dll2_t list;
const char *name; /* tag, intended to be in .rodata, not copied */
/* overallocated value */
} lws_metrics_tag_t;
LWS_EXTERN LWS_VISIBLE int
lws_metrics_tag_add(lws_dll2_owner_t *owner, const char *name, const char *val);
#if defined(LWS_WITH_SYS_METRICS)
/*
* wsi-specific version that also appends the tag value to the lifecycle tag
* used for logging the wsi identity
*/
LWS_EXTERN LWS_VISIBLE int
lws_metrics_tag_wsi_add(struct lws *wsi, const char *name, const char *val);
#else
#define lws_metrics_tag_wsi_add(_a, _b, _c)
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
/*
* ss-specific version that also appends the tag value to the lifecycle tag
* used for logging the ss identity
*/
#if defined(LWS_WITH_SYS_METRICS)
LWS_EXTERN LWS_VISIBLE int
lws_metrics_tag_ss_add(struct lws_ss_handle *ss, const char *name, const char *val);
#else
#define lws_metrics_tag_ss_add(_a, _b, _c)
#endif
#endif
LWS_EXTERN LWS_VISIBLE void
lws_metrics_tags_destroy(lws_dll2_owner_t *owner);
LWS_EXTERN LWS_VISIBLE size_t
lws_metrics_tags_serialize(lws_dll2_owner_t *owner, char *buf, size_t len);
LWS_EXTERN LWS_VISIBLE const char *
lws_metrics_tag_get(lws_dll2_owner_t *owner, const char *name);
/* histogram bucket */
typedef struct lws_metric_bucket {
struct lws_metric_bucket *next;
uint64_t count;
/* name + NUL is overallocated */
} lws_metric_bucket_t;
/* get overallocated name of bucket from bucket pointer */
#define lws_metric_bucket_name_len(_b) (*((uint8_t *)&(_b)[1]))
#define lws_metric_bucket_name(_b) (((const char *)&(_b)[1]) + 1)
/*
* These represent persistent local event measurements. They may aggregate
* a large number of events inbetween external dumping of summaries of the
* period covered, in two different ways
*
* 1) aggregation by sum or mean, to absorb multiple scalar readings
*
* - go / no-go ratio counting
* - mean averaging for, eg, latencies
* - min / max for averaged values
* - period the stats covers
*
* 2) aggregation by histogram, to absorb a range of outcomes that may occur
* multiple times
*
* - add named buckets to histogram
* - bucket has a 64-bit count
* - bumping a bucket just increments the count if already exists, else adds
* a new one with count set to 1
*
* The same type with a union covers both cases.
*
* The lws_system ops api that hooks lws_metrics up to a metrics backend is
* given a pointer to these according to the related policy, eg, hourly, or
* every event passed straight through.
*/
typedef struct lws_metric_pub {
const char *name;
/**< eg, "n.cn.dns", "vh.myendpoint" */
void *backend_opaque;
/**< ignored by lws, backend handler completely owns it */
lws_usec_t us_first;
/**< us time metric started collecting, reset to us_dumped at dump */
lws_usec_t us_last;
/**< 0, or us time last event, reset to 0 at last dump */
lws_usec_t us_dumped;
/**< 0 if never, else us time of last dump to external api */
/* scope of data in .u is "since last dump" --> */
union {
/* aggregation, by sum or mean */
struct {
u_mt_t sum[2];
/**< go, no-go summed for mean or plan sum */
u_mt_t min;
/**< smallest individual measurement */
u_mt_t max;
/**< largest individual measurement */
uint32_t count[2];
/**< go, no-go count of measurements in sum */
} agg;
/* histogram with dynamic named buckets */
struct {
lws_metric_bucket_t *head;
/**< first bucket in our bucket list */
uint64_t total_count;
/**< total count in all of our buckets */
uint32_t list_size;
/**< number of buckets in our bucket list */
} hist;
} u;
uint8_t flags;
} lws_metric_pub_t;
LWS_EXTERN LWS_VISIBLE void
lws_metrics_hist_bump_priv_tagged(lws_metric_pub_t *mt, lws_dll2_owner_t *tow,
lws_dll2_owner_t *tow2);
/*
* Calipers are a helper struct for implementing "hanging latency" detection,
* where setting the start time and finding the end time may happen in more than
* one place.
*
* There are convenience wrappers to eliminate caliper definitions and code
* cleanly if WITH_SYS_METRICS is disabled for the build.
*/
struct lws_metric;
typedef struct lws_metric_caliper {
struct lws_dll2_owner mtags_owner; /**< collect tags here during
* caliper lifetime */
struct lws_metric *mt; /**< NULL == inactive */
lws_usec_t us_start;
} lws_metric_caliper_t;
#if defined(LWS_WITH_SYS_METRICS)
#define lws_metrics_caliper_compose(_name) \
lws_metric_caliper_t _name;
#define lws_metrics_caliper_bind(_name, _mt) \
{ if (_name.mt) { \
lwsl_err("caliper: overwrite %s\n", \
lws_metrics_priv_to_pub(_name.mt)->name); \
assert(0); } \
_name.mt = _mt; _name.us_start = lws_now_usecs(); }
#define lws_metrics_caliper_declare(_name, _mt) \
lws_metric_caliper_t _name = { .mt = _mt, .us_start = lws_now_usecs() }
#define lws_metrics_caliper_report(_name, _go_nogo) \
{ if (_name.us_start) { lws_metric_event(_name.mt, _go_nogo, \
(u_mt_t)(lws_now_usecs() - \
_name.us_start)); \
} lws_metrics_caliper_done(_name); }
#define lws_metrics_caliper_report_hist(_name, pwsi) if (_name.mt) { \
lws_metrics_hist_bump_priv_tagged(lws_metrics_priv_to_pub(_name.mt), \
&_name.mtags_owner, \
pwsi ? &((pwsi)->cal_conn.mtags_owner) : NULL); \
lws_metrics_caliper_done(_name); }
#define lws_metrics_caliper_cancel(_name) { lws_metrics_caliper_done(_name); }
#define lws_metrics_hist_bump(_mt, _name) \
lws_metrics_hist_bump_(_mt, _name)
#define lws_metrics_hist_bump_priv(_mt, _name) \
lws_metrics_hist_bump_(lws_metrics_priv_to_pub(_mt), _name)
#define lws_metrics_caliper_done(_name) { \
_name.us_start = 0; _name.mt = NULL; \
lws_metrics_tags_destroy(&_name.mtags_owner); }
#else
#define lws_metrics_caliper_compose(_name)
#define lws_metrics_caliper_bind(_name, _mt)
#define lws_metrics_caliper_declare(_name, _mp)
#define lws_metrics_caliper_report(_name, _go_nogo)
#define lws_metrics_caliper_report_hist(_name, pwsiconn)
#define lws_metrics_caliper_cancel(_name)
#define lws_metrics_hist_bump(_mt, _name)
#define lws_metrics_hist_bump_priv(_mt, _name)
#define lws_metrics_caliper_done(_name)
#endif
/**
* lws_metrics_format() - helper to format a metrics object for logging
*
* \param pub: public part of metrics object
* \param buf: output buffer to place string in
* \param len: available length of \p buf
*
* Helper for describing the state of a metrics object as a human-readable
* string, accounting for how its flags indicate what it contains. This is not
* how you would report metrics, but during development it can be useful to
* log them inbetween possibily long report intervals.
*
* It uses the metric's flags to adapt the format shown appropriately, eg,
* as a histogram if LWSMTFL_REPORT_HIST etc
*/
LWS_EXTERN LWS_VISIBLE int
lws_metrics_format(lws_metric_pub_t *pub, lws_metric_bucket_t **sub,
char *buf, size_t len);
/**
* lws_metrics_hist_bump() - add or increment histogram bucket
*
* \param pub: public part of metrics object
* \param name: bucket name to increment
*
* Either increment the count of an existing bucket of the right name in the
* metrics object, or add a new bucket of the given name and set its count to 1.
*
* The metrics object must have been created with flag LWSMTFL_REPORT_HIST
*
* Normally, you will actually use the preprocessor wrapper
* lws_metrics_hist_bump() defined above, since this automatically takes care of
* removing itself from the build if WITH_SYS_METRICS is not defined, without
* needing any preprocessor conditionals.
*/
LWS_EXTERN LWS_VISIBLE int
lws_metrics_hist_bump_(lws_metric_pub_t *pub, const char *name);
LWS_VISIBLE LWS_EXTERN int
lws_metrics_foreach(struct lws_context *ctx, void *user,
int (*cb)(lws_metric_pub_t *pub, void *user));
LWS_VISIBLE LWS_EXTERN int
lws_metrics_hist_bump_describe_wsi(struct lws *wsi, lws_metric_pub_t *pub,
const char *name);
enum {
LMT_NORMAL = 0, /* related to successful events */
LMT_OUTLIER, /* related to successful events outside of bounds */
LMT_FAIL, /* related to failed events */
LMT_COUNT,
};
typedef enum lws_metric_rpt {
LMR_PERIODIC = 0, /* we are reporting on a schedule */
LMR_OUTLIER, /* we are reporting the last outlier */
} lws_metric_rpt_kind_t;
#define METRES_GO 0
#define METRES_NOGO 1