File Coverage

blib/lib/Unicorn/Manager/CLI/Proc.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Unicorn::Manager::CLI::Proc::Table;
2              
3 1     1   4 use Moo;
  1         2  
  1         5  
4 1     1   1182 use Time::HiRes 'usleep';
  1         1886  
  1         4  
5 1     1   220 use strict;
  1         9  
  1         29  
6 1     1   4 use warnings;
  1         2  
  1         37  
7 1     1   5 use autodie;
  1         1  
  1         8  
8 1     1   4679 use 5.010;
  1         9  
  1         50  
9              
10 1     1   2042 use Unicorn::Manager::Types;
  0            
  0            
11              
12             has ptable => (
13             is => 'rw',
14             isa => Unicorn::Manager::Types::hashref,
15             );
16              
17             sub BUILD {
18             my $self = shift;
19             $self->_parse_ps;
20             }
21              
22             sub refresh {
23             my $self = shift;
24             return $self->_parse_ps;
25             }
26              
27             # build a hash tree of the format
28             #
29             # {
30             # uid => {
31             # unicorn_master_pid => [
32             # list_of_worker_pids,
33             # { #
34             # new_master_pid => [ # during graceful restart via SIGUSR2 and SIGWINCH
35             # list_of_new_worker_pids #
36             # ] #
37             # } #
38             # ]
39             # }
40             # }
41             #
42             # TODO: ignore unicorn processes that are not daemonized
43             sub _parse_ps {
44             my $self = shift;
45             my @users;
46              
47             # grab the process table of unicorn_rails processes
48             # build tree skeleton
49             for (qx[ ps fauxn | grep unicorn_rails |grep -v grep ]) {
50             ( undef, my $user, my $pid ) = split /\s+/, $_;
51             push @users, { $user => $pid };
52             }
53              
54             my $tree = {};
55             my $sub_tree = {};
56              
57             # walk over users with unicorn_rails processes running
58             # and check which is worker and which is master
59             # then place them inside of the tree
60             #
61             # build a subtree of processes that have grandparents to
62             # sort them into the array of children in the next step
63             for (@users) {
64             my ( $uid, $current_pid ) = each %{$_};
65              
66             my $found_pid_status = 0;
67              
68             while ( not $found_pid_status ) {
69             $found_pid_status = 1 if -f "/proc/$current_pid/status";
70              
71             # check every 1ms
72             # TODO implement some timeout to prevent endless loop
73             Time::HiRes::usleep 1000;
74             }
75              
76             open my $fh, '<', "/proc/$current_pid/status";
77             while (<$fh>) {
78              
79             if ( $_ =~ /PPid:\t\d+/ ) {
80             my ( undef, $parent_pid ) = split /\s+/, $&;
81              
82             # ppid not equal to 1 means the process is a worker
83             # or a new master
84             if ( $parent_pid ne '1' ) {
85              
86             open my $parent_fh, '<', "/proc/$parent_pid/status";
87             while (<$parent_fh>) {
88              
89             if ( $_ =~ /PPid:\t\d+/ ) {
90             ( undef, my $parent_parent_pid ) = split /\s+/, $&;
91              
92             # pppid not equal to one means the process
93             # has a grandparent and therefor is a new
94             # master or a new masters child
95             if ( $parent_parent_pid ne '1' ) {
96             push @{ $sub_tree->{$uid}->{$parent_parent_pid}->{$parent_pid} }, $current_pid;
97             }
98             else {
99             push @{ $tree->{$uid}->{$parent_pid} }, $current_pid;
100             }
101              
102             }
103              
104             }
105             close $parent_fh;
106              
107             }
108             }
109             }
110             close $fh;
111              
112             }
113              
114             # build processes with grandparents into the tree
115             for my $user ( keys %{$sub_tree} ) {
116             for my $grandparent ( keys %{ $sub_tree->{$user} } ) {
117             for my $parent ( keys %{ $sub_tree->{$user}->{$grandparent} } ) {
118              
119             my $i = 0;
120             for ( @{ $tree->{$user}->{$grandparent} } ) {
121             if ( $parent == $_ ) {
122             ${ $tree->{$user}->{$grandparent} }[$i] = { $parent => $sub_tree->{$user}->{$grandparent}->{$parent} };
123             }
124             $i++;
125             }
126             }
127             }
128             }
129              
130             return $self->ptable($tree) ? 1 : 0;
131             }
132              
133             1;
134              
135             package Unicorn::Manager::CLI::Proc;
136              
137             use Moo;
138             use JSON;
139             use strict;
140             use warnings;
141             use autodie;
142             use 5.010;
143              
144             has process_table => ( is => 'rw', );
145              
146             sub BUILD {
147             my $self = shift;
148             $self->process_table( Unicorn::Manager::CLI::Proc::Table->new );
149             }
150              
151             sub refresh {
152             my $self = shift;
153             $self->process_table->refresh;
154             }
155              
156             sub as_json {
157             my $self = shift;
158              
159             my $json = JSON->new->utf8(1);
160              
161             return $json->encode( { $self->as_hash } );
162             }
163              
164             sub as_hash {
165             my $self = shift;
166             my $with_uids = shift;
167              
168             if ($with_uids) {
169             return %{ $self->process_table->ptable };
170             }
171             else {
172             return %{ $self->_replace_uid_with_name };
173             }
174             }
175              
176             sub _replace_uid_with_name {
177             my $self = shift;
178              
179             my %user_table = %{ $self->process_table->ptable };
180              
181             my @users = keys %user_table;
182              
183             for (@users) {
184             my $username = getpwuid $_;
185             $user_table{$username} = $user_table{$_};
186             delete $user_table{$_};
187             }
188              
189             return {%user_table};
190             }
191              
192             1;
193              
194             __END__