Skip to content

Commit 465b40e

Browse files
committed
Add memory usage analysis workflow and tests for RTI Connector
1 parent 637330b commit 465b40e

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

.github/workflows/build.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,30 @@ jobs:
188188
path: |
189189
coverage.txt
190190
coverage_report.txt
191+
192+
memory-usage:
193+
runs-on: ubuntu-latest
194+
steps:
195+
- uses: actions/checkout@v4
196+
197+
- name: Setup Go
198+
uses: actions/setup-go@v4
199+
with:
200+
go-version: '1.21'
201+
202+
- name: Install system dependencies
203+
run: |
204+
sudo apt-get update
205+
sudo apt-get install -y build-essential curl unzip bc
206+
207+
- name: Download RTI Connector libraries
208+
run: |
209+
go run ./cmd/download-libs -force
210+
211+
- name: Run memory usage analysis
212+
run: |
213+
echo "=== Memory Usage Analysis ==="
214+
echo "Measuring RTI Connector memory consumption..."
215+
216+
# Run single iteration to measure baseline memory usage
217+
MEMTEST_ITERATIONS=1 go test -v -run=TestMemoryUsage

memory_usage_test.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package rti
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"runtime"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
"time"
12+
)
13+
14+
// getProcessMemory returns RSS (Resident Set Size) in bytes
15+
func getProcessMemory() (int64, error) {
16+
pid := os.Getpid()
17+
cmd := exec.Command("ps", "-o", "rss=", "-p", strconv.Itoa(pid))
18+
output, err := cmd.Output()
19+
if err != nil {
20+
return 0, err
21+
}
22+
23+
rssKB, err := strconv.ParseInt(strings.TrimSpace(string(output)), 10, 64)
24+
if err != nil {
25+
return 0, err
26+
}
27+
28+
return rssKB * 1024, nil // Convert KB to bytes
29+
}
30+
31+
// TestMemoryUsage tests RTI Connector with comprehensive memory profiling
32+
func TestMemoryUsage(t *testing.T) {
33+
iterations := 1
34+
if iterStr := os.Getenv("MEMTEST_ITERATIONS"); iterStr != "" {
35+
if parsed, err := strconv.Atoi(iterStr); err == nil && parsed > 0 {
36+
iterations = parsed
37+
}
38+
}
39+
40+
// Get initial system memory
41+
initialRSS, err := getProcessMemory()
42+
if err != nil {
43+
t.Logf("Warning: Could not get initial RSS: %v", err)
44+
initialRSS = 0
45+
}
46+
47+
var m1, m2 runtime.MemStats
48+
runtime.GC()
49+
runtime.ReadMemStats(&m1)
50+
51+
t.Logf("Starting memory measurement:")
52+
t.Logf(" Initial RSS: %d bytes (%.2f MB)", initialRSS, float64(initialRSS)/1024/1024)
53+
t.Logf(" Initial Go heap: %d bytes (%.2f MB)", m1.HeapAlloc, float64(m1.HeapAlloc)/1024/1024)
54+
55+
for i := 0; i < iterations; i++ {
56+
runGoGetExample(t, i)
57+
58+
// Sample memory after each iteration
59+
if iterations > 1 && (i+1)%max(1, iterations/5) == 0 {
60+
currentRSS, _ := getProcessMemory()
61+
var currentMem runtime.MemStats
62+
runtime.ReadMemStats(&currentMem)
63+
t.Logf(" After iteration %d: RSS=%d bytes, Go heap=%d bytes",
64+
i+1, currentRSS, currentMem.HeapAlloc)
65+
}
66+
}
67+
68+
runtime.GC()
69+
time.Sleep(100 * time.Millisecond) // Give GC time to complete
70+
runtime.ReadMemStats(&m2)
71+
72+
// Get final system memory
73+
finalRSS, err := getProcessMemory()
74+
if err != nil {
75+
t.Logf("Warning: Could not get final RSS: %v", err)
76+
finalRSS = initialRSS
77+
}
78+
79+
allocDelta := m2.TotalAlloc - m1.TotalAlloc
80+
heapDelta := int64(m2.HeapAlloc) - int64(m1.HeapAlloc)
81+
rssDelta := finalRSS - initialRSS
82+
83+
t.Logf("\n=== Memory Analysis Results ===")
84+
t.Logf("Iterations: %d", iterations)
85+
t.Logf("\nGo Heap Memory:")
86+
t.Logf(" Total allocations: %d bytes (%.2f KB)", allocDelta, float64(allocDelta)/1024)
87+
t.Logf(" Heap delta: %d bytes (%.2f KB)", heapDelta, float64(heapDelta)/1024)
88+
t.Logf(" Final heap size: %d bytes (%.2f MB)", m2.HeapAlloc, float64(m2.HeapAlloc)/1024/1024)
89+
t.Logf(" GC runs: %d", m2.NumGC-m1.NumGC)
90+
t.Logf("\nSystem Memory (RSS - includes C libraries):")
91+
t.Logf(" Initial RSS: %d bytes (%.2f MB)", initialRSS, float64(initialRSS)/1024/1024)
92+
t.Logf(" Final RSS: %d bytes (%.2f MB)", finalRSS, float64(finalRSS)/1024/1024)
93+
t.Logf(" RSS delta: %d bytes (%.2f MB)", rssDelta, float64(rssDelta)/1024/1024)
94+
t.Logf("\nMemory per Operation:")
95+
if iterations > 0 {
96+
t.Logf(" Go heap per op: %.2f bytes", float64(allocDelta)/float64(iterations))
97+
t.Logf(" RSS per op: %.2f bytes", float64(rssDelta)/float64(iterations))
98+
}
99+
}
100+
101+
func max(a, b int) int {
102+
if a > b {
103+
return a
104+
}
105+
return b
106+
}
107+
108+
func runGoGetExample(t *testing.T, iteration int) {
109+
// Same XML config as the original example
110+
xmlConfig := `str://"<dds>
111+
<qos_library name="QosLibrary">
112+
<qos_profile name="DefaultProfile" base_name="BuiltinQosLibExp::Generic.StrictReliable" is_default_qos="true"/>
113+
</qos_library>
114+
115+
<types>
116+
<struct name="TestType">
117+
<member name="message" type="string"/>
118+
<member name="count" type="long"/>
119+
</struct>
120+
</types>
121+
122+
<domain_library name="MyDomainLibrary">
123+
<domain name="MyDomain" domain_id="0">
124+
<register_type name="TestType" type_ref="TestType"/>
125+
<topic name="TestTopic" register_type_ref="TestType"/>
126+
</domain>
127+
</domain_library>
128+
129+
<domain_participant_library name="MyParticipantLibrary">
130+
<domain_participant name="Zero" domain_ref="MyDomainLibrary::MyDomain">
131+
<publisher name="MyPublisher">
132+
<data_writer name="MyWriter" topic_ref="TestTopic"/>
133+
</publisher>
134+
</domain_participant>
135+
</domain_participant_library>
136+
</dds>"`
137+
138+
// Create connector from XML string
139+
connector, err := NewConnector("MyParticipantLibrary::Zero", xmlConfig)
140+
if err != nil {
141+
t.Fatalf("Iteration %d: Failed to create connector: %v", iteration, err)
142+
}
143+
defer connector.Delete()
144+
145+
// Get output (writer) and publish a simple message
146+
output, err := connector.GetOutput("MyPublisher::MyWriter")
147+
if err != nil {
148+
t.Fatalf("Iteration %d: Failed to get output: %v", iteration, err)
149+
}
150+
151+
// Publish test message
152+
output.Instance.SetString("message", fmt.Sprintf("Hello from iteration %d!", iteration))
153+
output.Instance.SetInt("count", iteration)
154+
err = output.Write()
155+
if err != nil {
156+
t.Fatalf("Iteration %d: Failed to write: %v", iteration, err)
157+
}
158+
159+
if iteration%10 == 0 {
160+
t.Logf("Completed iteration %d", iteration)
161+
}
162+
}
163+
164+
// BenchmarkConnectorMemory provides benchmark-based memory analysis
165+
func BenchmarkConnectorMemory(b *testing.B) {
166+
xmlConfig := `str://"<dds>
167+
<qos_library name="QosLibrary">
168+
<qos_profile name="DefaultProfile" base_name="BuiltinQosLibExp::Generic.StrictReliable" is_default_qos="true"/>
169+
</qos_library>
170+
171+
<types>
172+
<struct name="TestType">
173+
<member name="message" type="string"/>
174+
<member name="count" type="long"/>
175+
</struct>
176+
</types>
177+
178+
<domain_library name="MyDomainLibrary">
179+
<domain name="MyDomain" domain_id="0">
180+
<register_type name="TestType" type_ref="TestType"/>
181+
<topic name="TestTopic" register_type_ref="TestType"/>
182+
</domain>
183+
</domain_library>
184+
185+
<domain_participant_library name="MyParticipantLibrary">
186+
<domain_participant name="Zero" domain_ref="MyDomainLibrary::MyDomain">
187+
<publisher name="MyPublisher">
188+
<data_writer name="MyWriter" topic_ref="TestTopic"/>
189+
</publisher>
190+
</domain_participant>
191+
</domain_participant_library>
192+
</dds>"`
193+
194+
b.ResetTimer()
195+
for i := 0; i < b.N; i++ {
196+
connector, err := NewConnector("MyParticipantLibrary::Zero", xmlConfig)
197+
if err != nil {
198+
b.Fatalf("Failed to create connector: %v", err)
199+
}
200+
201+
output, err := connector.GetOutput("MyPublisher::MyWriter")
202+
if err != nil {
203+
connector.Delete()
204+
b.Fatalf("Failed to get output: %v", err)
205+
}
206+
207+
output.Instance.SetString("message", "Benchmark message")
208+
output.Instance.SetInt("count", i)
209+
output.Write()
210+
211+
connector.Delete()
212+
}
213+
}

0 commit comments

Comments
 (0)