.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Script details
2 |
3 | This repo is managed by cPanel. Please submit all bugs and feature requests via email, or submit a pull request via GitHub.
4 |
5 | Requirements:
6 |
7 | - Root-level SSH access
8 | - CentOS or RedHat system
9 |
10 | Why sys-snap?
11 |
12 | Resource shortages can feel overwhelming and impossible to track down without adequate data to diagnose the problem. Servers inevitably have problems when their sys-admins are not watching. While the Daily Process Log in WHM can be very helpful in these situations, sometimes more information is needed than WHM can provide.
13 |
14 |
15 | Sys-snap is designed to help you see what is causing the resource shortages, whether CPU or Memory related, even when no one is looking.
16 |
17 |
18 |
19 | This version of sys-snap is specifically designed to be used via SSH by the root user on cPanel servers, which means that this documentation and application is aimed at RedHat and CentOS systems.
20 |
21 |
22 | History of Sys-Snap
23 |
24 |
25 | System Snapshot began at EV1 Servers in the late 1990s or early 2000s. It was written by Mike Kroh before being extended by Nate Custer. The script is often used when traditional methods of investigation do not shed light on what is causing servers load to skyrocket without warning or the server to crash. Many versions of the script exist, as it has been carried by various techs to different companies who have extended and modified to fit both their general needs and specific special circumstances. The version presented here was modified for use by employees at Hostgator and AlphaRed before being merged into a different version being used by cPanel around 2011. A descendant of the 2011 version was ported to perl by Paul Trost at cPanel in 2013. The recent version discussed in this article was completed by Bryan Christensen at cPanel in 2015.
26 |
27 |
28 | How to use sys-snap
29 |
30 |
31 | Install
32 |
33 |
34 | Sys-snap’s installation is incredibly simple two step process: Download the script, and run the install.
35 |
36 |
37 | ```
38 | wget -O /root/sys-snap.pl https://raw.githubusercontent.com/cPanelTechs/SysSnapv2/master/sys-snap.pl && cd /root/ && chmod 744 sys-snap.pl && perl sys-snap.pl --start
39 | ```
40 |
41 | Once installed, the script continues to run on the server until you stop it, or until the server reboots. After a reboot, if you want the script to resume recording data, you would need to start it again.
42 |
43 |
44 | Every time you start the sys-snap.pl script, the existing data will be archived in .tar.gz format in the /root/ directory for your records.
45 |
46 |
47 | Starting and stopping the script
48 |
49 |
50 | To start the process, run the script with the --start flag:
51 |
52 |
53 | ```
54 | [~] perl /root/sys-snap.pl --start
55 | Sys-snap is not currently running
56 | Start sys-snap logging to '/root/system-snapshot/' (y/n)?:y
57 | Starting…
58 | [~]
59 | ```
60 |
61 | Sys-snap will run in the background. Logs will be written to /root/sys-snapshot/ every minute. Every hour a new folder with the current hour will be created. After 24 hours the folder should look similar to this:
62 |
63 |
64 | ```
65 | [~/system-snapshot] ls -lah | head
66 | total 104K
67 | drwxr-xr-x 26 root root 4.0K May 5 13:50 ./
68 | dr-xr-x---. 26 root root 4.0K May 4 22:05 ../
69 | drwxr-xr-x 2 root root 4.0K Apr 24 00:59 0/
70 | drwxr-xr-x 2 root root 4.0K Apr 24 01:52 1/
71 | drwxr-xr-x 2 root root 4.0K Apr 24 10:53 10/
72 | drwxr-xr-x 2 root root 4.0K Apr 24 11:54 11/
73 | drwxr-xr-x 2 root root 4.0K Apr 24 12:55 12/
74 | drwxr-xr-x 2 root root 4.0K Apr 24 13:33 13/
75 | drwxr-xr-x 2 root root 4.0K Apr 23 14:56 14/
76 | ```
77 |
78 | Each hour will have logs that were created for every minute of the hour:
79 |
80 |
81 | ```
82 | [~/system-snapshot/0] ls -lah |head
83 | total 3.5M
84 | drwxr-xr-x 2 root root 4.0K Apr 24 00:59 ./
85 | drwxr-xr-x 26 root root 4.0K May 5 13:51 ../
86 | -rw-r--r-- 1 root root 54K May 5 00:00 0.log
87 | -rw-r--r-- 1 root root 49K May 5 00:10 10.log
88 | -rw-r--r-- 1 root root 52K May 5 00:11 11.log
89 | -rw-r--r-- 1 root root 50K May 5 00:12 12.log
90 | -rw-r--r-- 1 root root 49K May 5 00:14 13.log
91 | -rw-r--r-- 1 root root 51K May 2 00:14 14.log
92 | -rw-r--r-- 1 root root 49K May 5 00:15 15.log
93 | ```
94 |
95 | After 24 hours the logs will start to overwrite the previous logs. Each minute will overwrite the oldest log file. The logs are based on 24 hour time. 0 is 12AM.
96 |
97 |
98 | To stop the process, run the script with the --stop flag, and the script will ask you to confirm the process it is stopping:
99 |
100 | ```
101 | [~] perl /root/sys-snap.pl --stop
102 | Current process: 20081 root perl sys-snap.pl --start
103 | Stop this process (y/n)?:y
104 | Stopping 20081
105 | [~]
106 | ```
107 |
108 | Gathering data from sys-snap
109 |
110 |
111 | If the load average of your server is larger than the number of processors, load issues can occur. cPanel backups, log processing, and stat processing are delayed if this happens. Tracking down the cause of the load increase is where sys-snap comes in.
112 |
113 |
114 | Using sar to narrow your window
115 |
116 |
117 | One utility that can be used in tandem with sys-snap to help you track and diagnose instability is called sar. To verify that the sysstat package is installed, use the command below. Please note: sysstat will only begin recording information after it is installed, and cannot provide insight for a server before the package was installed.
118 |
119 |
120 | ```
121 | yum install -y sysstat
122 | ```
123 |
124 | Using various flags you can display different information that has been recorded about your server’s state. In diagnosing instability, or resource shortages, using the -q and -r flags will likely be most helpful to you. Here is a small piece of output from the 'sar -q' command. The 'ldavg' is the load average. The 'ldavg-#' is the time range for the load average. The three rightmost columns represent the 1, 5, and 15 minutes load averages for the time listed in the leftmost column:
125 |
126 |
127 | ```
128 | 12:00:01 AM runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15
129 | ...
130 | 06:10:01 AM 7 107 1.22 1.19 0.76
131 | 06:20:01 AM 5 104 1.31 0.99 0.83
132 | 06:30:01 AM 26 151 21.11 13.58 6.76
133 | 06:40:11 AM 7 158 21.74 20.63 13.96
134 | 06:50:02 AM 25 146 22.29 21.95 17.84
135 | 07:00:01 AM 24 148 23.46 23.35 20.46
136 | 07:10:02 AM 24 138 23.50 23.05 21.56
137 | 07:20:01 AM 23 142 19.20 20.36 20.91
138 | 07:30:01 AM 17 135 14.67 16.01 18.45
139 | 07:40:01 AM 7 103 1.70 8.44 14.09
140 | 07:50:01 AM 4 103 0.06 1.49 7.66
141 | 08:00:01 AM 4 105 0.01 0.23 4.02
142 | 08:10:01 AM 4 102 0.03 0.08 2.12
143 | 08:20:01 AM 5 111 0.05 0.07 1.13
144 | 08:30:01 AM 6 110 0.06 0.06 0.60
145 | ...
146 | ```
147 | In the above example we see that the load was high from 6:30 AM to 7:50 AM, so that is where we need to focus our investigation. Sys-snap can print high resource using processes for a time range.
148 |
149 |
150 | Polling sys-snap for specific information using --print
151 |
152 |
153 | First, go on your server to the directory where the sys-snap.pl script is located, which is /root by default. The --print flag attempts to programmatically calculate what it calls memory and cpu scores. It does this by adding together the %MEM and %CPU columns, respectively. While mathematically incorrect, it gives us a general overview. You will see that each set of processes is sorted by user, and you’ll see a memory- and cpu- score displayed, similar to what is displayed below:
154 |
155 |
156 | ```
157 | [~] ./sys-snap.pl --print 9:00 10:00
158 | user: root
159 | cpu-score: 1.30
160 | memory-score: 299.60
161 | user: named
162 | cpu-score: 0.00
163 | memory-score: 28.60
164 | user: mysql
165 | cpu-score: 0.00
166 | memory-score: 80.60
167 | user: mailnull
168 | cpu-score: 0.00
169 | memory-score: 3.90
170 | user: dovecot
171 | cpu-score: 0.00
172 | memory-score: 1.30
173 | user: nobody
174 | cpu-score: 0.00
175 | memory-score: 19.50
176 | user: dovenull
177 | cpu-score: 0.00
178 | memory-score: 11.70
179 | [~]
180 | ```
181 |
182 | The --print flag assumes you will be polling the sys-snap data for a specific time, so always pass the --print flag with a defined start and end time. For example, if we are trying to look at information between 6:30 and 7:50, as would be helpful given the output from our example above, this command would print information for the time range:
183 |
184 |
185 | ```
186 | [~] /root/sys-snap.pl --print 6:30 7:50
187 | ```
188 |
189 | When you start the script, old data is compressed into a tar file to prevent overwriting. To use sys-snap to parse old data, untar the file and pass the path using the --path flag:
190 |
191 |
192 | ```
193 | [~] /root/sys-snap.pl --print 6:30 7:50 --dir=/system-snapshot.20150422
194 | ```
195 |
196 | If we add the verbose flag to --print we can get even more detail:
197 |
198 |
199 | ```
200 | [~] /root/sys-snap.pl --print 9:00 10:00 --v
201 | < manually truncated for brevity >
202 | user: dovenull
203 | cpu-score: 0.00
204 | C: 0.00 proc: \_ dovecot/imap-login
205 | C: 0.00 proc: \_ dovecot/pop3-login
206 | memory-score: 14.40
207 | M: 8.00 proc: \_ dovecot/imap-login
208 | M: 6.40 proc: \_ dovecot/pop3-login
209 | < manually truncated for brevity >
210 | ```
211 |
212 | sys-snap has a myriad of flags to make parsing through the information that is provides easier. Run the script with no flags to get the full list:
213 |
214 |
215 | ```
216 | [~] ./sys-snap.pl
217 | USAGE: ./sys-snap.pl [options]
218 | --start : creates, disowns, and drops 'sys-snap.pl --start' process into the background
219 | --print : Where time HH:MM, prints basic usage by default
220 | --v | v : verbose output from --print
221 | --check : checks if sys-snap is running
222 | --stop : stops sys-snap after confirming PID info
223 | --loadavg : Where time HH:MM, prints load average for time period - default 10 min interval
224 | --max-lines : max number of processes printed per mem/cpu section
225 | --no-cpu | --nc : skips CPU output
226 | --no-mem | --nm : skips memory output
227 | ```
228 |
229 | You can see some examples of these flags in use below in the ‘Advanced Examples’ section below.
230 |
231 |
232 |
233 | In the example output below, the user 'eve' is showing the highest CPU usage for the interval, and you can see the command that was being run by that user:
234 |
235 |
236 | ```
237 | user: dovecot
238 | memory-score: 84.30 memory-score:
239 | M: 84.30 proc: \_ dovecot/auth
240 | M: 0.00 proc: \_ dovecot/anvil
241 | cpu-score: 6.90
242 | C: 6.90 proc: \_ dovecot/auth
243 | C: 0.00 proc: \_ dovecot/anvil
244 |
245 | user: eve
246 | memory-score: 345.00
247 | M: 345.00 proc: /usr/bin/php /home/eve/public_html/website.com/script.php
248 | M: 0.00 proc: /usr/bin/ruby /usr/bin/mongrel_rails start -p 12008 -d -e production -P log/mongrel.pid
249 | cpu-score: 23847.00
250 | C: 23847.00 proc: /usr/bin/php /home/eve/public_html/website.com/script.php
251 | C: 0.00 proc: /usr/bin/ruby /usr/bin/mongrel_rails start -p 12008 -d -e production -P log/mongrel.pid
252 | ```
253 |
254 | Based on that output, the next step would be to investigate the '/home/eve/public_html/website.com/script.php script. This could be done by reading the script and checking the '/home/eve/access-logs/' logs to see what the script is doing. Many times there will be several processes and users that will need to be investigated. If a user is causing sever load, it might help to suspend them while the system administrator investigates the issue. If you have CloudLinux, the LVE manager could limit their resources and help increase server stability.
255 |
256 |
257 | http://docs.cloudlinux.com/cpanel_lve_manager.html
258 |
259 |
260 | If most of the users have the same resource usage but the server has high load, it’s time to think about upgrading the server hardware or moving some users to a different server.
261 |
262 |
263 | Advanced uses and examples
264 |
265 |
266 | Add an alias for quick access
267 |
268 |
269 | If you want to easily run sys-snap from any directory, add this alias to your /etc/bashrc file:
270 |
271 |
272 | ```
273 | alias syssnap="/root/sys-snap.pl"
274 | ```
275 |
276 | Print only CPU scores
277 |
278 | If you think you have a user that is spiking the processor between 11am and 11:15am, you would run a command like this to narrow down the user:
279 |
280 |
281 | ```
282 | [~] ./sys-snap.pl --print 11:00 11:15 --no-mem | head
283 | user: brock
284 | cpu-score: 142.76
285 | user: root
286 | cpu-score: 46.20
287 | user: ntp
288 | cpu-score: 0.00
289 | user: snapper
290 | cpu-score: 0.00
291 | user: mailnull
292 | cpu-score: 0.00
293 | user: brock
294 | cpu-score: 0.00
295 | ```
296 |
297 | Limit the number of lines per user in verbose output
298 |
299 |
300 | If you want to limit the number of lines that are output per user when you are parsing through the verbose output of --print, use the --max-lines flag.
301 |
302 |
303 | ```
304 | [~] ./sys-snap.pl --print 10:00 11:00 v --max-lines 5 | head 13
305 | user: root
306 | cpu-score: 47.70
307 | C: 41.00 proc: \_ spamd child
308 | C: 6.00 proc: \_ cpanellogd - updating bandwidth
309 | C: 0.70 proc: \_ [cpaneld - servi]
310 | C: 0.00 proc: \_ [cgroup]
311 | C: 0.00 proc: \_ [flush-252:0]
312 |
313 | memory-score: 541.60
314 | M: 386.70 proc: /usr/local/cpanel/3rdparty/bin/clamd
315 | M: 65.60 proc: \_ spamd child
316 | M: 23.40 proc: lfd - sleeping
317 | M: 15.60 proc: tailwatchd
318 | ```
319 |
320 | Display load averages (helpful for servers without Sar)
321 |
322 | If you want to print load averages for a given time frame, you can with the --loadavg flag.
323 |
324 | ```
325 | [~] ./sys-snap.pl --loadavg 11:00 11:30
326 | Time 1min-avg 5min-avg 15min-avg
327 | 11:00 0.19 0.07 0.03
328 | 11:10 0.06 0.04 0.00
329 | 11:20 0.00 0.02 0.00
330 | 11:30 0.06 0.03 0.00
331 | ```
332 |
333 | Add -i to change the interval between the load averages displayed
334 |
335 |
336 | ```
337 | [~] ./sys-snap.pl --loadavg 11:00 11:30 --i=5
338 |
339 | Time 1min-avg 5min-avg 15min-avg
340 | 11:00 0.19 0.07 0.03
341 | 11:05 0.03 0.05 0.02
342 | 11:10 0.06 0.04 0.00
343 | 11:15 0.02 0.05 0.00
344 | 11:20 0.00 0.02 0.00
345 | 11:25 0.31 0.14 0.10
346 | 11:30 0.06 0.03 0.00
347 | ```
348 |
349 | Parsing archived data
350 |
351 |
352 | When you start the script you will see that it archives historical data:
353 |
354 |
355 | ```
356 | [~] cd /root/ && chmod 744 sys-snap.pl && perl sys-snap.pl --start
357 | Sys-snap is not currently running
358 | Start sys-snap logging to '/root/system-snapshot/' (y/n)?:y
359 | Starting...
360 | tar: Removing leading `/' from member names
361 | [~]
362 | ```
363 |
364 | If you want to access that historical data, you just need to unarchive the folder and pass the --dir flag:
365 |
366 |
367 | ```
368 | [~] tar -xf system-snapshot.20150422.1341.tar.gz
369 | [~] ./sys-snap.pl --print 6:30 7:50 --dir=/root/system-snapshot.20150422.1341 | head -n11
370 | user: root
371 | cpu-score: 64.20
372 | memory-score: 1696.60
373 | user: roompod
374 | cpu-score: 9.30
375 | memory-score: 2.10
376 | user: patrick
377 | cpu-score: 8.50
378 | memory-score: 13.10
379 | user: msusci
380 | cpu-score: 0.20
381 | [~]
382 | ```
383 |
384 | Common problems
385 |
386 |
387 | You may see this error when attempting to install the script:
388 |
389 |
390 | ```
391 | [~] ./sys-snap.pl --install
392 |
393 | Can't locate Time/Piece.pm in @INC (@INC contains: /usr/local/lib/perl5/5.8.8/x86_64-linux /usr/local/lib/perl5/5.8.8 /usr/local/lib/perl5/site_perl/5.8.8/x86_64-linux /usr/local/lib/perl5/site_perl/5.8.8 /usr/local/lib/perl5/site_perl .) at ./sys-snap.pl line 126.
394 | BEGIN failed--compilation aborted at ./sys-snap.pl line 126.
395 | [~]
396 | ```
397 |
398 | You can correct it with this command:
399 |
400 |
401 | ```
402 | [~] cpan -i Time::Piece
403 | ```
404 |
--------------------------------------------------------------------------------
/sys-snap.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl
2 | # Copyright (C) 2015
3 | # This program is free software; you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation; either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | # You should have received a copy of the GNU General Public License
13 | # along with this program; if not, write to the Free Software Foundation,
14 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15 |
16 | # Author Bryan Christensen
17 |
18 | use warnings;
19 | use strict;
20 | use Getopt::Long;
21 | use Fcntl qw(:DEFAULT :flock);
22 |
23 | my %opt = (
24 | 'start' => 0,
25 | 'stop' => 0,
26 | 'check' => 0,
27 | 'print' => 0,
28 | 'loadavg' => 0,
29 | 'help' => 0,
30 | 'network' => 0,
31 | 'io' => 0,
32 | 'print_cpu' => 1,
33 | 'print_memory' => 1,
34 | 'interval' => 10,
35 | 'loadavg' => 0,
36 | 'dir' => '/root/system-snapshot',
37 | 'verbose' => '0',
38 | 'max-lines' => '20',
39 | 'line_length' => '145',
40 | 'pidfile' => '/var/run/sys-snap.pid',
41 | );
42 |
43 | GetOptions(
44 | \%opt,
45 | 'help|h+',
46 | 'print',
47 | 'network',
48 | 'start',
49 | 'stop',
50 | 'check|c',
51 | 'loadavg',
52 | 'io',
53 | 'cpu!' => \$opt{'print_cpu'},
54 | 'mem!' => \$opt{'print_memory'},
55 | 'interval|i=i' => \$opt{'interval'},
56 | 'dir|d=s' => \$opt{'dir'},
57 | 'verbose|v!' => \$opt{'verbose'},
58 | 'max-lines|ml=i' => \$opt{'max-lines'},
59 | ) or usage();
60 |
61 | ########################################################
62 | # start of parameters that don't need time
63 | ########################################################
64 |
65 | if ( $opt{'help'} ) {
66 | usage();
67 | exit;
68 | }
69 | elsif ( $opt{'start'} ) {
70 | run_install();
71 | exit;
72 | }
73 | elsif ( $opt{'stop'} ) {
74 | stop_syssnap();
75 | exit;
76 | }
77 | elsif ( $opt{'check'} ) {
78 | check_status();
79 | exit;
80 | }
81 |
82 | ########################################################
83 | # start of parameters that need time
84 | ########################################################
85 |
86 | # two extra parameters are expected if you are using options that need time
87 | if ( @ARGV < 2 ) {
88 | usage();
89 |
90 | #print "No time range specified\n";
91 | exit();
92 | }
93 | elsif ( @ARGV > 3 ) {
94 | print "Too many unknown parameters\n";
95 | exit;
96 | }
97 | elsif ( @ARGV == 2 ) {
98 | $opt{'time1'} = $ARGV[0];
99 | $opt{'time2'} = $ARGV[1];
100 | }
101 |
102 | if ( $opt{'loadavg'} ) {
103 | loadavg( \%opt );
104 | exit;
105 | }
106 |
107 | if ( $opt{'io'} ) {
108 | snap_io( \%opt );
109 | exit;
110 | }
111 |
112 | if ( $opt{'print'} ) {
113 | snap_print_range( \%opt );
114 | exit;
115 | }
116 |
117 | if ( $opt{'network'} ) {
118 | snap_network( \%opt );
119 | exit;
120 | }
121 |
122 | # I don't think the logic flow should ever hit this, but just in case
123 | usage();
124 | exit;
125 |
126 | sub snap_network {
127 | my %opt = %{ shift @_ };
128 | my $time1 = $opt{'time1'};
129 | my $time2 = $opt{'time2'};
130 | my $snapshot_dir = $opt{'dir'};
131 | my $detail_level = $opt{'verbose'};
132 | my $max_lines = $opt{'max-lines'};
133 | my $print_cpu = $opt{'print_cpu'};
134 | my $print_memory = $opt{'print_memory'};
135 |
136 | # old school 80 is standard, but 145 works well with 1366 width monitor
137 | my $line_length = $opt{'line_length'};
138 |
139 | #root_dir is legacy param, will remove later
140 | my $root_dir = "";
141 |
142 | # the default formatting where the process ID is added needs 16 lines
143 | # subtracting 16 here will make the specified width more "true"
144 | $line_length = $line_length - 16;
145 |
146 | module_sanity_check();
147 | eval("use Time::Piece;");
148 | if ($@) {
149 | print "***\nCould not install Time::Piece - try manually installing.\n***\n";
150 | exit;
151 | }
152 |
153 | my ( $time1_hour, $time1_minute, $time2_hour, $time2_minute ) = &parse_check_time( $time1, $time2 );
154 | my @snap_log_files = &get_range( $root_dir, $snapshot_dir, $time1_hour, $time1_minute, $time2_hour, $time2_minute );
155 |
156 | my %ip_connections;
157 | my ( %localip, %foreignip );
158 |
159 | #print "Time\t1min-avg\t5min-avg\t15min-avg\n";
160 | foreach my $file_name (@snap_log_files) {
161 |
162 | open( my $FILE, "<", $file_name ) or next; #die "Couldn't open file: $!";
163 | my $string = join( "", <$FILE> );
164 | close($FILE);
165 |
166 | my @lines;
167 |
168 | # reading line by line to split the sections might be faster
169 | my $matchme = "^Active Internet connections [^\n]+\n";
170 |
171 | #my $matchme = "^Process List:\n\nUSER[^\n]+COMMAND\n";
172 | if ( $string =~ /$matchme(.*)\nActive UNIX domain sockets \(servers and established\)/sm ) {
173 | my $baseString = $1;
174 | @lines = split( /\n/, $baseString );
175 | }
176 |
177 | # could add ports in the future and connection state
178 | # should skip listen and time_wait entries
179 | foreach my $line (@lines) {
180 | if ( $line =~ /[a-z]{3}\s+\d+\s+\d+\s+(\d+\.\d+\.\d+\.\d+):\d+\s+(\d+\.\d+\.\d+\.\d+):\d+\s+(?!TIME_WAIT)/ ) {
181 | if ( $ip_connections{$1}{$2} ) {
182 | $ip_connections{$1}{$2} += 1;
183 | }
184 | else {
185 | $ip_connections{$1}{$2} = 1;
186 | }
187 | }
188 | }
189 | }
190 |
191 | foreach my $localip ( keys %ip_connections ) {
192 | my @sorted_ip = sort { $ip_connections{$localip}{$b} <=> $ip_connections{$localip}{$a} } keys %{ $ip_connections{$localip} };
193 | print "$localip: \n";
194 | for (@sorted_ip) {
195 | printf "\t%-15s %-8d\n", $_, $ip_connections{$localip}{$_};
196 | }
197 | print "\n";
198 | }
199 | }
200 |
201 | sub usage {
202 | my $text = <<"ENDTXT";
203 | USAGE:
204 | ./sys-snap.pl [options]
205 | --start : Creates, disowns, and drops 'sys-snap.pl --start' process into the background
206 | --stop : stops sys-snap after confirming PID info
207 | --check : Checks if sys-snap is running
208 | --print : Where time HH:MM, prints basic usage by default
209 | --network : Prints IP connections durring time range
210 | --v | v : verbose output from --print
211 | --max-lines : max number of processes printed per mem/cpu section, default is 20
212 | --ll : line length, default is 145
213 | --no-cpu | --nc : skips CPU output
214 | --no-mem | --nm : skips memory output
215 | --loadavg : Where time HH:MM, prints load average for time period - default 10 min interval
216 | --dir : specifies a different sys-snap folder for --print and --loadavg
217 |
218 | ENDTXT
219 |
220 | print $text;
221 | exit;
222 | }
223 |
224 | sub snap_io {
225 | eval("use Time::Piece;");
226 | my %opt = %{ shift @_ };
227 | my $time1 = $opt{'time1'};
228 | my $time2 = $opt{'time2'};
229 | my $interval = $opt{'interval'};
230 | my $snapshot_dir = $opt{'dir'};
231 |
232 | #root_dir is legacy param, will remove later
233 | my $root_dir = "";
234 |
235 | if ( $interval > 60 || $interval < 0 ) {
236 | $interval = 10;
237 | }
238 |
239 | my ( $time1_hour, $time1_minute, $time2_hour, $time2_minute ) = &parse_check_time( $time1, $time2 );
240 | my @snap_log_files = &get_range( $root_dir, $snapshot_dir, $time1_hour, $time1_minute, $time2_hour, $time2_minute );
241 |
242 | print "avg-cpu:\t%user\t%nice\t%system\t%iowait\t%steal\t%idle\n";
243 | foreach my $file_name (@snap_log_files) {
244 |
245 | # load information is currently printed to the first line
246 | # only need to read first line
247 | open( my $FILE, "<", $file_name ) or next; #die "Couldn't open file: $!";
248 | #my $string = <$FILE>;
249 | my $string = join( "", <$FILE> );
250 | my ($min) = $string =~ m{^\d+\s+\d+\s+(\d+)\s+Load Average:}g;
251 |
252 | #print "$min\n";
253 | close($FILE);
254 |
255 | my @lines;
256 | if ( $string =~ /^IO wait:\n(.*)\nMYSQL Processes:$/sm ) {
257 | my $baseString = $1;
258 | @lines = split( /\n/, $baseString );
259 | }
260 |
261 | foreach my $line (@lines) {
262 | my ( $io_user, $nice, $io_system, $io_wait, $steal, $idle );
263 | ( $io_user, $nice, $io_system, $io_wait, $steal, $idle ) = $line =~ m{^\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)};
264 | if ( defined $io_user && ( $min % $interval == 0 ) ) {
265 | print "\t\t$io_user\t$nice\t$io_system\t$io_wait\t$steal\t$idle\n";
266 | }
267 | }
268 | }
269 | return;
270 | }
271 |
272 | sub loadavg {
273 | eval("use Time::Piece;");
274 | my %opt = %{ shift @_ };
275 | my $time1 = $opt{'time1'};
276 | my $time2 = $opt{'time2'};
277 | my $interval = $opt{'interval'};
278 | my $snapshot_dir = $opt{'dir'};
279 |
280 | #root_dir is legacy param, will remove later
281 | my $root_dir = "";
282 |
283 | if ( $interval > 60 || $interval < 0 ) {
284 | $interval = 10;
285 | }
286 |
287 | my ( $time1_hour, $time1_minute, $time2_hour, $time2_minute ) = &parse_check_time( $time1, $time2 );
288 |
289 | my @snap_log_files = &get_range( $root_dir, $snapshot_dir, $time1_hour, $time1_minute, $time2_hour, $time2_minute );
290 |
291 | print "Time\t1min-avg\t5min-avg\t15min-avg\n";
292 |
293 | foreach my $file_name (@snap_log_files) {
294 |
295 | # load information is currently printed to the first line
296 | # only need to read first line
297 | open( my $FILE, "<", $file_name ) or next; #die "Couldn't open file: $!";
298 | my $string = <$FILE>;
299 | close($FILE);
300 |
301 | my ( $avg1min, $avg5min, $avg15min, $hour, $min );
302 | ( $hour, $min, $avg1min, $avg5min, $avg15min ) = $string =~ m{^\d+\s+(\d+)\s+(\d+)\s+Load Average: (\d+\.\d+)\s(\d+\.\d+)\s(\d+\.\d+)\s.*$};
303 |
304 | if ( defined $hour && defined $min & defined $avg1min && defined $avg5min && defined $avg15min && ( $min % $interval == 0 ) ) {
305 | $min = "0" . $min if ( $min =~ m{^\d$} );
306 | print "$hour:$min\t$avg1min\t\t$avg5min\t\t$avg15min\n";
307 | }
308 | }
309 | return;
310 | }
311 |
312 | sub stop_syssnap {
313 | if ( my $pid = check_status() ) {
314 | print "Stop this process (y/n)?:";
315 | my $choice = "0";
316 | $choice = ;
317 | while ( $choice !~ /[yn]/i ) {
318 | print "Stop this process (y/n)?:";
319 | $choice = ;
320 | chomp($choice);
321 | }
322 | if ( $choice =~ /[y]/i ) {
323 | print "Stopping $pid\n";
324 | kill 9, $pid;
325 | unlink $opt{'pidfile'};
326 | exit;
327 | }
328 | else { print "Exiting...\n"; exit; }
329 | }
330 | return;
331 | }
332 |
333 | sub check_status {
334 | my $pid;
335 | my $pidfh;
336 | my $pidfile = $opt{'pidfile'};
337 | my $status = 0;
338 |
339 | sysopen( $pidfh, $pidfile, O_RDWR | O_CREAT )
340 | or die "Could not open $pidfile: $!\n";
341 | if ( flock( $pidfh, LOCK_NB | LOCK_EX ) ) {
342 | print "Sys-snap not currently running.\n";
343 | flock( $pidfh, LOCK_UN )
344 | or die "Problem releasing lock on '$pidfile': $!\n";
345 | $status = 0;
346 | }
347 | else {
348 | print "Sys-snap is running, PID: ";
349 | while ( $pid = <$pidfh> ) {
350 | print "'$pid'\n";
351 | $status = $pid;
352 | }
353 | }
354 | return $status;
355 | }
356 |
357 | sub parse_check_time {
358 |
359 | my $time1 = shift;
360 | my $time2 = shift;
361 |
362 | if ( !defined $time1 || !defined $time2 ) { print "Need 2 parameters, \"./snap-print start-time end-time\"\n"; exit; }
363 |
364 | my ( $time1_hour, $time1_minute, $time2_hour, $time2_minute );
365 |
366 | if ( ( $time1_hour, $time1_minute ) = $time1 =~ m{^(\d{1,2}):(\d{2})$} ) {
367 | if ( $time1_hour >= 0 && $time1_hour <= 23 && $time1_minute >= 0 && $time1_minute <= 59 ) {
368 |
369 | #print "$time1_hour $time1_minute\n";
370 | }
371 | else { print "Fail: Fictitious time.\n"; exit; }
372 |
373 | }
374 | else { print "Fail: Could not parse start time\n"; exit; }
375 |
376 | if ( ( $time2_hour, $time2_minute ) = $time2 =~ m{(\d{1,2}):(\d{2})} ) {
377 | if ( $time2_hour >= 0 && $time2_hour <= 23 && $time2_minute >= 0 && $time2_minute <= 59 ) {
378 |
379 | #print $time2_hour $time2_minute\n";
380 | }
381 | else { print "Fail: Fictitious time.\n"; exit; }
382 |
383 | }
384 | else { print "Fail: Could not parse end time\n"; exit; }
385 |
386 | if ( defined $time1_hour && defined $time2_hour ) { return ( $time1_hour, $time1_minute, $time2_hour, $time2_minute ); }
387 | return 0;
388 | }
389 |
390 | sub snap_print_range {
391 | my %opt = %{ shift @_ };
392 | my $time1 = $opt{'time1'};
393 | my $time2 = $opt{'time2'};
394 | my $snapshot_dir = $opt{'dir'};
395 | my $detail_level = $opt{'verbose'};
396 | my $max_lines = $opt{'max-lines'};
397 | my $print_cpu = $opt{'print_cpu'};
398 | my $print_memory = $opt{'print_memory'};
399 |
400 | # old school 80 is standard, but 145 works well with 1366 width monitor
401 | my $line_length = $opt{'line_length'};
402 |
403 | #root_dir is legacy param, will remove later
404 | my $root_dir = "";
405 |
406 | # the default formatting where the process ID is added needs 16 lines
407 | # subtracting 16 here will make the specified width more "true"
408 | $line_length = $line_length - 16;
409 |
410 | if ( !defined $time1 || !defined $time2 ) { print "Need 2 parameters, \"./snap-print start-time end-time\"\n"; exit; }
411 |
412 | module_sanity_check();
413 | eval("use Time::Piece;");
414 | if ($@) {
415 | print "***\nCould not install Time::Piece - try manually installing.\n***\n";
416 | exit;
417 | }
418 |
419 | use Time::Seconds;
420 |
421 | # not using this yet, but if we parse a range of data that crosses this file the resulting data is noncontigous
422 | # and might be misleading. printing a warning might be apropriate in this scenario or having some other flag
423 | # to indicate this has happened
424 | #my $newest_file = qx(ls -la ${root_dir}/system-snapshot/current);
425 |
426 | my ( $time1_hour, $time1_minute, $time2_hour, $time2_minute ) = &parse_check_time( $time1, $time2 );
427 |
428 | # get the files we want to read
429 | my @snap_log_files = &get_range( $root_dir, $snapshot_dir, $time1_hour, $time1_minute, $time2_hour, $time2_minute );
430 |
431 | my ( $tmp1, $tmp2 ) = &read_logs( \@snap_log_files );
432 |
433 | # users cumulative CPU and Mem score
434 | my %basic_usage = %$tmp1;
435 |
436 | #raw data from logs
437 | my %process_list_data = %$tmp2;
438 |
439 | # weighted process & memory
440 | my %users_wcpu_process;
441 | my %users_wmemory_process;
442 |
443 | if ( $detail_level == 0 ) { &run_basic( \%basic_usage, $print_cpu, $print_memory ); exit }
444 |
445 | # adding up memory and CPU usage per user's process
446 | foreach my $user ( sort keys %process_list_data ) {
447 | foreach my $process ( sort keys %{ $process_list_data{$user} } ) {
448 |
449 | $users_wcpu_process{$user}{$process} += $process_list_data{$user}{$process}{'cpu'};
450 | $users_wmemory_process{$user}{$process} += $process_list_data{$user}{$process}{'memory'};
451 | }
452 | }
453 |
454 | my $sort_param;
455 | if ($print_cpu) { $sort_param = "cpu"; }
456 | else { $sort_param = "memory"; }
457 |
458 | foreach my $user ( sort { $basic_usage{$b}->{$sort_param} <=> $basic_usage{$a}->{$sort_param} } keys %basic_usage ) {
459 |
460 | printf "user: %-15s", $user;
461 |
462 | my $num_lines = 0;
463 | if ($print_cpu) {
464 |
465 | my @sorted_cpu = sort { $users_wcpu_process{$user}{$b} <=> $users_wcpu_process{$user}{$a} } keys %{ $users_wcpu_process{$user} };
466 |
467 | printf "\n\tcpu-score: %-10.2f\n", $basic_usage{$user}{'cpu'};
468 | for (@sorted_cpu) {
469 | printf "\t\tC: %4.2f proc: ", $users_wcpu_process{$user}{$_};
470 | print substr( $_, 0, $line_length ) . "\n";
471 | if ( $num_lines >= $max_lines - 1 ) { last; }
472 | else { $num_lines += 1; }
473 | }
474 | }
475 |
476 | $num_lines = 0;
477 | if ($print_memory) {
478 |
479 | my @sorted_mem = sort { $users_wmemory_process{$user}{$b} <=> $users_wmemory_process{$user}{$a} } keys %{ $users_wmemory_process{$user} };
480 |
481 | printf "\n\tmemory-score: %-11.2f\n", $basic_usage{$user}{'memory'};
482 | for (@sorted_mem) {
483 | printf "\t\tM: %4.2f proc: ", $users_wmemory_process{$user}{$_};
484 | print substr( $_, 0, $line_length ) . "\n";
485 | if ( $num_lines >= $max_lines - 1 ) { last; }
486 | else { $num_lines += 1; }
487 | }
488 | }
489 | print "\n";
490 | }
491 | exit;
492 | }
493 |
494 | ## should be rewritten to take parameters of log subsections to be read
495 | # returns hash of hashes
496 | sub read_logs {
497 |
498 | my $tmp = shift;
499 | my @snap_log_files = @$tmp;
500 |
501 | my %process_list_data;
502 | my %basic_usage;
503 |
504 | foreach my $file_name (@snap_log_files) {
505 |
506 | my @lines;
507 |
508 | open( my $FILE, "<", $file_name ) or next; #die "Couldn't open file: $!";
509 | my $string = join( "", <$FILE> );
510 | close($FILE);
511 |
512 | # reading line by line to split the sections might be faster
513 | my $matchme = "^Process List:\n\nUSER[^\n]+COMMAND\n";
514 | if ( $string =~ /^$matchme(.*)\nNetwork Connections\:$/sm ) {
515 | my $baseString = $1;
516 | @lines = split( /\n/, $baseString );
517 | }
518 |
519 | foreach my $l (@lines) {
520 | my ( $user, $cpu, $memory, $command );
521 | ( $user, $cpu, $memory, $command ) = $l =~ m{^(\w+)\s+\d+\s+(\d{1,2}\.\d)\s+(\d{1,2}\.\d).*\d{1,2}:\d{2}\s+(.*)$};
522 |
523 | if ( defined $user && defined $cpu && defined $memory && defined $command ) {
524 |
525 | if ( $user !~ m/[a-zA-Z0-9_\.\-]+/ ) { next; }
526 | if ( $cpu !~ m/[0-9\.]+/ && $memory !~ m/[0-9\.]+/ ) { next; }
527 | $basic_usage{$user}{'memory'} += $memory;
528 | $basic_usage{$user}{'cpu'} += $cpu;
529 |
530 | # agrigate hash? of commands - roll object
531 |
532 | # if the process is the same, accumulate it, if not create it
533 | # assuming if we have a memory value for a command, we should have a cpu value - nothing can ever go wrong here :smiley face:
534 | if ( defined $process_list_data{$user}{$command}{'memory'} ) {
535 | $process_list_data{$user}{$command}{'memory'} += $memory;
536 | $process_list_data{$user}{$command}{'cpu'} += $cpu;
537 | }
538 | else {
539 | $process_list_data{$user}{$command}{'cpu'} = $cpu;
540 | $process_list_data{$user}{$command}{'memory'} = $memory;
541 | }
542 | }
543 | }
544 | }
545 | return ( \%basic_usage, \%process_list_data );
546 | }
547 |
548 | # returns ordered array of stings that represent file location
549 | # could create $accuracy variable to run modulo integers for faster processing at expense of accuracy
550 | sub get_range {
551 |
552 | my $root_dir = shift;
553 | my $snapshot_dir = shift;
554 | my $time1_hour = shift;
555 | my $time1_minute = shift;
556 | my $time2_hour = shift;
557 | my $time2_minute = shift;
558 | my $time1 = "$time1_hour:$time1_minute";
559 | my $time2 = "$time2_hour:$time2_minute";
560 |
561 | my @snap_log_files;
562 | my ( $file_hour, $file_minute );
563 |
564 | # Even if we want to ignore the date, Time::Piece will create one. This is probably easier than rolling a custom time cycle for over night periods such as 23:57 0:45,
565 | # and should make modification easier if longer date ranges are added too.
566 | # Mind the date format 'DAY MONTH YEAR(XXXX)'
567 | my $start_time = Time::Piece->strptime( "2-2-1993 $time1", "%d-%m-%Y %H:%M" );
568 | my $end_time;
569 |
570 | if ( $time1_hour < $time2_hour || ( $time1_hour == $time2_hour && $time1_minute < $time2_minute ) ) {
571 | $end_time = Time::Piece->strptime( "2-2-1993 $time2", "%d-%m-%Y %H:%M" );
572 | }
573 | else {
574 | $end_time = Time::Piece->strptime( "3-2-1993 $time2", "%d-%m-%Y %H:%M" );
575 | }
576 |
577 | while ( $start_time <= $end_time ) {
578 |
579 | #print $start_time->strftime('%H:%M') . "\n";
580 | ( $file_hour, $file_minute ) = split( /:/, $start_time->strftime('%H:%M') );
581 |
582 | #sys-snap not currently appending 0's to the front of files
583 | $file_minute =~ s/^0(\d)$/$1/;
584 | $file_hour =~ s/^0(\d)$/$1/;
585 |
586 | #print "$root_dir$snapshot_dir/$file_hour/$file_minute.log\n";
587 | push @snap_log_files, "$root_dir$snapshot_dir/$file_hour/$file_minute.log";
588 | $start_time += 60;
589 | }
590 |
591 | return @snap_log_files;
592 | }
593 |
594 | # since mem and cpu info gets printed to the same line, we already have the data at this point,
595 | # and even sorting a large number of users by usage is relativly inexpensive, just going to mute unwanted output
596 | sub run_basic {
597 | my $tmp = shift;
598 | my $print_cpu = shift;
599 | my $print_memory = shift;
600 | my %basic_usage;
601 | %basic_usage = %$tmp;
602 |
603 | my $sortby = 'cpu';
604 | if ( $print_cpu != 1 ) { $sortby = 'memory'; }
605 | foreach my $key (
606 | sort { $basic_usage{$b}->{$sortby} <=> $basic_usage{$a}->{$sortby} }
607 | keys %basic_usage
608 | ) {
609 | my $value = $basic_usage{$key};
610 |
611 | #printf( "user: %-15s\n\tcpu-score: %-12.2f \n\tmemory-score: %-12.2f\n\n", $key, $value->{cpu}, $value->{memory} );
612 | printf( "user: %-15s\n", $key );
613 | printf( "\tcpu-score: %-12.2f\n", $value->{cpu} ) if $print_cpu;
614 | printf( "\tmemory-score: %-12.2f\n", $value->{memory} ) if $print_memory;
615 | }
616 | print "\n";
617 |
618 | exit;
619 | }
620 |
621 | sub run_install {
622 | if ( not check_status ) {
623 | print "Start sys-snap logging to '/root/system-snapshot/' (y/n)?:";
624 | my $choice = "0";
625 | $choice = ;
626 | while ( $choice !~ /[yn]/i ) {
627 | print "Start sys-snap logging to '/root/system-snapshot/' (y/n)?:";
628 | $choice = ;
629 | chomp($choice);
630 | }
631 | if ( $choice =~ /[y]/i ) {
632 | print "Starting...\n";
633 | }
634 | else { print "Exiting...\n"; exit; }
635 | }
636 | else {
637 | print "Unable to start, only one sys-snap process should be active at a time\n";
638 | exit;
639 | }
640 |
641 | use File::Path qw(rmtree);
642 | use POSIX qw(setsid);
643 |
644 | ###############
645 | # Set Options #
646 | ###############
647 |
648 | # Set the time between snapshots in seconds
649 | my $sleep_time = 60;
650 |
651 | # The base directory under which to build the directory where snapshots are stored.
652 | my $root_dir = '/root';
653 |
654 | # Sometimes you won't have mysql and/or you won't have the root password to put in a .my.cnf file
655 | # if that's the case, set this to 0
656 | my $mysql = 1;
657 |
658 | # If the server has lighttpd or some other webserver, set this to 0
659 | # cPanel is autodetected later, so this setting is not used if running cPanel.
660 | my $apache = 1;
661 |
662 | # If you want extended data, set this to 1
663 | my $max_data = 0;
664 |
665 | # Get the date, hour, and min for various tasks
666 | my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
667 | $year += 1900; # Format year correctly
668 | $mon++; # Format month correctly
669 | $mon = 0 . $mon if $mon < 10;
670 | $mday = 0 . $mday if $mday < 10;
671 | my $date = $year . $mon . $mday;
672 |
673 | # Ensure target directory exists and is writable
674 | if ( !-d $root_dir ) {
675 | die "$root_dir is not a directory\n";
676 | }
677 | elsif ( !-w $root_dir ) {
678 | die "$root_dir is not writable\n";
679 | }
680 |
681 | if ( -d "$root_dir/system-snapshot" ) {
682 | system 'tar', 'czf', "${root_dir}/system-snapshot.${date}.${hour}${min}.tar.gz", "${root_dir}/system-snapshot";
683 | rmtree("$root_dir/system-snapshot");
684 | }
685 |
686 | if ( !-d "$root_dir/system-snapshot" ) {
687 | mkdir "$root_dir/system-snapshot";
688 | }
689 |
690 | # try to split process into background
691 | chdir '/' or die "Can't chdir to /: $!";
692 | open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
693 | open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
694 | defined( my $pid = fork ) or die "Can't fork: $!";
695 | exit if $pid;
696 | print "$pid\n";
697 | setsid or die "Can't start a new session: $!";
698 | open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
699 |
700 | # Create a PID file for the new child process
701 | my $childpid = $$;
702 | my $pidfh;
703 | my $pidfile = $opt{'pidfile'};
704 |
705 | sysopen( $pidfh, $pidfile, O_RDWR | O_CREAT )
706 | or die "Could not open $pidfile: $!\n";
707 | print $pidfh "$childpid";
708 | flock( $pidfh, LOCK_NB | LOCK_EX )
709 | or die "Could not get lock on $pidfile: $!\n";
710 |
711 | ##########
712 | # Main() #
713 | ##########
714 |
715 | while (1) {
716 |
717 | # Ensure we have a current date/time
718 | ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
719 | $year += 1900; # Format year correctly
720 | $mon++; # Format month correctly
721 | $mon = 0 . $mon if $mon < 10;
722 | $mday = 0 . $mday if $mday < 10;
723 | $date = $year . $mon . $mday;
724 |
725 | # go to the next log file
726 | mkdir "$root_dir/system-snapshot/$hour";
727 | my $current_interval = "$hour/$min";
728 |
729 | my $logfile = "$root_dir/system-snapshot/$current_interval.log";
730 | open( my $LOG, '>', $logfile )
731 | or die "Could not open log file $logfile, $!\n";
732 |
733 | # start actually logging #
734 | my $load = qx(cat /proc/loadavg);
735 |
736 | #print $LOG "Load Average:\n\n"; # without this line, you can get historical loads with head -n1 *
737 | print $LOG "$date $hour $min Load Average: $load\n";
738 |
739 | print $LOG "Memory Usage:\n\n";
740 | print $LOG qx(cat /proc/meminfo), "\n";
741 |
742 | print $LOG "Virtual Memory Stats:\n\n";
743 | print $LOG qx(vmstat 1 10), "\n";
744 |
745 | print $LOG "Process List:\n\n";
746 | print $LOG qx(ps awwxf -o user:20,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args), "\n";
747 |
748 | print $LOG "Network Connections:\n\n";
749 | print $LOG qx(netstat -anp), "\n";
750 |
751 | print $LOG "IO wait:\n\n";
752 | print $LOG qx(iostat), "\n";
753 |
754 | # optional logging
755 | if ($mysql) {
756 | print $LOG "MYSQL Processes:\n\n";
757 | print $LOG qx(mysqladmin proc), "\n";
758 | }
759 |
760 | print $LOG "Apache Processes:\n\n";
761 | if ( -f '/usr/local/cpanel/cpanel' ) {
762 | print $LOG qx(lynx --dump localhost/whm-server-status), "\n";
763 | }
764 | elsif ($apache) {
765 | print $LOG qx#lynx -width=1024 -dump http://localhost/server-status | egrep '(Client.+Request|GET|POST|HEAD)'#, "\n";
766 | }
767 |
768 | if ($max_data) {
769 | print $LOG "Process List for user Nobody:\n\n";
770 | my @process_list = qx(ps aux | grep [n]obody | awk '{print \$2}');
771 | foreach my $process (@process_list) {
772 | print $LOG qx(ls -al /proc/$process | grep cwd | grep home);
773 | }
774 | print $LOG "List of Open Files:\n\n";
775 | print $LOG qx(lsof), "\n";
776 | }
777 |
778 | close $LOG;
779 |
780 | # rotate the "current" pointer
781 | rmtree("$root_dir/system-snapshot/current");
782 | symlink "${current_interval}.log", "$root_dir/system-snapshot/current";
783 |
784 | sleep($sleep_time);
785 | }
786 | }
787 |
788 | sub module_sanity_check {
789 | eval("use Time::Piece;");
790 | if ($@) {
791 | print "WARNING: Perl Module Time::Piece.pm not installed!\n";
792 | print "Would you like sys-snap to attempt to install this moduel(y/n):";
793 |
794 | my $choice = ;
795 | if ( $choice =~ /yes|y/i ) {
796 | print "Installing now - Please stand by.\n";
797 | system("cpan -i Time::Piece");
798 | }
799 | else {
800 | exit;
801 | }
802 | }
803 | return;
804 | }
805 |
--------------------------------------------------------------------------------