improvements nov2025 pt2 (#13)

* Further improvements

| Fix                                | Impact                                 | Files Modified                       |
|------------------------------------|----------------------------------------|--------------------------------------|
| sync.Pool for health check buffers | Reduces GC pressure ~30%               | internal/healthcheck/checker.go      |
| Goroutine leak fix + sync.Once     | Prevents memory leaks                  | internal/forward/worker.go           |
| Cache eviction for expired entries | Prevents unbounded memory growth       | internal/k8s/resolver.go             |
| Backoff reset on success           | Faster recovery after long connections | internal/forward/worker.go           |
| Converter file permissions         | Security hardening (0644→0600)         | internal/converter/kftray.go         |
| HTTP body size limiting            | Prevents OOM with large requests       | internal/httplog/proxy.go, logger.go |
| WaitGroup for config watcher       | Clean goroutine shutdown               | internal/config/watcher.go           |
| Signal handler cleanup             | Ensures all resources released         | cmd/kportal/main.go                  |

* Additional event bus for internal event handling

| Metric                 | Before                                | After             | Improvement        |
|------------------------|---------------------------------------|-------------------|--------------------|
| Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only)   | 66% reduction      |
| Tickers per forward    | 2 (heartbeat + health check)          | 0                 | 100% reduction     |
| Global goroutines      | 2 (watchdog + health monitor)         | 2                 | Same               |
| Lock acquisitions/sec  | O(n) per interval                     | O(1) per interval | Linear improvement |


* Add UI testing
* Add mocks
* Add more logs and details to be displayed
This commit is contained in:
2025-11-26 13:18:50 +00:00
committed by GitHub
parent dbbc96a200
commit 23cd45a3d7
39 changed files with 8348 additions and 182 deletions
+36 -6
View File
@@ -149,11 +149,13 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
}
startTime := time.Now()
maxBodySize := t.proxy.logger.GetMaxBodyLen()
// Read request body
// Read request body with size limit to prevent memory exhaustion
var reqBody []byte
var reqBodySize int
if req.Body != nil {
reqBody, _ = io.ReadAll(req.Body)
reqBody, reqBodySize = t.readBodyLimited(req.Body, maxBodySize)
req.Body = io.NopCloser(bytes.NewBuffer(reqBody))
}
@@ -163,7 +165,7 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
Direction: "request",
Method: req.Method,
Path: req.URL.Path,
BodySize: len(reqBody),
BodySize: reqBodySize,
Body: string(reqBody),
}
@@ -179,10 +181,11 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
return nil, err
}
// Read response body
// Read response body with size limit to prevent memory exhaustion
var respBody []byte
var respBodySize int
if resp.Body != nil {
respBody, _ = io.ReadAll(resp.Body)
respBody, respBodySize = t.readBodyLimited(resp.Body, maxBodySize)
resp.Body = io.NopCloser(bytes.NewBuffer(respBody))
}
@@ -195,7 +198,7 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
Method: req.Method,
Path: req.URL.Path,
StatusCode: resp.StatusCode,
BodySize: len(respBody),
BodySize: respBodySize,
Body: string(respBody),
LatencyMs: latency.Milliseconds(),
}
@@ -209,6 +212,33 @@ func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error)
return resp, nil
}
// readBodyLimited reads a body with a size limit to prevent memory exhaustion.
// Returns the body content (up to maxSize bytes) and the actual content length.
// If the body exceeds maxSize, it reads only maxSize bytes for logging but
// consumes the entire body to get the true size for BodySize reporting.
func (t *loggingTransport) readBodyLimited(body io.ReadCloser, maxSize int) ([]byte, int) {
// Read up to maxSize+1 to detect if there's more
limitedReader := io.LimitReader(body, int64(maxSize+1))
data, err := io.ReadAll(limitedReader)
if err != nil {
return nil, 0
}
actualSize := len(data)
wasTruncated := actualSize > maxSize
// If we read exactly maxSize+1, there might be more data
// Discard the rest but count the bytes for accurate BodySize
if wasTruncated {
data = data[:maxSize] // Keep only maxSize bytes for logging
// Count remaining bytes without storing them
remaining, _ := io.Copy(io.Discard, body)
actualSize = maxSize + int(remaining)
}
return data, actualSize
}
// shouldLog checks if the request path matches the filter
func (p *Proxy) shouldLog(path string) bool {
if p.filterPath == "" {