line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Citrix::SessionSet; |
2
|
2
|
|
|
2
|
|
1866
|
use Data::Dumper; |
|
2
|
|
|
|
|
10446
|
|
|
2
|
|
|
|
|
4746
|
|
3
|
|
|
|
|
|
|
#use strict; |
4
|
|
|
|
|
|
|
#use warnings; |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = "0.25"; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
# TODO: Allow loading sessions from perl cache file or DB for mock testing |
9
|
|
|
|
|
|
|
# # http://support.citrix.com/proddocs/index.jsp?topic=/ps-unix/ps-unix-cmd-ref-commands-ctxquery.html |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Citrix::SessionSet - Query UNIX Citrix Sessions from a Citrix Farm. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 DESCRIPTION |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Citrix::SessionSet Allows querying: |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=over 4 |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=item 1) All sessions on farm (multiple, typically 2-8 hosts, by "farm context") |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=item 2) Sessions on a single host (by DNS hostname) |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=item 3) Sessions for an individual user (by username). |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=back |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Parses output from Citrix command line tools to Perl objects. |
30
|
|
|
|
|
|
|
The module tries to do its best to deal with traditional problems |
31
|
|
|
|
|
|
|
of sub-shell execution (command piping) and remote shelling (rsh). |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
A UNIX Citrix "application" is usually a complete Desktop environment, but may |
34
|
|
|
|
|
|
|
also be single app like X-Terminal, Mail Client or Word processor. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=cut |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# Use -f for short format opts -o For long fmt options |
42
|
|
|
|
|
|
|
# See p. 234 in Citrix Guide |
43
|
|
|
|
|
|
|
# d = 'DEVICE' (client) |
44
|
|
|
|
|
|
|
# i = 'HOST:ID' (Combo of host+sessid) |
45
|
|
|
|
|
|
|
# I = 'IDLE TIME' |
46
|
|
|
|
|
|
|
# S = STATE |
47
|
|
|
|
|
|
|
# u = USER |
48
|
|
|
|
|
|
|
# x = X display number |
49
|
|
|
|
|
|
|
# s = 'SERVER NAME' |
50
|
|
|
|
|
|
|
# l = LOGON TIME |
51
|
|
|
|
|
|
|
# p = APPLICATION NAME published app (APPID) |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head1 CLASS VARIABLES |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=head2 $Citrix::SessionSet::ctxcols |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
Column format string |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=head2 @Citrix::SessionSet::ctxattr |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Citrix Session set attributes (matching the letters in $ctxcols format string). |
62
|
|
|
|
|
|
|
These turn into hash keys in the sessionset collection. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head2 $Citrix::SessionSet::debug |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Class level "global" debugging level (Notice that also instance has a debug flag). Set to true to |
67
|
|
|
|
|
|
|
troubleshoot Citrix::SessionSet retrieval. |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=cut |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# The path of Citrix Command line utilities. |
73
|
|
|
|
|
|
|
# This is used as a path prefix for commands to eliminate runtime guesswork. |
74
|
|
|
|
|
|
|
#OLD: our $Citrix::binpath = "/opt/CTXSmf/bin"; |
75
|
|
|
|
|
|
|
# |
76
|
|
|
|
|
|
|
our $debug = 0; |
77
|
|
|
|
|
|
|
# Default Col layout of ctxquery |
78
|
|
|
|
|
|
|
our $ctxcols = "iSupd"; # t |
79
|
|
|
|
|
|
|
# Arributes to use in the session collection (mapping to tab output format |
80
|
|
|
|
|
|
|
# specifiers above). Notice col format specifiers in $ctxcols and this should match. |
81
|
|
|
|
|
|
|
my @ctxattr = ('HOST_SID','STATE','USERNAME','APPID','DEVICE',); #'TYPE' |
82
|
|
|
|
|
|
|
# OLD Unused: Legacy Default Citrix Query Timeout |
83
|
|
|
|
|
|
|
#our $tout = 5; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Provide a better alias (to use in API) |
86
|
|
|
|
|
|
|
*Citrix::SessionSet::usersessions = \&Citrix::SessionSet::mysess; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head1 METHODS |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head2 my $ss = Citrix::SessionSet->new($farmctx); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Construct a new Citrix session collection. |
94
|
|
|
|
|
|
|
Indicate Farm context of query by $fc (See L). |
95
|
|
|
|
|
|
|
Return empty session set (to be queried later) |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=cut |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
sub new { |
100
|
0
|
|
|
0
|
1
|
|
my ($class, $fc) = @_; |
101
|
|
|
|
|
|
|
#OLD:my $ss = []; |
102
|
0
|
0
|
|
|
|
|
if (!%$fc) {print("Session::new() : NO FC");return(undef);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
my $ss = {'sarr' => [], 'fc' => $fc}; |
104
|
0
|
|
|
|
|
|
bless($ss, $class); |
105
|
0
|
|
|
|
|
|
return($ss); |
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head2 $err = $ss->gethostsess('the-cx-host-67'); |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Get all sessions for a single host (passed as $host) and load the sessions (adding them) into |
111
|
|
|
|
|
|
|
session set instance. |
112
|
|
|
|
|
|
|
Return 1 for errors, 0 on success. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=cut |
115
|
|
|
|
|
|
|
sub gethostsess { |
116
|
0
|
|
|
0
|
1
|
|
my ($ss, $host) = @_; |
117
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
118
|
0
|
|
|
|
|
|
my $fh; |
119
|
0
|
|
|
|
|
|
my $ap = ''; |
120
|
0
|
|
|
|
|
|
my $fc = $ss->farmctx(); |
121
|
0
|
|
|
|
|
|
my $mh = $ss->getmh(); # $fc->masterhost(); |
122
|
0
|
|
|
|
|
|
my $ds = $fc->domainsuffix(); # OLD: {'ds'} |
123
|
0
|
|
|
|
|
|
my $cnt = scalar(@$sarr); |
124
|
0
|
|
|
|
|
|
my $usehost; # Final Host to use for query |
125
|
0
|
|
|
|
|
|
my $tout = $Citrix::touts->{'host'}; # 10; |
126
|
0
|
|
|
|
|
|
my @times = (); |
127
|
0
|
|
|
|
|
|
my $trace = debug($ss); |
128
|
0
|
0
|
0
|
|
|
|
if ($trace && $ENV{'HTTP_HOST'}) {print("");} |
|
0
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
#if ($host =~ /\./) {die("Expecting bare hostname (got: $host)");} |
130
|
0
|
0
|
|
|
|
|
if (!$host) {$usehost = $mh;$ap = ' -S';} |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
elsif ($ds) {$usehost = "$host.$ds";} |
132
|
0
|
|
|
|
|
|
my $cmd = "rsh $usehost $Citrix::binpath/ctxquery -f $ctxcols $ap"; # -S |
133
|
|
|
|
|
|
|
# Added loading of Net::Ping to circumvent |
134
|
0
|
|
|
|
|
|
eval {require(Net::Ping);}; |
|
0
|
|
|
|
|
|
|
135
|
0
|
0
|
|
|
|
|
if ($@) {} # print("Dont have Net::Ping (risk hanging)"); |
136
|
|
|
|
|
|
|
else { |
137
|
0
|
|
|
|
|
|
my $p = Net::Ping->new(); |
138
|
0
|
0
|
|
|
|
|
if ($p->ping($usehost)) {if ($trace) {print("$usehost is alive (reachable by PING).\n");}} |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Reuse $tout as state variable |
140
|
|
|
|
|
|
|
else {$tout = 0;} |
141
|
0
|
|
|
|
|
|
$p->close(); |
142
|
0
|
0
|
|
|
|
|
if (!$tout) {$ss->{'msg'} = "$usehost NOT Alive.\n";return(1);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
} |
144
|
0
|
0
|
|
|
|
|
if ($trace) {print("Launch Query: $cmd\n");$times[0] = time();} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
eval { |
146
|
|
|
|
|
|
|
local $SIG{'ALRM'} = sub { |
147
|
0
|
|
|
0
|
|
|
die("RSH Timeout ($usehost)\n"); |
148
|
0
|
|
|
|
|
|
die("Host '$usehost' was unable to return session within $tout\n"); |
149
|
0
|
|
|
|
|
|
}; |
150
|
|
|
|
|
|
|
#local $SIG{'CHLD'} = sub {die("Child ($usehost)\n");}; |
151
|
0
|
|
|
|
|
|
alarm($tout); |
152
|
0
|
0
|
|
|
|
|
if ($trace) {print("Opening Pipe ...\n");} |
|
0
|
|
|
|
|
|
|
153
|
0
|
|
|
|
|
|
my $ok = open($fh, "$cmd |"); |
154
|
0
|
0
|
|
|
|
|
if (!$ok) {die("Failed to open the pipe");} |
|
0
|
|
|
|
|
|
|
155
|
0
|
0
|
|
|
|
|
if ($trace) {print("Opened: '$cmd' (as $< / $>)\n");} |
|
0
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
}; |
158
|
|
|
|
|
|
|
# Enforce reset in a place which is always visited |
159
|
0
|
|
|
|
|
|
alarm(0); |
160
|
0
|
0
|
|
|
|
|
if ($trace) {$times[1] = time();print("Done Trying (Success, ",($times[1]-$times[0])," s.)\n");} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
|
162
|
0
|
0
|
|
|
|
|
if ($trace) {print("Reset Timeout ($tout => 0)\n");} |
|
0
|
|
|
|
|
|
|
163
|
0
|
0
|
|
|
|
|
if ($@) {$ss->{'msg'} = $@;return(2);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
164
|
0
|
0
|
|
|
|
|
if ($trace) {print("Parse Query (From: $fh)\n");} |
|
0
|
|
|
|
|
|
|
165
|
0
|
|
|
|
|
|
parse($fh, $sarr, \@ctxattr); |
166
|
0
|
|
|
|
|
|
my $cnt2 = scalar(@$sarr); |
167
|
0
|
0
|
|
|
|
|
if ($host) { |
168
|
0
|
|
|
|
|
|
my $cd = ($cnt2 - $cnt); |
169
|
0
|
|
|
|
|
|
$ss->{'stat'}->{$host}->{'cnt'} = $cd; |
170
|
|
|
|
|
|
|
#if (!$cd) {$ss->{'stat'}->{$host}->{'out'} = "$!";} |
171
|
|
|
|
|
|
|
} |
172
|
0
|
0
|
0
|
|
|
|
if ($trace && $ENV{'HTTP_HOST'}) {print("\n");} |
|
0
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
|
return(0); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head2 $err = $ss->getsession('the-cx-host-67:5234'); |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Get a session identified by $hostsess string (HOST:SESSID) from session set |
179
|
|
|
|
|
|
|
(Involves sequential search within session set as sessions are not |
180
|
|
|
|
|
|
|
indexed in current version). |
181
|
|
|
|
|
|
|
The composite key of form "HOST:SESSID" is required, because session set may contain |
182
|
|
|
|
|
|
|
sessions from multiple hosts (With single farm context though). |
183
|
|
|
|
|
|
|
Return the single identified session (as hash) or undef if no session by SESSID |
184
|
|
|
|
|
|
|
is found. |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=cut |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub getsession { |
189
|
0
|
|
|
0
|
1
|
|
my ($ss, $hostsess) = @_; |
190
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
191
|
0
|
|
|
|
|
|
my (@s) = grep({$_->{'HOST_SID'} eq $hostsess} @$sarr); |
|
0
|
|
|
|
|
|
|
192
|
0
|
0
|
|
|
|
|
if (@s < 1) {$ss->errstr("No Sessions for $hostsess'' ");return(undef);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
193
|
0
|
0
|
|
|
|
|
if (@s > 1) {$ss->errstr("Multiple session for Identified session '$hostsess'");return(undef);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
#ORG:return((@s == 1) ? $s[0] : undef); |
195
|
0
|
|
|
|
|
|
return($s[0]); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 $err = $ss->mysess('joecitrix'); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Load sessions for single user (by username) into session set. |
201
|
|
|
|
|
|
|
Usually loading takes place on an empty set to have truly the sessions for individual only. |
202
|
|
|
|
|
|
|
This can be used to create "My Sessions" views, but this is just "Sessions for User by ID". |
203
|
|
|
|
|
|
|
Return 1 (and up) for errors 0, for success. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
sub mysess { |
207
|
0
|
|
|
0
|
1
|
|
my ($ss, $userid) = @_; |
208
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
209
|
0
|
|
|
|
|
|
my $mh = $ss->getmh(); |
210
|
0
|
0
|
|
|
|
|
if (!$mh) {print("No Host to query from (master host for Farm)\n");return(1);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
211
|
0
|
0
|
|
|
|
|
if (!$userid) {print("Err: No User passed for getting sessions\n");return(1);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
my $cmd = "rsh $mh $Citrix::binpath/ctxquery -f $ctxcols -S user $userid"; # -S |
213
|
0
|
0
|
|
|
|
|
if ($ss->debug()) {print("$< / $>: $cmd \n");} |
|
0
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
|
my $fh; |
215
|
0
|
|
|
|
|
|
my $tout = $Citrix::touts->{'user'}; # 5; |
216
|
|
|
|
|
|
|
local $SIG{'ALRM'} = sub { |
217
|
|
|
|
|
|
|
# 'cx86-bh-1.bh' was not able respond within given timilimit |
218
|
|
|
|
|
|
|
#die("Query Timeout after $tout s. (sig: '$_[0]', masterhost '$mh')"); |
219
|
0
|
|
|
0
|
|
|
die("Citrix server '$mh' (master) was not able respond within given timilimit ($tout s.)"); |
220
|
0
|
|
|
|
|
|
}; |
221
|
0
|
|
|
|
|
|
alarm($tout); |
222
|
0
|
|
|
|
|
|
eval { |
223
|
0
|
|
|
|
|
|
my $ok = open($fh, "$cmd |"); |
224
|
0
|
0
|
|
|
|
|
if (!$ok) {die("Failed to open the pipe");} |
|
0
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
}; |
226
|
0
|
|
|
|
|
|
alarm(0); |
227
|
0
|
0
|
|
|
|
|
if ($@) {print("Failed: $@");return(3);} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
228
|
0
|
|
|
|
|
|
my $err = parse($fh, $sarr, \@ctxattr, 2); |
229
|
0
|
|
|
|
|
|
return(0); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Access (Set/Get) all session from session set. |
233
|
|
|
|
|
|
|
# Return session. |
234
|
|
|
|
|
|
|
sub getsessions { |
235
|
0
|
|
|
0
|
0
|
|
my ($ss, $set) = @_; |
236
|
0
|
0
|
|
|
|
|
if (defined($set)) {$ss->{'sarr'} = $set;} |
|
0
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
|
return($ss->{'sarr'}); |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# Get the master host of farm context related to session set. |
241
|
|
|
|
|
|
|
# Return master host name. |
242
|
|
|
|
|
|
|
sub getmh { |
243
|
0
|
|
|
0
|
0
|
|
my ($ss) = @_; |
244
|
0
|
|
|
|
|
|
$ss->{'fc'}->masterhost(); # OLD: {'mh'} |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Deprecated. See farmctx |
248
|
|
|
|
|
|
|
#sub getfc { |
249
|
|
|
|
|
|
|
# $_[0]->{'fc'}; |
250
|
|
|
|
|
|
|
#} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# Get the complete Farm context node for current session set. |
253
|
|
|
|
|
|
|
# Return Farm Context node. |
254
|
|
|
|
|
|
|
sub farmctx { |
255
|
0
|
0
|
|
0
|
0
|
|
if (@_ >= 2) {$_[0]->{'fc'} = $_[1];} |
|
0
|
|
|
|
|
|
|
256
|
0
|
|
|
|
|
|
$_[0]->{'fc'}; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 my $cnt = $ss->count(); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Accessor method to get the number of sessions stored in current session set. |
262
|
|
|
|
|
|
|
Return the (integer) count. |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=cut |
265
|
|
|
|
|
|
|
sub count { |
266
|
0
|
|
|
0
|
1
|
|
my ($ss) = @_; |
267
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
268
|
0
|
|
|
|
|
|
return(scalar(@$sarr)); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# See getsession() |
272
|
|
|
|
|
|
|
sub getsessbyid { |
273
|
0
|
|
|
0
|
0
|
|
my ($ss, $hostsess) = @_; |
274
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
275
|
0
|
|
|
|
|
|
my (@s) = grep({$_->{'HOST_SID'} eq $hostsess} @$sarr); |
|
0
|
|
|
|
|
|
|
276
|
0
|
0
|
|
|
|
|
if (scalar(@s) == 1) {return($s[0]);} |
|
0
|
|
|
|
|
|
|
277
|
0
|
|
|
|
|
|
return(undef); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# Toggle debug mode on in session set collection. |
281
|
|
|
|
|
|
|
# This may be used in various contexts to produce more verbose output. |
282
|
|
|
|
|
|
|
# Also class level (non-instance) debug flag is probed to find out |
283
|
|
|
|
|
|
|
# the desired debug level. |
284
|
|
|
|
|
|
|
# As a setter this can only affect the instance level debug setting. |
285
|
|
|
|
|
|
|
sub debug { |
286
|
0
|
|
|
0
|
1
|
|
my ($ss, $lv) = @_; |
287
|
0
|
0
|
|
|
|
|
if (defined($lv)) {$ss->{'debug'} = $lv;} |
|
0
|
|
|
|
|
|
|
288
|
0
|
|
0
|
|
|
|
return($ss->{'debug'} || $debug); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
sub errstr { |
291
|
0
|
|
|
0
|
0
|
|
my ($ss, $es) = @_; |
292
|
0
|
0
|
|
|
|
|
if (defined($es)) {$ss->{'errstr'} = $es;} |
|
0
|
|
|
|
|
|
|
293
|
0
|
|
|
|
|
|
$ss->{'errstr'}; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
# Parse output from Citrix Command pipe fiehandle $fh to $arr (which will be filled with hashes). |
296
|
|
|
|
|
|
|
# Space delimited fields will be parsed into attributes @$attr in hashes. |
297
|
|
|
|
|
|
|
# This parser is (sofar) applicable to all the possible outputs from |
298
|
|
|
|
|
|
|
# Citrix commands returning tabular sets. |
299
|
|
|
|
|
|
|
# For internal use only. Not part of exposed API. |
300
|
|
|
|
|
|
|
# Return 0 (indicating success) |
301
|
|
|
|
|
|
|
sub parse { |
302
|
0
|
|
|
0
|
0
|
|
my ($fh, $arr, $attr, $scnt) = @_; |
303
|
0
|
0
|
|
|
|
|
if (!$scnt) {$scnt = 0;} # To keep warnings silent |
|
0
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# Discard heading line |
305
|
0
|
|
|
|
|
|
<$fh>; |
306
|
0
|
0
|
|
|
|
|
if ($scnt > 1) {for (2..$scnt) {<$fh>}} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
307
|
0
|
|
|
|
|
|
my $i = 0; |
308
|
0
|
|
|
|
|
|
my $spcnt = scalar(@$attr); |
309
|
|
|
|
|
|
|
# Consider error message from Citrix server |
310
|
0
|
|
|
|
|
|
my $ere = qr/Session\s+info/; # not available |
311
|
0
|
|
|
|
|
|
while (<$fh>) { |
312
|
|
|
|
|
|
|
# Check early |
313
|
0
|
0
|
|
|
|
|
if (/$ere/) {last;} |
|
0
|
|
|
|
|
|
|
314
|
0
|
|
|
|
|
|
chomp(); |
315
|
0
|
|
|
|
|
|
s/^\s+//; |
316
|
0
|
0
|
|
|
|
|
if (!$_) {next;} |
|
0
|
|
|
|
|
|
|
317
|
0
|
|
|
|
|
|
my @a = split(/\s+/, $_, $spcnt); |
318
|
0
|
|
|
|
|
|
my %h; |
319
|
0
|
|
|
|
|
|
@h{@$attr} = @a; |
320
|
|
|
|
|
|
|
# Separate this to a parser hook ? |
321
|
0
|
|
|
|
|
|
@h{'HOST', 'SID'} = split(/:/, $h{'HOST_SID'}); |
322
|
0
|
|
|
|
|
|
$h{'APPID'} =~ s/^#//; |
323
|
|
|
|
|
|
|
# Never care about STATE=listen,conn - Not here |
324
|
0
|
|
|
|
|
|
push(@$arr, \%h); |
325
|
0
|
|
|
|
|
|
$i++; |
326
|
|
|
|
|
|
|
} |
327
|
0
|
|
|
|
|
|
close($fh); |
328
|
0
|
|
|
|
|
|
return(0); |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# Internal: Extract Host Statistics Information from a session set (total number, |
333
|
|
|
|
|
|
|
# distributions of various session states). |
334
|
|
|
|
|
|
|
# Return Host Statistics (Hash of hashes). |
335
|
|
|
|
|
|
|
sub hin { |
336
|
0
|
|
|
0
|
0
|
|
my ($ss) = @_; |
337
|
0
|
|
|
|
|
|
my $sarr = $ss->getsessions(); |
338
|
0
|
|
|
|
|
|
my %hosts; |
339
|
0
|
|
|
|
|
|
map({ |
340
|
0
|
|
|
|
|
|
$hosts{$_->{'HOST'}}->{'tot'}++; |
341
|
0
|
|
|
|
|
|
$hosts{$_->{'HOST'}}->{$_->{'STATE'}}++; |
342
|
|
|
|
|
|
|
} @$sarr); |
343
|
|
|
|
|
|
|
# Update Names into stats |
344
|
0
|
|
|
|
|
|
for (keys(%hosts)) {$hosts{$_}->{'host'} = $_;} |
|
0
|
|
|
|
|
|
|
345
|
0
|
|
|
|
|
|
return(\%hosts); |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
1; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
#__END__ |
351
|
|
|
|
|
|
|
# =head2 NOTES |
352
|
|
|
|
|
|
|
# |