From ae6f125c7b33454770aaa363101384e8daafc2a2 Mon Sep 17 00:00:00 2001 From: Damien Claisse Date: Wed, 30 Oct 2019 15:57:28 +0000 Subject: [PATCH] MINOR: sample: add us/ms support to date/http_date It can be sometimes interesting to have a timestamp with a resolution of less than a second. It is currently painful to obtain this, because concatenation of date and date_us lead to a shorter timestamp during first 100ms of a second, which is not parseable and needs ugly ACLs in configuration to prepend 0s when needed. To improve this, add an optional parameter to date sample to report an integer with desired unit. Also support this unit in http_date converter to report a date string with sub-second precision. --- doc/configuration.txt | 33 +++++++++++++++++++-------- include/proto/sample.h | 1 + src/http_conv.c | 49 +++++++++++++++++++++++++++++++-------- src/sample.c | 52 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 113 insertions(+), 22 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index e1257accf5..029b37da83 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -13245,13 +13245,17 @@ hex2i Converts a hex string containing two hex digits per input byte to an integer. If the input value cannot be converted, then zero is returned. -http_date([]) +http_date([]) Converts an integer supposed to contain a date since epoch to a string representing this date in a format suitable for use in HTTP header fields. If - an offset value is specified, then it is a number of seconds that is added to - the date before the conversion is operated. This is particularly useful to - emit Date header fields, Expires values in responses when combined with a - positive offset, or Last-Modified values when the offset is negative. + an offset value is specified, then it is added to the date before the + conversion is operated. This is particularly useful to emit Date header fields, + Expires values in responses when combined with a positive offset, or + Last-Modified values when the offset is negative. + If a unit value is specified, then consider the timestamp as either + "s" for seconds (default behavior), "ms" for milliseconds, or "us" for + microseconds since epoch. Offset is assumed to have the same unit as + input timestamp. in_table() Uses the string representation of the input sample to perform a look up in @@ -14062,18 +14066,29 @@ cpu_ns_tot : integer high cpu_calls count, for example when processing many HTTP chunks, and for this reason it is often preferred to log cpu_ns_avg instead. -date([]) : integer +date([, ]) : integer Returns the current date as the epoch (number of seconds since 01/01/1970). - If an offset value is specified, then it is a number of seconds that is added - to the current date before returning the value. This is particularly useful - to compute relative dates, as both positive and negative offsets are allowed. + + If an offset value is specified, then it is added to the current date before + returning the value. This is particularly useful to compute relative dates, + as both positive and negative offsets are allowed. It is useful combined with the http_date converter. + is facultative, and can be set to "s" for seconds (default behavior), + "ms" for milliseconds or "us" for microseconds. + If unit is set, return value is an integer reflecting either seconds, + milliseconds or microseconds since epoch, plus offset. + It is useful when a time resolution of less than a second is needed. + Example : # set an expires header to now+1 hour in every response http-response set-header Expires %[date(3600),http_date] + # set an expires header to now+1 hour in every response, with + # millisecond granularity + http-response set-header Expires %[date(3600000,ms),http_date(0,ms)] + date_us : integer Return the microseconds part of the date (the "second" part is returned by date sample). This sample is coherent with the date sample as it is comes diff --git a/include/proto/sample.h b/include/proto/sample.h index 606dcb62a1..f0be3fd208 100644 --- a/include/proto/sample.h +++ b/include/proto/sample.h @@ -45,6 +45,7 @@ struct sample_fetch *find_sample_fetch(const char *kw, int len); struct sample_fetch *sample_fetch_getnext(struct sample_fetch *current, int *idx); struct sample_conv *sample_conv_getnext(struct sample_conv *current, int *idx); int smp_resolve_args(struct proxy *p); +int smp_check_date_unit(struct arg *args, char **err); int smp_expr_output_type(struct sample_expr *expr); int c_none(struct sample *smp); int smp_dup(struct sample *smp); diff --git a/src/http_conv.c b/src/http_conv.c index 93b748c2f0..cd93aa9667 100644 --- a/src/http_conv.c +++ b/src/http_conv.c @@ -33,10 +33,17 @@ #include #include +static int smp_check_http_date_unit(struct arg *args, struct sample_conv *conv, + const char *file, int line, char **err) +{ + return smp_check_date_unit(args, err); +} /* takes an UINT value on input supposed to represent the time since EPOCH, * adds an optional offset found in args[0] and emits a string representing - * the date in RFC-1123/5322 format. + * the date in RFC-1123/5322 format. If optional unit param in args[1] is + * provided, decode timestamp in milliseconds ("ms") or microseconds("us"), + * and use relevant output date format. */ static int sample_conv_http_date(const struct arg *args, struct sample *smp, void *private) { @@ -44,23 +51,45 @@ static int sample_conv_http_date(const struct arg *args, struct sample *smp, voi const char mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; struct buffer *temp; struct tm *tm; - /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ - time_t curr_date = smp->data.u.sint & 0x007fffffffffffffLL; + int sec_frac = 0; + time_t curr_date; /* add offset */ if (args && (args[0].type == ARGT_SINT)) - curr_date += args[0].data.sint; + smp->data.u.sint += args[0].data.sint; + + /* report in milliseconds */ + if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_MS) { + sec_frac = smp->data.u.sint % 1000; + smp->data.u.sint /= 1000; + } + /* report in microseconds */ + else if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_US) { + sec_frac = smp->data.u.sint % 1000000; + smp->data.u.sint /= 1000000; + } + + /* With high numbers, the date returned can be negative, the 55 bits mask prevent this. */ + curr_date = smp->data.u.sint & 0x007fffffffffffffLL; tm = gmtime(&curr_date); if (!tm) return 0; temp = get_trash_chunk(); - temp->data = snprintf(temp->area, temp->size - temp->data, - "%s, %02d %s %04d %02d:%02d:%02d GMT", - day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], - 1900+tm->tm_year, - tm->tm_hour, tm->tm_min, tm->tm_sec); + if (args && args[1].type == ARGT_SINT && args[1].data.sint != TIME_UNIT_S) { + temp->data = snprintf(temp->area, temp->size - temp->data, + "%s, %02d %s %04d %02d:%02d:%02d.%d GMT", + day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], + 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, sec_frac); + } else { + temp->data = snprintf(temp->area, temp->size - temp->data, + "%s, %02d %s %04d %02d:%02d:%02d GMT", + day[tm->tm_wday], tm->tm_mday, mon[tm->tm_mon], + 1900+tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + } smp->data.u.str = *temp; smp->data.type = SMP_T_STR; @@ -328,7 +357,7 @@ static int smp_conv_res_capture(const struct arg *args, struct sample *smp, void /* Note: must not be declared as its list will be overwritten */ static struct sample_conv_kw_list sample_conv_kws = {ILH, { - { "http_date", sample_conv_http_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_T_STR}, + { "http_date", sample_conv_http_date, ARG2(0,SINT,STR), smp_check_http_date_unit, SMP_T_SINT, SMP_T_STR}, { "language", sample_conv_q_preferred, ARG2(1,STR,STR), NULL, SMP_T_STR, SMP_T_STR}, { "capture-req", smp_conv_req_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, { "capture-res", smp_conv_res_capture, ARG1(1,SINT), NULL, SMP_T_STR, SMP_T_STR}, diff --git a/src/sample.c b/src/sample.c index 98b5d573fc..1e4039d27b 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2940,14 +2940,60 @@ smp_fetch_env(const struct arg *args, struct sample *smp, const char *kw, void * return 1; } -/* retrieve the current local date in epoch time, and applies an optional offset - * of args[0] seconds. +/* Validates the data unit argument passed to "date" fetch. Argument 1 support an + * optional string representing the unit of the result: "s" for seconds, "ms" for + * milliseconds and "us" for microseconds. + * Returns 0 on error and non-zero if OK. + */ +int smp_check_date_unit(struct arg *args, char **err) +{ + if (args[1].type == ARGT_STR) { + if (strcmp(args[1].data.str.area, "s") == 0) { + args[1].data.sint = TIME_UNIT_S; + } + else if (strcmp(args[1].data.str.area, "ms") == 0) { + args[1].data.sint = TIME_UNIT_MS; + } + else if (strcmp(args[1].data.str.area, "us") == 0) { + args[1].data.sint = TIME_UNIT_US; + } + else { + memprintf(err, "expects 's', 'ms' or 'us', got '%s'", + args[1].data.str.area); + return 0; + } + free(args[1].data.str.area); + args[1].data.str.area = NULL; + args[1].type = ARGT_SINT; + } + else if (args[1].type != ARGT_STOP) { + memprintf(err, "Unexpected arg type"); + return 0; + } + + return 1; +} + +/* retrieve the current local date in epoch time, converts it to milliseconds + * or microseconds if asked to in optional args[1] unit param, and applies an + * optional args[0] offset. */ static int smp_fetch_date(const struct arg *args, struct sample *smp, const char *kw, void *private) { smp->data.u.sint = date.tv_sec; + /* report in milliseconds */ + if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_MS) { + smp->data.u.sint *= 1000; + smp->data.u.sint += date.tv_usec / 1000; + } + /* report in microseconds */ + else if (args && args[1].type == ARGT_SINT && args[1].data.sint == TIME_UNIT_US) { + smp->data.u.sint *= 1000000; + smp->data.u.sint += date.tv_usec; + } + /* add offset */ if (args && args[0].type == ARGT_SINT) smp->data.u.sint += args[0].data.sint; @@ -3259,7 +3305,7 @@ static struct sample_fetch_kw_list smp_kws = {ILH, { { "always_false", smp_fetch_false, 0, NULL, SMP_T_BOOL, SMP_USE_INTRN }, { "always_true", smp_fetch_true, 0, NULL, SMP_T_BOOL, SMP_USE_INTRN }, { "env", smp_fetch_env, ARG1(1,STR), NULL, SMP_T_STR, SMP_USE_INTRN }, - { "date", smp_fetch_date, ARG1(0,SINT), NULL, SMP_T_SINT, SMP_USE_INTRN }, + { "date", smp_fetch_date, ARG2(0,SINT,STR), smp_check_date_unit, SMP_T_SINT, SMP_USE_INTRN }, { "date_us", smp_fetch_date_us, 0, NULL, SMP_T_SINT, SMP_USE_INTRN }, { "hostname", smp_fetch_hostname, 0, NULL, SMP_T_STR, SMP_USE_INTRN }, { "nbproc", smp_fetch_nbproc,0, NULL, SMP_T_SINT, SMP_USE_INTRN }, -- 2.39.5