Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluate numbers and bools in tags and metrics for sampling rules #2521

Merged
merged 1 commit into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions ext/ddshared.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,62 @@ void ddshared_minit(void) {
ddtrace_php_version = Z_STR_P(zend_get_constant_str(ZEND_STRL("PHP_VERSION")));
}

bool dd_rule_matches(zval *pattern, zval *prop, int rulesFormat) {
bool dd_glob_rule_is_wildcards_only(zval *pattern) {
if (Z_TYPE_P(pattern) != IS_STRING || Z_STRLEN_P(pattern) == 0) {
return false;
}
char *p = Z_STRVAL_P(pattern);
while (*p == '*') {
++p;
}
return *p == 0;
}

bool dd_rule_matches(zval *pattern, zval *prop, int rulesFormat) {
if (Z_TYPE_P(pattern) != IS_STRING) {
return false;
}
if (Z_TYPE_P(prop) != IS_STRING) {
return true; // default case unset or null must be true, everything else is too then...
zend_string *str;
if (Z_TYPE_P(prop) == IS_STRING) {
str = zend_string_copy(Z_STR_P(prop));
} else {
if (Z_TYPE_P(prop) == IS_TRUE) {
#if PHP_VERSION_ID < 80200
str = zend_string_init("true", 4, 0);
#else
str = ZSTR_KNOWN(ZEND_STR_TRUE);
#endif
} else if (Z_TYPE_P(prop) == IS_FALSE) {
#if PHP_VERSION_ID < 80200
str = zend_string_init("false", 5, 0);
#else
str = ZSTR_KNOWN(ZEND_STR_FALSE);
#endif
} else if (Z_TYPE_P(prop) == IS_LONG) {
str = zend_long_to_str(Z_LVAL_P(prop));
} else if (Z_TYPE_P(prop) == IS_DOUBLE) {
zend_long to_long = zend_dval_to_lval(Z_DVAL_P(prop));
if (Z_DVAL_P(prop) == (double)to_long) {
str = zend_long_to_str(to_long);
} else {
return dd_glob_rule_is_wildcards_only(pattern);
}
} else {
return Z_STRLEN_P(pattern) == 0 || dd_glob_rule_is_wildcards_only(pattern);
}
}

bool result;
if (rulesFormat == DD_TRACE_SAMPLING_RULES_FORMAT_GLOB) {
return dd_glob_rule_matches(pattern, Z_STR_P(prop));
}
else {
return zai_match_regex(Z_STR_P(pattern), Z_STR_P(prop));
result = dd_glob_rule_matches(pattern, str);
} else {
result = zai_match_regex(Z_STR_P(pattern), str);
}
zend_string_release(str);
return result;
}

bool dd_glob_rule_matches(zval *pattern, zend_string* value) {
bool dd_glob_rule_matches(zval *pattern, zend_string *value) {
if (Z_TYPE_P(pattern) != IS_STRING) {
return false;
}
Expand All @@ -42,18 +81,15 @@ bool dd_glob_rule_matches(zval *pattern, zend_string* value) {
char *s = ZSTR_VAL(value);

int wildcards = 0;
int patternLength = 0;
int stringLength = ZSTR_LEN(value);
while (*p) {
if (*(p++) == '*') {
++wildcards;
}
patternLength++;
}

// If there are no wildcards, no need to go through the whole string if pattern is shorter than the input string
// Indeed wildcards (ie '*') can replace multiple characters while '?' canonly replace one
if (wildcards == 0 && patternLength < stringLength) {
// Indeed wildcards (i.e. '*') can replace multiple characters while '?' can only replace one
if (wildcards == 0 && Z_STRLEN_P(pattern) < ZSTR_LEN(value)) {
return false;
}

Expand All @@ -71,10 +107,14 @@ bool dd_glob_rule_matches(zval *pattern, zend_string* value) {
free_alloca(backtrack_points, use_heap);
return !*p;
}
if (*s == *p || *p == '?') {
// equal or case-insensitive match
if (*s == *p || *p == '?' || ((*s | ' ') == (*p | ' ') && (*p | ' ') >= 'a' && (*p | ' ') <= 'z')) {
++s, ++p;
} else if (*p == '*') {
backtrack_points[backtrack_idx++] = ++p;
do {
++p;
} while (*p == '*');
backtrack_points[backtrack_idx++] = p;
backtrack_points[backtrack_idx++] = s;
} else {
do {
Expand Down
3 changes: 2 additions & 1 deletion ext/priority_sampling/priority_sampling.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,12 @@ static bool dd_check_sampling_rule(zend_array *rule, ddtrace_span_data *span) {
if ((rule_pattern = zend_hash_str_find(rule, ZEND_STRL("tags"))) && Z_TYPE_P(rule_pattern) == IS_ARRAY) {
zend_array *tag_rules = Z_ARR_P(rule_pattern);
zend_array *meta = ddtrace_property_array(&span->property_meta);
zend_array *metrics = ddtrace_property_array(&span->property_metrics);
zend_string *tag_name;
ZEND_HASH_FOREACH_STR_KEY_VAL(tag_rules, tag_name, rule_pattern) {
if (tag_name) {
zval *value;
if (!(value = zend_hash_find(meta, tag_name))) {
if (!(value = zend_hash_find(meta, tag_name)) && !(value = zend_hash_find(metrics, tag_name))) {
return false;
}
if (!dd_rule_matches(rule_pattern, value, get_DD_TRACE_SAMPLING_RULES_FORMAT())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,90 @@ $tests = [
["f*o*e", "fooname", true],
["f*o*m?", "fooname", true],
["f*x*m?", "fooname", false],

["fooname", "FOONAME", true],
["fooNAME", "FOOname", true],
["fooNAME", "FOOname", true],
["[ooNAM]", "{OOnam}", false],
["{ooNAM}", "[OOnam]", false],
["*", 1.2, true],
["****", 1.2, true],
["", 1.2, false],
["1.2", 1.2, false],
["", 1, false],
["1", 1, true],
["1", 1.0, true],
["1*", 1.0, true],
["1*", 1, true],
["1*", 10, true],
["1*", 0, false],
["true", true, true],
["truee", true, false],
["*", true, true],
["FALSE", false, true],
["FALS", false, false],
["", null, true],
["*", null, true],
["?", null, false],
];

foreach ($tests as list($pattern, $name, $matches)) {
ini_set("datadog.trace.sampling_rules", '[{"name":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3}]');
if (\is_string($name)) {
ini_set("datadog.trace.sampling_rules", '[{"name":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3}]');

$root = DDTrace\root_span();
$root->name = $name;
$root = DDTrace\root_span();
$root->name = $name;

DDTrace\get_priority_sampling();
DDTrace\get_priority_sampling();

if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (name)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (service). Metrics found were: \n";
var_dump($root->metrics);
}
if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (name)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (service). Metrics found were: \n";
var_dump($root->metrics);
}

ini_set("datadog.trace_sampling_rules", '[{"service":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3]');
ini_set("datadog.trace.sampling_rules", '[{"service":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3}]');

$root = \DDTrace\root_span();
$root->service = $name;
$root = \DDTrace\root_span();
$root->service = $name;

\DDTrace\get_priority_sampling();
\DDTrace\get_priority_sampling();

if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (service)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (service). Metrics found were: \n";
var_dump($root->metrics);
}
if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (service)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (service). Metrics found were: \n";
var_dump($root->metrics);
}

ini_set("datadog.trace_sampling_rules", '[{"resource":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3]');
ini_set("datadog.trace.sampling_rules", '[{"resource":"' . $pattern . '","sample_rate":0.7},{"sample_rate": 0.3}]');

$root = \DDTrace\root_span();
$root->resource = $name;
$root = \DDTrace\root_span();
$root->resource = $name;

\DDTrace\get_priority_sampling();
\DDTrace\get_priority_sampling();

if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (resource)\n";
if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (resource)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (resource). Metrics found were: \n";
var_dump($root->metrics);
}
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (resource). Metrics found were: \n";
var_dump($root->metrics);
ini_set("datadog.trace.sampling_rules", '[{"tags":{"foo":"' . $pattern . '"},"sample_rate":0.7},{"sample_rate": 0.3}]');

$root = \DDTrace\root_span();
$root->meta["foo"] = $name;

\DDTrace\get_priority_sampling();

$name = var_export($name, true);
if ($root->metrics["_dd.rule_psr"] == ($matches ? 0.7 : 0.3)) {
echo "As expected, $pattern " . ($matches ? "matches" : "doesn't match") . " $name (tag)\n";
} else {
echo "$pattern " . ($matches ? "should have matched" : "shouldn't have matched") . " $name (tag). Metrics found were: \n";
var_dump($root->metrics);
}
}
}
?>
Expand Down Expand Up @@ -104,3 +144,37 @@ As expected, f*o*m? matches fooname (resource)
As expected, f*x*m? doesn't match fooname (name)
As expected, f*x*m? doesn't match fooname (service)
As expected, f*x*m? doesn't match fooname (resource)
As expected, fooname matches FOONAME (name)
As expected, fooname matches FOONAME (service)
As expected, fooname matches FOONAME (resource)
As expected, fooNAME matches FOOname (name)
As expected, fooNAME matches FOOname (service)
As expected, fooNAME matches FOOname (resource)
As expected, fooNAME matches FOOname (name)
As expected, fooNAME matches FOOname (service)
As expected, fooNAME matches FOOname (resource)
As expected, [ooNAM] doesn't match {OOnam} (name)
As expected, [ooNAM] doesn't match {OOnam} (service)
As expected, [ooNAM] doesn't match {OOnam} (resource)
As expected, {ooNAM} doesn't match [OOnam] (name)
As expected, {ooNAM} doesn't match [OOnam] (service)
As expected, {ooNAM} doesn't match [OOnam] (resource)
As expected, * matches 1.2 (tag)
As expected, **** matches 1.2 (tag)
As expected, doesn't match 1.2 (tag)
As expected, 1.2 doesn't match 1.2 (tag)
As expected, doesn't match 1 (tag)
As expected, 1 matches 1 (tag)
As expected, 1 matches 1.0 (tag)
As expected, 1* matches 1.0 (tag)
As expected, 1* matches 1 (tag)
As expected, 1* matches 10 (tag)
As expected, 1* doesn't match 0 (tag)
As expected, true matches true (tag)
As expected, truee doesn't match true (tag)
As expected, * matches true (tag)
As expected, FALSE matches false (tag)
As expected, FALS doesn't match false (tag)
As expected, matches NULL (tag)
As expected, * matches NULL (tag)
As expected, ? doesn't match NULL (tag)
Loading