perf(mem): replace Scheme and ExcludedReason string with uint8 type to reduce mem usage

This commit is contained in:
yusing
2025-10-15 14:35:44 +08:00
parent feafdf05f2
commit 290af4e311
6 changed files with 160 additions and 61 deletions

View File

@@ -13,6 +13,7 @@ import (
. "github.com/yusing/godoxy/internal/entrypoint"
"github.com/yusing/godoxy/internal/route"
"github.com/yusing/godoxy/internal/route/routes"
routeTypes "github.com/yusing/godoxy/internal/route/types"
"github.com/yusing/godoxy/internal/types"
"github.com/yusing/goutils/task"
)
@@ -78,7 +79,7 @@ func BenchmarkEntrypointReal(b *testing.B) {
r := &route.Route{
Alias: "test",
Scheme: "http",
Scheme: routeTypes.SchemeHTTP,
Host: host,
Port: route.Port{Proxy: portInt},
HealthCheck: &types.HealthCheckConfig{Disable: true},
@@ -119,7 +120,7 @@ func BenchmarkEntrypoint(b *testing.B) {
r := &route.Route{
Alias: "test",
Scheme: "http",
Scheme: routeTypes.SchemeHTTP,
Host: "localhost",
Port: route.Port{
Proxy: 8080,

View File

@@ -1,5 +1,7 @@
package route
import route "github.com/yusing/godoxy/internal/route/types"
var (
ImageNamePortMapTCP = map[string]int{
"mssql": 1433,
@@ -57,25 +59,25 @@ var (
}
)
func getSchemePortByImageName(imageName string) (scheme string, port int, ok bool) {
func getSchemePortByImageName(imageName string) (scheme route.Scheme, port int, ok bool) {
if port, ok := ImageNamePortMapHTTP[imageName]; ok {
return "http", port, true
return route.SchemeHTTP, port, true
}
if port, ok := ImageNamePortMapHTTPS[imageName]; ok {
return "https", port, true
return route.SchemeHTTPS, port, true
}
if port, ok := ImageNamePortMapTCP[imageName]; ok {
return "tcp", port, true
return route.SchemeTCP, port, true
}
return scheme, port, ok
}
func getSchemePortByAlias(alias string) (scheme string, port int, ok bool) {
func getSchemePortByAlias(alias string) (scheme route.Scheme, port int, ok bool) {
if port, ok := AliasPortMapHTTP[alias]; ok {
return "http", port, true
return route.SchemeHTTP, port, true
}
if port, ok := AliasPortMapHTTPS[alias]; ok {
return "https", port, true
return route.SchemeHTTPS, port, true
}
return scheme, port, ok
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/docker/docker/client"
D "github.com/yusing/godoxy/internal/docker"
"github.com/yusing/godoxy/internal/route"
T "github.com/yusing/godoxy/internal/route/types"
routeTypes "github.com/yusing/godoxy/internal/route/types"
expect "github.com/yusing/goutils/testing"
)
@@ -91,8 +91,8 @@ func TestApplyLabel(t *testing.T) {
b, ok := entries["b"]
expect.True(t, ok)
expect.Equal(t, a.Scheme, "https")
expect.Equal(t, b.Scheme, "https")
expect.Equal(t, a.Scheme, routeTypes.SchemeHTTPS)
expect.Equal(t, b.Scheme, routeTypes.SchemeHTTPS)
expect.Equal(t, a.Host, "app")
expect.Equal(t, b.Host, "app")
@@ -152,12 +152,12 @@ func TestApplyLabelWithAlias(t *testing.T) {
c, ok := entries["c"]
expect.True(t, ok)
expect.Equal(t, a.Scheme, "http")
expect.Equal(t, a.Scheme, routeTypes.SchemeHTTP)
expect.Equal(t, a.Port.Proxy, 3333)
expect.Equal(t, a.NoTLSVerify, true)
expect.Equal(t, b.Scheme, "http")
expect.Equal(t, b.Scheme, routeTypes.SchemeHTTP)
expect.Equal(t, b.Port.Proxy, 1234)
expect.Equal(t, c.Scheme, "https")
expect.Equal(t, c.Scheme, routeTypes.SchemeHTTPS)
}
func TestApplyLabelWithRef(t *testing.T) {
@@ -180,11 +180,11 @@ func TestApplyLabelWithRef(t *testing.T) {
c, ok := entries["c"]
expect.True(t, ok)
expect.Equal(t, a.Scheme, "http")
expect.Equal(t, a.Scheme, routeTypes.SchemeHTTP)
expect.Equal(t, a.Host, "localhost")
expect.Equal(t, a.Port.Proxy, 4444)
expect.Equal(t, b.Port.Proxy, 9999)
expect.Equal(t, c.Scheme, "https")
expect.Equal(t, c.Scheme, routeTypes.SchemeHTTPS)
expect.Equal(t, c.Port.Proxy, 1111)
}
@@ -229,12 +229,12 @@ func TestDynamicAliases(t *testing.T) {
r, ok := entries["app1"]
expect.True(t, ok)
expect.Equal(t, r.Scheme, "http")
expect.Equal(t, r.Scheme, routeTypes.SchemeHTTP)
expect.Equal(t, r.Port.Proxy, 1234)
r, ok = entries["app1_backend"]
expect.True(t, ok)
expect.Equal(t, r.Scheme, "http")
expect.Equal(t, r.Scheme, routeTypes.SchemeHTTP)
expect.Equal(t, r.Port.Proxy, 5678)
}
@@ -327,7 +327,7 @@ func TestStreamDefaultValues(t *testing.T) {
r, ok := makeRoutes(cont)["a"]
expect.True(t, ok)
expect.NoError(t, r.Validate())
expect.Equal(t, r.Scheme, T.Scheme("udp"))
expect.Equal(t, r.Scheme, routeTypes.SchemeUDP)
expect.Equal(t, r.TargetURL().Hostname(), privIP)
expect.Equal(t, r.Port.Listening, 0)
expect.Equal(t, r.Port.Proxy, int(privPort))
@@ -337,7 +337,7 @@ func TestStreamDefaultValues(t *testing.T) {
r, ok := makeRoutes(cont, testIP)["a"]
expect.True(t, ok)
expect.NoError(t, r.Validate())
expect.Equal(t, r.Scheme, T.Scheme("udp"))
expect.Equal(t, r.Scheme, routeTypes.SchemeUDP)
expect.Equal(t, r.TargetURL().Hostname(), testIP)
expect.Equal(t, r.Port.Listening, 0)
expect.Equal(t, r.Port.Proxy, int(pubPort))

View File

@@ -8,6 +8,7 @@ import (
"os"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"time"
@@ -40,7 +41,7 @@ type (
_ utils.NoCopy
Alias string `json:"alias"`
Scheme route.Scheme `json:"scheme,omitempty"`
Scheme route.Scheme `json:"scheme,omitempty" swaggertype:"string"`
Host string `json:"host,omitempty"`
Port route.Port `json:"port"`
Root string `json:"root,omitempty"`
@@ -71,8 +72,8 @@ type (
LisURL *nettypes.URL `json:"lurl,omitempty" swaggertype:"string" extensions:"x-nullable"`
ProxyURL *nettypes.URL `json:"purl,omitempty" swaggertype:"string"`
Excluded bool `json:"excluded,omitempty" extensions:"x-nullable"`
ExcludedReason string `json:"excluded_reason,omitempty" extensions:"x-nullable"`
Excluded bool `json:"excluded,omitempty" extensions:"x-nullable"`
ExcludedReason ExcludedReason `json:"excluded_reason,omitempty" extensions:"x-nullable"`
HealthMon types.HealthMonitor `json:"health,omitempty" swaggerignore:"true"`
// for swagger
@@ -272,7 +273,7 @@ func (r *Route) Validate() gperr.Error {
r.impl = impl
r.Excluded = r.ShouldExclude()
if r.Excluded {
r.ExcludedReason = r.GetExcludedReason()
r.ExcludedReason = r.findExcludedReason()
}
return nil
}
@@ -518,31 +519,73 @@ func (r *Route) ShouldExclude() bool {
return false
}
func (r *Route) GetExcludedReason() string {
if r.lastError != nil {
return string(gperr.Plain(r.lastError))
type ExcludedReason uint8
const (
ExcludedReasonNone ExcludedReason = iota
ExcludedReasonError
ExcludedReasonManual
ExcludedReasonNoPortContainer
ExcludedReasonNoPortSpecified
ExcludedReasonBlacklisted
ExcludedReasonBuildx
ExcludedReasonOld
)
func (re ExcludedReason) String() string {
switch re {
case ExcludedReasonNone:
return ""
case ExcludedReasonError:
return "Error"
case ExcludedReasonManual:
return "Manual exclusion"
case ExcludedReasonNoPortContainer:
return "No port exposed in container"
case ExcludedReasonNoPortSpecified:
return "No port specified"
case ExcludedReasonBlacklisted:
return "Blacklisted (backend service or database)"
case ExcludedReasonBuildx:
return "Buildx"
case ExcludedReasonOld:
return "Container renaming intermediate state"
default:
return "Unknown"
}
if r.ExcludedReason != "" {
}
func (re ExcludedReason) MarshalJSON() ([]byte, error) {
return strconv.AppendQuote(nil, re.String()), nil
}
// no need to unmarshal json because we don't store this
func (r *Route) findExcludedReason() ExcludedReason {
if r.lastError != nil {
return ExcludedReasonError
}
if r.ExcludedReason != ExcludedReasonNone {
return r.ExcludedReason
}
if r.Container != nil {
switch {
case r.Container.IsExcluded:
return "Manual exclusion"
return ExcludedReasonManual
case r.IsZeroPort() && !r.UseIdleWatcher():
return "No port exposed in container"
return ExcludedReasonNoPortContainer
case !r.Container.IsExplicit && docker.IsBlacklisted(r.Container):
return "Blacklisted (backend service or database)"
return ExcludedReasonBlacklisted
case strings.HasPrefix(r.Container.ContainerName, "buildx_"):
return "Buildx"
return ExcludedReasonBuildx
}
} else if r.IsZeroPort() && r.Scheme != route.SchemeFileServer {
return "No port specified"
return ExcludedReasonNoPortSpecified
}
if strings.HasSuffix(r.Alias, "-old") {
return "Container renaming intermediate state"
return ExcludedReasonOld
}
return ""
return ExcludedReasonNone
}
func (r *Route) UseLoadBalance() bool {
@@ -594,8 +637,8 @@ func (r *Route) Finalize() {
if isDocker {
scheme, port, ok := getSchemePortByImageName(cont.Image.Name)
if ok {
if r.Scheme == "" {
r.Scheme = route.Scheme(scheme)
if r.Scheme == route.SchemeNone {
r.Scheme = scheme
}
if pp == 0 {
pp = port
@@ -604,8 +647,8 @@ func (r *Route) Finalize() {
}
if scheme, port, ok := getSchemePortByAlias(r.Alias); ok {
if r.Scheme == "" {
r.Scheme = route.Scheme(scheme)
if r.Scheme == route.SchemeNone {
r.Scheme = scheme
}
if pp == 0 {
pp = port
@@ -620,7 +663,7 @@ func (r *Route) Finalize() {
} else {
pp = preferredPort(cont.PrivatePortMapping)
}
case r.Scheme == "https":
case r.Scheme == route.SchemeHTTPS:
pp = 443
default:
pp = 80
@@ -628,10 +671,10 @@ func (r *Route) Finalize() {
}
if isDocker {
if r.Scheme == "" {
if r.Scheme == route.SchemeNone {
for _, p := range cont.PublicPortMapping {
if int(p.PrivatePort) == pp && p.Type == "udp" {
r.Scheme = "udp"
r.Scheme = route.SchemeUDP
break
}
}
@@ -649,14 +692,14 @@ func (r *Route) Finalize() {
}
}
if r.Scheme == "" {
if r.Scheme == route.SchemeNone {
switch {
case lp != 0:
r.Scheme = "tcp"
r.Scheme = route.SchemeTCP
case pp%1000 == 443:
r.Scheme = "https"
r.Scheme = route.SchemeHTTPS
default: // assume its http
r.Scheme = "http"
r.Scheme = route.SchemeHTTP
}
}

View File

@@ -113,7 +113,7 @@ func TestRouteValidate(t *testing.T) {
t.Run("InvalidScheme", func(t *testing.T) {
r := &Route{
Alias: "test",
Scheme: "invalid",
Scheme: 123,
Host: "example.com",
Port: route.Port{Proxy: 80},
}

View File

@@ -1,29 +1,82 @@
package route
import (
"strconv"
"github.com/bytedance/sonic"
gperr "github.com/yusing/goutils/errs"
)
type Scheme string
type Scheme uint8
var ErrInvalidScheme = gperr.New("invalid scheme")
const (
SchemeHTTP Scheme = "http"
SchemeHTTPS Scheme = "https"
SchemeTCP Scheme = "tcp"
SchemeUDP Scheme = "udp"
SchemeFileServer Scheme = "fileserver"
SchemeHTTP Scheme = 1 << iota
SchemeHTTPS
SchemeTCP
SchemeUDP
SchemeFileServer
SchemeNone Scheme = 0
schemeReverseProxy = SchemeHTTP | SchemeHTTPS
schemeStream = SchemeTCP | SchemeUDP
schemeStrHTTP = "http"
schemeStrHTTPS = "https"
schemeStrTCP = "tcp"
schemeStrUDP = "udp"
schemeStrFileServer = "fileserver"
schemeStrUnknown = "unknown"
)
func (s Scheme) Validate() gperr.Error {
func (s Scheme) String() string {
switch s {
case SchemeHTTP, SchemeHTTPS,
SchemeTCP, SchemeUDP, SchemeFileServer:
return nil
case SchemeHTTP:
return schemeStrHTTP
case SchemeHTTPS:
return schemeStrHTTPS
case SchemeTCP:
return schemeStrTCP
case SchemeUDP:
return schemeStrUDP
case SchemeFileServer:
return schemeStrFileServer
default:
return schemeStrUnknown
}
return ErrInvalidScheme.Subject(string(s))
}
func (s Scheme) IsReverseProxy() bool { return s == SchemeHTTP || s == SchemeHTTPS }
func (s Scheme) IsStream() bool { return s == SchemeTCP || s == SchemeUDP }
func (s Scheme) MarshalJSON() ([]byte, error) {
return strconv.AppendQuote(nil, s.String()), nil
}
func (s *Scheme) UnmarshalJSON(data []byte) error {
var v string
if err := sonic.Unmarshal(data, &v); err != nil {
return err
}
return s.Parse(v)
}
// Parse implements strutils.Parser
func (s *Scheme) Parse(v string) error {
switch v {
case schemeStrHTTP:
*s = SchemeHTTP
case schemeStrHTTPS:
*s = SchemeHTTPS
case schemeStrTCP:
*s = SchemeTCP
case schemeStrUDP:
*s = SchemeUDP
case schemeStrFileServer:
*s = SchemeFileServer
default:
return ErrInvalidScheme.Subject(v)
}
return nil
}
func (s Scheme) IsReverseProxy() bool { return s&schemeReverseProxy != 0 }
func (s Scheme) IsStream() bool { return s&schemeStream != 0 }