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 )