From ae073296dcf284091b7b6eb300a734e511e068cd Mon Sep 17 00:00:00 2001 From: Oliver Gorwits Date: Fri, 17 Feb 2012 13:27:28 +0000 Subject: [PATCH] more performant implementation of active/age node search --- Netdisco/lib/Netdisco/DB/Result/ActiveNode.pm | 145 +----------------- .../Netdisco/DB/Result/ActiveNodeWithAge.pm | 27 ++++ Netdisco/lib/Netdisco/DB/Result/DevicePort.pm | 50 +++--- Netdisco/lib/Netdisco/DB/Result/Node.pm | 12 -- .../lib/Netdisco/DB/Result/NodeWithAge.pm | 27 ++++ .../lib/Netdisco/DB/ResultSet/ActiveNode.pm | 64 -------- .../lib/Netdisco/DB/ResultSet/DevicePort.pm | 29 ---- Netdisco/lib/Netdisco/Web/Device.pm | 9 +- 8 files changed, 90 insertions(+), 273 deletions(-) create mode 100644 Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm create mode 100644 Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm delete mode 100644 Netdisco/lib/Netdisco/DB/ResultSet/ActiveNode.pm diff --git a/Netdisco/lib/Netdisco/DB/Result/ActiveNode.pm b/Netdisco/lib/Netdisco/DB/Result/ActiveNode.pm index caa85cf5..9ab87c56 100644 --- a/Netdisco/lib/Netdisco/DB/Result/ActiveNode.pm +++ b/Netdisco/lib/Netdisco/DB/Result/ActiveNode.pm @@ -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 to which this Node entry was associated at the -time of discovery. - -The JOIN is of type LEFT, in case the C is no longer present in the -database but the relation is being used in C. - -=cut - -__PACKAGE__->belongs_to( device => 'Netdisco::DB::Result::Device', - { 'foreign.ip' => 'self.switch' }, { join_type => 'LEFT' } ); - -=head2 device_port - -Returns the single C to which this Node entry was associated at -the time of discovery. - -The JOIN is of type LEFT, in case the C is no longer present in the -database but the relation is being used in C. - -=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 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 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 field, accurate to the minute. - -The format is somewhat like ISO 8601 or RFC3339 but without the middle C -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 field, accurate to the minute. - -The format is somewhat like ISO 8601 or RFC3339 but without the middle C -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 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; diff --git a/Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm b/Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm new file mode 100644 index 00000000..b7cafdc5 --- /dev/null +++ b/Netdisco/lib/Netdisco/DB/Result/ActiveNodeWithAge.pm @@ -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; diff --git a/Netdisco/lib/Netdisco/DB/Result/DevicePort.pm b/Netdisco/lib/Netdisco/DB/Result/DevicePort.pm index 68592e5c..91933e90 100644 --- a/Netdisco/lib/Netdisco/DB/Result/DevicePort.pm +++ b/Netdisco/lib/Netdisco/DB/Result/DevicePort.pm @@ -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 to find only the active Nodes, instead. +Port. + +The C variants return only the subset of nodes currently in the switch +MAC address table, that is the active ones. + +The C variants add an additional column C, a +preformatted value for the Node's C field, which reads as "X +days/weeks/months/years". =over 4 @@ -85,11 +92,6 @@ Port. See C to find only the active Nodes, instead. Rows returned are sorted by the Node MAC address. -=item * - -The additional column C is a preformatted value for the Node's -C 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 Nodes whose MAC addresses are associated with -this Device Port. See C to find all Nodes (active and inactive). - -=over 4 - -=item * - -Rows returned are sorted by the Node MAC address. - -=item * - -The additional column C is a preformatted value for the Node's -C 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 diff --git a/Netdisco/lib/Netdisco/DB/Result/Node.pm b/Netdisco/lib/Netdisco/DB/Result/Node.pm index 8a096ef4..4c7bae03 100644 --- a/Netdisco/lib/Netdisco/DB/Result/Node.pm +++ b/Netdisco/lib/Netdisco/DB/Result/Node.pm @@ -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 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; diff --git a/Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm b/Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm new file mode 100644 index 00000000..3759a05e --- /dev/null +++ b/Netdisco/lib/Netdisco/DB/Result/NodeWithAge.pm @@ -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; diff --git a/Netdisco/lib/Netdisco/DB/ResultSet/ActiveNode.pm b/Netdisco/lib/Netdisco/DB/ResultSet/ActiveNode.pm deleted file mode 100644 index 53675347..00000000 --- a/Netdisco/lib/Netdisco/DB/ResultSet/ActiveNode.pm +++ /dev/null @@ -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, this returns a ResultSet of matching rows from the Node -table. - -=over 4 - -=item * - -The C parameter must be a hashref containing a key C with -the value to search for. - -=item * - -Results are ordered by time last seen. - -=item * - -Additional columns C and C provide -preformatted timestamps of the C and C fields. - -=item * - -A JOIN is performed on the Device table and the Device C column -prefetched. - -=back - -To limit results only to active nodes, set C<< {active => 1} >> in C. - -=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; diff --git a/Netdisco/lib/Netdisco/DB/ResultSet/DevicePort.pm b/Netdisco/lib/Netdisco/DB/ResultSet/DevicePort.pm index 345b7027..eb4a6621 100644 --- a/Netdisco/lib/Netdisco/DB/ResultSet/DevicePort.pm +++ b/Netdisco/lib/Netdisco/DB/ResultSet/DevicePort.pm @@ -37,35 +37,6 @@ sub with_times { }); } -=head2 with_node_age - -This is a modifier for any C (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. - -=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 (including the helpers below) which diff --git a/Netdisco/lib/Netdisco/Web/Device.pm b/Netdisco/lib/Netdisco/Web/Device.pm index 8f295ebc..1ec906b8 100644 --- a/Netdisco/lib/Netdisco/Web/Device.pm +++ b/Netdisco/lib/Netdisco/Web/Device.pm @@ -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) }