#!/usr/bin/perl



###########################################################################
# THIS PROGRAM IS DISTRIBUTED WITH THE BOOK:
#   How to Build Your Desktop Supercomputer ISBN 9798161121221 Oct 2026 
#
# Copyright © 2015-2026 Bassem W. Jamaleddine
# Copyright © 2026 Total Computing & Network Design, Inc.
# All rights reserved.
#
# READ THE LICENSE AGREEMENT BEFORE USING THIS SOFTWARE
#
# The Licensed Software is copyrighted by TOTAL COMPUTING & NETWORK DESIGN, INC.
# The source code, which is this program and all substantial portion of the
# Software on which the above copyright appears, cannot me modified or
# altered from the original without prior written authorization from
# TOTAL COMPUTING & NETWORK DESIGN, INC.
#
# The above copyright notice shall be included in
# all copies or substantial portions of the Software.
#
# TOTAL COMPUTING & NETWORK DESIGN INC. DISCLAIMS ALL WARRANTIES
# RELATING TO THIS SOFTWARE, WHETHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF 
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ALL
# SUCH WARRANTIES ARE EXPRESSLY AND SPECIFICALLY DISCLAIMED.
# NEITHER TOTAL COMPUTING & NETWORK DESIGN, INC. NOR
# ANYONE ELSE WHO HAS BEEN INVOLVED IN THE CREATION,
# PRODUCTION, OR DELIVERY OF THIS SOFTWARE SHALL BE LIABLE
# FOR ANY INDIRECT, CONSEQUENTIAL, OR INCIDENTAL DAMAGES
# ARISING OUT OF THE USE OR INABILITY TO USE SUCH SOFTWARE
# EVEN IF TOTAL COMPUTING & NETWORK DESIGN, INC. HAS BEEN
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR CLAIMS. IN NO
# EVENT SHALL TOTAL COMPUTING & NETWORK DESIGN, INC.'S
# LIABILITY FOR ANY DAMAGES EVER EXCEED THE PRICE PAID FOR THE
# LICENSE TO USE THE SOFTWARE, REGARDLESS OF THE FORM OF CLAIM. THE
# PERSON USING THE SOFTWARE BEARS ALL RISK AS TO THE QUALITY
# AND PERFORMANCE OF THE SOFTWARE.
#
# Surface mail:
# =============
# TOTAL COMPUTING & NETWORK DESIGN, INC.
# P.O.BOX 680
# New York, NY 10156
# USA
#
# FAX: +1-212-208-3039
# PHONE: +1-212-203-4379
#
# Author: Bassem W. Jamaleddine
# Email: jamaleddineb@aol.com
###########################################################################

use strict;
use Data::Dumper;

my $BRIEF = <<BRIEF;
Usage:

  $0 [-help] [-info] [-notime] [-epotime] [-cache] [-fromcached] [-replay]
       [-iter] [-delay] [-transpose] [-interrupts] [-headonce] 
       [-sortby] [-keeporder] [-NVU] [-prune]

dint is UNIX command to display the interrupts per CPU core.

dint can cache the results in the directory /tmp/cached_sysints
for replay at a later time. You can specify an alternate
directory by exporting the environment variable, for example:

  export DINT_CACHEDIR=/home/johndoe/cached

Use -info to take a quick look at your system interrupts:
  dint -info
These are the same that can be seen on Linux by issuing:
  cat /dev/interrupts

To display the first three cores and the last four cores use the
option -prune:
  dint -prune 3,4

To display the CPUs per line and the interrupts per column use
the option -transpose:


To collect the CPUs and their interrupts in a loop iteration use the
options -iter and -delay:
  dint  -iter 3 -delay 1
  dint  -iter 3 -delay 1 -prune 2,3

To collect and cache the data of an iteration to be replayed later on,
use the -cache option:
  dint  -iter 3 -delay 1 -prune 2,3 -cache

after command completed, this will print

INTERRUPTS CACHED IN /tmp/cached_sysints/interrupts_1718854605
  YOU CAN REPLAY THE INTERRUPTS COLLECTION BY ADDING THE OPTION -replay 1718854605

so now we can replay from cache the cached handle 1718854605
  dint  -iter 3 -delay 1 -prune 2,3 -replay 1718854605
  dint  -iter 3 -delay 1 -prune 3,4 -replay 1718854605 -transpose
  dint  -iter 3 -delay 1 -replay 1718854605 -transpose
  dint  -replay 1718854605 -transpose
display only 
  dint  -iter 3 -delay 1 -replay 1718854605 -transpose -prune 2,5 -interrupts PMI,MCE
display everything in the loop at once without delay:
  dint -replay 1718854605 -transpose -prune 2,5 -interrupts PMI,MCE -iter 999

When caching the data, all data is retained even if you pruned some cores while
the command is running, and replay can be used with any option.

The following command will display only the 5 busiest cores for the selected
interrupts TRM,LOC,4,8 and sorting them by the LOC interrupt, the -keeporder is
used to display the interrupts per the order known to your system interrupts:
  dint -iter 3 -delay 1 -NVU 5 -sortby LOC -transpose -interrupts TRM,LOC,4,8 -keeporder

BRIEF
my $USAGE = <<USAGE;
   5   10        20        30        40        50        60        70        80
|...+....1....+....2....+....3....+....4....+....5....+....6....+....7...
           |...+....1....+....2....+....3....+....4....+....5....+....6....+....7...

Usage:

  $0 [-help] [-info] [-notime] [-epotime] [-cache] [-fromcached] [-replay]
       [-iter] [-delay] [-transpose] [-interrupts] [-headonce] 
       [-sortby] [-keeporder] [-NVU] [-prune]

Where:

  -help                  Display this summary
  -info                  Print interrupts known in your system
  -notime                Don't display the time
  -epotime               Print using epock time
  -cache                 Cache result for later replay 
  -fromcached EPOTIME    Run command replaying from cache
  -replay EPOTIME        Run command replaying from cache
  -iter N                Iterate N times
  -delay D               Delay by D seconds between each iteration
  -transpose             Transpose CPUs and Interrupts
  -interrupts I1,I2,..   Specify the list of interrupts from the list
                           known to your system. Use -info to view them.
  -headonce              In an iteration print the header just once
  -sortby INT            You can specify one interrupt by name to have
                            it sorted by the order of CPUs
  -keeporder             Keep the order of the interrupts as being displayed
                            in /proc/interrupts. Use this option when specifying
                            the -interrupts option followed by any unorderd list
                            of interrupts  
  -NVU N                 Display only the top N cores this is useful when specifying
                            -sortby INT and you have lot of cores:
                           # dint -iter 3 -delay 1 -NVU 5 -sortby LOC -transpose -interrupts TRM,LOC,MC
  -prune n1,n2           Prune the cores showing only the first n1 cores 
                            and the last n2 cores; where n1 and n2 are numbers
  
USAGE
my $fh;

my $transpose = 0;

my $iter;
my $delay;
my $tranpose = 0;
my $interrupts;
my $headonce = 0;
my $sortby;
my $keeporder = 0;
my $NVU;
my $prune;
my $showtime = 1;
my $epotime = 0;

my $CACHED = 0;
my $FROMCACHED = 0;
my $TM;
my $INFO = 0;
my $CACHEDIR = '/tmp/cached_sysints';
if (defined $ENV{DINT_CACHEDIR}) {
	if  (-d $ENV{DINT_CACHEDIR}) {
		$CACHEDIR = $ENV{DINT_CACHEDIR};
	}
	else {
		print "
The env. variable DINT_CACHEDIR=$ENV{DINT_CACHEDIR} but the directory $ENV{DINT_CACHEDIR} does not exist!
You need to create the directory $ENV{DINT_CACHEDIR}
Program exiting.
";
	}
}
else {
	unless (-d $CACHEDIR) {
		`mkdir $CACHEDIR`;
	}
}

# if0block deleted
while(@ARGV) {
	if ($ARGV[0] eq '-info') { #
		shift @ARGV;
		$INFO = 1;
	}
	elsif ($ARGV[0] eq '-notime') { #
		shift @ARGV;
		$showtime = 0;
	}
	elsif ($ARGV[0] eq '-epotime') { #
		shift @ARGV;
		$epotime = 1;
	}
	elsif ($ARGV[0] eq '-cache') { #
		shift @ARGV;
		$CACHED = 1;
		$TM = time;
	}
	elsif ($ARGV[0] eq '-fromcached') { #
		shift @ARGV;
		$TM = shift @ARGV;
		$FROMCACHED = 1;
	}
	elsif ($ARGV[0] eq '-replay') { #
		shift @ARGV;
		$TM = shift @ARGV;
		$FROMCACHED = 1;
	}
	elsif ($ARGV[0] eq '-iter') { #
		shift @ARGV;
		$iter = shift @ARGV;
	} elsif ($ARGV[0] eq '-delay') { #
		shift @ARGV;
		$delay = shift @ARGV;
	} elsif ($ARGV[0] eq '-transpose') { # transpose the displayable matrix
		shift @ARGV;
		$transpose = 1;
	} elsif ($ARGV[0] eq '-interrupts') {
		shift @ARGV;
		$interrupts = shift @ARGV;
	} elsif ($ARGV[0] eq '-headonce') {
		shift @ARGV;
		$headonce = 1;
	} elsif ($ARGV[0] eq '-sortby') {
		shift @ARGV;
		$sortby = shift @ARGV;
	} elsif ($ARGV[0] eq '-keeporder') {
		shift @ARGV;
		$keeporder = 1;
	} elsif ($ARGV[0] eq '-NVU') {
		shift @ARGV;
		$NVU = shift @ARGV;
	} elsif ($ARGV[0] eq '-prune') {
		shift @ARGV;
		$prune = shift @ARGV;
	} elsif ($ARGV[0] eq '-www?') {
		open(F, 'lynx -dump https://www.tcnd.com/tools/dint.html|');
		while(<F>) {print $_; }
		exit;
	} elsif (($ARGV[0] eq "-h") || ($ARGV[0] eq "-help") || ($ARGV[0] eq "--Help") || ($ARGV[0] eq '-?')) {
		usage();
	} elsif (($ARGV[0] eq "-V") || ($ARGV[0] eq "--Version")) {
		pversion();
	} else { die "unrecognized arguments <@ARGV>"; }
}

if (($sortby) && (!$transpose)) { # headonce not allowed on sorted unless transpose
	$headonce = 0;
}
if ($epotime) { $showtime = 0; } # cancel showtime if epotime is selected and set to 1

if ($CACHED && $FROMCACHED) {
	die "CANNOT CACHE INTERRUPTS AND READ FROMCACHED AT ONCE!\n";
}

if ($CACHED) {
	my $cachefile = "$CACHEDIR/" . "interrupts_" . $TM;
	open($fh, '>',$cachefile) || die "cannot open file $cachefile for writing: $!\n";
	cacheintrspec($TM);
}

if ($FROMCACHED) {
	my $cachefile = "$CACHEDIR/" . "interrupts_$TM";
	die "CANNOT READ THE CACHED INTERRUPTS FILE $cachefile\n\n" unless -r $cachefile;
	open(FROMCACHED, "<$cachefile");
}

sub usage { print $USAGE; print $BRIEF; exit; }

my $CPUCNT;

my $IR = [];
my $ILONG = [];

my $A = [];
if ($FROMCACHED) { getintrspec($TM,$A,$IR,$ILONG); } else { getintr1st($A,$IR,$ILONG); }
if ($INFO) {
	print "  CPU COUNT $CPUCNT\n\n";
	print "  INTERRUPTS MNEMONICS: @$IR\n\n";
for (my $i=0; $i<@$IR; $i++) {
	printf "%6s    %s\n",$IR->[$i],$ILONG->[$i];
}
	print "\n  THE CACHEDIR IS $CACHEDIR\n";

# if0block deleted
exit;
}

# if0block deleted
$transpose //= 0;
$iter //= 1;
$delay //= 0;
my $DAA = $iter > 1 ? 1 : 0;

if (defined $interrupts) {
	my $IRx = join '|', @$IR;
	foreach (split(/\s*\,\s*/,$interrupts)) {
		die "UNKNOW INTERRUPT SPECIFIED: $_\n  MUST BE ONE OF:@$IR\n\n" unless $_ =~/^($IRx)$/i;
	}
}

$NVU //= $CPUCNT;
$interrupts //= join '|', @$IR;
my $iMne = join '|', split(/\s*\,\s*/,$interrupts);
my @IIINAME;
my @III;
if ($keeporder) {
	for (my $i=0;$i<@$IR;$i++) {
		my $t = $IR->[$i];
		if ($t =~ /^($iMne)$/i) { push(@III,$i); push(@IIINAME,$IR->[$i]); }
	}
}
else {
	@IIINAME = split(/\|/,$iMne);
	foreach my $n (@IIINAME) {
		for (my $i=0;$i<@$IR;$i++) {
			my $t = $IR->[$i];
			if ($t =~ /^$n$/i) { push(@III,$i); }
		}
	}
}
my $ONCE = 1;

loopint(
	{
		iter=>$iter, # 4,
		delay=>$delay, # 1,
		transpose=>$transpose, # 0,
		headonce=>$headonce,
		NVU=>$NVU,
		prune=>$prune,
		sortby => $sortby,
		showtime=>$showtime,
		epotime=>$epotime,
	}
);

if ($CACHED) {
	my $cachefile = "$CACHEDIR/" . "interrupts_" . $TM;
	print "INTERRUPTS CACHED IN $cachefile\n  YOU CAN REPLAY THE INTERRUPTS COLLECTION BY ADDING THE OPTION -replay $TM\n\n";
}

sub odl_cpus {
my($select,$C,$IR,$CPUs) = @_;
my %odl;
	foreach (my $i=0; $i <=$#{$C}; $i++) {
		my $t =  $IR->[$i];
		next unless $t =~ /^($select)$/i;
		map{ $odl{$_} = $C->[$i]->[$_] } (0..$#{$C->[0]});
	}
	foreach my $cpuid (reverse sort {$odl{$a}<=>$odl{$b}} (keys %odl)) {
		push(@$CPUs,$cpuid);
	}
}

sub loopint {
my $args = shift;
my ($iter,$delay,$transpose,$headonce,$NVU,$prune,$sortby,$showtime,$epotime) = 
	map{$args->{$_}}qw(iter delay transpose headonce NVU prune sortby showtime epotime);
# if0block deleted

my $A = [];
if ($FROMCACHED) { fromcached($A); } else { getintr($A); }

$headonce //= 0;
my $sorted = defined $sortby ? 1 : 0;

my $C = [];

my ($fromJ,$toJ) = (9000000,0);
{
	my ($copruLT,$copruRT) = (99999999,0);
	if ((defined $prune) && ($prune =~ /(\d+),(\d+)/)) {
		my($ij1,$ij2) = ($1,$2);
		$copruLT = $ij1-1; # $mcST = $j1;
		$copruRT = $ij2-1; # $mcED = $j2;
	}
	my ($mcST,$mcED) = (0,$CPUCNT-1);
	($fromJ,$toJ) = ($mcST+$copruLT+1, $mcED-$copruRT); # columns skipper
}

my ($fromI,$toI) = (9000000,0); # from row to row, i is rowno
{
	my ($ropruTP,$ropruBT) = (99999999,0);
	if ((defined $prune) && ($prune =~ /(\d+),(\d+)/)) {
		my($ij1,$ij2) = ($1,$2);
		$ropruTP = $ij1-1; # $mrST = $i1;
		$ropruBT = $ij2-1; # $mrED = $i2;
	}
	my ($mrST,$mrED) = (0,$CPUCNT-1);
	($fromI,$toI) = ($mrST+$ropruTP+1, $mrED-$ropruBT); # rows skipper
}

my $HDR;
		{ # sorted or not sorted will always rebuild the header
my $CPUs = [];
$CPUs = [0..$CPUCNT-1];
my @CCC = @$CPUs[0..$NVU-1];
			my $tt = sprintf "%19s  %6s ", " ", " ";
			my $once = 1;
			my $cnt = -1;
			foreach my $j (@CCC) {
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
				if ($once && ($cnt >= $fromJ)) { $once = 0; $tt .= sprintf("%9s ","..."); }
				$tt .= sprintf("%9s ","CPU$j");
			}
$tt .= sprintf("%9s ","summed");
$HDR = $tt;
		}
my $HDRt;
{
				my $tt = sprintf "%19s  %6s ", " ", " ";
				$tt .= sprintf("%9s ","$_") foreach (@IIINAME);
		$HDRt = $tt;
}
for (my $it=1; $it <= $iter; $it++) {
	my $B = [];
my $dummy = 0;
my $tIm = \$dummy;

	if ($it == 1) { sleep $DAA; } # warmup
	if ($FROMCACHED) { fromcached($B,$tIm); } else { getintr($B,$tIm); }

# if0block deleted
# if0block deleted
	foreach (my $i=0; $i <= $#{$A}; $i++) {
		foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
			$C->[$i]->[$j] = $B->[$i]->[$j] - $A->[$i]->[$j];
		}
	}

	my $CPUs = [];

	if ($sorted) {
		odl_cpus($sortby, $C,$IR,$CPUs);
	}
	else {
		$CPUs = [0..$CPUCNT-1];
	}
	my @CCC = @$CPUs[0..$NVU-1];

my $iMne;

	if (!$transpose) {
		if ($headonce && !$sorted) {
			if ($ONCE) { $ONCE = 0; print "$HDR\n"; }
# if0block deleted
		}
		elsif (!$sorted) {
			print "$HDR\n";
		}
		else { # sorted or not sorted will always rebuild the header
			my $tt = sprintf "%19s  %6s ", " ", " ";
			my $once = 1;
			my $cnt = -1;
			foreach my $j (@CCC) {
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
if (1) {
				if ($cnt < $fromJ) {
				}
				else {
					if ($once) {
						$once = 0;
						$tt .= sprintf("%9s ","...");
					}
				}
}
else {
				if ($once && ($cnt >= $fromJ)) { $once = 0; $tt .= sprintf("%9s ","..."); }
}
				$tt .= sprintf("%9s ","CPU$j");
			}
$tt .= sprintf("%9s ","summed");
print "$tt\n";
		}
# if0block deleted

		foreach my $i (@III) {
			my $s = "";
			my $tm = $showtime ? iso_time($$tIm) : $epotime ? $$tIm : " ";
			$s = sprintf "%19s  %6s ", $tm, $IR->[$i];
my $tot = 0;
			my $once = 1;
			my $cnt = -1;
			foreach my $cpuid (@CCC) {
				$tot += $C->[$i]->[$cpuid];
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
				if ($once && ($cnt >= $fromJ)) { $once = 0; $s .= sprintf("%9s ","..."); }
				$s .= sprintf("%9d ",$C->[$i]->[$cpuid]);
			}
$s .= sprintf("%9d ",$tot);
			$s .= sprintf "    %s", $ILONG->[$i];
			print "$s\n";
		}
	}
	else { # transpose
		my $Ct = [];
		push @$Ct, [] foreach @{$C->[0]};
		my $m;
		foreach my $row (@{$C}) {
			$m = 0;
			push(@{$Ct->[$m++]}, $_) foreach @{$row};
		}

		if ($headonce) {
			if ($ONCE) { $ONCE = 0; print "$HDRt\n"; }
		}
		else {
print "$HDRt\n";
		}
my $tot = [];
foreach (my $j=0; $j <= $#{$Ct->[0]}; $j++) {
my $tot->[$j] = 0; 
}
# if0block deleted
		foreach my $cpuid (@CCC) {
			foreach my $j (@III) {
$tot->[$j] += $Ct->[$cpuid]->[$j];
		}
		}
				my $once = 1;
		my $cnt = -1;		
		foreach my $cpuid (@CCC) {
				$cnt++;
				next if (($cnt >= $fromI) && ($cnt < $toI));
			my $s = "";
				if ($cnt < $fromI) {
				}
				else {
					if ($once) { $once = 0; $s .= sprintf("%19s  %6s\n"," ","  ..."); }
				}
			my $tm = $showtime ? iso_time($$tIm) : $epotime ? $$tIm : " ";
			$s .= sprintf "%19s  %6s ", $tm, "CPU$cpuid";
			foreach my $j (@III) {
				$s .= sprintf("%9d ",$Ct->[$cpuid]->[$j]);
			}
			print "$s\n";
		}
{
			my $s = "";
$s = sprintf "%19s  %6s ", " ", "summed";
			foreach my $j (@III) {
$s .= sprintf("%9d ",$tot->[$j]);
			}
			print "$s\n";
}
	}

	foreach (my $i=0; $i <$#{$A}; $i++) {
		foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
			$A->[$i]->[$j] = $B->[$i]->[$j];
		}
	}
print "\n";
sleep $delay;
}

}

# POD deleted
sub getintrspec {
my ($time,$A,$IR,$ILONG) = @_;
my $cachefile = "$CACHEDIR/" . "interrupts_spec_$time";
my $i = 0;
open(F,"cat $cachefile|");
while (my $l = <F>) {
	if ($i == 0) {
		my @CPUs = ($l =~ m/(CPU\d+)/xmsg);
		$CPUCNT = scalar @CPUs;
		$i++; 
		next; 
	}	
	chomp $l;
	$l =~ s/^\s+|\s+$//;
	my @p = split(/\s+/,$l,$CPUCNT+2);
	my $beg = shift @p;
	my $end = pop @p;
	$beg =~ s/\:$//;
	$end =~ s/\s*$//;
	next if $beg =~ /^(ERR|MIS)$/;
	push(@$IR,$beg);
	push(@$ILONG,$end);
	push(@$A,\@p);
}
close F;
}

sub cacheintrspec {
my $time = shift;
my $cachefile = "$CACHEDIR/" . "interrupts_spec_$time";

open(F,"cat /proc/interrupts|");
open(my $fh, '>',$cachefile) || die "cannot open file $cachefile for writing: $!\n";
while (my $l = <F>) {
	print $fh $l;
}
close F;
close $fh;
}
sub fromcached {
my ($A,$tm) = @_;

{
	my $l;
	while (($l = <FROMCACHED>) && ($l !~ /^\-\-\s+(\d+)\s*$/)) {
		push(@$A,[split(/\s+/,$l)]);
	}
	unless (($$tm) = ($l =~ m/^\-\-\s+(\d+)\s*$/)) {
		print "DONE WITH REPLAY\n";
		exit;
	}
}
# if0block deleted
}
sub getintr {
my ($A,$tm) = @_;

my $i = 0;
open(F,"cat /proc/interrupts|");
while (my $l = <F>) {
	if ($i == 0) {
		$i++; 
		next; 
	}
	chomp $l;
	$l =~ s/^\s+|\s+$//;
	my @p = split(/\s+/,$l,$CPUCNT+2);
	my $beg = shift @p;
	my $end = pop @p;
	next if $beg =~ /^(ERR:|MIS:)$/;
	push(@$A,\@p);
}
close F;
$$tm = time;
if ($CACHED) {
	foreach (my $i=0; $i <$#{$A}; $i++) {
		print $fh "@{$A->[$i]}";
		print $fh "\n";
	}
	print $fh "-- $$tm\n";
}

}

sub getintr1st {
my ($A,$IR,$ILONG) = @_;

my $i = 0;
open(F,"cat /proc/interrupts|");
while (my $l = <F>) {
	if ($i == 0) {
		my @CPUs = ($l =~ m/(CPU\d+)/xmsg);
		$CPUCNT = scalar @CPUs;
		$i++; 
		next; 
	}	
	chomp $l;
	$l =~ s/^\s+|\s+$//;
	my @p = split(/\s+/,$l,$CPUCNT+2);
	my $beg = shift @p;
	my $end = pop @p;
	$beg =~ s/\:$//;
	$end =~ s/\s*$//;
	next if $beg =~ /^(ERR|MIS)$/;
	push(@$IR,$beg); 
	push(@$ILONG,$end);
	push(@$A,\@p);
}
}

sub iso_time {
my $tm = @_ ? shift : time;
my($sec,$min,$hours,$mday,$mon,$year) = localtime($tm);
my $date = ($year + 1900).'-'._dd($mon + 1).'-'._dd($mday);
my $time = _dd($hours).':'._dd($min).':'._dd($sec);
return "$date $time";
}

sub _dd { my $_dd = $_[0]; $_dd = '0'.$_dd if( length($_dd) == 1 ); return $_dd; }

