Files
golib/socket/server/unix/performance_test.go
nabbar 3837f0b2bb Improvements, test & documentatons (2025-12 #1)
[file/bandwidth]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[file/perm]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases
- ADD function to parse form "rwx-wxr-x" or "-rwx-w-r-x"
- ADD function to ParseFileMode to convert os.FileMode to file.Perm

[file/progress]
- ADD documentation: add enhanced README and TESTING guidelines
- ADD tests: complete test suites with benchmarks, concurrency, and edge cases

[ioutils/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases

[logger/...]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- UPDATE config: remove FileBufferSize from OptionFile (rework hookfile)
- UPDATE fields: expose Store function in interface
- REWORK hookfile: rework package, use aggregator to allow multi write and
  single file
- FIX hookstderr: fix bug with NonColorable
- FIX hookstdout: fix bug with NonColorable
- FIX hookwriter: fix bug with NonColorable

[network/protocol]
- ADD function IsTCP, IsUDP, IsUnixLike to check type of protocol

[runner]
- FIX typo

[socket]
- UPDATE documentation: update enhanced README and TESTING guidelines
- ADD documentation: add enhanced README and TESTING guidelines for sub
  packages
- UPDATE tests: complete test suites with benchmarks, concurrency, and edge cases
- REWORK server: use context compatible io.reader, io.writer, io.closer
  instead of reader / writer
- REWORK server: simplify, optimize server
- REMOVE reader, writer type
- ADD context: add new interface in root socket interface to expose
  context interface that extend context, io reader/writer/closer,
dediacted function to server (IsConnected, ...)
2025-12-02 02:56:20 +01:00

332 lines
9.4 KiB
Go

//go:build linux || darwin
/*
* MIT License
*
* Copyright (c) 2025 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
// performance_test.go provides performance benchmarks for the Unix socket server.
// Measures throughput, latency, and resource usage using gmeasure.
package unix_test
import (
"context"
"sync"
"time"
scksru "github.com/nabbar/golib/socket/server/unix"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gmeasure"
)
var _ = Describe("Unix Server Performance Benchmarks", Ordered, func() {
var (
srv scksru.ServerUnix
socketPath string
c context.Context
cnl context.CancelFunc
experiment *gmeasure.Experiment
)
BeforeAll(func() {
experiment = gmeasure.NewExperiment("Unix Server Performance")
AddReportEntry(experiment.Name, experiment)
})
BeforeEach(func() {
socketPath = getTestSocketPath()
c, cnl = context.WithCancel(globalCtx)
})
AfterEach(func() {
if srv != nil {
_ = srv.Close()
}
if cnl != nil {
cnl()
}
cleanupSocketFile(socketPath)
time.Sleep(100 * time.Millisecond)
})
Context("server startup and shutdown", func() {
It("should measure server start time", func() {
experiment.Sample(func(idx int) {
experiment.MeasureDuration("start", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServer(srv, 2*time.Second)
})
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("start")
AddReportEntry("Start Time Stats", stats)
Expect(stats.DurationFor(gmeasure.StatMedian)).To(BeNumerically("<", 100*time.Millisecond))
})
It("should measure server stop time", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServer(srv, 2*time.Second)
experiment.Sample(func(idx int) {
experiment.MeasureDuration("stop", func() {
err := srv.Shutdown(c)
Expect(err).ToNot(HaveOccurred())
})
// Restart for next sample
if idx < 9 {
startServerInBackground(c, srv)
waitForServer(srv, 2*time.Second)
}
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("stop")
AddReportEntry("Stop Time Stats", stats)
Expect(stats.DurationFor(gmeasure.StatMedian)).To(BeNumerically("<", 200*time.Millisecond))
})
})
Context("connection throughput", func() {
It("should measure single connection throughput", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
experiment.Sample(func(idx int) {
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
experiment.MeasureDuration("single-conn-echo", func() {
for i := 0; i < 100; i++ {
data := []byte("Test message")
_ = sendAndReceive(con, data)
}
})
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("single-conn-echo")
AddReportEntry("Single Connection Throughput", stats)
})
It("should measure concurrent connection throughput", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
experiment.Sample(func(idx int) {
experiment.MeasureDuration("concurrent-10-conns", func() {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
for j := 0; j < 10; j++ {
data := []byte("Concurrent test")
_ = sendAndReceive(con, data)
}
}()
}
wg.Wait()
})
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("concurrent-10-conns")
AddReportEntry("Concurrent Connections (10)", stats)
})
})
Context("connection latency", func() {
It("should measure connection establishment latency", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
experiment.Sample(func(idx int) {
experiment.MeasureDuration("conn-establish", func() {
con := connectToServer(socketPath)
_ = con.Close()
})
}, gmeasure.SamplingConfig{N: 50, Duration: 30 * time.Second})
stats := experiment.GetStats("conn-establish")
AddReportEntry("Connection Establishment Latency", stats)
Expect(stats.DurationFor(gmeasure.StatMedian)).To(BeNumerically("<", 10*time.Millisecond))
})
It("should measure echo round-trip latency", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
experiment.Sample(func(idx int) {
data := []byte("Latency test message")
experiment.MeasureDuration("echo-rtt", func() {
_ = sendAndReceive(con, data)
})
}, gmeasure.SamplingConfig{N: 100, Duration: 30 * time.Second})
stats := experiment.GetStats("echo-rtt")
AddReportEntry("Echo Round-Trip Time", stats)
Expect(stats.DurationFor(gmeasure.StatMedian)).To(BeNumerically("<", 5*time.Millisecond))
})
})
Context("scalability", func() {
It("should measure performance with many connections", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
experiment.Sample(func(idx int) {
const numConns = 50
experiment.MeasureDuration("many-conns-50", func() {
wg := sync.WaitGroup{}
wg.Add(numConns)
for i := 0; i < numConns; i++ {
go func() {
defer wg.Done()
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
data := []byte("Load test")
_ = sendAndReceive(con, data)
}()
}
wg.Wait()
})
}, gmeasure.SamplingConfig{N: 5, Duration: 30 * time.Second})
stats := experiment.GetStats("many-conns-50")
AddReportEntry("Scalability (50 connections)", stats)
})
})
Context("message size impact", func() {
It("should measure small message performance", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
smallData := []byte("Small")
experiment.Sample(func(idx int) {
experiment.MeasureDuration("echo-small", func() {
for i := 0; i < 100; i++ {
_ = sendAndReceive(con, smallData)
}
})
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("echo-small")
AddReportEntry("Small Messages (5 bytes)", stats)
})
It("should measure large message performance", func() {
cfg := createDefaultConfig(socketPath)
var err error
srv, err = scksru.New(nil, echoHandler, cfg)
Expect(err).ToNot(HaveOccurred())
startServerInBackground(c, srv)
waitForServerAcceptingConnections(socketPath, 2*time.Second)
con := connectToServer(socketPath)
defer func() { _ = con.Close() }()
largeData := make([]byte, 8192)
for i := range largeData {
largeData[i] = byte(i % 256)
}
experiment.Sample(func(idx int) {
experiment.MeasureDuration("echo-large", func() {
for i := 0; i < 10; i++ {
_ = sendAndReceive(con, largeData)
}
})
}, gmeasure.SamplingConfig{N: 10, Duration: 30 * time.Second})
stats := experiment.GetStats("echo-large")
AddReportEntry("Large Messages (8KB)", stats)
})
})
})