sbperf.pl 15.2 KB
Newer Older
Xavier Guimard's avatar
Xavier Guimard committed
1
#!/usr/bin/perl
Xavier Guimard's avatar
Xavier Guimard committed
2 3 4 5 6 7
#
# Session Backend Performance Test
# --------------------------------
#
# This test is used to compare different session backend. To use it, you have
# to:
Xavier Guimard's avatar
Xavier Guimard committed
8
#  * have a PostgreSQL database running on this host. PostgreSQL DB must:
Xavier Guimard's avatar
Xavier Guimard committed
9
#    * listen on 127.0.0.1:5432
Xavier Guimard's avatar
Xavier Guimard committed
10 11
#    * have a database names "sessions"
#    * have "hstore" extension enabled is database "sessions":
Xavier Guimard's avatar
Xavier Guimard committed
12
#        psql# CREATE EXTENSION hstore;
Xavier Guimard's avatar
Xavier Guimard committed
13 14 15 16 17 18
#    * have a Pg user named "sso" identified by "sso" password
#    * "sso" user must have right to create tables in database "sessions"
#  * have a MySQL or MriaDB database running on this host. DB must:
#    * listen on 127.0.0.1:3306
#    * have a database names "sessions"
#    * have a user named "sso" identified by "sso" password
Xavier Guimard's avatar
Xavier Guimard committed
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#    * "sso" user must have right to create tables in database "sessions"
#  * have a Redis server installed on this host listen on 127.0.0.1:6379
#
# If you want to enable LDAP test:
#  * set LLNGTESTLDAP environment variable to 1
#  * if OpenLDAP schemes aren't available in /etc/slapd/schema, set the scheme
#    directory in environment variables:
#      LLNGTESTLDAP_SCHEMA_DIR=/etc/ldap/schema
#  * prepare some coffee or tea
#  * open the window or light a fan
#  * launch the test, run away and come back 5mn later
#
#
# (c) Copyright: 2017, LemonLDAP::NG team
#
#This library is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2, or (at your option)
#any later version.
#
#This program 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 General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program.  If not, see L<http://www.gnu.org/licenses/>.
Xavier Guimard's avatar
Xavier Guimard committed
46 47 48 49 50 51 52 53 54 55

use strict;
use DBI;
use JSON qw(from_json to_json);
use Time::HiRes;
use LWP::UserAgent;
use Redis;

system 'make stop_web_server';

56
eval { Redis->new->flushall };
Xavier Guimard's avatar
Xavier Guimard committed
57

Xavier Guimard's avatar
Xavier Guimard committed
58 59 60 61
my @legend = (
    'Apache::Session::Browseable::LDAP'        => 'LDAP',
    'Apache::Session::MySQL (no lock)'         => 'BMySQL',
    'Apache::Session::Browseable::MySQL'       => 'BiMySQL',
Xavier Guimard's avatar
Xavier Guimard committed
62
    'Apache::Session::Browseable::MySQLJSON'   => 'MySQLJSON',
Xavier Guimard's avatar
Xavier Guimard committed
63 64 65 66
    'Apache::Session::Postgres (logged table)' => 'Postgres',
    'Apache::Session::Postgres'                => 'UPostgres',
    'Apache::Session::Browseable::Postgres'    => 'BPostgres',
    'Apache::Session::Browseable::PgJSON'      => 'PgJSON',
67
    'Apache::Session::Browseable::PgJSONB'     => 'PgJSONB',
Xavier Guimard's avatar
Xavier Guimard committed
68 69 70 71 72
    'Apache::Session::Browseable::PgHstore'    => 'PgHstore',
    'Apache::Session::Redis'                   => 'Redis',
    'Apache::Session::Browseable::Redis'       => 'BRedis',
);

Xavier Guimard's avatar
Xavier Guimard committed
73
my $tests = {
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    (
        $ENV{LLNGTESTLDAP}
        ? (
            LDAP => {
                cmd => 'cd lemonldap-ng-portal;perl t/test-ldap.pm;cd -',
                globalStorage        => 'Apache::Session::Browseable::LDAP',
                globalStorageOptions => {
                    ldapServer       => 'ldap://localhost:19389',
                    ldapBindDN       => 'cn=admin,dc=example,dc=com',
                    ldapBindPassword => 'admin',
                    ldapConfBase     => 'ou=sessions,dc=example,dc=com',
                    Index            => '_whatToTrace _session_kind'
                },
            }
          )
        : ()
    ),
Xavier Guimard's avatar
Xavier Guimard committed
91 92 93 94 95 96 97
    Redis => {
        globalStorage        => 'Apache::Session::Browseable::Redis',
        globalStorageOptions => {},
    },
    BRedis => {
        globalStorage        => 'Apache::Session::Browseable::Redis',
        globalStorageOptions => {
Xavier Guimard's avatar
Xavier Guimard committed
98
            Index => '_whatToTrace _session_kind'
Xavier Guimard's avatar
Xavier Guimard committed
99 100
        },
    },
Xavier Guimard's avatar
Xavier Guimard committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    BMySQL => {
        globalStorage        => 'Apache::Session::Browseable::MySQL',
        globalStorageOptions => {
            DataSource => 'dbi:mysql:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
'CREATE TABLE sessions (id varchar(64) not null primary key, a_session text)',
        ],
    },
    BiMySQL => {
        globalStorage        => 'Apache::Session::Browseable::MySQL',
        globalStorageOptions => {
            DataSource => 'dbi:mysql:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Index      => '_whatToTrace _session_kind _utime'
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
'CREATE TABLE sessions (id varchar(64) not null primary key, a_session text, _whatToTrace varchar(64), _session_kind varchar(15), _utime bigint)',
            'CREATE INDEX uid1 ON sessions (_whatToTrace) USING BTREE',
            'CREATE INDEX _s1 ON sessions (_session_kind) USING BTREE',
            'CREATE INDEX _u1 ON sessions (_utime) USING BTREE',
        ],
    },
Xavier Guimard's avatar
Xavier Guimard committed
129 130 131 132 133 134 135 136 137
    MySQLJSON => {
        globalStorage        => 'Apache::Session::Browseable::MySQLJSON',
        globalStorageOptions => {
            DataSource => 'dbi:mysql:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
Xavier Guimard's avatar
Tidy  
Xavier Guimard committed
138 139 140 141 142 143 144 145 146 147
'CREATE TABLE sessions (id varchar(64) not null primary key, a_session json,'
              . 'as_wt varchar(32) AS (a_session->"$._whatToTrace") VIRTUAL,'
              . 'as_sk varchar(12)  AS (a_session->"$._session_kind") VIRTUAL,'
              . 'as_ut bigint  AS (a_session->"$._utime") VIRTUAL,'
              . 'as_ip varchar(64) AS (a_session->"$.ipAddr") VIRTUAL,'
              . 'KEY as_wt (as_wt),'
              . 'KEY as_sk (as_sk),'
              . 'KEY as_ut (as_ut),'
              . 'KEY as_ip (as_ip))'
              . 'ENGINE=InnoDB',
Xavier Guimard's avatar
Xavier Guimard committed
148 149
        ],
    },
Xavier Guimard's avatar
Xavier Guimard committed
150 151 152 153 154 155 156 157 158 159 160 161
    Postgres => {
        globalStorage        => 'Apache::Session::Browseable::Postgres',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
Xavier Guimard's avatar
Xavier Guimard committed
162
            'DROP INDEX IF EXISTS _u1',
Xavier Guimard's avatar
Xavier Guimard committed
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
'CREATE TABLE sessions (id varchar(64) not null primary key, a_session text)',
        ],
    },
    UPostgres => {
        globalStorage        => 'Apache::Session::Browseable::Postgres',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
Xavier Guimard's avatar
Xavier Guimard committed
178
            'DROP INDEX IF EXISTS _u1',
Xavier Guimard's avatar
Xavier Guimard committed
179 180 181 182 183 184 185 186 187 188
'CREATE UNLOGGED TABLE sessions (id varchar(64) not null primary key, a_session text)',
        ],
    },
    BPostgres => {
        globalStorage        => 'Apache::Session::Browseable::Postgres',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
Xavier Guimard's avatar
Xavier Guimard committed
189
            Index      => '_whatToTrace _session_kind _utime'
Xavier Guimard's avatar
Xavier Guimard committed
190 191 192 193 194
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
Xavier Guimard's avatar
Xavier Guimard committed
195
            'DROP INDEX IF EXISTS _u1',
Xavier Guimard's avatar
Xavier Guimard committed
196
'CREATE UNLOGGED TABLE sessions (id varchar(64) not null primary key, a_session text, _whatToTrace text, _session_kind text, _utime bigint)',
Xavier Guimard's avatar
Tidy  
Xavier Guimard committed
197
'CREATE INDEX uid1 ON sessions USING BTREE (_whatToTrace text_pattern_ops)',
Xavier Guimard's avatar
Xavier Guimard committed
198
            'CREATE INDEX _s1 ON sessions (_session_kind)',
Xavier Guimard's avatar
Xavier Guimard committed
199
            'CREATE INDEX _u1 ON sessions (_utime)',
Xavier Guimard's avatar
Xavier Guimard committed
200 201
        ],
    },
Xavier Guimard's avatar
Xavier Guimard committed
202 203 204 205 206 207 208 209 210 211 212 213
    PgHstore => {
        globalStorage        => 'Apache::Session::Browseable::PgHstore',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
Xavier Guimard's avatar
Xavier Guimard committed
214
            'DROP INDEX IF EXISTS _u1',
Xavier Guimard's avatar
Xavier Guimard committed
215
'CREATE UNLOGGED TABLE sessions (id varchar(64) not null primary key, a_session hstore)',
216
"CREATE INDEX uid1 ON sessions USING BTREE ( (a_session -> '_whatToTrace') text_pattern_ops )",
Xavier Guimard's avatar
Xavier Guimard committed
217
            "CREATE INDEX _s1  ON sessions ( (a_session -> '_session_kind') )",
218
"CREATE INDEX _u1  ON sessions ( ( cast(a_session -> '_utime' AS bigint) ) )",
Xavier Guimard's avatar
Xavier Guimard committed
219 220
        ],
    },
Xavier Guimard's avatar
Xavier Guimard committed
221 222 223 224 225 226 227 228 229 230 231 232
    PgJSON => {
        globalStorage        => 'Apache::Session::Browseable::PgJSON',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
Xavier Guimard's avatar
Xavier Guimard committed
233
            'DROP INDEX IF EXISTS _u1',
Xavier Guimard's avatar
Xavier Guimard committed
234
'CREATE UNLOGGED TABLE sessions (id varchar(64) not null primary key, a_session json)',
235
"CREATE INDEX uid1 ON sessions USING BTREE ( (a_session ->> '_whatToTrace') text_pattern_ops )",
Xavier Guimard's avatar
Xavier Guimard committed
236
            "CREATE INDEX _s1  ON sessions ( (a_session ->> '_session_kind') )",
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
"CREATE INDEX _u1  ON sessions ( ( cast(a_session ->> '_utime' AS bigint) ) )",
        ],
    },
    PgJSONB => {
        globalStorage        => 'Apache::Session::Browseable::PgJSON',
        globalStorageOptions => {
            DataSource => 'dbi:Pg:host=127.0.0.1;database=sessions',
            UserName   => 'sso',
            Password   => 'sso',
            Commit     => 1,
        },
        pg => [
            'DROP TABLE IF EXISTS sessions',
            'DROP INDEX IF EXISTS uid1',
            'DROP INDEX IF EXISTS _s1',
            'DROP INDEX IF EXISTS _u1',
'CREATE UNLOGGED TABLE sessions (id varchar(64) not null primary key, a_session jsonb)',
"CREATE INDEX uid1 ON sessions USING BTREE ( (a_session ->> '_whatToTrace') text_pattern_ops )",
            "CREATE INDEX _s1  ON sessions ( (a_session ->> '_session_kind') )",
"CREATE INDEX _u1  ON sessions ( ( cast(a_session ->> '_utime' AS bigint) ) )",
Xavier Guimard's avatar
Xavier Guimard committed
257 258 259 260 261 262
        ],
    },
};

my $times = {};

Xavier Guimard's avatar
Xavier Guimard committed
263 264 265 266 267 268
if(@ARGV) {
    foreach my $t ( keys %$tests ) {
        delete $tests->{$t} unless(grep /^$t$/, @ARGV);
    }
}

Xavier Guimard's avatar
Xavier Guimard committed
269 270
foreach my $name ( keys %$tests ) {
    my $opts = $tests->{$name}->{globalStorageOptions};
271 272 273 274
    if ( my $cmd = delete $tests->{$name}->{cmd} ) {
        system $cmd;
    }
    if ( my $cmd = delete $tests->{$name}->{pg} ) {
Xavier Guimard's avatar
Xavier Guimard committed
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        my $dbh = DBI->connect( $opts->{DataSource}, $opts->{UserName},
            $opts->{Password}, { RaiseError => 1, AutoCommit => 1 } );
        foreach (@$cmd) {
            print STDERR "$_\n";
            $dbh->do($_);
        }
        $dbh->disconnect;
    }
    system 'make start_web_server';
    print STDERR "Removing manager protection\n";
    system
q(perl -i -pe 's/protection\s*=\s*manager/protection=none/' e2e-tests/conf/lemonldap-ng.ini);
    print STDERR "Read conf\n";
    open F, 'e2e-tests/conf/lmConf-1.json' or die;
    my $conf = join '', <F>;
    close F;
    $conf = from_json($conf);

    foreach my $k ( keys %{ $tests->{$name} } ) {
        $conf->{$k} = $tests->{$name}->{$k};
    }
Xavier Guimard's avatar
Xavier Guimard committed
296 297 298

    # Fix timeout to 2h
    $conf->{timeout} = 7200;
Xavier Guimard's avatar
Xavier Guimard committed
299 300 301 302
    print STDERR "Write conf\n";
    open F, '>e2e-tests/conf/lmConf-1.json' or die;
    print F to_json($conf);
    close F;
303

Xavier Guimard's avatar
Xavier Guimard committed
304 305 306 307
    #system 'cat e2e-tests/conf/lmConf-1.json';
    sleep(1);
    system 'make reload_web_server';

Xavier Guimard's avatar
Xavier Guimard committed
308
    # Insert 1000 sessions
Xavier Guimard's avatar
Xavier Guimard committed
309 310 311 312
    my $t = Time::HiRes::time();
    system './e2e-tests/populate.pl';
    $times->{$name}->{insert} = Time::HiRes::time() - $t;

Xavier Guimard's avatar
Xavier Guimard committed
313
    # Initialize manager
Xavier Guimard's avatar
Xavier Guimard committed
314 315
    my $ua = LWP::UserAgent->new;
    $ua->get('http://manager.example.com:19876/sessions.html');
Xavier Guimard's avatar
Xavier Guimard committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329

    my $tmp = {
        read       => 0,
        getLetter  => 0,
        getUid     => 0,
        getSession => 0,
    };

    # First loop isn't used in averages
    foreach my $i ( 0 .. 10 ) {

        # Test first Session Explorer access
        $t = Time::HiRes::time();
        my $res = $ua->get(
Xavier Guimard's avatar
Xavier Guimard committed
330
'http://manager.example.com:19876/manager.fcgi/sessions/global?groupBy=substr(_whatToTrace,1)'
Xavier Guimard's avatar
Xavier Guimard committed
331 332 333 334 335 336 337 338
        );
        $tmp->{read} += Time::HiRes::time() - $t if ($i);
        $res = from_json( $res->content );

        # Partial "_whatToTrace" search
        my $letter = $res->{values}->[0]->{value};
        $t   = Time::HiRes::time();
        $res = $ua->get(
339
'http://manager.example.com:19876/manager.fcgi/sessions/global?_whatToTrace='
Xavier Guimard's avatar
Xavier Guimard committed
340 341 342 343 344 345 346 347 348
              . $letter
              . '*&groupBy=_whatToTrace' );
        $tmp->{getLetter} += Time::HiRes::time() - $t if ($i);
        $res = from_json( $res->content );

        # Search for an uid
        my $user = $res->{values}->[$i]->{value};
        $t   = Time::HiRes::time();
        $res = $ua->get(
349
'http://manager.example.com:19876/manager.fcgi/sessions/global?_whatToTrace='
Xavier Guimard's avatar
Xavier Guimard committed
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
              . $user );
        $tmp->{getUid} += Time::HiRes::time() - $t if ($i);
        $res = from_json( $res->content );

        # Get a session
        my $id = $res->{values}->[0]->{session};
        $t   = Time::HiRes::time();
        $res = $ua->get(
            'http://manager.example.com:19876/manager.fcgi/sessions/global/'
              . $id );
        $tmp->{getSession} += Time::HiRes::time() - $t if ($i);
        $res = from_json( $res->content );
    }

    # Average
    foreach my $type ( keys %$tmp ) {
        $times->{$name}->{$type} = $tmp->{$type} / 10;
    }

    # Purge half sessions
    $t = Time::HiRes::time();
    system 'LLNG_DEFAULTCONFFILE=e2e-tests/conf/lemonldap-ng.ini '
      . 'perl -Ilemonldap-ng-common/blib/lib '
      . 'lemonldap-ng-portal/site/cron/purgeCentralCache';
    $times->{$name}->{purge} = Time::HiRes::time() - $t;

    # Turn off webserver
Xavier Guimard's avatar
Xavier Guimard committed
377 378 379
    system 'make stop_web_server';
}

380
if ( $ENV{LLNGTESTLDAP} ) {
Xavier Guimard's avatar
Xavier Guimard committed
381 382 383 384
    if ( open F, 'lemonldap-ng-portal/t/testslapd/slapd.pid' ) {
        my $pid = join '', <F>;
        system "kill $pid";
    }
385 386 387 388
    system 'rm -rf lemonldap-ng-portal/t/testslapd/slapd.d';
    system 'rm -rf lemonldap-ng-portal/t/testslapd/data';
    system 'rm -rf lemonldap-ng-portal/t/testslapd/slapd-test.ldif';
}
Xavier Guimard's avatar
Xavier Guimard committed
389 390 391 392 393

#use Data::Dumper;
#print Dumper($times);

print <<EOT;
Xavier Guimard's avatar
Xavier Guimard committed
394 395 396 397
+-----------------------------------------+-------------------------------------+-----------------------------------+
|                                         |              Main use               |          Session explorer         |
|                 Backend                 | Insert 1000 |   Get 1   | Purge 500 | Parse all |  1 letter |   1 user  |
+-----------------------------------------+-------------------------------------+-----------------------------------+
Xavier Guimard's avatar
Xavier Guimard committed
398
EOT
399 400 401 402 403
for ( my $i = 0 ; $i < @legend ; $i += 2 ) {
    my $type = $legend[ $i + 1 ];
    next unless ( $times->{$type} );
    printf "|%40s |%11.5f  |  %.5f  |  %7.4f  |  %.5f  |  %.5f  |  %.5f  |\n",
      $legend[$i],
Xavier Guimard's avatar
Xavier Guimard committed
404 405
      map { $times->{$type}->{$_} }
      qw(insert getSession purge read getLetter getUid);
Xavier Guimard's avatar
Xavier Guimard committed
406
}
Xavier Guimard's avatar
Xavier Guimard committed
407
print
Xavier Guimard's avatar
Xavier Guimard committed
408
"+-----------------------------------------+-------------------------------------+-----------------------------------+\n";