Scaling FastAGI Applications with Go Lefteris Zafiris NTA Ltd Astricon 2014 Las Vegas
Jul 08, 2015
Scaling FastAGI Applications with Go
Lefteris Zafiris NTA LtdAstricon 2014 Las Vegas
Who we are
VoIP engineer at NTA ltd
Voice and text toolset
- Flite Asterisk App- eSpeak Asterisk App- googleTTS- speech recognition - Google speech API- Text translation
github.com/zaf
Who we are
NTA Ltd - nta.co.uk
- UK based VoIP provider since 2001- Hosted PBX services- SIP- FOSS- Kamailio / Asterisk- Perl
Perl stack
- FastAGI server
- Net::Server
- fork / prefork
- Memory
- Capacity - calls / server
Enter Go
2007 Google
Rob Pike, Ken Thompson, Robert Griesemer
- Statically typed- Garbage-collected- Natively compiled- Concurrency- C family- Pragmatic, minimalistic
Enter Go
Do not communicate by sharing memory.
Share memory by communicating.
- Goroutines
lightweight threads
cheap
multiplexed in OS threads
- Channels
communication
synchronization
Enter Go
/* A simple example in go*/
package main
import "fmt"
func main() { var msg string msg = "Hello Astricon"
// Retroactively say hello to all Astricons! for i := 0; i <= 10; i++ { fmt.Println(msg, 2004+i) }}
Go toolset
- go command
testfmtbuildinstall
- Debugging
gdb- Package management
go get
AGI Package
- AGI and FastAGI support
- Simple familiar interface
- BSD Licence
- Install:
go get github.com/zaf/agi
AGI Package
- Session structure
AGI environment variables
Env map[string]string
- Reply structure
Numeric result of the AGI command
Res intAdditional returned data
Dat string
AGI Package
- AGI commands as methods of the Session struct
func (a *Session) Answer() (Reply, error)
func (a *Session) SendText(text string) (Reply, error)
func (a *Session) Hangup(channel ...string) (Reply, error)
func (a *Session) SayDigits(digit int,escape string) (Reply, error)
AGI Package usage
- Create a new session:
myAgi := agi.New()
- Initialize, read and parse AGI environment:
myAgi.Init(nil)
- Execute AGI commands:
myAgi.StreamFile("hello-world", "#")
AGI examplepackage main
import ( "log" "github.com/zaf/agi")
func main() { // Create a new AGI session and Parse the AGI environment. myAgi := agi.New() err := myAgi.Init(nil) if err != nil { log.Fatalf("Error Parsing AGI environment: %v\n", err) } // Print a message on the asterisk console using Verbose. _, err := myAgi.Verbose("Hello World") if err != nil { log.Fatalf("AGI reply error: %v\n", err) }}
FastAGI Example
func main() { // Create a tcp listener on port 4573 and start a new goroutine for each connection. ln, err := net.Listen("tcp", ":4573") if err != nil { log.Fatal(err) } defer ln.Close() for { conn, err := ln.Accept() if err != nil { log.Println(err) continue } go connHandle(conn) }}
FastAGI Examplefunc connHandle(c net.Conn) { defer c.Close() myAgi := agi.New() // Create I/O buffers rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) err := myAgi.Init(rw) if err != nil { log.Println(err) return } rep, err := myAgi.StreamFile("hello", "1234567890#*") if err != nil { log.Println(err) return }
// Check AGI command return value if rep.Res == -1 { log.Println("Failed to playback file") }}
Error Handlingfunc connHandle(c net.Conn) { defer func() { c.Close() if err := recover(); err != nil { log.Println("Session terminated:", err) } }() myAgi := agi.New() rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) err := myAgi.Init(rw) checkErr(err) _, err := myAgi.Verbose("Hello World") checkErr(err)}//Check for AGI Protocol errors or hangupsfunc checkErr(e error) { if e != nil { panic(e) }}
AGI Debug
- Error testing- Performance testing- Security testing
Eliminate the use of Asterisk
Custom AGI Payloads
Agistress
A client that can connect to servers using the Asterisk Gateway Interface
- user defined payloads- user controlled session parameters- fast - parallel session spawning- performance measuring- written in Go
go get github.com/zaf/agistress
Agistress
Run once and display full debug output of the AGI session:
agistress -host 127.0.0.1 -single
Stress test the AGI server by spawning 10 sessions 10 times per second and display performance details:
agistress -host 127.0.0.1 -sess 10 -runs 10
Run once using custom AGI payload from config file:agistress -host 127.0.0.1 -single -conf payload.conf
Agistress
Config file:
- JSON format
- Environment variables
- AGI Command replies
- Reply delay
Agistress
Config file sample:
{"AgiPayload": [
{"Msg": "200 result=0", "Delay": 10}, {"Msg": "200 result=0", "Delay": 10}, {"Msg": "200 result=1 endpos=1234", "Delay": 3000}, {"Msg": "HANGUP"}
]}
Agistress
Sample output:Running FastAGI bench against: 192.168.1.10:4573Press Enter to stop.
A new run each: 100msSessions per run: 10Reply delay: 50ms
FastAGI SessionsActive: 20Completed: 520Duration: 206121479 ns (last 10000 sessions average)Failed: 0
Measuring
Comparison between FastAGI implementations:
- Perl:
Asterisk::AGI
Net::Server::PreFork
- Go:
agi package
net package
Measuring
Simple sound file Playback application:
- Start session and parse AGI Env- Parse FastAGI request URI
agi://10.11.12.13/myagi?file=echo-test
- Check channel status- Answer channel- Playback given file
Perl: 73 loc (http://goo.gl/hdsxuw)
Go: 90 loc (http://goo.gl/uxezg7)
Benchmark
Stress test using agistress:- 1 session/sec
agistress -delay=0- 25 sessions/sec
agistress -runs=5 -sess=5 -delay=0- 50 sessions/sec
agistress -runs=10 -sess=5 -delay=0- 100 sessions/sec
agistress -runs=10 -sess=10 -delay=0- 200 and 400 sessions/sec
Benchmark
Memory usage:
Many active sessions
Custom AGI Payload, 5 sec playback duration
- 12 active sessions:
agistress -sess=2 -conf=sample.conf
- 24 active sessions:
agistress -sess=4 -conf=sample.conf
- 48, 96 and 192 sessions
Results
- Up to 3 times less CPU usage
- Up to 2.5 times shorter runtime
- Enormous memory savings
Thank You!