more performant implementation of active/age node search

This commit is contained in:
Oliver Gorwits
2012-02-17 13:27:28 +00:00
parent 802867a5d0
commit ae073296dc
8 changed files with 90 additions and 273 deletions

View File

@@ -4,145 +4,16 @@ package Netdisco::DB::Result::ActiveNode;
use strict;
use warnings;
use base 'DBIx::Class::Core';
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
use base 'Netdisco::DB::Result::Node';
__PACKAGE__->load_components('Helper::Row::SubClass');
__PACKAGE__->subclass;
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table("active_node");
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(
'SELECT * FROM node WHERE active'
);
__PACKAGE__->add_columns(
"mac",
{ data_type => "macaddr", is_nullable => 0 },
"switch",
{ data_type => "inet", is_nullable => 0 },
"port",
{ data_type => "text", is_nullable => 0 },
"active",
{ data_type => "boolean", is_nullable => 1 },
"oui",
{ data_type => "varchar", is_nullable => 1, size => 8 },
"time_first",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"time_recent",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
"time_last",
{
data_type => "timestamp",
default_value => \"current_timestamp",
is_nullable => 1,
original => { default_value => \"now()" },
},
);
__PACKAGE__->set_primary_key("mac", "switch", "port");
=head1 RELATIONSHIPS
=head2 device
Returns the single C<device> to which this Node entry was associated at the
time of discovery.
The JOIN is of type LEFT, in case the C<device> is no longer present in the
database but the relation is being used in C<search()>.
=cut
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device',
{ 'foreign.ip' => 'self.switch' }, { join_type => 'LEFT' } );
=head2 device_port
Returns the single C<device_port> to which this Node entry was associated at
the time of discovery.
The JOIN is of type LEFT, in case the C<device> is no longer present in the
database but the relation is being used in C<search()>.
=cut
# device port may have been deleted (reconfigured modules?) but node remains
__PACKAGE__->belongs_to( device_port => 'Netdisco::DB::Result::DevicePort',
{ 'foreign.ip' => 'self.switch', 'foreign.port' => 'self.port' },
{ join_type => 'LEFT' }
);
=head2 ips
Returns the set of C<node_ip> entries associated with this Node. That is, the
IP addresses which this MAC address was hosting at the time of discovery.
Note that the Active status of the returned IP entries will all be the same as
the current Node's.
=cut
__PACKAGE__->has_many( ips => 'Netdisco::DB::Result::NodeIp',
{ 'foreign.mac' => 'self.mac', 'foreign.active' => 'self.active' } );
=head2 oui
Returns the C<oui> table entry matching this Node. You can then join on this
relation and retrieve the Company name from the related table.
The JOIN is of type LEFT, in case the OUI table has not been populated.
=cut
__PACKAGE__->belongs_to( oui => 'Netdisco::DB::Result::Oui', 'oui',
{ join_type => 'LEFT' } );
=head1 ADDITIONAL COLUMNS
=head2 time_first_stamp
Formatted version of the C<time_first> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub time_first_stamp { return (shift)->get_column('time_first_stamp') }
=head2 time_last_stamp
Formatted version of the C<time_last> field, accurate to the minute.
The format is somewhat like ISO 8601 or RFC3339 but without the middle C<T>
between the date stamp and time stamp. That is:
2012-02-06 12:49
=cut
sub time_last_stamp { return (shift)->get_column('time_last_stamp') }
=head2 time_last_age
Formatted version of the C<time_last> field, accurate to the minute.
The format is in "X days/months/years" style, similar to:
1 year 4 months 05:46:00
=cut
sub time_last_age { return (shift)->get_column('time_last_age') }
__PACKAGE__->result_source_instance->view_definition(q{
SELECT * FROM node WHERE active
});
1;

View File

@@ -0,0 +1,27 @@
use utf8;
package Netdisco::DB::Result::ActiveNodeWithAge;
use strict;
use warnings;
use base 'Netdisco::DB::Result::ActiveNode';
__PACKAGE__->load_components('Helper::Row::SubClass');
__PACKAGE__->subclass;
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table("active_node_with_age");
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(q{
SELECT *,
replace(age( date_trunc( 'minute', time_last + interval '30 second' ) ) ::text, 'mon', 'month')
AS time_last_age
FROM node WHERE active
});
__PACKAGE__->add_columns(
"time_last_age",
{ data_type => "text", is_nullable => 1 },
);
1;

View File

@@ -74,10 +74,17 @@ Returns the Device table entry to which the given Port is related.
__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', 'ip');
=head2 nodes
=head2 nodes / active_nodes / nodes_with_age / active_nodes_with_age
Returns the set of Nodes whose MAC addresses are associated with this Device
Port. See C<active_nodes()> to find only the active Nodes, instead.
Port.
The C<active> variants return only the subset of nodes currently in the switch
MAC address table, that is the active ones.
The C<with_age> variants add an additional column C<time_last_age>, a
preformatted value for the Node's C<time_last> field, which reads as "X
days/weeks/months/years".
=over 4
@@ -85,11 +92,6 @@ Port. See C<active_nodes()> to find only the active Nodes, instead.
Rows returned are sorted by the Node MAC address.
=item *
The additional column C<time_last_age> is a preformatted value for the Node's
C<time_last> field, which reads as "X days/weeks/months/years".
=back
=cut
@@ -102,25 +104,13 @@ __PACKAGE__->has_many( nodes => 'Netdisco::DB::Result::Node',
{ join_type => 'LEFT' },
);
=head2 active_nodes
Returns the set of I<active> Nodes whose MAC addresses are associated with
this Device Port. See C<nodes()> to find all Nodes (active and inactive).
=over 4
=item *
Rows returned are sorted by the Node MAC address.
=item *
The additional column C<time_last_age> is a preformatted value for the Node's
C<time_last> field, which reads as "X days/weeks/months/years".
=back
=cut
__PACKAGE__->has_many( nodes_with_age => 'Netdisco::DB::Result::NodeWithAge',
{
'foreign.switch' => 'self.ip',
'foreign.port' => 'self.port',
},
{ join_type => 'LEFT' },
);
__PACKAGE__->has_many( active_nodes => 'Netdisco::DB::Result::ActiveNode',
{
@@ -130,6 +120,14 @@ __PACKAGE__->has_many( active_nodes => 'Netdisco::DB::Result::ActiveNode',
{ join_type => 'LEFT' },
);
__PACKAGE__->has_many( active_nodes_with_age => 'Netdisco::DB::Result::ActiveNodeWithAge',
{
'foreign.switch' => 'self.ip',
'foreign.port' => 'self.port',
},
{ join_type => 'LEFT' },
);
=head2 neighbor_alias
When a device port has an attached neighbor device, this relationship will

View File

@@ -132,16 +132,4 @@ between the date stamp and time stamp. That is:
sub time_last_stamp { return (shift)->get_column('time_last_stamp') }
=head2 time_last_age
Formatted version of the C<time_last> field, accurate to the minute.
The format is in "X days/months/years" style, similar to:
1 year 4 months 05:46:00
=cut
sub time_last_age { return (shift)->get_column('time_last_age') }
1;

View File

@@ -0,0 +1,27 @@
use utf8;
package Netdisco::DB::Result::NodeWithAge;
use strict;
use warnings;
use base 'Netdisco::DB::Result::Node';
__PACKAGE__->load_components('Helper::Row::SubClass');
__PACKAGE__->subclass;
__PACKAGE__->table_class('DBIx::Class::ResultSource::View');
__PACKAGE__->table("node_with_age");
__PACKAGE__->result_source_instance->is_virtual(1);
__PACKAGE__->result_source_instance->view_definition(q{
SELECT *,
replace(age( date_trunc( 'minute', time_last + interval '30 second' ) ) ::text, 'mon', 'month')
AS time_last_age
FROM node
});
__PACKAGE__->add_columns(
"time_last_age",
{ data_type => "text", is_nullable => 1 },
);
1;

View File

@@ -1,64 +0,0 @@
package Netdisco::DB::ResultSet::ActiveNode;
use base 'DBIx::Class::ResultSet';
use strict;
use warnings FATAL => 'all';
=head1 search_by_mac( \%cond, \%attrs? )
my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
Like C<search()>, this returns a ResultSet of matching rows from the Node
table.
=over 4
=item *
The C<cond> parameter must be a hashref containing a key C<mac> with
the value to search for.
=item *
Results are ordered by time last seen.
=item *
Additional columns C<time_first_stamp> and C<time_last_stamp> provide
preformatted timestamps of the C<time_first> and C<time_last> fields.
=item *
A JOIN is performed on the Device table and the Device C<dns> column
prefetched.
=back
To limit results only to active nodes, set C<< {active => 1} >> in C<cond>.
=cut
sub search_by_mac {
my ($rs, $cond, $attrs) = @_;
die "mac address required for search_by_mac\n"
if ref {} ne ref $cond or !exists $cond->{mac};
$cond->{'me.mac'} = delete $cond->{mac};
$attrs ||= {};
return $rs
->search_rs({}, {
order_by => {'-desc' => 'time_last'},
'+columns' => [qw/ device.dns /],
'+select' => [
\"to_char(time_first, 'YYYY-MM-DD HH24:MI')",
\"to_char(time_last, 'YYYY-MM-DD HH24:MI')",
],
'+as' => [qw/ time_first_stamp time_last_stamp /],
join => 'device',
})
->search($cond, $attrs);
}
1;

View File

@@ -37,35 +37,6 @@ sub with_times {
});
}
=head2 with_node_age
This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:
=over 4
=item $nodes.time_last_age
=back
You can pass in the table alias for the Nodes relation, which defaults to
C<nodes>.
=cut
sub with_node_age {
my ($rs, $alias) = @_;
$alias ||= 'nodes';
return $rs
->search_rs({},
{
'+select' =>
[\"replace(age(date_trunc('minute', $alias.time_last + interval '30 second'))::text, 'mon', 'month')"],
'+as' => [ "$alias.time_last_age" ],
});
}
=head2 with_vlan_count
This is a modifier for any C<search()> (including the helpers below) which

View File

@@ -99,16 +99,15 @@ ajax '/ajax/content/device/ports' => sub {
# get number of vlans on the port to control whether to list them or not
$set = $set->with_vlan_count if param('c_vmember');
# retrieve active/all connected nodes, and device, if asked for
# what kind of nodes are we interested in?
my $nodes_name = (param('n_archived') ? 'nodes' : 'active_nodes');
$nodes_name .= '_with_age' if param('c_connected') and param('n_age');
# retrieve active/all connected nodes, and device, if asked for
$set = $set->search_rs({}, {
prefetch => [{$nodes_name => 'ips'}, {neighbor_alias => 'device'}],
}) if param('c_connected');
# add constructed node age col if requested (and showing connected)
$set = $set->with_node_age($nodes_name)
if param('c_connected') and param('n_age');
# sort, and filter by free ports
# the filter could be in the template but here allows a 'no records' msg
my $results = [ sort { &netdisco::sort_port($a->port, $b->port) }