4 use vars qw($conf $system $DEBUG $me );
5 use base qw( FS::svc_Common );
6 use FS::Record qw( qsearch qsearchs dbh );
10 use Date::Format qw(time2str);
14 $me = '[FS::svc_port]';
16 FS::UID->install_callback( sub {
18 $system = $conf->config('network_monitoring_system');
23 FS::svc_port - Object methods for svc_port records
29 $record = new FS::svc_port \%hash;
30 $record = new FS::svc_port { 'column' => 'value' };
32 $error = $record->insert;
34 $error = $new_record->replace($old_record);
36 $error = $record->delete;
38 $error = $record->check;
40 $error = $record->suspend;
42 $error = $record->unsuspend;
44 $error = $record->cancel;
48 An FS::svc_port object represents a router port. FS::table_name inherits from
49 FS::svc_Common. The following fields are currently supported:
55 =item serviceid - Torrus serviceid (in srvexport and reportfields tables)
65 Creates a new port. To add the port to the database, see L<"insert">.
67 Note that this stores the hash reference, not a distinct copy of the hash it
68 points to. You can ask the object for a copy with the I<hash> method.
72 sub table { 'svc_port'; }
77 #'name_plural' => 'Ports', #optional,
78 #'longname_plural' => 'Ports', #optional
79 'sorts' => [ 'svcnum', 'serviceid' ], # optional sort field (or arrayref of sort fields, main first)
80 'display_weight' => 75,
81 'cancel_weight' => 10,
83 'serviceid' => 'Torrus serviceid',
88 =item search_sql STRING
90 Class method which returns an SQL fragment to search for the given string.
94 #or something more complicated if necessary
96 my($class, $string) = @_;
97 $class->search_sql_field('serviceid', $string);
102 Returns a meaningful identifier for this port
108 $self->serviceid; #or something more complicated if necessary
113 Adds this record to the database. If there is an error, returns the error,
114 otherwise returns false.
116 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
117 defined. An FS::cust_svc record will be created and inserted.
125 $error = $self->SUPER::insert;
126 return $error if $error;
133 Delete this record from the database.
141 $error = $self->SUPER::delete;
142 return $error if $error;
148 =item replace OLD_RECORD
150 Replaces the OLD_RECORD with this one in the database. If there is an error,
151 returns the error, otherwise returns false.
156 my ( $new, $old ) = ( shift, shift );
159 $error = $new->SUPER::replace($old);
160 return $error if $error;
167 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
171 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
175 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
179 Checks all fields to make sure this is a valid port. If there is
180 an error, returns the error, otherwise returns false. Called by the insert
188 my $x = $self->setfixed;
189 return $x unless ref($x);
192 my $error = $self->ut_textn('serviceid'); #too lenient?
193 return $error if $error;
200 Returns a PNG graph for this port.
202 The following options must be specified:
213 sub _format_bandwidth {
217 $space = ' ' if $space;
221 warn "$me _format_bandwidth $value" if $DEBUG;
223 if ( $value >= 1000 && $value < 1000000 ) {
224 $value = ($value/1000);
225 $suffix = $space. "k";
227 elsif( $value >= 1000000 && $value < 1000000000 ) {
228 $value = ($value/1000/1000);
229 $suffix = $space . "M";
231 elsif( $value >= 1000000000 && $value < 1000000000000 ) {
232 $value = ($value/1000/1000/1000);
233 $suffix = $space . "G";
235 # and hopefully we don't have folks doing Tbps on a single port :)
237 $value = sprintf("%.2f$suffix",$value) if $value >= 0;
243 my($self, %opt) = @_;
244 my $serviceid = $self->serviceid;
246 if($serviceid && $system eq 'Torrus_Internal') {
251 $start = $opt{start} if $opt{start};
252 $end = $opt{end} if $opt{end};
254 return 'Invalid date range' if ($start < 0 || $start >= $end
255 || $end <= $start || $end < 0 || $end > $now || $start > $now
256 || $end-$start > 86400*366 );
258 my $serviceid_sql = "('${serviceid}_IN','${serviceid}_OUT')";
261 if ( $dbh->{Driver}->{Name} eq 'Pg' ) {
263 'table' => 'srvexport',
264 'select' => "*, date_part('epoch',to_timestamp(srv_date||' '||srv_time,'YYYY-MM-DD HH:MI:SS')) as _date",
265 'extra_sql' => "where serviceid in $serviceid_sql and
266 date_part('epoch',to_timestamp(srv_date||' '||srv_time,'YYYY-MM-DD HH:MI:SS')) >= $start
267 and date_part('epoch',to_timestamp(srv_date||' '||srv_time,'YYYY-MM-DD HH:MI:SS')) <= $end",
268 'order_by' => "order by date_part('epoch',to_timestamp(srv_date||' '||srv_time,'YYYY-MM-DD HH:MI:SS')) asc",
270 } elsif ( $dbh->{Driver}->{Name} eq 'mysql' ) {
272 'table' => 'srvexport',
273 'select' => "*, unix_timestamp(srv_date||' '||srv_time) as _date",
274 'extra_sql' => "where serviceid in $serviceid_sql and
275 unix_timestamp(srv_date||' '||srv_time) >= $start
276 and unix_timestamp(srv_date||' '||srv_time) <= $end",
277 'order_by' => "order by unix_timestamp(srv_date||' '||srv_time) asc",
280 return 'Unsupported DBMS';
284 # assume data in DB is correct,
285 # assume always _IN and _OUT pair, assume intvl = 300
290 foreach my $rec ( @records ) {
291 push @times, $rec->_date
292 unless grep { $_ eq $rec->_date } @times;
293 push @in, $rec->value if $rec->serviceid =~ /_IN$/;
294 push @out, $rec->value if $rec->serviceid =~ /_OUT$/;
297 my $timediff = $times[-1] - $times[0]; # they're sorted ascending
299 my $y_min = 999999999999; # ~1Tbps
303 my $in_min = 999999999999;
305 my $out_min = 999999999999;
307 foreach my $in ( @in ) {
308 $y_max = $in if $in > $y_max;
309 $y_min = $in if $in < $y_min;
311 $in_max = $in if $in > $in_max;
312 $in_min = $in if $in < $in_min;
314 foreach my $out ( @out ) {
315 $y_max = $out if $out > $y_max;
316 $y_min = $out if $out < $y_min;
318 $out_max = $out if $out > $out_max;
319 $out_min = $out if $out < $out_min;
321 my $bwdiff = $y_max - $y_min;
322 $in_min = $self->_format_bandwidth($in_min);
323 $out_min = $self->_format_bandwidth($out_min);
324 $in_max = $self->_format_bandwidth($in_max);
325 $out_max = $self->_format_bandwidth($out_max);
326 my $in_curr = $self->_format_bandwidth($in[-1]);
327 my $out_curr = $self->_format_bandwidth($out[-1]);
328 my $numsamples = scalar(@records)/2;
329 my $in_avg = $self->_format_bandwidth($in_sum/$numsamples);
330 my $out_avg = $self->_format_bandwidth($out_sum/$numsamples);
332 warn "$me timediff=$timediff bwdiff=$bwdiff start=$start end=$end "
333 . "in_min=$in_min out_min=$out_min in_max=$in_max "
334 . "out_max=$out_max in_avg=$in_avg out_avg=$out_avg "
335 . " # records = " . scalar(@records) . "\n\ntimes:\n"
336 . Dumper(@times) . "\n\nin:\n" . Dumper(@in) . "\n\nout:\n"
337 . Dumper(@out) if $DEBUG;
339 my @data = ( \@times, \@in, \@out );
341 # hardcoded size, colour, etc.
342 my $graph = new GD::Graph::mixed(600,400);
344 types => ['area','lines'],
345 dclrs => ['green','blue'],
346 x_label => "(In Out) Current: $in_curr $out_curr Average: $in_avg $out_avg Maximum: $in_max $out_max Minimum: $in_min $out_min",
347 x_tick_number => 'auto',
348 x_number_format => sub {
350 if ( $timediff < 86401 ) { # one day
351 $value = time2str("%a %H:%M",$value)
352 } elsif ( $timediff < 86401*7 ) { # one week
353 $value = time2str("%d",$value)
354 } elsif ( $timediff < 86401*30 ) { # one month
355 $value = time2str("Week %U",$value)
356 } elsif ( $timediff < 86401*366 ) { # one year
357 $value = time2str("%b",$value)
361 y_number_format => sub {
363 $self->_format_bandwidth($value,1);
366 legend_placement => 'BR',
367 title => $self->serviceid,
368 ) or return "can't create graph: ".$graph->error;
370 $graph->set_text_clr('black')
371 or return "can't set text colour: ".$graph->error;
372 $graph->set_legend(('In','Out'))
373 or return "can't set legend: ".$graph->error;
375 my $gd = $graph->plot(\@data);
376 return "graph error: ".$graph->error unless($gd);
389 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
390 L<FS::cust_pkg>, schema.html from the base documentation.