1#! @PERL@ 2 3##--------------------------------------------------------------------## 4##--- Massif's results printer ms_print.in ---## 5##--------------------------------------------------------------------## 6 7# This file is part of Massif, a Valgrind tool for profiling memory 8# usage of programs. 9# 10# Copyright (C) 2007-2017 Nicholas Nethercote 11# njn@valgrind.org 12# 13# This program is free software; you can redistribute it and/or 14# modify it under the terms of the GNU General Public License as 15# published by the Free Software Foundation; either version 2 of the 16# License, or (at your option) any later version. 17# 18# This program is distributed in the hope that it will be useful, but 19# WITHOUT ANY WARRANTY; without even the implied warranty of 20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21# General Public License for more details. 22# 23# You should have received a copy of the GNU General Public License 24# along with this program; if not, write to the Free Software 25# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 26# 02111-1307, USA. 27# 28# The GNU General Public License is contained in the file COPYING. 29 30use warnings; 31use strict; 32 33#---------------------------------------------------------------------------- 34# Global variables, main data structures 35#---------------------------------------------------------------------------- 36 37# Command line of profiled program. 38my $cmd; 39 40# Time unit used in profile. 41my $time_unit; 42 43# Threshold dictating what percentage an entry must represent for us to 44# bother showing it. 45my $threshold = 1.0; 46 47# Graph x and y dimensions. 48my $graph_x = 72; 49my $graph_y = 20; 50 51# Input file name 52my $input_file = undef; 53 54# Where to create tmp files. See also function VG_(tmpdir) in m_libcfile.c. 55my $tmp_dir = $ENV{"TMPDIR"}; 56$tmp_dir = "@VG_TMPDIR@" if (! $tmp_dir); 57$tmp_dir = "/tmp" if (! $tmp_dir); 58 59# Tmp file name. 60my $tmp_file = "$tmp_dir/ms_print.tmp.$$"; 61 62# Version number. 63my $version = "@VERSION@"; 64 65# Args passed, for printing. 66my $ms_print_args; 67 68# Usage message. 69my $usage = <<END 70usage: ms_print [options] massif-out-file 71 72 options for the user, with defaults in [ ], are: 73 -h --help show this message 74 --version show version 75 --threshold=<m.n> significance threshold, in percent [$threshold] 76 --x=<4..1000> graph width, in columns [72] 77 --y=<4..1000> graph height, in rows [20] 78 79 ms_print is Copyright (C) 2007-2017 Nicholas Nethercote. 80 and licensed under the GNU General Public License, version 2. 81 Bug reports, feedback, admiration, abuse, etc, to: njn\@valgrind.org. 82 83END 84; 85 86# Used in various places of output. 87my $fancy = '-' x 80; 88my $fancy_nl = $fancy . "\n"; 89 90# Returns 0 if the denominator is 0. 91sub safe_div_0($$) 92{ 93 my ($x, $y) = @_; 94 return ($y ? $x / $y : 0); 95} 96 97#----------------------------------------------------------------------------- 98# Argument and option handling 99#----------------------------------------------------------------------------- 100sub process_cmd_line() 101{ 102 my @files; 103 104 # Grab a copy of the arguments, for printing later. 105 for my $arg (@ARGV) { 106 $ms_print_args .= " $arg"; # The arguments. 107 } 108 109 for my $arg (@ARGV) { 110 111 # Option handling 112 if ($arg =~ /^-/) { 113 114 # --version 115 if ($arg =~ /^--version$/) { 116 die("ms_print-$version\n"); 117 118 # --threshold=X (tolerates a trailing '%') 119 } elsif ($arg =~ /^--threshold=([\d\.]+)%?$/) { 120 $threshold = $1; 121 ($1 >= 0 && $1 <= 100) or die($usage); 122 123 } elsif ($arg =~ /^--x=(\d+)$/) { 124 $graph_x = $1; 125 (4 <= $graph_x && $graph_x <= 1000) or die($usage); 126 127 } elsif ($arg =~ /^--y=(\d+)$/) { 128 $graph_y = $1; 129 (4 <= $graph_y && $graph_y <= 1000) or die($usage); 130 131 } else { # -h and --help fall under this case 132 die($usage); 133 } 134 } else { 135 # Not an option. Remember it as a filename. 136 push(@files, $arg); 137 } 138 } 139 140 # Must have chosen exactly one input file. 141 if (scalar @files) { 142 $input_file = $files[0]; 143 } else { 144 die($usage); 145 } 146} 147 148#----------------------------------------------------------------------------- 149# Reading the input file: auxiliary functions 150#----------------------------------------------------------------------------- 151 152# Gets the next line, stripping comments and skipping blanks. 153# Returns undef at EOF. 154sub get_line() 155{ 156 while (my $line = <INPUTFILE>) { 157 $line =~ s/#.*$//; # remove comments 158 if ($line !~ /^\s*$/) { 159 return $line; # return $line if non-empty 160 } 161 } 162 return undef; # EOF: return undef 163} 164 165sub equals_num_line($$) 166{ 167 my ($line, $fieldname) = @_; 168 defined($line) 169 or die("Line $.: expected \"$fieldname\" line, got end of file\n"); 170 $line =~ s/^$fieldname=(.*)\s*$// 171 or die("Line $.: expected \"$fieldname\" line, got:\n$line"); 172 return $1; 173} 174 175sub is_significant_XPt($$$) 176{ 177 my ($is_top_node, $xpt_szB, $total_szB) = @_; 178 ($xpt_szB <= $total_szB) or die; 179 # Nb: we always consider the alloc-XPt significant, even if the size is 180 # zero. 181 return $is_top_node || 0 == $threshold || 182 ( $total_szB != 0 && $xpt_szB * 100 / $total_szB >= $threshold ); 183} 184 185#----------------------------------------------------------------------------- 186# Reading the input file: reading heap trees 187#----------------------------------------------------------------------------- 188 189# Forward declaration, because it's recursive. 190sub read_heap_tree($$$$$); 191 192# Return pair: if the tree was significant, both are zero. If it was 193# insignificant, the first element is 1 and the second is the number of 194# bytes. 195sub read_heap_tree($$$$$) 196{ 197 # Read the line and determine if it is significant. 198 my ($is_top_node, $this_prefix, $child_midfix, $arrow, $mem_total_B) = @_; 199 my $line = get_line(); 200 (defined $line and $line =~ /^\s*n(\d+):\s*(\d+)(.*)$/) 201 or die("Line $.: expected a tree node line, got:\n$line\n"); 202 my $n_children = $1; 203 my $bytes = $2; 204 my $details = $3; 205 my $perc = safe_div_0(100 * $bytes, $mem_total_B); 206 # Nb: we always print the alloc-XPt, even if its size is zero. 207 my $is_significant = is_significant_XPt($is_top_node, $bytes, $mem_total_B); 208 209 # We precede this node's line with "$this_prefix.$arrow". We precede 210 # any children of this node with "$this_prefix$child_midfix$arrow". 211 if ($is_significant) { 212 # Nb: $details might have '%' in it, so don't embed directly in the 213 # format string. 214 printf(TMPFILE 215 "$this_prefix$arrow%05.2f%% (%sB)%s\n", $perc, commify($bytes), 216 $details); 217 } 218 219 # Now read all the children. 220 my $n_insig_children = 0; 221 my $total_insig_children_szB = 0; 222 my $this_prefix2 = $this_prefix . $child_midfix; 223 for (my $i = 0; $i < $n_children; $i++) { 224 # If child is the last sibling, the midfix is empty. 225 my $child_midfix2 = ( $i+1 == $n_children ? " " : "| " ); 226 my ($is_child_insignificant, $child_insig_bytes) = 227 # '0' means it's not the top node of the tree. 228 read_heap_tree(0, $this_prefix2, $child_midfix2, "->", 229 $mem_total_B); 230 $n_insig_children += $is_child_insignificant; 231 $total_insig_children_szB += $child_insig_bytes; 232 } 233 234 if ($is_significant) { 235 # If this was significant but any children were insignificant, print 236 # the "in N places" line for them. 237 if ($n_insig_children > 0) { 238 $perc = safe_div_0(100 * $total_insig_children_szB, $mem_total_B); 239 printf(TMPFILE "%s->%05.2f%% (%sB) in %d+ places, all below " 240 . "ms_print's threshold (%05.2f%%)\n", 241 $this_prefix2, $perc, commify($total_insig_children_szB), 242 $n_insig_children, $threshold); 243 print(TMPFILE "$this_prefix2\n"); 244 } 245 246 # If this node has no children, print an extra (mostly) empty line. 247 if (0 == $n_children) { 248 print(TMPFILE "$this_prefix2\n"); 249 } 250 return (0, 0); 251 252 } else { 253 return (1, $bytes); 254 } 255} 256 257#----------------------------------------------------------------------------- 258# Reading the input file: main 259#----------------------------------------------------------------------------- 260 261sub max_label_2($$) 262{ 263 my ($szB, $szB_scaled) = @_; 264 265 # For the label, if $szB is 999B or below, we print it as an integer. 266 # Otherwise, we print it as a float with 5 characters (including the '.'). 267 # Examples (for bytes): 268 # 1 --> 1 B 269 # 999 --> 999 B 270 # 1000 --> 0.977 KB 271 # 1024 --> 1.000 KB 272 # 10240 --> 10.00 KB 273 # 102400 --> 100.0 KB 274 # 1024000 --> 0.977 MB 275 # 1048576 --> 1.000 MB 276 # 277 if ($szB < 1000) { return sprintf("%5d", $szB); } 278 elsif ($szB_scaled < 10) { return sprintf("%5.3f", $szB_scaled); } 279 elsif ($szB_scaled < 100) { return sprintf("%5.2f", $szB_scaled); } 280 else { return sprintf("%5.1f", $szB_scaled); } 281} 282 283# Work out the units for the max value, measured in instructions. 284sub i_max_label($) 285{ 286 my ($nI) = @_; 287 288 # We repeat until the number is less than 1000. 289 my $nI_scaled = $nI; 290 my $unit = "i"; 291 # Nb: 'k' is the "kilo" (1000) prefix. 292 if ($nI_scaled >= 1000) { $unit = "ki"; $nI_scaled /= 1024; } 293 if ($nI_scaled >= 1000) { $unit = "Mi"; $nI_scaled /= 1024; } 294 if ($nI_scaled >= 1000) { $unit = "Gi"; $nI_scaled /= 1024; } 295 if ($nI_scaled >= 1000) { $unit = "Ti"; $nI_scaled /= 1024; } 296 if ($nI_scaled >= 1000) { $unit = "Pi"; $nI_scaled /= 1024; } 297 if ($nI_scaled >= 1000) { $unit = "Ei"; $nI_scaled /= 1024; } 298 if ($nI_scaled >= 1000) { $unit = "Zi"; $nI_scaled /= 1024; } 299 if ($nI_scaled >= 1000) { $unit = "Yi"; $nI_scaled /= 1024; } 300 301 return (max_label_2($nI, $nI_scaled), $unit); 302} 303 304# Work out the units for the max value, measured in bytes. 305sub B_max_label($) 306{ 307 my ($szB) = @_; 308 309 # We repeat until the number is less than 1000, but we divide by 1024 on 310 # each scaling. 311 my $szB_scaled = $szB; 312 my $unit = "B"; 313 # Nb: 'K' or 'k' are acceptable as the "binary kilo" (1024) prefix. 314 # (Strictly speaking, should use "KiB" (kibibyte), "MiB" (mebibyte), etc, 315 # but they're not in common use.) 316 if ($szB_scaled >= 1000) { $unit = "KB"; $szB_scaled /= 1024; } 317 if ($szB_scaled >= 1000) { $unit = "MB"; $szB_scaled /= 1024; } 318 if ($szB_scaled >= 1000) { $unit = "GB"; $szB_scaled /= 1024; } 319 if ($szB_scaled >= 1000) { $unit = "TB"; $szB_scaled /= 1024; } 320 if ($szB_scaled >= 1000) { $unit = "PB"; $szB_scaled /= 1024; } 321 if ($szB_scaled >= 1000) { $unit = "EB"; $szB_scaled /= 1024; } 322 if ($szB_scaled >= 1000) { $unit = "ZB"; $szB_scaled /= 1024; } 323 if ($szB_scaled >= 1000) { $unit = "YB"; $szB_scaled /= 1024; } 324 325 return (max_label_2($szB, $szB_scaled), $unit); 326} 327 328# Work out the units for the max value, measured in ms/s/h. 329sub t_max_label($) 330{ 331 my ($szB) = @_; 332 333 # We scale from millisecond to seconds to hours. 334 # 335 # XXX: this allows a number with 6 chars, eg. "3599.0 s" 336 my $szB_scaled = $szB; 337 my $unit = "ms"; 338 if ($szB_scaled >= 1000) { $unit = "s"; $szB_scaled /= 1000; } 339 if ($szB_scaled >= 3600) { $unit = "h"; $szB_scaled /= 3600; } 340 341 return (max_label_2($szB, $szB_scaled), $unit); 342} 343 344# This prints four things: 345# - the output header 346# - the graph 347# - the snapshot summaries (number, list of detailed ones) 348# - the snapshots 349# 350# The first three parts can't be printed until we've read the whole input file; 351# but the fourth part is much easier to print while we're reading the file. So 352# we print the fourth part to a tmp file, and then dump the tmp file at the 353# end. 354# 355sub read_input_file() 356{ 357 my $desc = ""; # Concatenated description lines. 358 my $peak_mem_total_szB = 0; 359 360 # Info about each snapshot. 361 my @snapshot_nums = (); 362 my @times = (); 363 my @mem_total_Bs = (); 364 my @is_detaileds = (); 365 my $peak_num = -1; # An initial value that will be ok if no peak 366 # entry is in the file. 367 368 #------------------------------------------------------------------------- 369 # Read start of input file. 370 #------------------------------------------------------------------------- 371 open(INPUTFILE, "< $input_file") 372 || die "Cannot open $input_file for reading\n"; 373 374 # Read "desc:" lines. 375 my $line; 376 while ($line = get_line()) { 377 if ($line =~ s/^desc://) { 378 $desc .= $line; 379 } else { 380 last; 381 } 382 } 383 384 # Read "cmd:" line (Nb: will already be in $line from "desc:" loop above). 385 ($line =~ /^cmd:\s*(.*)$/) or die("Line $.: missing 'cmd' line\n"); 386 $cmd = $1; 387 388 # Read "time_unit:" line. 389 $line = get_line(); 390 ($line =~ /^time_unit:\s*(.*)$/) or 391 die("Line $.: missing 'time_unit' line\n"); 392 $time_unit = $1; 393 394 #------------------------------------------------------------------------- 395 # Print snapshot list header to $tmp_file. 396 #------------------------------------------------------------------------- 397 open(TMPFILE, "> $tmp_file") 398 || die "Cannot open $tmp_file for writing\n"; 399 400 my $time_column = sprintf("%14s", "time($time_unit)"); 401 my $column_format = "%3s %14s %16s %16s %13s %12s\n"; 402 my $header = 403 $fancy_nl . 404 sprintf($column_format 405 , "n" 406 , $time_column 407 , "total(B)" 408 , "useful-heap(B)" 409 , "extra-heap(B)" 410 , "stacks(B)" 411 ) . 412 $fancy_nl; 413 print(TMPFILE $header); 414 415 #------------------------------------------------------------------------- 416 # Read body of input file. 417 #------------------------------------------------------------------------- 418 $line = get_line(); 419 while (defined $line) { 420 my $snapshot_num = equals_num_line($line, "snapshot"); 421 my $time = equals_num_line(get_line(), "time"); 422 my $mem_heap_B = equals_num_line(get_line(), "mem_heap_B"); 423 my $mem_heap_extra_B = equals_num_line(get_line(), "mem_heap_extra_B"); 424 my $mem_stacks_B = equals_num_line(get_line(), "mem_stacks_B"); 425 my $mem_total_B = $mem_heap_B + $mem_heap_extra_B + $mem_stacks_B; 426 my $heap_tree = equals_num_line(get_line(), "heap_tree"); 427 428 # Print the snapshot data to $tmp_file. 429 printf(TMPFILE $column_format, 430 , $snapshot_num 431 , commify($time) 432 , commify($mem_total_B) 433 , commify($mem_heap_B) 434 , commify($mem_heap_extra_B) 435 , commify($mem_stacks_B) 436 ); 437 438 # Remember the snapshot data. 439 push(@snapshot_nums, $snapshot_num); 440 push(@times, $time); 441 push(@mem_total_Bs, $mem_total_B); 442 push(@is_detaileds, ( $heap_tree eq "empty" ? 0 : 1 )); 443 $peak_mem_total_szB = $mem_total_B 444 if $mem_total_B > $peak_mem_total_szB; 445 446 # Read the heap tree, and if it's detailed, print it and a subsequent 447 # snapshot list header to $tmp_file. 448 if ($heap_tree eq "empty") { 449 $line = get_line(); 450 } elsif ($heap_tree =~ "(detailed|peak)") { 451 # If "peak", remember the number. 452 if ($heap_tree eq "peak") { 453 $peak_num = $snapshot_num; 454 } 455 # '1' means it's the top node of the tree. 456 read_heap_tree(1, "", "", "", $mem_total_B); 457 458 # Print the header, unless there are no more snapshots. 459 $line = get_line(); 460 if (defined $line) { 461 print(TMPFILE $header); 462 } 463 } else { 464 die("Line $.: expected 'empty' or '...' after 'heap_tree='\n"); 465 } 466 } 467 468 close(INPUTFILE); 469 close(TMPFILE); 470 471 #------------------------------------------------------------------------- 472 # Print header. 473 #------------------------------------------------------------------------- 474 print($fancy_nl); 475 print("Command: $cmd\n"); 476 print("Massif arguments: $desc"); 477 print("ms_print arguments:$ms_print_args\n"); 478 print($fancy_nl); 479 print("\n\n"); 480 481 #------------------------------------------------------------------------- 482 # Setup for graph. 483 #------------------------------------------------------------------------- 484 # The ASCII graph. 485 # Row 0 ([0..graph_x][0]) is the X-axis. 486 # Column 0 ([0][0..graph_y]) is the Y-axis. 487 # The rest ([1][1]..[graph_x][graph_y]) is the usable graph area. 488 my @graph; 489 my $x; 490 my $y; 491 492 my $n_snapshots = scalar(@snapshot_nums); 493 ($n_snapshots > 0) or die; 494 my $end_time = $times[$n_snapshots-1]; 495 ($end_time >= 0) or die; 496 497 # Setup graph[][]. 498 $graph[0][0] = '+'; # axes join point 499 for ($x = 1; $x <= $graph_x; $x++) { $graph[$x][0] = '-'; } # X-axis 500 for ($y = 1; $y <= $graph_y; $y++) { $graph[0][$y] = '|'; } # Y-axis 501 $graph[$graph_x][0] = '>'; # X-axis arrow 502 $graph[0][$graph_y] = '^'; # Y-axis arrow 503 for ($x = 1; $x <= $graph_x; $x++) { # usable area 504 for ($y = 1; $y <= $graph_y; $y++) { 505 $graph[$x][$y] = ' '; 506 } 507 } 508 509 #------------------------------------------------------------------------- 510 # Write snapshot bars into graph[][]. 511 #------------------------------------------------------------------------- 512 # Each row represents K bytes, which is 1/graph_y of the peak size 513 # (and K can be non-integral). When drawing the column for a snapshot, 514 # in order to fill the slot in row y (where the first row drawn on is 515 # row 1) with a full-char (eg. ':'), it must be >= y*K. For example, if 516 # K = 10 bytes, then the values 0, 4, 5, 9, 10, 14, 15, 19, 20, 24, 25, 517 # 29, 30 would be drawn like this (showing one per column): 518 # 519 # y y * K 520 # - ----------- 521 # 30 | : 3 3 * 10 = 30 522 # 20 | ::::: 2 2 * 10 = 20 523 # 10 | ::::::::: 1 1 * 10 = 10 524 # 0 +------------- 525 526 my $peak_char = '#'; 527 my $detailed_char = '@'; 528 my $normal_char = ':'; 529 530 # Work out how many bytes each row represents. If the peak size was 0, 531 # make it 1 so that the Y-axis covers a non-zero range of values. 532 # Likewise for end_time. 533 if (0 == $peak_mem_total_szB) { $peak_mem_total_szB = 1; } 534 if (0 == $end_time ) { $end_time = 1; } 535 my $K = $peak_mem_total_szB / $graph_y; 536 537 $x = 0; 538 my $prev_x = 0; 539 my $prev_y_max = 0; 540 my $prev_char = ':'; 541 542 for (my $i = 0; $i < $n_snapshots; $i++) { 543 544 # Work out which column this snapshot belongs to. 545 $prev_x = $x; 546 my $x_pos_frac = ($times[$i] / ($end_time)) * $graph_x; 547 $x = int($x_pos_frac) + 1; # +1 due to Y-axis 548 # The final snapshot will spill over into the n+1th column, which 549 # doesn't get shown. So we fudge that one and pull it back a 550 # column, as if the end_time was actually end_time+epsilon. 551 if ($times[$i] == $end_time) { 552 ($x == $graph_x+1) or die; 553 $x = $graph_x; 554 } 555 556 # If there was a gap between the previous snapshot's column and this 557 # one, we draw a horizontal line in the gap (so long as it doesn't 558 # trash the x-axis). Without this, graphs with a few sparse 559 # snapshots look funny -- as if the memory usage is in temporary 560 # spikes. 561 if ($prev_y_max > 0) { 562 for (my $x2 = $prev_x + 1; $x2 < $x; $x2++) { 563 $graph[$x2][$prev_y_max] = $prev_char; 564 } 565 } 566 567 # Choose the column char. 568 my $char; 569 if ($i == $peak_num) { $char = $peak_char; } 570 elsif ($is_detaileds[$i]) { $char = $detailed_char; } 571 else { $char = $normal_char; } 572 573 # Grow this snapshot bar from bottom to top. 574 my $y_max = 0; 575 for ($y = 1; $y <= $graph_y; $y++) { 576 if ($mem_total_Bs[$i] >= $y * $K) { 577 # Priority order for chars: peak > detailed > normal 578 my $should_draw_char = 579 (($char eq $peak_char) 580 or 581 ($char eq $detailed_char and 582 $graph[$x][$y] ne $peak_char 583 ) 584 or 585 ($char eq $normal_char and 586 $graph[$x][$y] ne $peak_char and 587 $graph[$x][$y] ne $detailed_char 588 ) 589 ); 590 591 if ($should_draw_char) { 592 $graph[$x][$y] = $char; 593 } 594 $y_max = $y; 595 } 596 } 597 $prev_y_max = $y_max; 598 $prev_char = $char; 599 } 600 601 #------------------------------------------------------------------------- 602 # Print graph[][]. 603 #------------------------------------------------------------------------- 604 my ($y_label, $y_unit) = B_max_label($peak_mem_total_szB); 605 my ($x_label, $x_unit); 606 if ($time_unit eq "i") { ($x_label, $x_unit) = i_max_label($end_time) } 607 elsif ($time_unit eq "ms") { ($x_label, $x_unit) = t_max_label($end_time) } 608 elsif ($time_unit eq "B") { ($x_label, $x_unit) = B_max_label($end_time) } 609 else { die "bad time_unit: $time_unit\n"; } 610 611 printf(" %2s\n", $y_unit); 612 for ($y = $graph_y; $y >= 0; $y--) { 613 if ($graph_y == $y) { # top row 614 print($y_label); 615 } elsif (0 == $y) { # bottom row 616 print(" 0 "); 617 } else { # anywhere else 618 print(" "); 619 } 620 621 # Axis and data for the row. 622 for ($x = 0; $x <= $graph_x; $x++) { 623 printf("%s", $graph[$x][$y]); 624 } 625 if (0 == $y) { 626 print("$x_unit\n"); 627 } else { 628 print("\n"); 629 } 630 } 631 printf(" 0%s%5s\n", ' ' x ($graph_x-5), $x_label); 632 633 #------------------------------------------------------------------------- 634 # Print snapshot numbers. 635 #------------------------------------------------------------------------- 636 print("\n"); 637 print("Number of snapshots: $n_snapshots\n"); 638 print(" Detailed snapshots: ["); 639 my $first_detailed = 1; 640 for (my $i = 0; $i < $n_snapshots; $i++) { 641 if ($is_detaileds[$i]) { 642 if ($first_detailed) { 643 printf("$i"); 644 $first_detailed = 0; 645 } else { 646 printf(", $i"); 647 } 648 if ($i == $peak_num) { 649 print(" (peak)"); 650 } 651 } 652 } 653 print("]\n\n"); 654 655 #------------------------------------------------------------------------- 656 # Print snapshots, from $tmp_file. 657 #------------------------------------------------------------------------- 658 open(TMPFILE, "< $tmp_file") 659 || die "Cannot open $tmp_file for reading\n"; 660 661 while (my $line = <TMPFILE>) { 662 print($line); 663 } 664 unlink($tmp_file); 665} 666 667#----------------------------------------------------------------------------- 668# Misc functions 669#----------------------------------------------------------------------------- 670sub commify ($) { 671 my ($val) = @_; 672 1 while ($val =~ s/^(\d+)(\d{3})/$1,$2/); 673 return $val; 674} 675 676 677#---------------------------------------------------------------------------- 678# "main()" 679#---------------------------------------------------------------------------- 680process_cmd_line(); 681read_input_file(); 682 683##--------------------------------------------------------------------## 684##--- end ms_print.in ---## 685##--------------------------------------------------------------------## 686