App-Netdisco
view release on metacpan or search on metacpan
lib/App/Netdisco/Web/Metrics.pm view on Meta::CPAN
package App::Netdisco::Web::Metrics;
use Dancer ':syntax';
use Dancer::Plugin::DBIC;
use App::Netdisco::Util::Permission 'acl_matches';
use POSIX 'floor';
use Try::Tiny;
sub _header {
my ($name, $help) = @_;
return sprintf("# HELP %s %s\n# TYPE %s gauge\n", $name, $help, $name);
}
sub _sample {
my ($name, $value, %labels) = @_;
my $label_str = '';
if (%labels) {
$label_str = '{'. join(',', map { qq($_="$labels{$_}") } sort keys %labels) .'}';
}
return sprintf("%s%s %s\n", $name, $label_str, $value // 0);
}
if (my $metrics_path = setting('metrics_path')) {
get $metrics_path => sub {
# Optional IP range restriction
my $allow = setting('metrics_allow');
if ($allow and ref $allow eq ref []) {
my $remote = request->remote_address;
unless (acl_matches($remote, $allow)) {
status 403;
return 'Forbidden';
}
}
# Optional bearer token auth
my $token = setting('metrics_token');
if ($token) {
my $auth = request->header('Authorization') // '';
unless ($auth eq "Bearer $token") {
status 401;
header 'WWW-Authenticate' => 'Bearer realm="netdisco metrics"';
return 'Unauthorized';
}
}
content_type 'text/plain; version=0.0.4; charset=utf-8';
my @tenants = ('netdisco');
if (my $tdbs = setting('tenant_databases')) {
push @tenants, map { $_->{'tag'} } @$tdbs;
}
my $output = '';
# -- Statistics metrics (one row per tenant) -------------------------------
my @stat_metrics = (
[ netdisco_devices => 'device_count', 'Total number of discovered devices' ],
[ netdisco_device_ips => 'device_ip_count', 'Total number of device IP addresses' ],
[ netdisco_device_links => 'device_link_count', 'Total number of layer2 links between devices' ],
[ netdisco_device_ports => 'device_port_count', 'Total number of device ports' ],
[ netdisco_device_ports_up => 'device_port_up_count','Total number of device ports with up/up status' ],
[ netdisco_ip_table => 'ip_table_count', 'Total number of IP table entries' ],
[ netdisco_ip_active => 'ip_active_count', 'Total number of active IP entries' ],
[ netdisco_nodes => 'node_table_count', 'Total number of node entries' ],
[ netdisco_nodes_active => 'node_active_count', 'Total number of active nodes' ],
[ netdisco_phones => 'phone_count', 'Total number of discovered VoIP phones' ],
[ netdisco_waps => 'wap_count', 'Total number of discovered wireless access points' ],
);
foreach my $m (@stat_metrics) {
my ($metric, $col, $help) = @$m;
$output .= _header($metric, $help);
foreach my $tenant (@tenants) {
my $stats = try {
schema($tenant)->resultset('Statistics')
->search(undef, { order_by => { -desc => 'day' }, rows => 1 })->first;
};
next unless $stats;
$output .= _sample($metric, $stats->$col // 0, tenant => $tenant);
}
$output .= "\n";
}
# Age of latest statistics row in seconds
$output .= _header('netdisco_stats_age_seconds', 'Age of the latest statistics snapshot in seconds');
foreach my $tenant (@tenants) {
my $age = try {
schema($tenant)->resultset('Statistics')->search(undef, {
select => [ \"extract(epoch FROM (now() - max(day)::timestamp))" ],
as => ['age'],
})->first->get_column('age');
};
next unless defined $age;
$output .= _sample('netdisco_stats_age_seconds', floor($age), tenant => $tenant);
}
$output .= "\n";
# -- Backend / worker health -----------------------------------------------
$output .= _header('netdisco_backends', 'Number of active backend instances');
$output .= _header('netdisco_workers', 'Total number of worker slots across all backends');
my $backends_output = '';
my $workers_output = '';
foreach my $tenant (@tenants) {
my @backends = try {
schema($tenant)->resultset('DeviceSkip')
( run in 1.525 second using v1.01-cache-2.11-cpan-39bf76dae61 )