end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

「三隣亡」算出の元となる「二十四節気」は、国立天文台が毎年2月の官報で発表

「三隣亡」日の算出は、グレゴリオ暦 → QREKIによる旧暦算出... のように考えていましたが、 単純な計算では無理っぽい。

平成32(2020)年暦要項の発表 | 国立天文台(NAOJ) によれば

国立天文台は、毎年2月の最初の官報で翌年の暦要項(れきようこう)を発表しています。 暦要項には、国立天文台で推算した翌年の暦 (国民の祝日、日曜表、二十四節気および雑節、朔弦望、東京の日出入、日食・月食など) を掲載しています。

らしい。

以下、その他 参考

Web::Scraper for perl 経由で www.whatismybrowser.com を使い、access_log にあるuser agentを分析

perl scriptとしては、以下の通り。

apacheaccess_logに user agentが記載されていますが 最近のuser agent 文字列は複雑で、「結局、OSやブラウザは何?」となった為、 書いてみた。

これまで 同様のscriptを複数回、書いていますが、 最近では、www.whatismybrowser.com が良さそうなので、改めて

https://end0tknr.hateblo.jp/entry/20170901/1504248374

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode;
use HTTP::Request::Common;
use LWP::UserAgent;
use Web::Scraper;
use Data::Dumper;


my $REGEXP = join(' ',
                  '^([^ ]*) ([^ ]*) ([^ ]*) \[([^]]*)\] "([^ ]*)(?: *([^ ]*)',
                  '*([^ ]*))?" ([^ ]*) ([^ ]*) "(.*?)" "(.*?)"');
my $GZIP_CMD = '/usr/bin/gzip';
my $USER_AGENT_STR = 'https://www.whatismybrowser.com/';
my $SCRAPER = scraper {
    process 'div.string-major',   'user_agent'  => 'TEXT';
};
my $AGENT_OS_CACHE = {};


main(@ARGV);

sub main {
    my (@log_gz_files) = @_;

    my $summary = {};
    
    for my $log_gz_file ( @log_gz_files ){
        my $gzip_cmd = "$GZIP_CMD -dc $log_gz_file";

        open my $fh, '-|', $gzip_cmd or die "fail open $gzip_cmd $!";
        while( my $log_line = <$fh>){
            chomp($log_line);

            my ($host, $ident, $user, $datetime, $method, $resource,
                $proto, $status, $bytes, $referer, $agent, $time) =
                    $log_line =~ /$REGEXP/o;
            
            next if( not $user or $user eq "-");
            # id=shm0623995,ou=user,o=sso,ou=services,... のような形式で
            # fanへaccessされるケースがある為
            if($user =~ /^id\=([^,]+)/o ){
               $user = $1;
            }


            my $user_agent;
            my $user_os;
            if(defined($AGENT_OS_CACHE->{$agent})){
                $user_agent = $AGENT_OS_CACHE->{$agent}->{agent};
                $user_os    = $AGENT_OS_CACHE->{$agent}->{os};
            } else {
                ($user_agent,$user_os)= short_name_from_user_anget($agent);
                $AGENT_OS_CACHE->{$agent}->{agent} = $user_agent;
                $AGENT_OS_CACHE->{$agent}->{os} =    $user_os;
            }
            
            my $agent_key = join("\t",$user, $user_agent,$user_os,$agent);
            print STDERR "$log_gz_file : $agent_key\n";
            $summary->{$agent_key} += 1;
        }
        
        close $fh or die "fail close $gzip_cmd $!";
    }

    
    for my $info_keys (sort keys %$summary ){
        print $info_keys,"\t$summary->{$info_keys}\n";
    }
}


sub short_name_from_user_anget {
    my ($user_agent_org) = @_;

    my $ua = LWP::UserAgent->new;
    $ua->agent($user_agent_org);
    $ua->timeout(10);

    my $response = $ua->get($USER_AGENT_STR);

    unless($response->is_success) {
        print STDERR $response->status_line;
        return "";
    }

    my $html_str = $response->content;
    my $scraper_res = $SCRAPER->scrape($html_str);

    my ($user_anget, $user_os) = split(/ on /,$scraper_res->{user_agent});
    
    return $user_anget, $user_os;
}

負荷テストツール - siege ver.4.0.4

これまで負荷テストには、 apache bench (ab)を使用してきましたが、 ランダムなurlにアクセスすることで、より本番に近い負荷を発生させたい為、 siege を試してみます。

https://github.com/JoeDog/siege

install

$ wget https://github.com/JoeDog/siege/archive/v4.0.4.tar.gz
$ tar -xvf v4.0.4.tar.gz
$ cd siege-4.0.4
$ less INSTALL
$ ./utils/bootstrap 
+ aclocal
+ autoheader
+ automake --foreign --copy
+ autoconf
+ utils/manifier doc/siege.pod doc/siege.1.in 'Siege Load Tester' 1
+ utils/manifier doc/siege.config.pod doc/siege.config.1.in 'siege.config utility' 1
+ utils/manifier doc/bombardment.pod doc/bombardment.1.in bombardment 1
+ utils/manifier doc/bombardment.pod doc/siege2csv.1.in siege2csv 1

$ ./configure
   :
config.status: executing default-6 commands
--------------------------------------------------------
Configuration is complete

Run the following commands to complete the installation:
  make 
  make install

For complete documentation:        http://www.joedog.org
--------------------------------------------------------
$ make
$ make check
$ sudo make install
$ /usr/local/bin/siege --version
SIEGE 4.0.4

Copyright (C) 2017 by Jeffrey Fulmer, et al.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.

config

と言っても、基本的に設定は不要?です。

私の場合、テスト対象のサイトに basic認証がある為、「login = ~」 を追記しています。

$ vi ~/.siegerc/siege.conf
login = ないしょID:ないしょID

負荷生成

$ vi ./urls.txt
↓url一覧を記載して下さい
http://cent80.a5.jp/?????/GXuchuu.pl
http://cent80.a5.jp/?????/GXuchuu.pl?ac=view_juchuu&i_hid=331078
   :
$ /usr/local/bin/siege \
       --concurrent=5 \
       --time=120S \
       --benchmark \
       --file=./urls.txt \
       --internet 

bashで特定プロセスのCPU(%), MEM(%), 該当プロセス数 を表示

簡単なscriptはperlで書くことが多いのですが、 今後、perlがない環境で使用するかも知れませんので、bash

#!/usr/bin/bash

echo "YYYY-MM-DD HH:MM:SS   cpu(%)  mem(%)  procs count"

while true
do
    datetime=`date "+%F %T"`
    cpu=`ps aux | grep "/httpd/bin/httpd" | awk '{sum+=$3}END{print sum}'`
    mem=`ps aux | grep "/httpd/bin/httpd" | awk '{sum+=$4}END{print sum}'`
    procs=$((`ps aux | grep "/httpd/bin/httpd"| wc -l`-1))
    echo "$datetime $cpu   $mem   $procs"
    
    sleep 10
done

↑こう書くと、↓こう表示されます

$ ./watch_resource.sh 
YYYY-MM-DD HH:MM:SS     cpu(%)  mem(%)  processes count
2019-12-13 15:43:37     2.6     2       4
2019-12-13 15:43:47     0.6     2       4
2019-12-13 15:43:57     0.3     2       4
2019-12-13 15:44:07     1.2     2.5     4
2019-12-13 15:44:17     4.1     5.2     5
2019-12-13 15:44:28     14.6    10.6    4
2019-12-13 15:44:39     15.2    8.2     2
2019-12-13 15:44:51     8.6     0.4     2
2019-12-13 15:45:02     21.8    29.7    4
2019-12-13 15:45:13     35.9    43      5
    :                   :       :       :

IISサーバのログを自力で集計

iisサーバのログ書式 - end0tknr's kipple - 新web写経開発

「今どき、ログファイルベースのアクセス分析って、古いよねぇ」と思いつつ、 上記エントリの関連で、以下の perl script & ddl (sql)を書いてみた

#!/usr/local/bin/perl
use utf8;
use strict;
use warnings;
use DBI;
use Encode;
use MIME::Types;
use Time::Piece;
use Data::Dumper;

my $DBH;
my $UNZIP_CMD = '/usr/bin/unzip';  # win環境の場合、cygwinのexeを使うかな?
my $DB_CONF = {host=>'localhost',
               port=> 3306,
               name=>'analyze_iis_log',
               user=>'ないしょ',
               pass=>'ないしょ',
               opt=>{AutoCommit=>0, RaiseError=>1, mysql_enable_utf8=>1} };
my $DEL_LIMIT = {daily=>93, monthly=>24};
my $SAME_VISIT_MIN_LIMIT = 60;
my $BULK_INSERT_LIMIT = 50;


main(@ARGV);


sub main {
    my (@iis_log_zips) = @_;

    if(not @iis_log_zips){ # 引数 check
        return disp_usage_this_script();
    }

    $DBH = connect_db();

    del_old_parsed_log();  # 古いrecordが大量にあると、遅くなる為、削除
    del_old_analyzed();

    #### 単に? IISログをparseし、db tableへ保存 (GMT->JST程度は行います)
    my @update_dates; # 日別集計対象の日付群
    
    for my $iis_log_zip_path ( @iis_log_zips ){
        print "PARSE LOG - $iis_log_zip_path\n";
        # iis logのあるpathから filenameのみ抽出.
        my ($iis_log_zip) = $iis_log_zip_path =~ /([^\/\\]+)$/go;
        # 既に同じlogを集計している可能性がある為、一旦、削除
        del_parsed_log_by_log_file($iis_log_zip);
        # IISログをparseし、db tableへ保存
        push(@update_dates, parse_and_insert_log($iis_log_zip));
    }

    #### 日別集計
    my $update_months = {}; # 月別集計対象の年月群
    for my $update_date ( @update_dates ){
        print "ANALYZE DAILY- $update_date\n";
        
        my ($update_month) = $update_date =~ /^(\d+\D\d+)/go;
        $update_months->{$update_month} = 1;
        # 集計
        my ($new_analyzed,$new_analyzed_user) = analyze_daily($update_date);
        # 重複登録しないように削除
        del_analyzed("daily_analyzed",     $update_date);
        del_analyzed("daily_analyzed_user",$update_date);
        # 集計結果保存
        insert_daily_analyzed($new_analyzed);
        insert_daily_analyzed_user($new_analyzed_user);
    }

    #### 月別集計
    for my $update_month (keys %$update_months){
        print "ANALYZE MONTHLY - $update_month\n";
        # 集計
        my $new_analyzed = analyze_monthly($update_month);
        my $new_analyzed_user = analyze_monthly_user($update_month);
        # 重複登録しないように削除
        del_analyzed("monthly_analyzed",       $update_month ."-01");
        del_analyzed("monthly_analyzed_user",  $update_month ."-01");
        # 集計結果保存
        insert_monthly_analyzed($update_month ."-01",      $new_analyzed);
        insert_monthly_analyzed_user($update_month ."-01", $new_analyzed_user);
    }
    $DBH->commit;
    $DBH->disconnect;
}

sub disp_usage_this_script {
    print "Usage: $0  IIS_LOG_ZIP_1  [ IIS_LOG_ZIP_2 ... ]\n";
}


sub parse_and_insert_log {
    my ($iis_log_zip) = @_;
    
    my $cmd = "$UNZIP_CMD -p $iis_log_zip";
    open my $fh ,"-|", $cmd  or die "fail open $cmd $!";

    my @vals_groups;
    my $update_days = {};
    
    while(my $line = <$fh>){
        $line = Encode::decode('utf8',$line);
        
        next if $line =~ /^\#/o;  # IISログの先頭数行は「#」で始まるコメント
        
        my $log_cols = parse_log_line($line);

        # mime_type や 404 等の処理結果等で、分析対象を絞り込み
        next unless is_target_for_analyze($log_cols);

        # GMT->JST
        my $time_piece_gmt =
            Time::Piece->strptime("$log_cols->{date} $log_cols->{time}",
                                  '%Y-%m-%d %H:%M:%S');
        my $time_piece_jst = $time_piece_gmt + (3600 * 9);
        $update_days->{$time_piece_jst->strftime('%Y-%m-%d')} = 1;
        
        push(@vals_groups,
             [$iis_log_zip,
              $time_piece_jst->strftime('%Y-%m-%d %H:%M:%S'),
              lc($log_cols->{cs_uri_stem}),  # winは、大文字小文字を区別しない為
              $log_cols->{cs_username} ]);
        if( scalar(@vals_groups) > $BULK_INSERT_LIMIT ){
            bulk_insert_sql("parsed_log",
                            [qw/log_file datetime_jst url user/],
                            \@vals_groups );
            @vals_groups = ();
        }
    }

    if( scalar(@vals_groups) > 0 ){
        bulk_insert_sql("parsed_log",
                        [qw/log_file datetime_jst url user/],
                        \@vals_groups );
    }

    close($fh) or die "fail close $cmd $!";

    return keys %$update_days;
}

sub del_parsed_log_by_log_file {
    my ($log_file)  = @_;

    my $sql = "delete from parsed_log where log_file = ?";
    my $sth = $DBH->prepare($sql);
    unless( $sth->execute($log_file) ){
        die $sth->errstr,"$!";
    }
    return 1;
}

sub del_old_parsed_log {

    my $tm = Time::Piece::localtime;

    my $limit_daily =   $tm - (3600 * 24 * $DEL_LIMIT->{daily});
    my $limit_daily_str =   $limit_daily->ymd("-");

    my $sql = "delete from parsed_log where datetime_jst < ?";
    my $sth = $DBH->prepare($sql);
    unless( $sth->execute($limit_daily_str) ){
        die $sth->errstr,"$!";
    }
    return 1;
}

sub del_old_analyzed {

    my $tm = Time::Piece::localtime;

    my $limit_daily =   $tm - (3600 * 24 * $DEL_LIMIT->{daily});
    my $limit_monthly = $tm - (3600 * 24 * 30 * $DEL_LIMIT->{monthly});
    my $limit_daily_str =   $limit_daily->ymd("-");
    my $limit_monthly_str = $limit_daily->ymd("-");

    for my $tbl_and_limit (["daily_analyzed",          $limit_daily_str],
                           ["daily_analyzed_user",     $limit_daily_str],
                           ["monthly_analyzed_user",   $limit_monthly_str],
                           ["monthly_analyzed_user",   $limit_monthly_str]) {

        my $sql = "delete from $tbl_and_limit->[0] where date_jst < ?";
        my $sth = $DBH->prepare($sql);
        unless( $sth->execute($tbl_and_limit->[1]) ){
            die $sth->errstr,"$!";
        }
    }
    return 1;
}

sub del_analyzed {
    my ($tbl_name,$date) = @_;

    my $sql = "delete from $tbl_name where date_jst = ?";
    my $sth = $DBH->prepare($sql);
    unless( $sth->execute($date) ){
        die $sth->errstr,"$!";
    }
    return 1;
}

sub analyze_monthly {
    my ($month)  = @_;
    my $sql =<<EOF;
select
  url, sum(views) as views, sum(visits) as visits, sum(users) as users
from daily_analyzed
where date_jst between ? and ?
group by url
EOF
    my $sth = $DBH->prepare($sql);
    my $month_1st_day = Time::Piece->strptime($month."-01",'%Y-%m-%d');
    my @vals = ($month_1st_day->ymd("-"), join('-',$month,$month_1st_day->month_last_day));

    unless( $sth->execute(@vals) ){
        die $sth->errstr,"$sql $!";
    }

    my $ret = {};
    while(my $row = $sth->fetchrow_hashref){
        $ret->{$row->{url}}->{views} =  $row->{views};
        $ret->{$row->{url}}->{visits} = $row->{visits};
        $ret->{$row->{url}}->{users} =  $row->{users};
    }
    return $ret;
}

sub analyze_monthly_user {
    my ($month)  = @_;
    my $sql =<<EOF;
select
  url, user, sum(views) as views, sum(visits) as visits
from daily_analyzed_user
where date_jst between ? and ?
group by url,user
EOF
    my $sth = $DBH->prepare($sql);

    my $month_1st_day = Time::Piece->strptime($month."-01",'%Y-%m-%d');
    my @vals = ($month_1st_day->ymd("-"), join('-',$month,$month_1st_day->month_last_day));
    
    unless( $sth->execute(@vals) ){
        die $sth->errstr,"$sql $!";
    }

    my $ret = {};
    while(my $row = $sth->fetchrow_hashref){
        $ret->{"$row->{url}\t$row->{user}"}->{views} =  $row->{views};
        $ret->{"$row->{url}\t$row->{user}"}->{visits} = $row->{visits};
    }
    return $ret;
}


sub insert_monthly_analyzed {
    my ($date, $analyzed) = @_;

    my @insert_vals_groups;
    for my $url (sort keys %$analyzed){
        push(@insert_vals_groups,
             [$date, $url,
              $analyzed->{$url}->{views},
              $analyzed->{$url}->{visits},
              $analyzed->{$url}->{users}]);
            
        if(scalar(@insert_vals_groups) > $BULK_INSERT_LIMIT ){
            bulk_insert_sql("monthly_analyzed",
                            [qw/date_jst url views visits users/],
                            \@insert_vals_groups );
            @insert_vals_groups = ();
        }
    }
    if(scalar(@insert_vals_groups) > 0 ){
        bulk_insert_sql("monthly_analyzed",
                        [qw/date_jst url views visits users/],
                        \@insert_vals_groups );
    }
    
    return 1;
}

sub insert_monthly_analyzed_user {
    my ($date, $analyzed) = @_;

    my @insert_vals_groups;
    for my $url_user (keys %$analyzed){
        my ($url,$user) = split(/\t/,$url_user);
        
        push(@insert_vals_groups,
             [$date, $url, $user,
              $analyzed->{$url_user}->{views},
              $analyzed->{$url_user}->{visits}]);
        
        if(scalar(@insert_vals_groups) > $BULK_INSERT_LIMIT ){
            bulk_insert_sql("monthly_analyzed_user",
                            [qw/date_jst url user views visits/],
                            \@insert_vals_groups );
            @insert_vals_groups = ();
        }
    }
    if(scalar(@insert_vals_groups) > 0 ){
        bulk_insert_sql("monthly_analyzed_user",
                        [qw/date_jst url user views visits/],
                        \@insert_vals_groups );
    }
    return 1;
}


sub insert_daily_analyzed {
    my ($analyzed) = @_;

    my @insert_vals_groups;
    for my $date (sort keys %$analyzed ){

        for my $url (sort keys %{$analyzed->{$date}}){
            push(@insert_vals_groups,
                 [$date, $url,
                  $analyzed->{$date}->{$url}->{views},
                  $analyzed->{$date}->{$url}->{visits},
                  $analyzed->{$date}->{$url}->{users}]);
            
            if(scalar(@insert_vals_groups) > $BULK_INSERT_LIMIT ){
                bulk_insert_sql("daily_analyzed",
                                [qw/date_jst url views visits users/],
                                \@insert_vals_groups );
                @insert_vals_groups = ();
            }
        }
    }
    if(scalar(@insert_vals_groups) > 0 ){
        bulk_insert_sql("daily_analyzed",
                        [qw/date_jst url views visits users/],
                        \@insert_vals_groups );
    }
    
    return 1;
}

sub insert_daily_analyzed_user {
    my ($analyzed) = @_;

    my @insert_vals_groups;
    for my $date (sort keys %$analyzed ){

        for my $url_user (keys %{$analyzed->{$date}}){
            my ($url,$user) = split(/\t/,$url_user);
            
            push(@insert_vals_groups,
                 [$date, $url, $user,
                  $analyzed->{$date}->{$url_user}->{views},
                  $analyzed->{$date}->{$url_user}->{visits}]);
            
            if(scalar(@insert_vals_groups) > $BULK_INSERT_LIMIT ){
                bulk_insert_sql("daily_analyzed_user",
                                [qw/date_jst url user views visits/],
                                \@insert_vals_groups );
                @insert_vals_groups = ();
            }
        }
    }
    if(scalar(@insert_vals_groups) > 0 ){
        bulk_insert_sql("daily_analyzed_user",
                        [qw/date_jst url user views visits/],
                        \@insert_vals_groups );
    }
    return 1;
}

sub bulk_insert_sql {
    my ($tbl_name, $col_names, $vals_groups) = @_;

    my $col_names_str = join(',',@$col_names);
    my $place_holders = '('. '?,'x scalar(@$col_names);
    chop($place_holders);
    $place_holders .= ')';
    
    my @place_holders_groups;
    my @vals_all;
    for my $vals (@$vals_groups){
        push(@place_holders_groups, $place_holders);
        push(@vals_all, @$vals);
    }
    my $place_holders_groups_str = join(',', @place_holders_groups);
    
    my $sql =<<EOF;
insert into $tbl_name ($col_names_str)
values $place_holders_groups_str
EOF

    my $sth = $DBH->prepare($sql);

    unless ( $sth->execute(@vals_all) ){
        die $sth->errstr , $sql;
    }
    return 1;
}

sub analyze_daily {
    my ($update_date) = @_;

    my $sql =<<EOF;
select * from parsed_log
where datetime_jst between ? and ?
order by datetime_jst
EOF
    my $sth = $DBH->prepare($sql);
    my @vals = ("$update_date 00:00:00","$update_date 23:59:59");

    unless( $sth->execute(@vals) ){
        die $sth->errstr,"$!";
    }

    my $count_daily_user = {};  # url & user別分析
    my $count_daily = {};       # url別分析

    
    while(my $log_cols = $sth->fetchrow_hashref ){
        my ($date,$time) = split(/ /,$log_cols->{datetime_jst});
        my $url  = $log_cols->{url};
        my $user = $log_cols->{user};
        
        #### url & user別分析

        my $url_user = join("\t",$url, $user);
        # page view
        $count_daily_user->{$date}->{$url_user}->{views} += 1;
        $count_daily->{$date}->{$url}->{views} += 1;

        # 訪問数 : 最終accessからの経過時間で判断
        my $last_date_time = $count_daily_user->{$date}->{$url_user}->{last_time};
        my $new_date_time =  "$date $time";
        
        if(not is_same_visit($last_date_time, $new_date_time) ){
            $count_daily_user->{$date}->{$url_user}->{visits} += 1;
            $count_daily->{$date}->{$url}->{visits} += 1;
        }
        $count_daily_user->{$date}->{$url_user}->{last_time} = $new_date_time;

        $count_daily->{$date}->{$url}->{users}->{$user} = 1;    # user数
    }


    # 先程のwhile loopでは、{users} に user id 一覧を
    # hash mapで登録した為、user数に変換
    $count_daily = conv_user_hashes_to_size($count_daily);
    
    return $count_daily,$count_daily_user;
}

# 最終閲覧からの経過時間で、同一訪問 or notを判定
sub is_same_visit {
    my ($last_date_time_str, $new_date_time_str) = @_;

    return unless $last_date_time_str;

    my $last_date_time =
        Time::Piece->strptime($last_date_time_str,'%Y-%m-%d %H:%M:%S');
    my $new_date_time =
        Time::Piece->strptime($new_date_time_str,'%Y-%m-%d %H:%M:%S');

    my $time_seconds_obj = $new_date_time - $last_date_time;
    if($time_seconds_obj->minutes < $SAME_VISIT_MIN_LIMIT ){
        return 1;
    }
    return;
}


sub conv_user_hashes_to_size {
    my ($count_daily) = @_;
    
    for my $date (keys %$count_daily ){
        for my $url (keys %{$count_daily->{$date}} ){
            my $users_size =
                scalar(keys %{$count_daily->{$date}->{$url}->{users}});
            $count_daily->{$date}->{$url}->{users} = $users_size;
        }
    }
    return $count_daily;
}

sub is_target_for_analyze {
    my ($log_cols) = @_;

    if($log_cols->{cs_username} eq "-"){ #「-」はSmile未loginユーザによるアクセス
        return;
    }

    if($log_cols->{sc_status} eq "301" or       # MOVED_PERMANENTLY
       $log_cols->{sc_status} eq "307" or       # TEMPORARY_REDIRECT
       $log_cols->{sc_status} eq "401" or       # UNAUTHORIZED
       $log_cols->{sc_status} eq "404" or       # NOT FOUNT
       $log_cols->{sc_status} eq "500"){        # INTERNAL_SERVER_ERROR
        return;
    }

    my $mime_type = calc_mime_type($log_cols->{cs_uri_stem});
    if( $mime_type =~ /^image/o or
        $mime_type eq "text/css" or
        $mime_type eq "application/javascript"){
        return;
    }
    if( $log_cols->{cs_uri_stem} =~ /\/counter\.exe$/o ){
        return;
    }
    return 1;
}

sub parse_log_line {
    my ($line) = @_;
    my $log_cols = {};
    ($log_cols->{date},      $log_cols->{time},         $log_cols->{s_ip},
     $log_cols->{cs_method}, $log_cols->{cs_uri_stem},  $log_cols->{cs_uri_query},
     $log_cols->{s_port},    $log_cols->{cs_username},  $log_cols->{c_ip},
     $log_cols->{user_agent},$log_cols->{sc_status},    $log_cols->{sc_substatus},
     $log_cols->{sc_win32_status},                      $log_cols->{time_taken}) =
         split(/ /,$line);
    return $log_cols;
}

sub calc_mime_type {
    my ($cs_uri_stem) = @_;

    my $mt = MIME::Types->new();

    my $extention = "";
    if($cs_uri_stem =~ /\.([a-z]+)$/io){
        $extention = lc($1);
        my $mime_type  = $mt->mimeTypeOf($extention);
        return $mime_type  if $mime_type;
        return "unknown/$extention";
    }
    return "unknown";
}

sub connect_db {
    my $db = join(";",
                  "DBI:mysql:database=$DB_CONF->{name}",
                  "host=$DB_CONF->{host}",
                  "port=$DB_CONF->{port}");
    my $dbh = DBI->connect($db,
                           $DB_CONF->{user},
                           $DB_CONF->{pass},
                           $DB_CONF->{opt});
    return $dbh;
}
CREATE DATABASE analyze_iis_log CHARACTER SET utf8;

CREATE TABLE parsed_log (
log_file        varchar(40),
datetime_jst    datetime,
url             varchar(200),
user            varchar(40)
) comment='日別/月別集計の手間を軽減する為、まずIISのlogをこのtableに保存';
create index parsed_log_log_file on parsed_log(log_file);
create index parsed_log_log_file on parsed_log(datetime_jst);


CREATE TABLE daily_analyzed_user (
date_jst        date,
url             varchar(200),
user            varchar(40),
views           int,
visits          int,
primary key(date_jst,url,user)
) comment='日別 x URL x USER別 集計';

CREATE TABLE daily_analyzed (
date_jst        date,
url             varchar(200),
views           int,
visits          int,
users           int,
primary key(date_jst,url)
) comment='日別 x URL 別 集計';

CREATE TABLE monthly_analyzed_user (
date_jst        date,
url             varchar(200),
user            varchar(40),
views           int,
visits          int,
primary key(date_jst,url,user)
) comment='月別 x URL x USER別 集計';

CREATE TABLE monthly_analyzed (
date_jst        date,
url             varchar(200),
views           int,
visits          int,
users           int,
primary key(date_jst,url)
) comment='月別 x URL 別 集計';

apacheのmpmを prefork -> event に変更

https://end0tknr.hateblo.jp/entry/20170814/1502686286

上記urlのentryにあるように、 以前から apacheのmpmには prefork を 使用してきましたが、eventに変更。

apache の mpm = {prefork , worker , event} については 次のurlが分かりやすいです。

https://milestone-of-se.nesuke.com/sv-basic/linux-basic/apache-mpm-prefork-worker-event/

apache httpd の install

srcからのinstallの場合、手順は以下の通り、 configureに「--enable-mpms-shared=all」を付与するだけです。

$ tar -xvf httpd-2.4.27.tar.gz
$ cd httpd-2.4.27/srclib

$ wget http://ftp.kddilabs.jp/infosystems/apache/apr/apr-1.6.2.tar.gz
$ tar -zxvf apr-1.6.2.tar.gz
$ mv apr-1.6.2 apr

$ wget http://ftp.kddilabs.jp/infosystems/apache/apr/apr-util-1.6.0.tar.gz
$ tar -zxvf apr-util-1.6.0.tar.gz
$ mv apr-util-1.6.0 apr-util

$ cd ..
$ ./configure --prefix=/sing/local/httpd \
            --enable-mpms-shared=all \
            --enable-proxy \
            --enable-modules=all \
            --enable-so

    :
configure: summary of build options:
    Server Version: 2.4.27
    Install prefix: /home/endo/local/apache24
    C compiler:     gcc -std=gnu99
    CFLAGS:          -g -O2 -pthread
    LDFLAGS:         
    LIBS:           
    CPPFLAGS:        -DLINUX -D_REENTRANT -D_GNU_SOURCE
    C preprocessor: gcc -E

$ make
$ make install

apache httpd の 設定 (httpd.conf)

--enable-mpms-shared=all により httpd.conf に mpm????module が選択できるようになる為、コメントアウト/イン で 切り替えることができます。

LoadModule mpm_event_module modules/mod_mpm_event.so
#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
#LoadModule mpm_worker_module modules/mod_mpm_worker.so

#Include conf/extra/httpd-mpm.conf

apache httpd の 確認

httpd -V を実行することで、Server MPM を確認できます

$ httpd/bin/httpd -V
Server version: Apache/2.4.41 (Unix)
Server built:   Dec 10 2019 06:19:06
Server's Module Magic Number: 20120211:88
Server loaded:  APR 1.7.0, APR-UTIL 1.6.1
Compiled using: APR 1.7.0, APR-UTIL 1.6.1
Architecture:   64-bit
Server MPM:     event   #### ココ
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
   :

selenium + IEDriverServer.exe で ie11を起動する場合、「保護モードを有効」に

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import getopt
import os
import sys
from selenium import webdriver
from time import sleep

CONF = \
    {'web_driver':'c:/home/end0tknr/tmp/SELENIUM/IEDriverServer.exe'}

def main():
    browser = init_browser()

    browser.get("http://www.yahoo.co.jp")

    sleep(5)
    browser.close()
    browser.quit()


def init_browser():
    browser = webdriver.Ie(CONF['web_driver'])
    return browser
    

if __name__ == '__main__':
    main()

↑こちらを python3.7.5 for winで 実行したところ、IE11が起動せず、以下のerror

selenium.common.exceptions.SessionNotCreatedException:
Message: Unexpected error launching Internet Explorer.
Protected Mode settings are not the same for all zones.
Enable Protected Mode must be set to the same value (enabled or disabled)
for all zones.

どうやら、IEの設定(インターネットオプション)で 「保護モードを有効にする」を「インターネット」「ローカル イントラネット」 「信頼済みサイト」「制限付きサイト」で有効にする必要があるらしい。

更に「詳細設定」タブでは「拡張保護モードを有効にする」をオフにすると IEの起動や画面遷移が可能になります

f:id:end0tknr:20191209214102p:plain

f:id:end0tknr:20191209214116p:plain

python 3.7 for win 付属 の pipで 認証付きproxy経由で installする

以下のようにやれば、OKみたい

C:\Users\end0tknr > pip install selenium \
   --proxy http://ないしょID:ないしょPW@proxy.example.com \
   --trusted-host pypi.org \
   --trusted-host pypi.python.org \
   --trusted-host files.pythonhosted.org

perl による 素朴な csrf 対策用 token発行 と 照合

http://www.kent-web.com/perl/crypt/step05.html

上記urlを参考に、以下のような methodを活用すれば、良い気がします

use Digest::MD5;


sub get_csrf_token {
    my ($self) = @_;

    # 8文字のランダム文字列をsaltに使用します
    my @salt_chars = ('a' .. 'f', 0 .. 9);
    my $salt_str;
    for (1 .. 8) {
        $salt_str .= $salt_chars[int(rand(@salt_chars))];
    }

    my $org_token = join('', $salt_str,$self->{user}->{id});
    my $enc_token = Digest::MD5::md5_hex($org_token);
    my $csrf_token = join('',$salt_str, $enc_token);

    return $csrf_token;
}

sub chk_csrf_token {
    my ($self,$csrf_token) = @_;

    my $salt_str = substr($csrf_token,0,8); # 先頭にあるsalt値取得
    my $org_token = join('', $salt_str,$self->{user}->{id});
    my $enc_token = Digest::MD5::md5_hex($org_token);

    if( $csrf_token eq "$salt_str$enc_token" ){
        return 1;
    }

    return;
}

perl に install済 module 一覧を version付きで表示する one liner

標準モジュールは、Module::CoreList ( corelist ) を利用

$ perl -le 'system("corelist -v $^V")'

The following modules were in perl v5.30.0 CORE
Amiga::ARexx                                 0.04
Amiga::Exec                                  0.02
AnyDBM_File                                  1.01
App::Cpan                                    1.672
App::Prove                                   3.42
App::Prove::State                            3.42
   :                                          :

追加インストールしたモジュールは、ExtUtils::Installed ( instmodsh ) を利用

$ perl -MExtUtils::Installed -le \
   '$x=ExtUtils::Installed->new; print "$_ ",$x->version($_) for $x->modules'
   
Algorithm::Diff 1.1903
Alien::Build 1.89
Alien::Libxml2 0.09
App::cpanminus 1.7044
B::COW 0.001
B::Hooks::EndOfScope 0.24
Bit::Vector 7.4
CPAN::Meta::Check 0.014
Capture::Tiny 0.48
Carp::Clan 6.08
    :        :

emacsにおける window 切替えを「C-x o」から「C-t」に変更

.emacsへ以下のように記載すれば、OK。

「C-t」デフォルトで、隣の文字との入れ替えを行う「transpose-chars」に割り当てられており、 私は全く利用しないので、問題なし

; window 切り替える。 初期値 は transpose-chars
(define-key global-map (kbd "C-t") 'other-window)

OWASP ZAP 2.8.0 で BASIC認証サイトへの脆弱性SCAN

忘れてたので、メモ

STEP1 - まずはスクリプトの追加

f:id:end0tknr:20191130092827p:plain

STEP2 - 次にスクリプト名等の設定

f:id:end0tknr:20191130092843p:plain

STEP3 - 最後にスクリプトの記述

f:id:end0tknr:20191130092852p:plain

上記のテキストエリアには次のように入力します。

ただし「????????」部は、「$USER_ID:$PASSWD」をBASE64エンコードしたもの

org.parosproxy.paros.network.HttpSender.addListener(
  new org.zaproxy.zap.network.HttpSenderListener {
    getListenerOrder: function() { return 1; },
    onHttpRequestSend: function(msg, initiator) {
      msg.getRequestHeader().setHeader(
         "Authorization","Basic ????????"
         );
     },
     onHttpResponseReceive: function(msg, initiator) {}
});

以上で、脆弱性SCAN が可能になります

Re: Tomcat7から9へのアップグレード

Tomcat7から9へのアップグレード | GMOアドパートナーズグループ TECH BLOG byGMO

正に上記urlの通りだった。tomcatをverion up したら、エラー発生。

なので

$ sudo vi cd /usr/share/tomcat8/conf/context.conf
<Context></Context>ブロックの末尾(ファイル末尾)に、↓の行を追加
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />

で解消

openssl ver.1.1.1 で 「Using -iter or -pbkdf2 would be better.」や「bad decrypt」 error

以前、記載した entry の openssl ver.1.1.1 版.

openssl でファイルの暗号化と復号化 - end0tknr's kipple - 新web写経開発

openssl ver.1.0 で暗号化したファイルを openssl ver.1.1.1 で復号化しようとしたところ、以下のエラー。

$ /usr/local/openssl_1_1_1/bin/openssl \
> aes-256-cbc -d -base64 -k $PASSWD -in $FILE.CSV.EC -out $FILE.CSV
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
bad decrypt
139675971123008:error:06065064:digital envelope routines:
   EVP_DecryptFinal_ex:bad decrypt:crypto/evp/evp_enc.c:570:

の2コが原因らしい。

既に openssl ver.1.0 で暗号化したファイルの復号化は、 以下のように「-md md5」を追加すれば、warning は表示されますが、復号化できます。

$ /usr/local/openssl_1_1_1/bin/openssl \
  enc -d -aes-256-cbc -base64 -md md5
  -k $PASSWD -in $FILE.CSV.EC -out $FILE.CSV
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

今後、openssl ver.1.1.1での暗号化と復号化するには、 更に「-iter 100」を追加すると、よさそう。

$ /usr/local/openssl_1_1_1/bin/openssl \
  enc -e -aes-256-cbc -base64 -md md5 -iter 100 \
  -k $PASSWD -in $FILE.CSV.EC -out $FILE.CSV


$ /usr/local/openssl_1_1_1/bin/openssl \
  enc -d -aes-256-cbc -base64 -md md5 -iter 100 \
  -k $PASSWD -in $FILE.CSV.EC -out $FILE.CSV

$ 以下、参考url