4 use vars qw($conf $system $DEBUG $me );
5 use base qw( FS::svc_Common );
6 use FS::Record qw( qsearch qsearchs dbh
7 str2time_sql str2time_sql_closing concat_sql ); #dbh
11 use Date::Format qw(time2str);
15 $me = '[FS::svc_port]';
17 FS::UID->install_callback( sub {
19 $system = $conf->config('network_monitoring_system');
24 FS::svc_port - Object methods for svc_port records
30 $record = new FS::svc_port \%hash;
31 $record = new FS::svc_port { 'column' => 'value' };
33 $error = $record->insert;
35 $error = $new_record->replace($old_record);
37 $error = $record->delete;
39 $error = $record->check;
41 $error = $record->suspend;
43 $error = $record->unsuspend;
45 $error = $record->cancel;
49 An FS::svc_port object represents a router port. FS::table_name inherits from
50 FS::svc_Common. The following fields are currently supported:
56 =item serviceid - Torrus serviceid (in srvexport and reportfields tables)
66 Creates a new port. To add the port to the database, see L<"insert">.
68 Note that this stores the hash reference, not a distinct copy of the hash it
69 points to. You can ask the object for a copy with the I<hash> method.
73 sub table { 'svc_port'; }
78 #'name_plural' => 'Ports', #optional,
79 #'longname_plural' => 'Ports', #optional
80 'sorts' => [ 'svcnum', 'serviceid' ], # optional sort field (or arrayref of sort fields, main first)
81 'display_weight' => 75,
82 'cancel_weight' => 10,
84 'serviceid' => 'Torrus serviceid',
89 =item search_sql STRING
91 Class method which returns an SQL fragment to search for the given string.
95 #or something more complicated if necessary
97 my($class, $string) = @_;
98 $class->search_sql_field('serviceid', $string);
103 Returns a meaningful identifier for this port
109 $self->serviceid; #or something more complicated if necessary
114 Adds this record to the database. If there is an error, returns the error,
115 otherwise returns false.
117 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be
118 defined. An FS::cust_svc record will be created and inserted.
126 $error = $self->SUPER::insert;
127 return $error if $error;
134 Delete this record from the database.
142 $error = $self->SUPER::delete;
143 return $error if $error;
149 =item replace OLD_RECORD
151 Replaces the OLD_RECORD with this one in the database. If there is an error,
152 returns the error, otherwise returns false.
157 my ( $new, $old ) = ( shift, shift );
160 $error = $new->SUPER::replace($old);
161 return $error if $error;
168 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
172 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
176 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
180 Checks all fields to make sure this is a valid port. If there is
181 an error, returns the error, otherwise returns false. Called by the insert
189 my $x = $self->setfixed;
190 return $x unless ref($x);
193 my $error = $self->ut_textn('serviceid'); #too lenient?
194 return $error if $error;
201 Returns a PNG graph for this port.
203 The following options must be specified:
214 sub _format_bandwidth {
218 $space = ' ' if $space;
222 warn "$me _format_bandwidth $value" if $DEBUG > 1;
224 if ( $value >= 1000 && $value < 1000000 ) {
225 $value = ($value/1000);
226 $suffix = $space. "k";
228 elsif( $value >= 1000000 && $value < 1000000000 ) {
229 $value = ($value/1000/1000);
230 $suffix = $space . "M";
232 elsif( $value >= 1000000000 && $value < 1000000000000 ) {
233 $value = ($value/1000/1000/1000);
234 $suffix = $space . "G";
236 # and hopefully we don't have folks doing Tbps on a single port :)
238 $value = sprintf("%.2f$suffix",$value) if $value >= 0;
244 my($self, %opt) = @_;
245 my $serviceid = $self->serviceid;
247 if($serviceid && $system eq 'Torrus_Internal') {
252 $start = $opt{start} if $opt{start};
253 $end = $opt{end} if $opt{end};
255 return 'Invalid date range' if ($start < 0 || $start >= $end
256 || $end <= $start || $end < 0 || $end > $now || $start > $now
257 || $end-$start > 86400*366 );
259 my $_date = str2time_sql. concat_sql([ 'srv_date', "' '", 'srv_time' ]).
260 str2time_sql_closing;
262 my $serviceid_sql = "('${serviceid}_IN','${serviceid}_OUT')";
264 local($FS::Record::nowarn_classload) = 1;
265 my @records = qsearch({
266 'table' => 'srvexport',
267 'select' => "*, $_date as _date",
268 'extra_sql' => "where serviceid in $serviceid_sql
271 'order_by' => "order by $_date asc",
274 warn "$me ". scalar(@records). " records returned for $serviceid\n"
277 # assume data in DB is correct,
278 # assume always _IN and _OUT pair, assume intvl = 300
283 foreach my $rec ( @records ) {
284 push @times, $rec->_date
285 unless grep { $_ eq $rec->_date } @times;
286 push @in, $rec->value if $rec->serviceid =~ /_IN$/;
287 push @out, $rec->value if $rec->serviceid =~ /_OUT$/;
290 my $timediff = $times[-1] - $times[0]; # they're sorted ascending
292 my $y_min = 999999999999; # ~1Tbps
296 my $in_min = 999999999999;
298 my $out_min = 999999999999;
300 foreach my $in ( @in ) {
301 $y_max = $in if $in > $y_max;
302 $y_min = $in if $in < $y_min;
304 $in_max = $in if $in > $in_max;
305 $in_min = $in if $in < $in_min;
307 foreach my $out ( @out ) {
308 $y_max = $out if $out > $y_max;
309 $y_min = $out if $out < $y_min;
311 $out_max = $out if $out > $out_max;
312 $out_min = $out if $out < $out_min;
314 my $bwdiff = $y_max - $y_min;
315 $in_min = $self->_format_bandwidth($in_min);
316 $out_min = $self->_format_bandwidth($out_min);
317 $in_max = $self->_format_bandwidth($in_max);
318 $out_max = $self->_format_bandwidth($out_max);
319 my $in_curr = $self->_format_bandwidth($in[-1]);
320 my $out_curr = $self->_format_bandwidth($out[-1]);
321 my $numsamples = scalar(@records)/2;
322 my $in_avg = $self->_format_bandwidth($in_sum/$numsamples);
323 my $out_avg = $self->_format_bandwidth($out_sum/$numsamples);
325 warn "$me timediff=$timediff bwdiff=$bwdiff start=$start end=$end "
326 . "in_min=$in_min out_min=$out_min in_max=$in_max "
327 . "out_max=$out_max in_avg=$in_avg out_avg=$out_avg "
328 . " # records = " . scalar(@records) . "\n\ntimes:\n"
329 . Dumper(@times) . "\n\nin:\n" . Dumper(@in) . "\n\nout:\n"
330 . Dumper(@out) if $DEBUG > 1;
332 my @data = ( \@times, \@in, \@out );
334 # hardcoded size, colour, etc.
335 my $graph = new GD::Graph::mixed(600,320); #600,400
337 types => ['area','lines'],
338 dclrs => ['green','blue'],
339 x_label => "(In Out) Current: $in_curr $out_curr Average: $in_avg $out_avg Maximum: $in_max $out_max Minimum: $in_min $out_min",
340 x_tick_number => 'auto',
341 x_number_format => sub {
343 if ( $timediff < 86401 ) { # one day
344 $value = time2str("%a %H:%M",$value)
345 } elsif ( $timediff < 86401*7 ) { # one week
346 $value = time2str("%d",$value)
347 } elsif ( $timediff < 86401*30 ) { # one month
348 $value = time2str("Week %U",$value)
349 } elsif ( $timediff < 86401*366 ) { # one year
350 $value = time2str("%b",$value)
354 y_number_format => sub {
356 $self->_format_bandwidth($value,1);
359 legend_placement => 'BR',
360 title => $self->serviceid,
361 ) or return "can't create graph: ".$graph->error;
363 $graph->set_text_clr('black')
364 or return "can't set text colour: ".$graph->error;
365 $graph->set_legend(('In','Out'))
366 or return "can't set legend: ".$graph->error;
368 my $gd = $graph->plot(\@data);
369 return "graph error: ".$graph->error unless($gd);
382 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
383 L<FS::cust_pkg>, schema.html from the base documentation.