330 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			330 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  |  /*
 | ||
|  |  * 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
 | ||
|  | 
 | ||
|  | 
 |