Skip to content

Commit

Permalink
Merge ea9be47 into 4156080
Browse files Browse the repository at this point in the history
  • Loading branch information
Castaglia committed Mar 26, 2017
2 parents 4156080 + ea9be47 commit 82a5b69
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 3 deletions.
83 changes: 80 additions & 3 deletions mod_statsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ module statsd_module;
#define STATSD_DEFAULT_SAMPLING 1.0F

static int statsd_engine = STATSD_DEFAULT_ENGINE;
static const char *statsd_exclude_filter = NULL;
#ifdef PR_USE_REGEX
static pr_regex_t *statsd_exclude_pre = NULL;
#endif /* PR_USE_REGEX */
static float statsd_sampling = STATSD_DEFAULT_SAMPLING;
static struct statsd *statsd = NULL;

Expand Down Expand Up @@ -76,6 +80,19 @@ static char *get_timeout_metric(pool *p, const char *timeout) {
return metric;
}

static int should_exclude(cmd_rec *cmd) {
int exclude = FALSE;

#ifdef PR_USE_REGEX
if (pr_regexp_exec(statsd_exclude_pre, (char *) cmd->argv[0], 0, NULL, 0, 0,
0) == 0) {
exclude = TRUE;
}
#endif /* PR_USE_REGEX */

return exclude;
}

static int should_sample(float sampling) {
float p;

Expand Down Expand Up @@ -121,6 +138,47 @@ MODRET set_statsdengine(cmd_rec *cmd) {
return PR_HANDLED(cmd);
}

/* usage: StatsdExcludeFilter regex|"none" */
MODRET set_statsdexcludefilter(cmd_rec *cmd) {
#ifdef PR_USE_REGEX
pr_regex_t *pre = NULL;
config_rec *c;
char *pattern;
int res;

CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

if (strcasecmp(cmd->argv[1], "none") == 0) {
(void) add_config_param(cmd->argv[0], 0);
return PR_HANDLED(cmd);
}

pre = pr_regexp_alloc(&statsd_module);

pattern = cmd->argv[1];
res = pr_regexp_compile(pre, pattern, REG_EXTENDED|REG_NOSUB);
if (res != 0) {
char errstr[256] = {'\0'};

pr_regexp_error(res, pre, errstr, sizeof(errstr));
pr_regexp_free(NULL, pre);

CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", pattern,
"' failed regex compilation: ", errstr, NULL));
}

c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pstrdup(c->pool, pattern);
c->argv[1] = (void *) pre;

return PR_HANDLED(cmd);
#else
CONF_ERROR(cmd, "The StatsdExcludeFilter directive cannot be used on this "
"system, as you do not have POSIX compliant regex support");
#endif
}

/* usage: StatsdSampling percentage */
MODRET set_statsdsampling(cmd_rec *cmd) {
config_rec *c;
Expand Down Expand Up @@ -251,6 +309,13 @@ MODRET statsd_log_any(cmd_rec *cmd) {
return PR_DECLINED(cmd);
}

if (should_exclude(cmd) == TRUE) {
pr_trace_msg(trace_channel, 9,
"command '%s' excluded by StatsdExcludeFilter '%s'", (char *) cmd->argv[0],
statsd_exclude_filter);
return PR_DECLINED(cmd);
}

if (should_sample(statsd_sampling) != TRUE) {
pr_trace_msg(trace_channel, 28, "skipping sampling of metric for '%s'",
(char *) cmd->argv[0]);
Expand Down Expand Up @@ -336,6 +401,10 @@ static void statsd_sess_reinit_ev(const void *event_data, void *user_data) {

/* Reset internal state. */
statsd_engine = STATSD_DEFAULT_ENGINE;
statsd_exclude_filter = NULL;
#ifdef PR_USE_REGEX
statsd_exclude_pre = NULL;
#endif /* PR_USE_REGEX */
statsd_sampling = STATSD_DEFAULT_SAMPLING;

if (statsd != NULL) {
Expand Down Expand Up @@ -450,6 +519,13 @@ static int statsd_sess_init(void) {
srand((unsigned int) (time(NULL) ^ getpid()));
#endif /* HAVE_SRANDOM */

c = find_config(main_server->conf, CONF_PARAM, "StatsdExcludeFilter", FALSE);
if (c != NULL &&
c->argc == 2) {
statsd_exclude_filter = c->argv[0];
statsd_exclude_pre = c->argv[1];
}

c = find_config(main_server->conf, CONF_PARAM, "StatsdSampling", FALSE);
if (c != NULL) {
statsd_sampling = *((float *) c->argv[0]);
Expand Down Expand Up @@ -494,9 +570,10 @@ static int statsd_init(void) {
*/

static conftable statsd_conftab[] = {
{ "StatsdEngine", set_statsdengine, NULL },
{ "StatsdSampling", set_statsdsampling, NULL },
{ "StatsdServer", set_statsdserver, NULL },
{ "StatsdEngine", set_statsdengine, NULL },
{ "StatsdExcludeFilter", set_statsdexcludefilter, NULL },
{ "StatsdSampling", set_statsdsampling, NULL },
{ "StatsdServer", set_statsdserver, NULL },
{ NULL }
};

Expand Down
21 changes: 21 additions & 0 deletions mod_statsd.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ <h2>Author</h2>
<h2>Directives</h2>
<ul>
<li><a href="#StatsdEngine">StatsdEngine</a>
<li><a href="#StatsdExcludeFilter">StatsdExcludeFilter</a>
<li><a href="#StatsdSampling">StatsdSampling</a>
<li><a href="#StatsdServer">StatsdServer</a>
</ul>
Expand All @@ -53,6 +54,26 @@ <h3><a name="StatsdEngine">StatsdEngine</a></h3>
The <code>StatsdEngine</code> directive enables or disables the emitting of
metrics to the configured <code>statsd</code> server.

<hr>
<h3><a name="StatsdExcludeFilter">StatsdExcludeFilter</a></h3>
<strong>Syntax:</strong> StatsdExcludeFilter <em>regex|"none"</em><br>
<strong>Default:</strong> None<br>
<strong>Context:</strong> server config, <code>&lt;VirtualHost&gt;</code>, <code>&lt;Global&gt;</code><br>
<strong>Module:</strong> mod_statsd<br>
<strong>Compatibility:</strong> 1.3.6rc1 and later

<p>
The <code>StatsdExcludeFilter</code> directive configures a regular expression
filter that is applied to every command. Any command which matches the configured
regular expression will <b>not</b> be sampled by <code>mod_statsd</code>.

<p>
Example:
<pre>
# Exclude SYST commands from our metrics
StatsdExcludeFilter ^SYST$
</pre>

<hr>
<h3><a name="StatsdSampling">StatsdSampling</a></h3>
<strong>Syntax:</strong> StatsdSampling <em>percentage</em><br>
Expand Down
120 changes: 120 additions & 0 deletions t/lib/ProFTPD/Tests/Modules/mod_statsd.pm
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ my $TESTS = {
test_class => [qw(forking)],
},

statsd_exclude_filter => {
order => ++$order,
test_class => [qw(forking)],
},

};

sub new {
Expand Down Expand Up @@ -886,4 +891,119 @@ sub statsd_timeout_login {
test_cleanup($setup->{log_file}, $ex);
}

sub statsd_exclude_filter {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'statsd');

my $statsd_port = 8125;

my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'statsd:20 statsd.statsd:20 statsd.metric:20',

AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},

IfModules => {
'mod_statsd.c' => {
StatsdEngine => 'on',
StatsdExcludeFilter => '^SYST$',
StatsdServer => "udp://127.0.0.1:$statsd_port",
},

'mod_delay.c' => {
DelayEngine => 'off',
},
},
};

my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);

delete_statsd_info();

# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}

my $ex;

# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->syst();
$client->quit();
};
if ($@) {
$ex = $@;
}

$wfh->print("done\n");
$wfh->flush();

} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}

exit 0;
}

# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);

my $counters = get_statsd_info('counters');

my $counter_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];

foreach my $counter_name (@$counter_names) {
my $counts = $counters->{$counter_name};
$self->assert($counts > 0,
"Expected count values for $counter_name, found none");
}

my $timers = get_statsd_info('timers');

# For timers, we simply expect to HAVE timings
my $timer_names = [qw(
command.USER.331
command.PASS.230
command.QUIT.221
)];

foreach my $timer_name (@$timer_names) {
my $timings = $timers->{$timer_name};
$self->assert(scalar(@$timings) > 0,
"Expected timing values for $timer_name, found none");
}

my $gauges = get_statsd_info('gauges');

# Our connection gauge is a GAUGE; we expect it to have the same value after
# as before.
$self->assert($gauges->{connection} == 0,
"Expected connection gauge 0, got $gauges->{connection}");

test_cleanup($setup->{log_file}, $ex);
}

1;

0 comments on commit 82a5b69

Please sign in to comment.