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 <unit> 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.
This commit is contained in:
Damien Claisse 2019-10-30 15:57:28 +00:00 committed by Willy Tarreau
parent e1583751b6
commit ae6f125c7b
4 changed files with 113 additions and 22 deletions

View File

@ -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([<offset>])
http_date([<offset, unit>])
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(<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([<offset>]) : integer
date([<offset>, <unit>]) : 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.
<unit> 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

View File

@ -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);

View File

@ -33,10 +33,17 @@
#include <proto/sample.h>
#include <proto/stream.h>
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 <const> 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},

View File

@ -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 },