├── README.md └── dialer.pl /README.md: -------------------------------------------------------------------------------- 1 | FreeSWITCH call generator for performance tests 2 | =============================================== 3 | 4 | This is a simple dialer that connects to FreeSWITCH via event socket and 5 | originates calls at a given interval. 6 | 7 | The script subsututes the question mark signs (?) with random digits in 8 | caller ID and the destination number. 9 | 10 | There are two ways to specify the call destination: 11 | 12 | 1. Loopback endpoint 13 | -------------------- 14 | 15 | If used with `--content` and `--dest` options, the dialer originates the 16 | calls into the specified context via loopback endpoint. For example, the 17 | following contexts will forward all calls to some remote servers: 18 | 19 | ``` 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | Keep in mind that the default limit of sessions per second in FreeSWITCH 35 | is 30 (`sessions-per-second` parameter in 36 | `autoload_configs/switch.conf.xml`). Because of the loopback endpoint, 37 | each call occupies 3 channels, and this results in 10 calls per second 38 | maximum. 39 | 40 | For example, the following command would start 100 calls, and each call 41 | would last 10 minutes: 42 | 43 | ``` 44 | perl /opt/freeswitch-perf-dialer/dialer.pl \ 45 | --ncalls=100 --cps=9 --duration=600 --context=dialer01 \ 46 | --dest='01234?????' 47 | ``` 48 | 49 | 2. Endpoint string 50 | ------------------ 51 | 52 | You can specify explicitly the endpoint string. In case of SIP 53 | endpoints, that will instruct FreeSWITCH to use a specified SIP profile 54 | and gateway for example: 55 | 56 | ``` 57 | perl /opt/freeswitch-perf-dialer/dialer.pl --cid='+3333???????' \ 58 | --endpoint='sofia/external/+777???????@10.250.250.23' --cps=5 --forever 59 | 60 | 61 | perl /opt/freeswitch-perf-dialer/dialer.pl --cid='+3333???????' \ 62 | --endpoint='sofia/gateway/voxbeam/+777???????' --cps=5 --forever 63 | ``` 64 | 65 | Transferring instead of playback 66 | -------------------------------- 67 | 68 | The `--exec` option allows you to send the call to a dialplan context 69 | instead of playing back the media. The following example sends the call 70 | after originating to default dialplan context, with destination number 71 | 12345678 and caller ID 87654321: 72 | 73 | ``` 74 | perl /opt/freeswitch-perf-dialer/dialer.pl --cid='12345678' \ 75 | --endpoint='sofia/gateway/voxbeam/87654321' --cps=5 --forever \ 76 | --exec='12345678 XML default 87654321 87654321' 77 | ``` 78 | 79 | The general syntax of the exec string is as follows: 80 | 81 | ``` 82 | XML 83 | ``` 84 | 85 | 86 | Installing on Debian 8 87 | ---------------------- 88 | 89 | ``` 90 | apt-get install -y curl git 91 | 92 | cat >/etc/apt/sources.list.d/freeswitch.list < 140 | 141 | This software is distributed under the MIT license. -------------------------------------------------------------------------------- /dialer.pl: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Stanislav Sinyagin 2 | 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | use strict; 23 | use warnings; 24 | use Getopt::Long; 25 | use Time::HiRes; 26 | use POSIX; 27 | use ESL; 28 | 29 | $| = 1; 30 | 31 | my $fs_host = '127.0.0.1'; 32 | my $fs_port = 8021; 33 | my $fs_password = 'ClueCon'; 34 | 35 | my $callerid = '12126647665'; 36 | my $dest = '13115552368'; 37 | my $playback = 'local_stream://moh'; 38 | my $context = 'public'; 39 | my $endpoint; 40 | my $exec; 41 | 42 | my $duration = 60; 43 | my $ncalls = 10; 44 | my $forever; 45 | my $interval; 46 | my $cps; 47 | my $rnd_interval; 48 | my $rnd_cps; 49 | 50 | 51 | my $help_needed; 52 | 53 | my $ok = GetOptions 54 | ( 55 | 'fs_host=s' => \$fs_host, 56 | 'fs_port=s' => \$fs_port, 57 | 'fs_password=s' => \$fs_password, 58 | 'cid=s' => \$callerid, 59 | 'dest=s' => \$dest, 60 | 'context=s' => \$context, 61 | 'endpoint=s' => \$endpoint, 62 | 'duration=i' => \$duration, 63 | 'ncalls=i' => \$ncalls, 64 | 'forever' => \$forever, 65 | 'cps=f' => \$cps, 66 | 'interval=f' => \$interval, 67 | 'rc=f' => \$rnd_cps, 68 | 'ri=f' => \$rnd_interval, 69 | 'play=s' => \$playback, 70 | 'exec=s' => \$exec, 71 | 'help' => \$help_needed, 72 | ); 73 | 74 | 75 | if( not $ok or $help_needed or scalar(@ARGV) > 0 ) 76 | { 77 | print STDERR "Usage: $0 --cps=10 [options...]\n", 78 | "Options:\n", 79 | " --fs_host=HOST \[$fs_host\] FreeSWITCH host\n", 80 | " --fs_port=PORT \[$fs_port\] FreeSWITCH ESL port\n", 81 | " --fs_password=PW \[$fs_password\] FreeSWITCH ESL password\n", 82 | " --cid=NUMBER \[$callerid\] caller ID\n", 83 | " --dest=NUMBER \[$dest\] destination number\n", 84 | " --context=NAME \[$context\] FreeSWITCH context name\n", 85 | " --endpoint=STRING destination endpoint\n", 86 | " --duration=N \[$duration\] call duration in seconds\n", 87 | " --ncalls=N \[$ncalls\] total number of calls\n", 88 | " --forever run endlessly and ignore ncalls\n", 89 | " --cps=F rate in calls per second\n", 90 | " --interval=F interval between calls in seconds (CPS\*\*-1)\n", 91 | " --rc=F random factor in CPS (should be higher than CPS*2)\n", 92 | " --ri=F random factor in interval (should be less than interval/2)\n", 93 | " --play=STRING \[$playback\] playback argument\n", 94 | " --exec=STRING application to execute instead of playback\n", 95 | " --help this help message\n", 96 | "\n", 97 | "If endpoint is specified, --dest and --context are ignored.\n", 98 | "Otherwise, the call is sent to the loopback endpoint with the specified\n", 99 | "context and destination number\n", 100 | "The question mark characters (?) in numbers are replaced with random digits\n", 101 | "\n", 102 | ; 103 | exit 1; 104 | } 105 | 106 | if( defined($cps) and defined($interval) ) 107 | { 108 | print STDERR "Only one of CPS and interval must be defined\n"; 109 | exit 1; 110 | } 111 | 112 | if( not defined($cps) and not defined($interval) ) 113 | { 114 | print STDERR "Either CPS or interval must be defined\n"; 115 | exit 1; 116 | } 117 | 118 | if( defined($rnd_cps) ) 119 | { 120 | if( not defined($cps) ) 121 | { 122 | print STDERR "--rc can only be defined together with --cps\n"; 123 | exit 1; 124 | } 125 | elsif( $rnd_cps < $cps * 2 ) 126 | { 127 | print STDERR "--rc shoudl be higher than CPS*2\n"; 128 | exit 1; 129 | } 130 | } 131 | 132 | if( defined($rnd_interval) ) 133 | { 134 | if( not defined($interval) ) 135 | { 136 | print STDERR "--ri can only be defined together with --interval\n"; 137 | exit 1; 138 | } 139 | elsif( $rnd_interval > $interval / 2 ) 140 | { 141 | print STDERR "--ri should be lower than interval/2\n"; 142 | exit 1; 143 | } 144 | } 145 | 146 | 147 | 148 | my $originate_string = 149 | 'originate ' . 150 | '{ignore_early_media=true,' . 151 | 'origination_uuid=%s,' . 152 | 'originate_timeout=60,' . 153 | 'origination_caller_id_number=' . $callerid . ',' . 154 | 'origination_caller_id_name=dialer_pl}'; 155 | 156 | if( defined($endpoint) ) 157 | { 158 | $originate_string .= $endpoint; 159 | } 160 | else 161 | { 162 | $originate_string .= 'loopback/' . $dest . '/' . $context; 163 | } 164 | 165 | if( defined($exec) ) 166 | { 167 | $originate_string .= ' ' . $exec; 168 | } 169 | else 170 | { 171 | $originate_string .= ' ' . '&playback(' . $playback . ')'; 172 | } 173 | 174 | 175 | my $esl = new ESL::ESLconnection($fs_host, 176 | sprintf('%d', $fs_port), 177 | $fs_password); 178 | 179 | $esl->connected() or die("Cannot connect to FreeSWITCH"); 180 | 181 | if( not defined($interval) ) 182 | { 183 | $interval = 1.0/$cps; 184 | } 185 | 186 | if( defined($rnd_cps) ) 187 | { 188 | $rnd_interval = 1.0/$rnd_cps; 189 | } 190 | 191 | 192 | my $nc = 0; 193 | my $start = Time::HiRes::time(); 194 | 195 | while( $forever or $nc < $ncalls ) 196 | { 197 | my $next_time = $start + $nc * $interval; 198 | 199 | if( defined($rnd_interval) ) 200 | { 201 | $next_time += rand($rnd_interval); 202 | } 203 | 204 | # Replace "?" with random digits 205 | my $orig_str = $originate_string; 206 | while( $orig_str =~ /\?/o ) 207 | { 208 | my $random_digit = sprintf('%d', rand(10)); 209 | $orig_str =~ s/\?/$random_digit/; 210 | } 211 | 212 | my $now = Time::HiRes::time(); 213 | if( $next_time > $now ) 214 | { 215 | Time::HiRes::sleep($next_time - $now); 216 | } 217 | 218 | my $uuid = $esl->api('create_uuid')->getBody(); 219 | my ($time_epoch, $time_hires) = Time::HiRes::gettimeofday(); 220 | $esl->bgapi(sprintf($orig_str, $uuid)); 221 | $esl->bgapi(sprintf('sched_hangup +%d %s', $duration, $uuid)); 222 | 223 | printf("%.6d: %s.%.6d\n", 224 | $nc, 225 | POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime($time_epoch)), 226 | $time_hires); 227 | $nc++; 228 | } 229 | --------------------------------------------------------------------------------