[9597] | 1 | package Common;
|
---|
| 2 |
|
---|
| 3 | #
|
---|
| 4 | # Some common functions
|
---|
| 5 | #
|
---|
| 6 | # Revision: $Header: trunk/BNC/scripts/Common.pm 9747 2022-06-01 09:45:39Z wiese $
|
---|
| 7 | #
|
---|
| 8 |
|
---|
| 9 | use strict;
|
---|
| 10 | use warnings;
|
---|
| 11 | use Exporter;
|
---|
| 12 | use Data::Dumper;
|
---|
| 13 | use Log::Log4perl qw(:easy);
|
---|
| 14 | use File::Temp;
|
---|
| 15 | use File::Basename;
|
---|
| 16 | use File::Spec::Functions;
|
---|
| 17 |
|
---|
| 18 | #$| = 1; # Buffer off (print)
|
---|
| 19 |
|
---|
| 20 | sub trim {
|
---|
| 21 | my ($text) = @_;
|
---|
| 22 | $text =~ s/^[\s]*//g;
|
---|
| 23 | $text =~ s/[\s]*$//g;
|
---|
| 24 | return $text;
|
---|
| 25 | }
|
---|
| 26 |
|
---|
| 27 | # run a command in the shell.
|
---|
| 28 | sub runCmd {
|
---|
| 29 | my ($cmd) = shift;
|
---|
| 30 |
|
---|
| 31 | DEBUG("run cmd: \"$cmd\"");
|
---|
| 32 |
|
---|
| 33 | my $tmpfh = File::Temp->new( UNLINK => 1, SUFFIX => '.dat' );
|
---|
| 34 | my $tmpfile = $tmpfh->filename();
|
---|
| 35 |
|
---|
| 36 | $cmd = "$cmd 2>$tmpfile |";
|
---|
| 37 | my $PIPE;
|
---|
| 38 | unless ( open $PIPE, $cmd ) {
|
---|
| 39 | ERROR("open $cmd | failed ($!)");
|
---|
| 40 | return ( -1, "", "" );
|
---|
| 41 | }
|
---|
| 42 | my $stdout = join '', <$PIPE>;
|
---|
| 43 | close $PIPE;
|
---|
| 44 | my $exit_code = $?;
|
---|
| 45 |
|
---|
| 46 | my $stderr = getContent($tmpfile);
|
---|
| 47 | return ( $exit_code, $stdout, $stderr );
|
---|
| 48 | }
|
---|
| 49 |
|
---|
[9747] | 50 | sub mkDir {
|
---|
| 51 | my $dir = shift;
|
---|
| 52 | return 1 if ( -d $dir );
|
---|
| 53 |
|
---|
| 54 | eval { mkpath($dir) };
|
---|
| 55 | if ($@) {
|
---|
| 56 | warn("Could not create path [$dir]: $@");
|
---|
| 57 | return 0;
|
---|
| 58 | }
|
---|
| 59 | return 1;
|
---|
| 60 | }
|
---|
| 61 |
|
---|
[9597] | 62 | #** @function amInteractiv ()
|
---|
| 63 | # @brief Checks if a program runs interactivly, or e.g. was invoked by cron.
|
---|
| 64 | # @see Perl Cookbook Kap. 15.2
|
---|
| 65 | # @return true if interactive, else false.
|
---|
| 66 | #*
|
---|
| 67 | sub amInteractiv {
|
---|
| 68 | use POSIX qw(getpgrp tcgetpgrp);
|
---|
| 69 | my $tty;
|
---|
| 70 | open ( $tty, "<", "/dev/tty" ) || LOGDIE "Could not open /dev/tty: $!";
|
---|
| 71 | my $tpgrp = tcgetpgrp( fileno ($tty) ); # get terminal foreground process group
|
---|
| 72 | my $pgrp = getpgrp (); # get the current process group
|
---|
| 73 | close $tty;
|
---|
| 74 | return ( $tpgrp == $pgrp );
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | #** @function printData ($data)
|
---|
| 78 | # @brief Prints perl data structures. Uses Data::Dumper.
|
---|
| 79 | # @param $data [required] Daten
|
---|
| 80 | # @return -
|
---|
| 81 | #*
|
---|
| 82 | sub printData {
|
---|
| 83 | my $data = shift;
|
---|
| 84 | my $d = Data::Dumper->new($data);
|
---|
| 85 | $d->Indent(3); # pretty print with array indices
|
---|
| 86 | print $d->Dump;
|
---|
| 87 | }
|
---|
| 88 |
|
---|
| 89 | sub getContent {
|
---|
| 90 | my ($fileName) = @_;
|
---|
| 91 | my $errmsg = "";
|
---|
| 92 | if ( -f $fileName ) {
|
---|
| 93 | my $readMode = $/;
|
---|
| 94 | undef $/;
|
---|
| 95 | open ( FILE, "<", $fileName )
|
---|
| 96 | or ERROR("Kann Datei nicht zum lesen oeffnen");
|
---|
| 97 | $errmsg = <FILE>;
|
---|
| 98 | close (FILE);
|
---|
| 99 | $/ = $readMode;
|
---|
| 100 | }
|
---|
| 101 | return $errmsg;
|
---|
| 102 | }
|
---|
| 103 |
|
---|
| 104 | #** @function filePosition ($file, $filePos)
|
---|
| 105 | # @brief Save position of a file in an idxfile.
|
---|
| 106 | #
|
---|
| 107 | # Especially for logfiles. For performance reasons, logfiles should not parsed always from the
|
---|
| 108 | # beginning, the next read access should start at the pos of the last read.
|
---|
| 109 | # Saves the position of the last read in a help-file ".filePos" in
|
---|
| 110 | # the directory of the logfile. If the param $filePos is set, then this position will be saved (Write).
|
---|
| 111 | # Otherwise the current position will be returned (Read).
|
---|
| 112 | # @param $file [required] File of interest
|
---|
| 113 | # @param $filePos [optional] if given, this position ()in Bytes) will be saved, else return current position
|
---|
| 114 | # @return file position in Bytes from beginning of the file
|
---|
| 115 | #*
|
---|
| 116 | sub filePosition {
|
---|
| 117 | my ( $file, $filePos ) = @_;
|
---|
| 118 |
|
---|
| 119 | my ( $filName, $filDir ) = fileparse($file);
|
---|
| 120 | my $idxFil = catfile( $filDir, ".filePos" );
|
---|
| 121 |
|
---|
| 122 | if ($filePos) {
|
---|
| 123 |
|
---|
| 124 | # Save this position in idxfile
|
---|
| 125 | my $newPos = "$filName $filePos";
|
---|
| 126 | if ( -e $idxFil ) {
|
---|
| 127 | unless ( replace( $idxFil, "^${filName} \\d+", $newPos ) ) {
|
---|
| 128 |
|
---|
| 129 | # could not replace -> append
|
---|
| 130 | blurt( "$newPos\n", $idxFil, { append => 1 } );
|
---|
| 131 | # my $INP;
|
---|
| 132 | # if ( !open ( $INP, ">>", $idxFil ) ) {
|
---|
| 133 | # ERROR("Could not append file [$idxFil]: $!");
|
---|
| 134 | # return 0;
|
---|
| 135 | # }
|
---|
| 136 | # print $INP "$newPos\n";
|
---|
| 137 | # close ($INP);
|
---|
| 138 | }
|
---|
| 139 | }
|
---|
| 140 | else {
|
---|
| 141 | # initial write
|
---|
| 142 | blurt( "$newPos\n", $idxFil );
|
---|
| 143 | #writeTextInFile( $idxFil, "$newPos\n" );
|
---|
| 144 | }
|
---|
| 145 | }
|
---|
| 146 | else {
|
---|
| 147 |
|
---|
| 148 | # Read current position from idxfile
|
---|
| 149 | # ----------------------------------
|
---|
| 150 | if ( !-e $idxFil ) {
|
---|
| 151 | return 0;
|
---|
| 152 | }
|
---|
| 153 | my $line = "";
|
---|
| 154 | my $idx_fh;
|
---|
| 155 | if ( !open ( $idx_fh, "<", $idxFil ) ) {
|
---|
| 156 | ERROR("Could not open for read $idxFil: $!");
|
---|
| 157 | return 0;
|
---|
| 158 | }
|
---|
| 159 | while (<$idx_fh>) {
|
---|
| 160 | if ( $_ =~ /^$filName (\d+)/ ) { # RegEx problematic with getContentFiltered()
|
---|
| 161 | $line .= $_;
|
---|
| 162 | $filePos = $1;
|
---|
| 163 | last;
|
---|
| 164 | }
|
---|
| 165 | }
|
---|
| 166 | close ($idx_fh);
|
---|
| 167 |
|
---|
| 168 | if ( $line && !$filePos ) {
|
---|
| 169 | ERROR("Could not read file position in idxfile $idxFil. Weired line \"$line\"");
|
---|
| 170 | $filePos = 0;
|
---|
| 171 | }
|
---|
| 172 |
|
---|
| 173 | if ($filePos) {
|
---|
| 174 |
|
---|
| 175 | # If the file was meanwhile newly created, e.g. after restart from BNC
|
---|
| 176 | my $fileSize = -s $file;
|
---|
| 177 | if ( $filePos > $fileSize ) {
|
---|
| 178 | WARN "file $file seams to be newly created";
|
---|
| 179 | DEBUG("size:$fileSize pos:$filePos");
|
---|
| 180 | $filePos = 0;
|
---|
| 181 | }
|
---|
| 182 | }
|
---|
| 183 | else {
|
---|
| 184 | WARN("No position entry found in idxfile for \"$filName\"");
|
---|
| 185 | $filePos = 0;
|
---|
| 186 | }
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | return $filePos;
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | #** @function isAlreadyRunning ()
|
---|
| 193 | # @brief Check if this program is already running.
|
---|
| 194 | # @details Usage: isAlreadyRunning() && die "Same job is already running!\n";@n
|
---|
| 195 | # Purpose: e.g. to avoid a program running twice
|
---|
| 196 | # @see bernese gpsutil::check_job()
|
---|
| 197 | # @return pid of already running job else false
|
---|
| 198 | #*
|
---|
| 199 | sub isAlreadyRunning {
|
---|
| 200 | my ($scriptName) = fileparse($0);
|
---|
| 201 |
|
---|
| 202 | use Proc::ProcessTable;
|
---|
| 203 | my $pt = new Proc::ProcessTable;
|
---|
| 204 | my $pid_found = 0; # from already running job
|
---|
| 205 | foreach my $p ( @{ $pt->table } ) {
|
---|
| 206 | my $cmd = $p->cmndline;
|
---|
| 207 | if ( $cmd =~ /$scriptName/ && $p->pid != $$ ) {
|
---|
| 208 | next if ( $p->pid == getppid () ); # when started from cron!
|
---|
| 209 | DEBUG( "Found: " . $p->pid . ", myself: $$" );
|
---|
| 210 | $pid_found = $p->pid;
|
---|
| 211 | DEBUG "Process $cmd is running";
|
---|
| 212 | last;
|
---|
| 213 | }
|
---|
| 214 | }
|
---|
| 215 |
|
---|
| 216 | return $pid_found;
|
---|
| 217 | }
|
---|
| 218 |
|
---|
[9627] | 219 | # toLineProtocol($rcd)
|
---|
| 220 | # @brief converts $rcd to InfluxDB line protocol
|
---|
| 221 | # @see https://docs.influxdata.com/influxdb/v2.1/reference/syntax/line-protocol/
|
---|
| 222 | sub toLineProtocol {
|
---|
| 223 | my ($rcd) = @_;
|
---|
| 224 |
|
---|
| 225 | my $err = 0;
|
---|
| 226 | my $str = $rcd->{'measurement'} . ',';
|
---|
| 227 |
|
---|
| 228 | # Tags (sort by key (in lexicographic order) for performance reasons)
|
---|
| 229 | foreach ( sort { lc ($a) cmp lc ($b) } keys %{ $rcd->{'tags'} } ) {
|
---|
| 230 | if ( !exists $rcd->{'tags'}->{$_} ) {
|
---|
| 231 | $err++;
|
---|
| 232 | #next; # ?
|
---|
| 233 | }
|
---|
| 234 | $str .= $_ . "=" . $rcd->{'tags'}->{$_} . ",";
|
---|
| 235 | }
|
---|
| 236 | chop $str;
|
---|
| 237 | $str .= " ";
|
---|
| 238 |
|
---|
| 239 | # Fields
|
---|
| 240 | while ( my ( $k, $v ) = each %{ $rcd->{'fields'} } ) {
|
---|
| 241 | if ( !defined $v ) {
|
---|
| 242 | $err++;
|
---|
| 243 | }
|
---|
| 244 | $str .= $k . "=" . $v . ",";
|
---|
| 245 | }
|
---|
| 246 | chop $str;
|
---|
| 247 | $str .= " " . $rcd->{'time'}; # prec=s
|
---|
| 248 | if ($err) {
|
---|
| 249 | return 0;
|
---|
| 250 | }
|
---|
| 251 | return $str;
|
---|
| 252 | }
|
---|
| 253 |
|
---|
[9597] | 254 | #** @function rms ($numbers)
|
---|
| 255 | # @brief Compute the Root mean square.
|
---|
| 256 | # @param $numbers [required] List with your numbers
|
---|
| 257 | # @return RMS value
|
---|
| 258 | # @note use PDL stats() if installed!
|
---|
| 259 | # @see http://rosettacode.org/wiki/Averages/Root_mean_square#Perl
|
---|
| 260 | #*
|
---|
| 261 | sub rms {
|
---|
| 262 | my $r = 0;
|
---|
| 263 | $r += $_**2 for @_;
|
---|
| 264 | return sqrt ( $r / @_ );
|
---|
| 265 | }
|
---|
| 266 |
|
---|
| 267 | #** @function avg ($numbers)
|
---|
| 268 | # @brief Compute the arithmetic mean value.
|
---|
| 269 | # @param $numbers [required] List with your numbers
|
---|
| 270 | # @note use PDL stats() if installed!
|
---|
| 271 | # @return mean value
|
---|
| 272 | # @see http://rosettacode.org/wiki/Average/Arithmetic_mean#Perl
|
---|
| 273 | #*
|
---|
| 274 | sub avg {
|
---|
| 275 | @_ or return 0;
|
---|
| 276 | my $sum = 0;
|
---|
| 277 | $sum += $_ foreach @_;
|
---|
| 278 | return $sum / @_;
|
---|
| 279 | }
|
---|
| 280 |
|
---|
| 281 | #** @function sigma ($numbers)
|
---|
| 282 | # @brief Compute the STDDEV of the arithmetic mean.
|
---|
| 283 | # @param $numbers [required] List with your numbers
|
---|
| 284 | # @return Sigma value
|
---|
| 285 | # @note use use PDL::Primitive qw(stats) if installed!
|
---|
| 286 | #*
|
---|
| 287 | sub sigma {
|
---|
| 288 | my $n = scalar (@_);
|
---|
| 289 |
|
---|
| 290 | if ( $n == 1 ) {
|
---|
| 291 | WARN "Could not compute sigma for only one sample";
|
---|
| 292 | return;
|
---|
| 293 | }
|
---|
| 294 |
|
---|
| 295 | my $e = 0; # arithm. Mittel
|
---|
| 296 | $e += $_ for @_;
|
---|
| 297 | $e = $e / $n;
|
---|
| 298 |
|
---|
| 299 | my @v = map { $e - $_ } @_;
|
---|
| 300 | my $vv = 0;
|
---|
| 301 | $vv += $_**2 for @v;
|
---|
| 302 | return sqrt ( $vv / ( $n * ( $n - 1 ) ) );
|
---|
| 303 | }
|
---|
| 304 |
|
---|
| 305 | #** @function median (@numbers)
|
---|
| 306 | # @brief Compute the median of a list of numbers.
|
---|
| 307 | # @param @numbers [required] List with your numbers
|
---|
| 308 | # @see https://en.wikipedia.org/wiki/Median
|
---|
| 309 | # @note use PDL stats() if installed!
|
---|
| 310 | # @return median
|
---|
| 311 | #*
|
---|
| 312 | sub median {
|
---|
| 313 | my $mid = int ( @_ / 2 );
|
---|
| 314 | my @sorted = sort { $a <=> $b } (@_);
|
---|
| 315 | if ( @sorted % 2 ) {
|
---|
| 316 | return $sorted[$mid];
|
---|
| 317 | }
|
---|
| 318 | else {
|
---|
| 319 | return ( $sorted[ $mid - 1 ] + $sorted[$mid] ) / 2;
|
---|
| 320 | }
|
---|
| 321 |
|
---|
| 322 | return; # undef
|
---|
| 323 | }
|
---|
| 324 |
|
---|
| 325 | 1; # End of Common
|
---|
| 326 |
|
---|