2013-03-12 01:47:58 +04:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2013-02-13 03:14:15 +04:00
/***
2013-03-12 01:47:58 +04:00
This file is part of systemd .
2012-10-18 03:01:12 +04:00
2013-06-11 20:26:03 +04:00
Copyright ( C ) 2009 - 2013 Intel Corporation
2013-02-13 03:14:15 +04:00
Authors :
Auke Kok < auke - jan . h . kok @ intel . com >
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
2012-10-18 03:01:12 +04:00
2013-02-16 21:40:33 +04:00
/***
Many thanks to those who contributed ideas and code :
- Ziga Mahkovec - Original bootchart author
- Anders Norgaard - PyBootchartgui
- Michael Meeks - bootchart2
- Scott James Remnant - Ubuntu C - based logger
- Arjan van der Ven - for the idea to merge bootgraph . pl functionality
* * */
2012-10-18 03:01:12 +04:00
# include <sys/time.h>
# include <sys/types.h>
# include <sys/resource.h>
2013-02-14 14:26:05 +04:00
# include <sys/stat.h>
2012-10-18 03:01:12 +04:00
# include <stdio.h>
# include <signal.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include <time.h>
# include <getopt.h>
# include <limits.h>
# include <errno.h>
2013-02-14 14:26:05 +04:00
# include <fcntl.h>
2013-02-15 00:32:49 +04:00
# include <stdbool.h>
2013-04-16 03:23:42 +04:00
# include <systemd/sd-journal.h>
2012-10-18 03:01:12 +04:00
2013-01-10 23:34:58 +04:00
# include "util.h"
2013-02-14 15:26:13 +04:00
# include "fileio.h"
2013-02-15 00:32:49 +04:00
# include "macro.h"
# include "conf-parser.h"
# include "strxcpyx.h"
2013-02-15 01:03:40 +04:00
# include "path-util.h"
2013-03-12 01:47:58 +04:00
# include "store.h"
# include "svg.h"
# include "bootchart.h"
2013-04-25 01:56:15 +04:00
# include "list.h"
2012-10-18 03:01:12 +04:00
double graph_start ;
double log_start ;
struct ps_struct * ps_first ;
int pscount ;
int cpus ;
double interval ;
2013-03-08 00:28:03 +04:00
FILE * of = NULL ;
2012-10-18 03:01:12 +04:00
int overrun = 0 ;
static int exiting = 0 ;
2013-03-08 00:28:03 +04:00
int sysfd = - 1 ;
2012-10-18 03:01:12 +04:00
/* graph defaults */
2013-03-12 01:47:58 +04:00
bool arg_entropy = false ;
2013-02-15 00:32:49 +04:00
bool initcall = true ;
2013-03-12 01:47:58 +04:00
bool arg_relative = false ;
bool arg_filter = true ;
bool arg_show_cmdline = false ;
bool arg_pss = false ;
2012-10-18 03:01:12 +04:00
int samples ;
2013-03-12 01:47:58 +04:00
int arg_samples_len = 500 ; /* we record len+1 (1 start sample) */
double arg_hz = 25.0 ; /* 20 seconds log time */
double arg_scale_x = 100.0 ; /* 100px = 1sec */
double arg_scale_y = 20.0 ; /* 16px = 1 process bar */
2013-04-25 01:56:15 +04:00
static struct list_sample_data * sampledata ;
struct list_sample_data * head ;
2012-10-18 03:01:12 +04:00
2013-03-12 01:47:58 +04:00
char arg_init_path [ PATH_MAX ] = " /sbin/init " ;
char arg_output_path [ PATH_MAX ] = " /run/log " ;
2012-10-18 03:01:12 +04:00
2013-03-12 01:47:58 +04:00
static void signal_handler ( int sig ) {
2013-01-10 01:38:03 +04:00
if ( sig + + )
sig - - ;
exiting = 1 ;
2012-10-18 03:01:12 +04:00
}
2013-03-31 02:43:34 +04:00
# define BOOTCHART_CONF " / etc / systemd / bootchart.conf"
2013-02-15 00:32:49 +04:00
2013-04-16 03:23:42 +04:00
# define BOOTCHART_MAX (16*1024*1024)
2013-03-31 02:43:34 +04:00
static void parse_conf ( void ) {
char * init = NULL , * output = NULL ;
2013-02-15 00:32:49 +04:00
const ConfigTableItem items [ ] = {
2013-03-12 01:47:58 +04:00
{ " Bootchart " , " Samples " , config_parse_int , 0 , & arg_samples_len } ,
{ " Bootchart " , " Frequency " , config_parse_double , 0 , & arg_hz } ,
{ " Bootchart " , " Relative " , config_parse_bool , 0 , & arg_relative } ,
{ " Bootchart " , " Filter " , config_parse_bool , 0 , & arg_filter } ,
{ " Bootchart " , " Output " , config_parse_path , 0 , & output } ,
{ " Bootchart " , " Init " , config_parse_path , 0 , & init } ,
{ " Bootchart " , " PlotMemoryUsage " , config_parse_bool , 0 , & arg_pss } ,
{ " Bootchart " , " PlotEntropyGraph " , config_parse_bool , 0 , & arg_entropy } ,
{ " Bootchart " , " ScaleX " , config_parse_double , 0 , & arg_scale_x } ,
{ " Bootchart " , " ScaleY " , config_parse_double , 0 , & arg_scale_y } ,
2013-02-15 00:32:49 +04:00
{ NULL , NULL , NULL , 0 , NULL }
} ;
2013-03-31 02:43:34 +04:00
_cleanup_fclose_ FILE * f ;
int r ;
2013-01-10 01:38:03 +04:00
2013-03-31 02:43:34 +04:00
f = fopen ( BOOTCHART_CONF , " re " ) ;
if ( ! f )
return ;
2013-01-10 01:38:03 +04:00
2013-04-16 06:25:58 +04:00
r = config_parse ( NULL , BOOTCHART_CONF , f ,
2013-04-25 02:53:16 +04:00
NULL , config_item_table_lookup , ( void * ) items , true , false , NULL ) ;
2013-03-31 02:43:34 +04:00
if ( r < 0 )
log_warning ( " Failed to parse configuration file: %s " , strerror ( - r ) ) ;
if ( init ! = NULL )
strscpy ( arg_init_path , sizeof ( arg_init_path ) , init ) ;
if ( output ! = NULL )
strscpy ( arg_output_path , sizeof ( arg_output_path ) , output ) ;
}
static int parse_args ( int argc , char * argv [ ] ) {
static struct option options [ ] = {
{ " rel " , no_argument , NULL , ' r ' } ,
{ " freq " , required_argument , NULL , ' f ' } ,
{ " samples " , required_argument , NULL , ' n ' } ,
{ " pss " , no_argument , NULL , ' p ' } ,
{ " output " , required_argument , NULL , ' o ' } ,
{ " init " , required_argument , NULL , ' i ' } ,
{ " no-filter " , no_argument , NULL , ' F ' } ,
{ " cmdline " , no_argument , NULL , ' C ' } ,
{ " help " , no_argument , NULL , ' h ' } ,
{ " scale-x " , required_argument , NULL , ' x ' } ,
{ " scale-y " , required_argument , NULL , ' y ' } ,
{ " entropy " , no_argument , NULL , ' e ' } ,
{ NULL , 0 , NULL , 0 }
} ;
int c ;
while ( ( c = getopt_long ( argc , argv , " erpf:n:o:i:FChx:y: " , options , NULL ) ) > = 0 ) {
int r ;
switch ( c ) {
2013-01-10 01:38:03 +04:00
case ' r ' :
2013-03-12 01:47:58 +04:00
arg_relative = true ;
2013-01-10 01:38:03 +04:00
break ;
case ' f ' :
2013-03-12 01:47:58 +04:00
r = safe_atod ( optarg , & arg_hz ) ;
2013-02-16 22:29:28 +04:00
if ( r < 0 )
log_warning ( " failed to parse --freq/-f argument '%s': %s " ,
optarg , strerror ( - r ) ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' F ' :
2013-03-12 01:47:58 +04:00
arg_filter = false ;
2013-01-10 01:38:03 +04:00
break ;
2013-03-07 11:52:54 +04:00
case ' C ' :
2013-03-12 01:47:58 +04:00
arg_show_cmdline = true ;
2013-03-07 11:52:54 +04:00
break ;
2013-01-10 01:38:03 +04:00
case ' n ' :
2013-03-12 01:47:58 +04:00
r = safe_atoi ( optarg , & arg_samples_len ) ;
2013-02-16 22:29:28 +04:00
if ( r < 0 )
log_warning ( " failed to parse --samples/-n argument '%s': %s " ,
optarg , strerror ( - r ) ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' o ' :
2013-02-15 01:03:40 +04:00
path_kill_slashes ( optarg ) ;
2013-03-12 01:47:58 +04:00
strscpy ( arg_output_path , sizeof ( arg_output_path ) , optarg ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' i ' :
2013-02-15 01:03:40 +04:00
path_kill_slashes ( optarg ) ;
2013-03-12 01:47:58 +04:00
strscpy ( arg_init_path , sizeof ( arg_init_path ) , optarg ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' p ' :
2013-03-12 01:47:58 +04:00
arg_pss = true ;
2013-01-10 01:38:03 +04:00
break ;
case ' x ' :
2013-03-12 01:47:58 +04:00
r = safe_atod ( optarg , & arg_scale_x ) ;
2013-02-16 22:29:28 +04:00
if ( r < 0 )
log_warning ( " failed to parse --scale-x/-x argument '%s': %s " ,
optarg , strerror ( - r ) ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' y ' :
2013-03-12 01:47:58 +04:00
r = safe_atod ( optarg , & arg_scale_y ) ;
2013-02-16 22:29:28 +04:00
if ( r < 0 )
log_warning ( " failed to parse --scale-y/-y argument '%s': %s " ,
optarg , strerror ( - r ) ) ;
2013-01-10 01:38:03 +04:00
break ;
case ' e ' :
2013-03-12 01:47:58 +04:00
arg_entropy = true ;
2013-01-10 01:38:03 +04:00
break ;
case ' h ' :
fprintf ( stderr , " Usage: %s [OPTIONS] \n " , argv [ 0 ] ) ;
2013-02-17 00:16:24 +04:00
fprintf ( stderr , " --rel, -r Record time relative to recording \n " ) ;
2013-03-12 01:47:58 +04:00
fprintf ( stderr , " --freq, -f f Sample frequency [%f] \n " , arg_hz ) ;
fprintf ( stderr , " --samples, -n N Stop sampling at [%d] samples \n " , arg_samples_len ) ;
fprintf ( stderr , " --scale-x, -x N Scale the graph horizontally [%f] \n " , arg_scale_x ) ;
fprintf ( stderr , " --scale-y, -y N Scale the graph vertically [%f] \n " , arg_scale_y ) ;
2013-02-17 00:16:24 +04:00
fprintf ( stderr , " --pss, -p Enable PSS graph (CPU intensive) \n " ) ;
fprintf ( stderr , " --entropy, -e Enable the entropy_avail graph \n " ) ;
2013-03-12 01:47:58 +04:00
fprintf ( stderr , " --output, -o [PATH] Path to output files [%s] \n " , arg_output_path ) ;
fprintf ( stderr , " --init, -i [PATH] Path to init executable [%s] \n " , arg_init_path ) ;
2013-02-17 00:16:24 +04:00
fprintf ( stderr , " --no-filter, -F Disable filtering of processes from the graph \n " ) ;
2013-01-10 01:38:03 +04:00
fprintf ( stderr , " that are of less importance or short-lived \n " ) ;
2013-03-07 19:19:38 +04:00
fprintf ( stderr , " --cmdline, -C Display the full command line with arguments \n " ) ;
fprintf ( stderr , " of processes, instead of only the process name \n " ) ;
2013-02-17 00:16:24 +04:00
fprintf ( stderr , " --help, -h Display this message \n " ) ;
2013-02-16 22:29:28 +04:00
fprintf ( stderr , " See bootchart.conf for more information. \n " ) ;
2013-01-10 01:38:03 +04:00
exit ( EXIT_SUCCESS ) ;
break ;
default :
break ;
}
}
2013-03-12 01:47:58 +04:00
if ( arg_hz < = 0.0 ) {
2013-01-10 01:38:03 +04:00
fprintf ( stderr , " Error: Frequency needs to be > 0 \n " ) ;
2013-03-31 02:43:34 +04:00
return - EINVAL ;
2013-01-10 01:38:03 +04:00
}
2013-03-31 02:43:34 +04:00
return 0 ;
}
2013-04-16 03:23:42 +04:00
static void do_journal_append ( char * file )
{
struct iovec iovec [ 5 ] ;
int r , f , j = 0 ;
ssize_t n ;
2013-04-18 11:11:22 +04:00
_cleanup_free_ char * bootchart_file = NULL , * bootchart_message = NULL ,
2013-04-16 03:23:42 +04:00
* p = NULL ;
bootchart_file = strappend ( " BOOTCHART_FILE= " , file ) ;
if ( bootchart_file )
IOVEC_SET_STRING ( iovec [ j + + ] , bootchart_file ) ;
IOVEC_SET_STRING ( iovec [ j + + ] , " MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518 " ) ;
IOVEC_SET_STRING ( iovec [ j + + ] , " PRIORITY=7 " ) ;
bootchart_message = strjoin ( " MESSAGE=Bootchart created: " , file , NULL ) ;
if ( bootchart_message )
IOVEC_SET_STRING ( iovec [ j + + ] , bootchart_message ) ;
p = malloc ( 9 + BOOTCHART_MAX ) ;
if ( ! p ) {
r = log_oom ( ) ;
return ;
}
memcpy ( p , " BOOTCHART= " , 10 ) ;
f = open ( file , O_RDONLY ) ;
if ( f < 0 ) {
2013-04-17 20:49:03 +04:00
log_error ( " Failed to read bootchart data: %m \n " ) ;
2013-04-16 03:23:42 +04:00
return ;
}
n = loop_read ( f , p + 10 , BOOTCHART_MAX , false ) ;
if ( n < 0 ) {
log_error ( " Failed to read bootchart data: %s \n " , strerror ( - n ) ) ;
close ( f ) ;
return ;
}
close ( f ) ;
iovec [ j ] . iov_base = p ;
iovec [ j ] . iov_len = 10 + n ;
j + + ;
r = sd_journal_sendv ( iovec , j ) ;
if ( r < 0 )
log_error ( " Failed to send bootchart: %s " , strerror ( - r ) ) ;
}
2013-03-31 02:43:34 +04:00
int main ( int argc , char * argv [ ] ) {
_cleanup_free_ char * build = NULL ;
2013-04-06 06:24:10 +04:00
struct sigaction sig = {
. sa_handler = signal_handler ,
} ;
2013-03-31 02:43:34 +04:00
struct ps_struct * ps ;
char output_file [ PATH_MAX ] ;
char datestr [ 200 ] ;
time_t t = 0 ;
int r ;
struct rlimit rlim ;
parse_conf ( ) ;
r = parse_args ( argc , argv ) ;
if ( r < 0 )
return EXIT_FAILURE ;
2013-01-10 01:38:03 +04:00
/*
2013-02-16 22:29:28 +04:00
* If the kernel executed us through init = / usr / lib / systemd / systemd - bootchart , then
2013-01-10 01:38:03 +04:00
* fork :
* - parent execs executable specified via init_path [ ] ( / sbin / init by default ) as pid = 1
* - child logs data
*/
if ( getpid ( ) = = 1 ) {
if ( fork ( ) ) {
/* parent */
2013-03-12 01:47:58 +04:00
execl ( arg_init_path , arg_init_path , NULL ) ;
2013-01-10 01:38:03 +04:00
}
}
2013-02-15 01:03:40 +04:00
argv [ 0 ] [ 0 ] = ' @ ' ;
2013-01-10 01:38:03 +04:00
2013-03-12 01:54:36 +04:00
rlim . rlim_cur = 4096 ;
rlim . rlim_max = 4096 ;
( void ) setrlimit ( RLIMIT_NOFILE , & rlim ) ;
2013-01-10 01:38:03 +04:00
/* start with empty ps LL */
2013-01-10 23:35:00 +04:00
ps_first = calloc ( 1 , sizeof ( struct ps_struct ) ) ;
2013-01-10 01:38:03 +04:00
if ( ! ps_first ) {
2013-01-10 23:35:00 +04:00
perror ( " calloc(ps_struct) " ) ;
2013-01-10 01:38:03 +04:00
exit ( EXIT_FAILURE ) ;
}
/* handle TERM/INT nicely */
sigaction ( SIGHUP , & sig , NULL ) ;
2013-03-12 01:47:58 +04:00
interval = ( 1.0 / arg_hz ) * 1000000000.0 ;
2013-01-10 01:38:03 +04:00
log_uptime ( ) ;
2013-04-25 01:56:15 +04:00
LIST_HEAD_INIT ( struct list_sample_data , head ) ;
2013-01-10 01:38:03 +04:00
/* main program loop */
2013-03-31 03:34:23 +04:00
for ( samples = 0 ; ! exiting & & samples < arg_samples_len ; samples + + ) {
2013-01-10 01:38:03 +04:00
int res ;
double sample_stop ;
struct timespec req ;
time_t newint_s ;
long newint_ns ;
double elapsed ;
double timeleft ;
2013-04-25 01:56:15 +04:00
sampledata = new0 ( struct list_sample_data , 1 ) ;
if ( sampledata = = NULL ) {
log_error ( " Failed to allocate memory for a node: %m " ) ;
return - 1 ;
}
sampledata - > sampletime = gettime_ns ( ) ;
sampledata - > counter = samples ;
2013-01-10 01:38:03 +04:00
2013-03-12 01:47:58 +04:00
if ( ! of & & ( access ( arg_output_path , R_OK | W_OK | X_OK ) = = 0 ) ) {
2013-02-13 03:36:29 +04:00
t = time ( NULL ) ;
strftime ( datestr , sizeof ( datestr ) , " %Y%m%d-%H%M " , localtime ( & t ) ) ;
2013-03-12 01:47:58 +04:00
snprintf ( output_file , PATH_MAX , " %s/bootchart-%s.svg " , arg_output_path , datestr ) ;
2013-02-13 03:36:29 +04:00
of = fopen ( output_file , " w " ) ;
}
2013-03-12 01:47:58 +04:00
if ( sysfd < 0 )
2013-02-14 14:26:05 +04:00
sysfd = open ( " /sys " , O_RDONLY ) ;
2013-02-13 03:36:29 +04:00
2013-03-12 01:47:58 +04:00
if ( ! build )
2013-02-14 14:26:07 +04:00
parse_env_file ( " /etc/os-release " , NEWLINE ,
" PRETTY_NAME " , & build ,
NULL ) ;
2013-01-10 01:38:03 +04:00
/* wait for /proc to become available, discarding samples */
2013-03-31 03:34:23 +04:00
if ( graph_start < = 0.0 )
2013-01-10 01:38:03 +04:00
log_uptime ( ) ;
else
2013-04-25 01:56:15 +04:00
log_sample ( samples , & sampledata ) ;
2013-01-10 01:38:03 +04:00
sample_stop = gettime_ns ( ) ;
2013-04-25 01:56:15 +04:00
elapsed = ( sample_stop - sampledata - > sampletime ) * 1000000000.0 ;
2013-01-10 01:38:03 +04:00
timeleft = interval - elapsed ;
newint_s = ( time_t ) ( timeleft / 1000000000.0 ) ;
newint_ns = ( long ) ( timeleft - ( newint_s * 1000000000.0 ) ) ;
/*
* check if we have not consumed our entire timeslice . If we
* do , don ' t sleep and take a new sample right away .
* we ' ll lose all the missed samples and overrun our total
* time
*/
2013-03-31 03:34:23 +04:00
if ( newint_ns > 0 | | newint_s > 0 ) {
2013-01-10 01:38:03 +04:00
req . tv_sec = newint_s ;
req . tv_nsec = newint_ns ;
res = nanosleep ( & req , NULL ) ;
if ( res ) {
if ( errno = = EINTR ) {
/* caught signal, probably HUP! */
break ;
}
perror ( " nanosleep() " ) ;
exit ( EXIT_FAILURE ) ;
}
} else {
overrun + + ;
/* calculate how many samples we lost and scrap them */
2013-03-31 03:34:23 +04:00
arg_samples_len - = ( int ) ( newint_ns / interval ) ;
2013-01-10 01:38:03 +04:00
}
2013-04-25 01:56:15 +04:00
LIST_PREPEND ( struct list_sample_data , link , head , sampledata ) ;
2013-01-10 01:38:03 +04:00
}
/* do some cleanup, close fd's */
ps = ps_first ;
while ( ps - > next_ps ) {
ps = ps - > next_ps ;
if ( ps - > schedstat )
close ( ps - > schedstat ) ;
if ( ps - > sched )
close ( ps - > sched ) ;
if ( ps - > smaps )
fclose ( ps - > smaps ) ;
}
2013-02-13 03:36:29 +04:00
if ( ! of ) {
t = time ( NULL ) ;
strftime ( datestr , sizeof ( datestr ) , " %Y%m%d-%H%M " , localtime ( & t ) ) ;
2013-03-12 01:47:58 +04:00
snprintf ( output_file , PATH_MAX , " %s/bootchart-%s.svg " , arg_output_path , datestr ) ;
2013-02-13 03:36:29 +04:00
of = fopen ( output_file , " w " ) ;
}
2013-01-10 01:38:03 +04:00
if ( ! of ) {
2013-02-16 22:29:28 +04:00
fprintf ( stderr , " opening output file '%s': %m \n " , output_file ) ;
2013-01-10 01:38:03 +04:00
exit ( EXIT_FAILURE ) ;
}
2013-02-14 14:26:07 +04:00
svg_do ( build ) ;
2013-01-10 01:38:03 +04:00
2013-02-16 22:29:28 +04:00
fprintf ( stderr , " systemd-bootchart wrote %s \n " , output_file ) ;
2013-03-12 01:47:58 +04:00
2013-04-16 03:23:42 +04:00
do_journal_append ( output_file ) ;
2013-03-12 01:47:58 +04:00
if ( of )
fclose ( of ) ;
2013-01-10 01:38:03 +04:00
2013-02-14 14:26:05 +04:00
closedir ( proc ) ;
2013-03-12 01:47:58 +04:00
if ( sysfd > = 0 )
close ( sysfd ) ;
2013-02-14 14:26:05 +04:00
2013-01-10 01:38:03 +04:00
/* nitpic cleanups */
2013-04-25 01:56:15 +04:00
ps = ps_first - > next_ps ;
2013-01-10 01:38:03 +04:00
while ( ps - > next_ps ) {
2013-04-25 01:56:15 +04:00
struct ps_struct * old ;
old = ps ;
old - > sample = ps - > first ;
2013-01-10 01:38:03 +04:00
ps = ps - > next_ps ;
2013-04-25 01:56:15 +04:00
while ( old - > sample - > next ) {
struct ps_sched_struct * oldsample = old - > sample ;
old - > sample = old - > sample - > next ;
free ( oldsample ) ;
}
2013-01-10 01:38:03 +04:00
free ( old - > sample ) ;
free ( old ) ;
}
free ( ps - > sample ) ;
free ( ps ) ;
2013-04-25 01:56:15 +04:00
sampledata = head ;
while ( sampledata - > link_prev ) {
struct list_sample_data * old_sampledata = sampledata ;
sampledata = sampledata - > link_prev ;
free ( old_sampledata ) ;
}
free ( sampledata ) ;
2013-01-10 01:38:03 +04:00
/* don't complain when overrun once, happens most commonly on 1st sample */
if ( overrun > 1 )
2013-02-16 22:29:28 +04:00
fprintf ( stderr , " systemd-boochart: Warning: sample time overrun %i times \n " , overrun ) ;
2013-01-10 01:38:03 +04:00
return 0 ;
2012-10-18 03:01:12 +04:00
}