“One day coding” mini-application for manage and monitoring application server.
In practice average count of connection for one application is 150-300 users.
Total count of corporate application servers more 20 with more 70 business critical application.
The web application is written because this kind of application server (1C) has a property crash time by time
For install depends:
apt-get -y install libev-perl apt-get -y install libcrypt-passwdmd5-perl apt-get -y install libio-socket-socks-perl apt-get -y install libapache-htpasswd-perl apt-get -y install libjs-prettify wget http://wiki.unix7.org/_media/dist/jessi/libmojolicious-perl_7.43_dfsg-1_all.deb dpkg -i libmojolicious-perl_7.43_dfsg-1_all.deb
For install the app:
wget http://wiki.unix7.org/_media/onecsw/onecsw_0.01-1_all.deb dpkg -i onecsw_0.01-1_all.deb
For srv1cv83 you must configure:
{ "loglevel":"debug", "service":"srv1cv83" }
#!/usr/bin/env perl #------------------ #--- CONTROLLER --- #------------------ package OneCsw::Model; use strict; use warnings; use Mojo::Util qw(dumper); use File::Basename qw(basename dirname); use POSIX; sub new { my ($class, $app) = @_; my $self = { app => $app, }; bless $self, $class; return $self; } sub app { return shift->{app}; } sub systemComm { my ($self, $comm) = @_; return undef unless $comm; open HR, "$comm |" or return undef; my $out; while (my $str = <HR>) { $out .= $str; }; return $out; } sub serviceStatus { my $self = shift; my $name = $self->app->config("service"); return undef unless $name; my $out = $self->systemComm("systemctl status $name 2>&1") || ''; return 'up' if $out =~ m/Active: active/; return 'down' if $out =~ m/Active: inactive/; return undef; } sub serviceStart { my $self = shift; my $name = $self->app->config("service"); return undef unless $name; my $out = $self->systemComm("systemctl start $name 2>&1") || ''; $self->app->log->debug("--- serviceStart $name"); my $s = $self->serviceStatus($name) || ''; return 1 if $s eq 'up'; return undef; } sub serviceStop { my $self = shift; my $name = $self->app->config("service"); return undef unless $name; my $out = $self->systemComm("systemctl stop $name 2>&1") || ''; $self->app->log->debug("--- serviceStop $name"); $self->killAll; $self->removepid; my $s = $self->serviceStatus($name) || ''; return 1 if $s eq 'down'; return undef; } sub killAll { my $self = shift; my $list = $self->listproc; return undef unless scalar @{$list}; kill 'INT', @{$list}; sleep 1; kill 'KILL', @{$list}; $self->app->log->debug("--- killAll: send signame to process list"); } sub removepid { my $self = shift; my $name = $self->app->config("service"); return undef unless $name; my $pidfile = "/var/run/$name.pid"; unlink "$pidfile" if -w (dirname($pidfile) && -r $pidfile); $self->app->log->debug("--- removepid: try remove pid file $pidfile"); } sub listproc { my $self = shift; my $dir = "/proc"; opendir (my $ph, $dir); my @list; while (my $name = readdir $ph) { next unless $name =~ /^\d+/; open (my $fh, "<:raw", "$dir/$name/cmdline"); next unless $fh; my $buf; eval { read($fh, $buf, 1048576); }; next if $@; close $fh; my (@argv) = split(/\0/s, $buf); my $procname = $argv[0] || undef; next unless $procname; next unless $procname =~ /1C/; push @list, $name+0; } @list = sort { $a <=> $b } @list; return \@list; } sub cmdline { my ($self, $pid) = @_; return undef unless $pid; open (my $fh, "<:raw", "/proc/$pid/cmdline"); return undef unless $fh; read($fh, my $buf, 1048576); close $fh; my @argv = split(/\0/s, $buf); return \@argv if @argv; return undef; } sub statm { my ($self, $pid) = @_; return undef unless $pid; my $dir = "/proc"; open (my $fh, "<:raw", "$dir/$pid/statm"); return undef unless $fh; read($fh, my $buf, 1048576); close $fh; my %statm; ($statm{size}, $statm{rss}, $statm{shared}, $statm{text}, $statm{lib}, $statm{data}, $statm{dt}) = split(/ /s, $buf); return \%statm;; } sub loadavg { my $self = shift; open (my $fh, "<:raw", "/proc/loadavg"); return undef unless $fh; read($fh, my $buf, 1048576); close $fh; my %loadavg; ($loadavg{p1m}, $loadavg{p5m}, $loadavg{p15m}) = split(/ /s, $buf); return \%loadavg;; } sub cpucount { my $self = shift; open (my $fh, "<:raw", "/proc/cpuinfo"); return undef unless $fh; my $num; while (my $line = <$fh>) { $num++ if $line =~/^processor/; } close $fh; return $num; } sub meminfo { my $self = shift; open (my $fh, "<:raw", "/proc/meminfo"); return undef unless $fh; my %mem; my $mem1 = 0; while (my $line = <$fh>) { chomp $line; ($mem{free}) = $line =~ /MemFree:\s+(\d+)/ if $line =~ /MemFree/; ($mem{total}) = $line =~ /MemTotal:\s+(\d+)/ if $line =~ /MemTotal/; } close $fh; print $mem1; return \%mem; } 1; package OneCsw::Controller; use utf8; use strict; use warnings; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Util qw(md5_sum dumper quote encode); use Mojo::JSON qw(encode_json decode_json); use Apache::Htpasswd; use File::Basename qw(fileparse); sub hello { my $self = shift; $self->render(template => 'hello'); } #--- controller::agent --- sub sessionAuth { return 1 if shift->session('username'); return undef; } sub checkPassword { my ($self, $username, $password) = @_; return undef unless $username; return undef unless $password; my $passwdFile = $self->app->config('pwdfile'); do { $self->app->log->error("Cannot read password file '$passwdFile'"); return undef; } unless -r $passwdFile; my $result = undef; eval { my $ht = Apache::Htpasswd->new({ passwdFile => $passwdFile, ReadOnly => 1 }); $result = $ht->htCheckPassword($username, $password); }; do { $self->app->log->debug("Auth module error: $@"); return undef; } if $@; return 1 if $result; $self->app->log->info("Bad auth from ".$self->tx->remote_address); return undef; } sub login { my $self = shift; return $self->redirect_to('/hello') if $self->sessionAuth; my $username = $self->req->param('username') || undef; my $password = $self->req->param('password') || undef; my $auth = $self->checkPassword($username, $password); if ($auth) { $self->session(username => $username); return $self->redirect_to('/hello'); } $self->render(template => 'login', req => $self->req); } sub onecForm { my $self = shift; $self->render(template => 'onecForm', req => $self->req); } sub logout { my $self = shift; $self->sessionDeAuth; $self->render(template => 'login', req => $self->req); } sub sessionDeAuth { return 1 if shift->session(expires => 1); return undef; } 1; #----------- #--- APP --- #----------- package OneCsw; use utf8; use strict; use warnings; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; } 1; #------------ #--- MAIN --- #------------ use strict; use warnings; use utf8; use POSIX qw(setuid setgid); use Mojo::Server::Prefork; use Mojo::IOLoop::Subprocess; use Mojo::Util qw(monkey_patch b64_encode b64_decode md5_sum getopt dumper); use Sys::Hostname qw(hostname); use File::Basename qw(basename dirname); use Apache::Htpasswd; use Cwd qw(getcwd abs_path); my $appfile = abs_path(__FILE__); my $appname = basename($appfile, ".pl"); $0 = $appfile; getopt 'h|help' => \my $help, '4|ipv4listen=s' => \my $ipv4listen, '6|ipv6listen=s' => \my $ipv6listen, 'c|config=s' => \my $conffile, 'p|pwdfile=s' => \my $pwdfile, 'd|confdir=s' => \my $confdir, 'l|logfile=s' => \my $logfile, 'i|pidfile=s' => \my $pidfile, 'v|loglevel=s' => \my $loglevel, 'f|nofork' => \my $nofork, 'u|user=s' => \my $user, 'g|group=s' => \my $group; if ($help) { print qq( Usage: app [OPTIONS] Options -h | --help This help -4 | --ipv4listen=address:port Listen address and port, defaults 127.0.0.1:5100 -6 | --ipv6listen=[address]:port Listen address and port, defaults [::1]:5100 -c | --config=path Path to config file -p | --pwdfile=path Path to user password file -l | --logfile=path Path to log file -i | --pidfile=path Path to process ID file -v | --loglevel=level Verbose level: debug, info, warn, error, fatal -u | --user=user System owner of process -g | --group=group System group -f | --nofork Dont fork process, for debugging All path option override option from configuration file )."\n"; exit 0; } my $server = Mojo::Server::Prefork->new; my $app = $server->build_app('OneCsw'); $app = $app->controller_class('OneCsw::Controller'); $app->config( hostname => hostname, listenIPv4 => $ipv4listen || "0.0.0.0", listenIPv6 => $ipv6listen || "[::]", listenPort => "1008", service => "srv1cv82", pwdfile => $pwdfile || "@APP_CONFDIR@/$appname.pw", pidfile => $pidfile || "@APP_RUNDIR@/$appname.pid", logfile => $logfile || "@APP_LOGDIR@/$appname.log", conffile => $conffile || "@APP_CONFDIR@/$appname.conf", maxrequestsize => 1024*1024*1024, tlscert => "@APP_CONFDIR@/$appname.crt", tlskey => "@APP_CONFDIR@/$appname.key", appuser => $user || "@APP_USER@", appgroup => $group || "@APP_GROUP@", mode => 'production', loglevel => $loglevel || 'info', libdir => '@APP_LIBDIR@', ); $conffile = $app->config('conffile'); do { $app->log->debug("Load configuration from $conffile "); my $config = $app->plugin( 'JSONConfig', { file => $conffile } ); } if -r $conffile; $app->log->level($app->config('loglevel')); my $tlscert = $app->config('tlscert'); my $tlskey = $app->config('tlskey'); $confdir = $app->config('confdir'); my $rundir = dirname ($app->config('pidfile')); my $logdir = dirname ($app->config('logfile')); $pwdfile = $app->config('pwdfile'); ###do { print "Error: Cannot read from data direcory $confdir\n"; exit 1; } unless -r $confdir; do { print "Error: Cannot write to run direcory $rundir\n"; exit 1; } unless -w $rundir; do { print "Error: Cannot write to log direcory $logdir\n"; exit 1; } unless -w $logdir; do { print "Error: Cannot read TLS certificate $tlscert\n"; exit 1; } unless -r $tlscert; do { print "Error: Cannot read TLS key $tlskey\n"; exit 1; } unless -r $tlskey; do { print "Error: Cannot read password file $pwdfile\n"; exit 1; } unless -r $pwdfile; my $appUser = $app->config('appuser'); my $appGroup = $app->config('appgroup'); my $appUID = getpwnam($appUser); my $appGID = getgrnam($appGroup); #do { print "System user $appUser not exist.\n"; exit 1; } unless $appUID; #do { print "System group $appGroup not exist.\n"; exit 1; } unless $appGID; $app->moniker($appname); $app->mode($app->config("mode")); $app->secrets(['6d578e43ba88260e0375a1a35fd7954b']); $app->helper( model => sub { state $model = OneCsw::Model->new( $app ); } ); $app->hook(before_dispatch => sub { my $c = shift; my $remoteIPaddr = $c->tx->remote_address; my $method = $c->req->method; my $base = $c->req->url->base->to_string; my $path = $c->req->url->path->to_string; my $username = $c->session('username') || 'undef'; my $loglevel = $c->app->log->level; unless ($loglevel eq 'debug') { my $url = $c->req->url->to_abs->to_string; $c->app->log->info("$method $url from $remoteIPaddr as $username"); } if ($loglevel eq 'debug') { my $url = $c->req->url->to_abs->to_string; $c->app->log->debug("$method $url from $remoteIPaddr as $username"); } }); $app->static->paths->[0] = $app->config('libdir').'/public'; $app->renderer->paths->[0] = $app->config('libdir').'/templs'; my $r = $app->routes; $r->add_condition( auth => sub { my ($route, $c, $captures, $hash) = @_; return 1 if $c->sessionAuth; return undef; } ); $r->any('/login')->to('Controller#login'); $r->any('/hello')->over('auth')->to('Controller#hello'); $r->any('/onec/service')->over('auth')->to('Controller#onecForm'); $r->any('/logout')->to('Controller#logout'); $r->any('/')->over('auth')->to('Controller#hello'); $app->helper('reply.not_found' => sub { my $c = shift; return $c->redirect_to('/login') unless $c->sessionAuth; $c->render(template => 'not_found.production'); }); my $tlsParam = ''; $tlsParam .= '?'; $tlsParam .= 'cert='.$tlscert; $tlsParam .= '&key='.$tlskey; my $listenPort = $app->config('listenPort'); my $listenIPv4 = $app->config('listenIPv4'); my $listenIPv6 = $app->config('listenIPv6'); #$server->listen([ # "http://$listenIPv4:$listenPort$tlsParam", #]); $server->listen([ "https://$listenIPv4:$listenPort$tlsParam", ]); $server->pid_file($app->config('pidfile')); $server->heartbeat_interval(3); $server->heartbeat_timeout(60); unless ($nofork) { my $pid = fork; if ($pid == 0) { setuid($appUID) if $appUID; setgid($appGID) if $appGID; $app->log(Mojo::Log->new( path => $app->config('logfile') )); open (my $STDOUT2, '>&', STDOUT); open (STDOUT, '>>', '/dev/null'); open (my $STDERR2, '>&', STDERR); open (STDERR, '>>', '/dev/null'); chdir($confdir); local $SIG{HUP} = sub { $app->log->info('Catch HUP signal'); $app->log(Mojo::Log->new( path => $app->config('logfile'), level => $app->config('loglevel'), )); }; $server->run; } } else { setuid($appUID) if $appUID; setgid($appGID) if $appGID; $server->run; } #EOF
%# %# $Id$ %# % layout 'default'; % title 'oneC Sw'; % use Mojo::Util qw(dumper); % use utf8; % use strict; % sub _roundup { % my $num = shift; % return int($num/1024+0.5); % } % my $m = $self->app->model; % my $action = $self->req->param('action') || 'undef'; % $m->serviceStop if $action eq 'stop'; % $m->serviceStart if $action eq 'start'; <div class="text-center"> <h5> Process list <a href="/onec/service"><i class="fi-refresh" style="font-size:1.3rem;"></i></a> </h5> % my $totalData = 0; % my $meminfo = $m->meminfo; % my $loadavg = $m->loadavg; <div class="row column"> all mem:<%= _roundup($meminfo->{'total'}) %>M, free mem:<%= _roundup($meminfo->{'free'}) %>M </div> <div class="row column"> avg load 15,5,1m: <%= $loadavg->{'p15m'} %>, <%= $loadavg->{'p5m'} %>, <%= $loadavg->{'p1m'} %> per <%= $m->cpucount %> cpu </div> </div> <table id="table" class="display" > <thead> <tr> <td>#</td> <td>pid</td> <td>tmem</td> <td>rss</td> <td>data</td> <td>name</td> </tr> </thead> % my $list = $m->listproc; <tbody> % my $num = 1; % foreach my $pid (@{$list}) { % my $procname = @{$m->cmdline($pid)}[0]; % my $statm = $m->statm($pid); % my $size = $statm->{'size'} || 0; % my $shared = $statm->{'shared'} || 0; % my $data = $statm->{'data'} || 0; % my $rss = $statm->{'rss'} || 0; <tr> <td><%= $num %></td> <td><%= $pid %></td> <td><%= _roundup($size) %></td> <td><%= _roundup($rss) %></td> <td><%= _roundup($data) %></td> <td><%= $procname %></td> </tr> % $num++; % } </tbody> </table> <script> $.extend(true, $.fn.dataTable.defaults, { "searching": true, "ordering": true, "pageLength": 50, "lengthMenu": [ [10, 25, 50,100, -1], [10, 25, 50,100, "All"] ], fixedColumns: { leftColumns: 2, }, "language": { "search": "", "decimal": ".", "thousands": ",", "lengthMenu": "_MENU_", }, } ); $(document).ready(function() { $('#table').DataTable(); }); </script> %#EOF
<!doctype html> <html class="no-js" lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= title %></title> <link rel="stylesheet" href="/icons/foundation-icons.css"> <link rel="stylesheet" href="/css/foundation-float.min.css"> <link rel="stylesheet" href="/css/datatables.min.css"/> <link rel="stylesheet" href="/css/app.css"> <script src="/js/jquery.min.js"></script> <script src="/js/datatables.min.js"></script> <script src="/js/foundation.min.js"></script> </head> <body> <div class="title-bar" data-responsive-toggle="topbar-menu" data-hide-for="medium"> <button class="menu-icon" type="button" data-toggle="topbar-menu"></button> <div class="title-bar-title">OneC Sw 0.01</div> </div> <div class="top-bar" id="topbar-menu"> <div class="top-bar-left"> <ul class="dropdown menu" data-dropdown-menu> <li class="menu-text show-for-medium">oneC Sw 0.01</li> <li><a href="/onec/service">Status <i class="fi-zoom-in"></i></a></li> <li><a href="#" data-open="modal-action-start">Start <i class="fi-play"></i></a></li> <li><a href="#" data-open="modal-action-stop">Stop <i class="fi-x"></i></a></li> <li><a href="/logout">[<%= $self->session('username') %>]</a></li> </ul> </div> <div class="top-bar-center"> </div> </div> <div class="reveal" id="modal-action-stop" data-reveal> <div class="text-center"> <h5>Do stop service?</h5> </div> <form accept-charset="UTF-8" action="/onec/service" method="get"> <input type="hidden" name="action" value="stop" /> <p class="text-center"> <button type="submit" class="button alert">Yes, I agree</button> <button class="button" data-close="modal-action-stop" type="button">No, Escape</button> </p> </form> <button class="close-button" data-close="modal-action-stop" type="button">×</button> </div> <div class="reveal" id="modal-action-start" data-reveal> <div class="text-center"> <h5>Do start service again?</h5> </div> <form accept-charset="UTF-8" action="/onec/service" method="get"> <input type="hidden" name="action" value="start" /> <p class="text-center"> <button type="submit" class="button alert">Yes, I agree</button> <button class="button" data-close="modal-action-start" type="button">No, Escape</button> </p> </form> <button class="close-button" data-close="modal-action-start" type="button">×</button> </div> <div class="row"> </div> <div class="row"> <!- end of head template -> <%= content %> <!- begin of tail template -> </div> </div> <hr/> <div class="row"> <p class="text-center">Made by <a href="http://wiki.unix7.org">Borodin Oleg</a></p> </div> <script src="/js/app.js"></script> </body> </html> <!- end of tail template -> <!- EOF ->