Skip to content

Commit

Permalink
Merge pull request #32 from percipia/cpu_usage
Browse files Browse the repository at this point in the history
Performance improvements
  • Loading branch information
mkravos committed Jun 25, 2024
2 parents f4b8cb9 + eec3ac4 commit e53449c
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 65 deletions.
8 changes: 4 additions & 4 deletions command/call/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ package call

import (
"fmt"
"github.com/percipia/eslgo/command"
"net/textproto"
"strconv"

"github.com/percipia/eslgo/command"
)

type Execute struct {
Expand Down Expand Up @@ -73,7 +74,7 @@ func (e *Execute) BuildMessage() string {
}
sendMsg := command.SendMessage{
UUID: e.UUID,
Headers: make(textproto.MIMEHeader),
Headers: make(textproto.MIMEHeader, 4), // preallocating for the 4+ headers that are always set to reduce amount of dynamic allocations
Sync: e.Sync,
SyncPri: e.SyncPri,
}
Expand All @@ -87,8 +88,7 @@ func (e *Execute) BuildMessage() string {

// According to documentation that is the max header length
if len(e.AppArgs) > 2048 || e.ForceBody {
sendMsg.Headers.Set("content-type", "text/plain")
sendMsg.Headers.Set("content-length", strconv.Itoa(len(e.AppArgs)))
sendMsg.Headers.Set("Content-Type", "text/plain")
sendMsg.Body = e.AppArgs
} else {
sendMsg.Headers.Set("execute-app-arg", e.AppArgs)
Expand Down
35 changes: 20 additions & 15 deletions command/call/execute_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
/*
* Copyright (c) 2020 Percipia
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Contributor(s):
* Andrew Querol <aquerol@percipia.com>
*/
package call

import (
"github.com/stretchr/testify/assert"
"sort"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

// Normalizes the headers in a message to ensure they are always in the same order before comparison
func normalizeMessage(message string) string {
parts := strings.Split(message, "\r\n\r\n")
headers := strings.Split(parts[0], "\r\n")
sort.Strings(headers)
normalizedHeaders := strings.Join(headers, "\r\n")

if len(parts) > 1 {
return normalizedHeaders + "\r\n\r\n" + parts[1]
}
return normalizedHeaders
}

var (
TestExecMessage = strings.ReplaceAll(`sendmsg none
Call-Command: execute
Expand Down Expand Up @@ -54,7 +59,7 @@ func TestExecute_BuildMessage(t *testing.T) {
AppName: "playback",
AppArgs: "/tmp/test.wav",
}
assert.Equal(t, TestExecMessage, exec.BuildMessage())
assert.Equal(t, normalizeMessage(TestExecMessage), normalizeMessage(exec.BuildMessage()))
}

func TestSet_BuildMessage(t *testing.T) {
Expand All @@ -63,7 +68,7 @@ func TestSet_BuildMessage(t *testing.T) {
Key: "hello",
Value: "world",
}
assert.Equal(t, TestSetMessage, set.BuildMessage())
assert.Equal(t, normalizeMessage(TestSetMessage), normalizeMessage(set.BuildMessage()))
}

func TestExport_BuildMessage(t *testing.T) {
Expand All @@ -72,7 +77,7 @@ func TestExport_BuildMessage(t *testing.T) {
Key: "hello",
Value: "world",
}
assert.Equal(t, TestExportMessage, export.BuildMessage())
assert.Equal(t, normalizeMessage(TestExportMessage), normalizeMessage(export.BuildMessage()))
}

func TestPush_BuildMessage(t *testing.T) {
Expand All @@ -81,5 +86,5 @@ func TestPush_BuildMessage(t *testing.T) {
Key: "hello",
Value: "world",
}
assert.Equal(t, TestPushMessage, push.BuildMessage())
assert.Equal(t, normalizeMessage(TestPushMessage), normalizeMessage(push.BuildMessage()))
}
5 changes: 3 additions & 2 deletions command/call/nomedia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
package call

import (
"github.com/stretchr/testify/assert"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

var TestNoMediaMessage = strings.ReplaceAll(`sendmsg none
Expand All @@ -25,5 +26,5 @@ func TestNoMedia_BuildMessage(t *testing.T) {
UUID: "none",
NoMediaUUID: "test",
}
assert.Equal(t, TestNoMediaMessage, nomedia.BuildMessage())
assert.Equal(t, normalizeMessage(TestNoMediaMessage), normalizeMessage(nomedia.BuildMessage()))
}
5 changes: 3 additions & 2 deletions command/call/unicast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
package call

import (
"github.com/stretchr/testify/assert"
"net"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

var TestUnicastMessage = strings.ReplaceAll(`sendmsg none
Expand All @@ -35,5 +36,5 @@ func TestUnicast_BuildMessage(t *testing.T) {
Remote: testRemote,
Flags: "native",
}
assert.Equal(t, TestUnicastMessage, unicast.BuildMessage())
assert.Equal(t, normalizeMessage(TestUnicastMessage), normalizeMessage(unicast.BuildMessage()))
}
15 changes: 14 additions & 1 deletion command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var crlfToLF = strings.NewReplacer("\r\n", "\n")
// FormatHeaderString - Writes headers in a FreeSWITCH ESL friendly format. Converts headers containing \r\n to \n
func FormatHeaderString(headers textproto.MIMEHeader) string {
var ws strings.Builder
ws.Grow(estimateSize(headers))

keys := make([]string, len(headers))
i := 0
Expand All @@ -46,5 +47,17 @@ func FormatHeaderString(headers textproto.MIMEHeader) string {
}
}
// Remove the extra \r\n
return ws.String()[:ws.Len()-2]
str := ws.String()
return str[:len(str)-2]
}

// helper for FormatHeaderString that estimates the size of the final header string to avoid multiple allocations
func estimateSize(headers textproto.MIMEHeader) int {
size := 0
for key, values := range headers {
for _, value := range values {
size += len(key) + len(value) + 4 // 4 extra characters for ": " and "\r\n"
}
}
return size
}
22 changes: 17 additions & 5 deletions command/sendmsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"fmt"
"net/textproto"
"strconv"
"strings"
)

type SendMessage struct {
Expand All @@ -25,28 +26,39 @@ type SendMessage struct {
}

func (s *SendMessage) BuildMessage() string {
var headers []string

if s.Headers == nil {
s.Headers = make(textproto.MIMEHeader)
}

// Waits for this event to finish before continuing even in async mode
if s.Sync {
s.Headers.Set("event-lock", "true")
headers = append(headers, "event-lock: true")
}

// No documentation on this flag, I assume it takes priority over the other flag?
if s.SyncPri {
s.Headers.Set("event-lock-pri", "true")
headers = append(headers, "event-lock-pri: true")
}

// Ensure the correct content length is set in the header
if len(s.Body) > 0 {
s.Headers.Set("Content-Length", strconv.Itoa(len(s.Body)))
headers = append(headers, "Content-Length: "+strconv.Itoa(len(s.Body)))
} else {
delete(s.Headers, "Content-Length")
}

// Format the headers
headerString := FormatHeaderString(s.Headers)
if _, ok := s.Headers["Content-Length"]; ok {
for key, values := range s.Headers {
for _, value := range values {
headers = append(headers, key+": "+value)
}
}

headerString := strings.Join(headers, "\r\n")

if len(s.Body) > 0 {
return fmt.Sprintf("sendmsg %s\r\n%s\r\n\r\n%s", s.UUID, headerString, s.Body)
}
return fmt.Sprintf("sendmsg %s\r\n%s", s.UUID, headerString)
Expand Down
52 changes: 31 additions & 21 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,32 @@ import (
"bufio"
"context"
"errors"
"github.com/google/uuid"
"github.com/percipia/eslgo/command"
"fmt"
"net"
"net/textproto"
"sync"
"time"

"github.com/percipia/eslgo/command"
)

type Conn struct {
conn net.Conn
reader *bufio.Reader
header *textproto.Reader
writeLock sync.Mutex
runningContext context.Context
stopFunc func()
responseChannels map[string]chan *RawResponse
responseChanMutex sync.RWMutex
eventListenerLock sync.RWMutex
eventListeners map[string]map[string]EventListener
outbound bool
logger Logger
exitTimeout time.Duration
closeOnce sync.Once
closeDelay time.Duration
conn net.Conn
reader *bufio.Reader
header *textproto.Reader
writeLock sync.Mutex
runningContext context.Context
stopFunc func()
responseChannels map[string]chan *RawResponse
responseChanMutex sync.RWMutex
eventListenerLock sync.RWMutex
eventListeners map[string]map[string]EventListener
eventListenerCounter int
outbound bool
logger Logger
exitTimeout time.Duration
closeOnce sync.Once
closeDelay time.Duration
}

// Options - Generic options for an ESL connection, either inbound or outbound
Expand Down Expand Up @@ -97,7 +99,8 @@ func (c *Conn) RegisterEventListener(channelUUID string, listener EventListener)
c.eventListenerLock.Lock()
defer c.eventListenerLock.Unlock()

id := uuid.New().String()
c.eventListenerCounter++
id := fmt.Sprintf("%d", c.eventListenerCounter)
if _, ok := c.eventListeners[channelUUID]; ok {
c.eventListeners[channelUUID][id] = listener
} else {
Expand All @@ -118,10 +121,8 @@ func (c *Conn) RemoveEventListener(channelUUID string, id string) {

// SendCommand - Sends the specified ESL command to FreeSWITCH with the provided context. Returns the response data and any errors encountered.
func (c *Conn) SendCommand(ctx context.Context, cmd command.Command) (*RawResponse, error) {
c.writeLock.Lock()
defer c.writeLock.Unlock()

if linger, ok := cmd.(command.Linger); ok {
c.writeLock.Lock()
if linger.Enabled {
if linger.Seconds > 0 {
c.closeDelay = linger.Seconds
Expand All @@ -131,19 +132,26 @@ func (c *Conn) SendCommand(ctx context.Context, cmd command.Command) (*RawRespon
} else {
c.closeDelay = 0
}
c.writeLock.Unlock()
}

if deadline, ok := ctx.Deadline(); ok {
c.writeLock.Lock()
_ = c.conn.SetWriteDeadline(deadline)
c.writeLock.Unlock()
}

c.writeLock.Lock()
_, err := c.conn.Write([]byte(cmd.BuildMessage() + EndOfMessage))
c.writeLock.Unlock()
if err != nil {
return nil, err
}

// Get response
c.responseChanMutex.RLock()
defer c.responseChanMutex.RUnlock()
timeout := time.After(10 * time.Second)
select {
case response := <-c.responseChannels[TypeReply]:
if response == nil {
Expand All @@ -159,6 +167,8 @@ func (c *Conn) SendCommand(ctx context.Context, cmd command.Command) (*RawRespon
return response, nil
case <-ctx.Done():
return nil, ctx.Err()
case <-timeout:
return nil, errors.New("command timed out while waiting for a response from Freeswitch")
}
}

Expand Down
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@ module github.com/percipia/eslgo

go 1.14

require (
github.com/google/uuid v1.3.0
github.com/stretchr/testify v1.7.0
)
require github.com/stretchr/testify v1.7.0
7 changes: 0 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/percipia/eslgo v1.4.1 h1:FpYtzCwrzQuSgB24NyuQC5ibMSHVqCdzaieAcDTltYw=
github.com/percipia/eslgo v1.4.1/go.mod h1:Icri58AZUSyAo+ObKUXhVwSC2aUPZaLzJYO/vKE3kew=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18 changes: 14 additions & 4 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (
"context"
"errors"
"fmt"
"github.com/percipia/eslgo/command"
"github.com/percipia/eslgo/command/call"
"io"
"log"
"time"

"github.com/percipia/eslgo/command"
"github.com/percipia/eslgo/command/call"
)

func (c *Conn) EnableEvents(ctx context.Context) error {
Expand Down Expand Up @@ -79,9 +81,17 @@ func (c *Conn) WaitForDTMF(ctx context.Context, uuid string) (byte, error) {
if event.GetName() == "DTMF" {
dtmf := event.GetHeader("DTMF-Digit")
if len(dtmf) > 0 {
done <- dtmf[0]
select {
case done <- dtmf[0]:
default:
}
} else {
select {
case done <- 0:
default:
}
}
done <- 0
time.Sleep(10 * time.Millisecond)
}
})
defer func() {
Expand Down

0 comments on commit e53449c

Please sign in to comment.