Optimize Policy RouteID (#5359)

This commit is contained in:
Joe Kralicky 2024-11-06 12:31:52 -05:00 committed by GitHub
parent 82fb9cf29d
commit ebd9eea30e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 540 additions and 122 deletions

View file

@ -77,7 +77,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
], ],
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "route-5d678ee30d16332b", "cluster": "route-b8e37dd1f9d65ddd",
"hashPolicy": [ "hashPolicy": [
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true }, { "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
{ "connectionProperties": { "sourceIp": true }, "terminal": true } { "connectionProperties": { "sourceIp": true }, "terminal": true }
@ -94,7 +94,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6730505273956774699" "route_id": "13322630463485271517"
} }
} }
} }
@ -130,7 +130,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
], ],
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "route-5d678ee30d16332b", "cluster": "route-b8e37dd1f9d65ddd",
"hashPolicy": [ "hashPolicy": [
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true }, { "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
{ "connectionProperties": { "sourceIp": true }, "terminal": true } { "connectionProperties": { "sourceIp": true }, "terminal": true }
@ -147,7 +147,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6730505273956774699" "route_id": "13322630463485271517"
} }
} }
} }

View file

@ -303,79 +303,102 @@ func Test_buildPolicyRoutes(t *testing.T) {
oneMinute := time.Minute oneMinute := time.Minute
ten := time.Second * 10 ten := time.Second * 10
// note: within each policy below, fields that do not affect the route ID
// are grouped separately, after the fields that do affect the route ID.
policies := []config.Policy{
0: { // skipped by host filter
From: "https://ignore.example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
PassIdentityHeaders: ptr(true),
},
1: {
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
PassIdentityHeaders: ptr(true),
},
2: {
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
3: {
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Prefix: "/some/prefix/",
SetRequestHeaders: map[string]string{"HEADER-KEY": "HEADER-VALUE"},
UpstreamTimeout: &oneMinute,
PassIdentityHeaders: ptr(true),
},
4: {
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Regex: `^/[a]+$`,
PassIdentityHeaders: ptr(true),
},
5: { // same route ID as 3
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Prefix: "/some/prefix/",
RemoveRequestHeaders: []string{"HEADER-KEY"},
UpstreamTimeout: &oneMinute,
PassIdentityHeaders: ptr(true),
},
6: { // same route ID as 2
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowSPDY: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
7: { // same route ID as 2
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowSPDY: true,
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
8: {
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/websocket-timeout",
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
UpstreamTimeout: &ten,
},
}
routeIDs := []string{
1: "772697672458217856",
2: "6032229746964560472",
3: "13317665674438641304",
4: "9768293332770157550",
5: "13317665674438641304", // same as 3
6: "6032229746964560472", // same as 2
7: "6032229746964560472", // same as 2
8: "1591581179179639728",
}
b := &Builder{filemgr: filemgr.NewManager(), reproxy: reproxy.New()} b := &Builder{filemgr: filemgr.NewManager(), reproxy: reproxy.New()}
routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{ routes, err := b.buildRoutesForPoliciesWithHost(&config.Config{Options: &config.Options{
CookieName: "pomerium", CookieName: "pomerium",
DefaultUpstreamTimeout: time.Second * 3, DefaultUpstreamTimeout: time.Second * 3,
SharedKey: cryptutil.NewBase64Key(), SharedKey: cryptutil.NewBase64Key(),
Policies: []config.Policy{ Policies: policies,
{
From: "https://ignore.example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Prefix: "/some/prefix/",
SetRequestHeaders: map[string]string{"HEADER-KEY": "HEADER-VALUE"},
UpstreamTimeout: &oneMinute,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Regex: `^/[a]+$`,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Prefix: "/some/prefix/",
RemoveRequestHeaders: []string{"HEADER-KEY"},
UpstreamTimeout: &oneMinute,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowSPDY: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/some/path",
AllowSPDY: true,
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
},
{
From: "https://example.com",
To: mustParseWeightedURLs(t, "https://to.example.com"),
Path: "/websocket-timeout",
AllowWebsockets: true,
PreserveHostHeader: true,
PassIdentityHeaders: ptr(true),
UpstreamTimeout: &ten,
},
},
}}, "example.com") }}, "example.com")
require.NoError(t, err) require.NoError(t, err)
@ -445,7 +468,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "16913502743845432363" "route_id": "`+routeIDs[1]+`"
} }
} }
} }
@ -516,7 +539,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "911713133804109577" "route_id": "`+routeIDs[2]+`"
} }
} }
} }
@ -586,7 +609,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6407864870815560799" "route_id": "`+routeIDs[3]+`"
} }
} }
} }
@ -658,7 +681,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "1103677309004574500" "route_id": "`+routeIDs[4]+`"
} }
} }
} }
@ -729,7 +752,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6407864870815560799" "route_id": "`+routeIDs[5]+`"
} }
} }
} }
@ -799,7 +822,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "911713133804109577" "route_id": "`+routeIDs[6]+`"
} }
} }
} }
@ -870,7 +893,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "911713133804109577" "route_id": "`+routeIDs[7]+`"
} }
} }
} }
@ -941,7 +964,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "17831746838845374842" "route_id": "`+routeIDs[8]+`"
} }
} }
} }
@ -1124,7 +1147,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "10474912405080199536" "route_id": "11959552038839924732"
} }
} }
} }
@ -1196,7 +1219,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "15730681265277585877" "route_id": "9444248534316924938"
} }
} }
} }
@ -1294,7 +1317,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "16598125949405432745" "route_id": "5652544858774142715"
} }
} }
} }
@ -1366,14 +1389,14 @@ func Test_buildPolicyRoutes(t *testing.T) {
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": { "header": {
"key": "x-pomerium-reproxy-policy", "key": "x-pomerium-reproxy-policy",
"value": "2222095689633600553" "value": "5799631121007486501"
} }
}, },
{ {
"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD", "appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
"header": { "header": {
"key": "x-pomerium-reproxy-policy-hmac", "key": "x-pomerium-reproxy-policy-hmac",
"value": "/cH0S/ODZYaW4CALohG926c+TH22+/bD79Kb82k8/Eg=" "value": "v4w8DAUFdw2qw7RJLUZYBHWndqBOdz5Me6A+1vbDQPY="
} }
} }
], ],
@ -1405,7 +1428,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2222095689633600553" "route_id": "5799631121007486501"
} }
} }
} }
@ -1535,7 +1558,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }
@ -1606,7 +1629,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }
@ -1682,7 +1705,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }
@ -1753,7 +1776,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }
@ -1824,7 +1847,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }
@ -1900,7 +1923,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "13828028232508831592" "route_id": "1410576726089372267"
} }
} }
} }

View file

@ -654,30 +654,63 @@ func (p *Policy) Checksum() uint64 {
return hashutil.MustHash(p) return hashutil.MustHash(p)
} }
// RouteID returns a unique identifier for a route // RouteID returns a unique identifier for a route.
//
// The following fields are used to compute the ID:
// - from
// - prefix
// - path
// - regex
// - to/redirect/response (whichever is set)
func (p *Policy) RouteID() (uint64, error) { func (p *Policy) RouteID() (uint64, error) {
id := routeID{ // this function is in the hot path, try not to allocate too much memory here
From: p.From, hash := hashutil.NewDigest()
Prefix: p.Prefix, hash.WriteStringWithLen(p.From)
Path: p.Path, hash.WriteStringWithLen(p.Prefix)
Regex: p.Regex, hash.WriteStringWithLen(p.Path)
} hash.WriteStringWithLen(p.Regex)
switch {
if len(p.To) > 0 { case len(p.To) > 0:
dst, _, err := p.To.Flatten() _, _ = hash.Write([]byte{1}) // case 1
if err != nil { hash.WriteInt32(int32(len(p.To)))
return 0, err for _, to := range p.To {
hash.WriteStringWithLen(to.URL.Scheme)
hash.WriteStringWithLen(to.URL.Opaque)
if to.URL.User == nil {
_, _ = hash.Write([]byte{0})
} else {
_, _ = hash.Write([]byte{1})
hash.WriteStringWithLen(to.URL.User.Username())
p, _ := to.URL.User.Password()
hash.WriteStringWithLen(p)
}
hash.WriteStringWithLen(to.URL.Host)
hash.WriteStringWithLen(to.URL.Path)
hash.WriteStringWithLen(to.URL.RawPath)
hash.WriteBool(to.URL.OmitHost)
hash.WriteBool(to.URL.ForceQuery)
hash.WriteStringWithLen(to.URL.Fragment)
hash.WriteStringWithLen(to.URL.RawFragment)
hash.WriteUint32(to.LbWeight)
} }
id.To = dst case p.Redirect != nil:
} else if p.Redirect != nil { _, _ = hash.Write([]byte{2}) // case 2
id.Redirect = p.Redirect hash.WriteBoolPtr(p.Redirect.HTTPSRedirect)
} else if p.Response != nil { hash.WriteStringPtrWithLen(p.Redirect.SchemeRedirect)
id.Response = p.Response hash.WriteStringPtrWithLen(p.Redirect.HostRedirect)
} else { hash.WriteUint32Ptr(p.Redirect.PortRedirect)
hash.WriteStringPtrWithLen(p.Redirect.PathRedirect)
hash.WriteStringPtrWithLen(p.Redirect.PrefixRewrite)
hash.WriteInt32Ptr(p.Redirect.ResponseCode)
hash.WriteBoolPtr(p.Redirect.StripQuery)
case p.Response != nil:
_, _ = hash.Write([]byte{3}) // case 3
hash.WriteInt32(int32(p.Response.Status))
hash.WriteStringWithLen(p.Response.Body)
default:
return 0, errEitherToOrRedirectOrResponseRequired return 0, errEitherToOrRedirectOrResponseRequired
} }
return hash.Sum64(), nil
return hashutil.Hash(id)
} }
func (p *Policy) MustRouteID() uint64 { func (p *Policy) MustRouteID() uint64 {
@ -811,16 +844,6 @@ func (p *Policy) GetPassIdentityHeaders(options *Options) bool {
return false return false
} }
type routeID struct {
From string
To []string
Prefix string
Path string
Regex string
Redirect *PolicyRedirect
Response *DirectResponse
}
/* /*
SortPolicies sorts policies to match the following SQL order: SortPolicies sorts policies to match the following SQL order:

View file

@ -3,7 +3,9 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
mathrand "math/rand/v2"
"net/url" "net/url"
"strings"
"testing" "testing"
envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" envoy_config_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
@ -13,6 +15,7 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
) )
func Test_PolicyValidate(t *testing.T) { func Test_PolicyValidate(t *testing.T) {
@ -421,3 +424,192 @@ func mustParseWeightedURLs(t testing.TB, urls ...string) []WeightedURL {
require.NoError(t, err) require.NoError(t, err)
return wu return wu
} }
func TestRouteID(t *testing.T) {
randomString := func() string {
return strings.TrimSuffix(cryptutil.NewRandomStringN(mathrand.IntN(31)+1), "=")
}
randomBool := func() bool {
return mathrand.N(2) == 0
}
randomURL := func() *url.URL {
u, err := url.Parse(fmt.Sprintf("https://%s.example.com/%s?foo=%s#%s",
randomString(), randomString(), randomString(), randomString()))
require.NoError(t, err)
return u
}
baseFieldMutators := []func(p *Policy){
func(p *Policy) { p.From = randomString() },
func(p *Policy) { p.Prefix = randomString() },
func(p *Policy) { p.Path = randomString() },
func(p *Policy) { p.Regex = randomString() },
}
toMutators := func(p *Policy) {
p.To = make(WeightedURLs, mathrand.N(9)+1)
for i := 0; i < len(p.To); i++ {
p.To[i] = WeightedURL{URL: *randomURL(), LbWeight: mathrand.Uint32()}
}
}
redirectMutators := []func(p *PolicyRedirect){
func(p *PolicyRedirect) { p.HTTPSRedirect = randomPtr(10, randomBool()) },
func(p *PolicyRedirect) { p.SchemeRedirect = randomPtr(10, randomString()) },
func(p *PolicyRedirect) { p.HostRedirect = randomPtr(10, randomString()) },
func(p *PolicyRedirect) { p.PortRedirect = randomPtr(10, mathrand.Uint32()) },
func(p *PolicyRedirect) { p.PathRedirect = randomPtr(10, randomString()) },
func(p *PolicyRedirect) { p.PrefixRewrite = randomPtr(10, randomString()) },
func(p *PolicyRedirect) { p.ResponseCode = randomPtr(10, mathrand.Int32()) },
func(p *PolicyRedirect) { p.StripQuery = randomPtr(10, randomBool()) },
}
responseMutators := []func(p *DirectResponse){
func(p *DirectResponse) { p.Status = mathrand.Int() },
func(p *DirectResponse) { p.Body = randomString() },
}
t.Run("random policies", func(t *testing.T) {
hashes := make(map[uint64]struct{}, 10000)
for i := 0; i < 10000; i++ {
p := Policy{}
for _, m := range baseFieldMutators {
m(&p)
}
switch mathrand.IntN(3) {
case 0:
toMutators(&p)
case 1:
p.Redirect = &PolicyRedirect{}
for _, m := range redirectMutators {
m(p.Redirect)
}
case 2:
p.Response = &DirectResponse{}
for _, m := range responseMutators {
m(p.Response)
}
}
routeID, err := p.RouteID()
require.NoError(t, err)
hashes[routeID] = struct{}{} // odds of a collision should be pretty low here
// check that computing the route id again results in the same value
routeID2, err := p.RouteID()
require.NoError(t, err)
assert.Equal(t, routeID, routeID2)
}
assert.Len(t, hashes, 10000)
})
t.Run("incremental policy", func(t *testing.T) {
hashes := make(map[uint64]Policy, 5000)
p := Policy{}
checkAdd := func(p *Policy) {
routeID, err := p.RouteID()
require.NoError(t, err)
if existing, ok := hashes[routeID]; ok {
require.Equal(t, existing, *p)
} else {
hashes[routeID] = *p
}
// check that computing the route id again results in the same value
routeID2, err := p.RouteID()
require.NoError(t, err)
assert.Equal(t, routeID, routeID2)
}
// to
toMutators(&p)
checkAdd(&p)
// set base fields
for _, m := range baseFieldMutators {
m(&p)
checkAdd(&p)
}
// redirect
p.To = nil
p.Redirect = &PolicyRedirect{}
for range 1000 {
for _, m := range redirectMutators {
m(p.Redirect)
checkAdd(&p)
}
}
// update base fields
for _, m := range baseFieldMutators {
m(&p)
checkAdd(&p)
}
// direct response
p.Redirect = nil
p.Response = &DirectResponse{}
for range 1000 {
for _, m := range responseMutators {
m(p.Response)
checkAdd(&p)
}
}
// update base fields
for _, m := range baseFieldMutators {
m(&p)
checkAdd(&p)
}
// sanity check
assert.Greater(t, len(hashes), 2000)
})
t.Run("field separation", func(t *testing.T) {
cases := []struct {
a, b *Policy
}{
{
&Policy{From: "foo", Prefix: "bar"},
&Policy{From: "f", Prefix: "oobar"},
},
{
&Policy{From: "foo", Prefix: "bar"},
&Policy{From: "foobar", Prefix: ""},
},
{
&Policy{From: "foobar", Prefix: ""},
&Policy{From: "", Prefix: "foobar"},
},
{
&Policy{From: "foo", Prefix: "", Path: "bar"},
&Policy{From: "foo", Prefix: "bar", Path: ""},
},
{
&Policy{From: "", Prefix: "foo", Path: "bar"},
&Policy{From: "foo", Prefix: "bar", Path: ""},
},
{
&Policy{From: "", Prefix: "foo", Path: "bar"},
&Policy{From: "foo", Prefix: "", Path: "bar"},
},
}
for _, c := range cases {
c.a.To = mustParseWeightedURLs(t, "https://foo")
c.b.To = mustParseWeightedURLs(t, "https://foo")
}
for _, c := range cases {
a, err := c.a.RouteID()
require.NoError(t, err)
b, err := c.b.RouteID()
require.NoError(t, err)
assert.NotEqual(t, a, b)
}
})
}
func randomPtr[T any](nilChance int, t T) *T {
if mathrand.N(nilChance) == 0 {
return nil
}
return &t
}

View file

@ -6,6 +6,7 @@ import (
"io" "io"
"math/rand/v2" "math/rand/v2"
"net/http" "net/http"
"runtime"
"testing" "testing"
"github.com/pomerium/pomerium/internal/testenv" "github.com/pomerium/pomerium/internal/testenv"
@ -26,6 +27,7 @@ func init() {
} }
func TestRequestLatency(t *testing.T) { func TestRequestLatency(t *testing.T) {
runtime.MemProfileRate = 0
env := testenv.New(t, testenv.Silent()) env := testenv.New(t, testenv.Silent())
users := []*scenarios.User{} users := []*scenarios.User{}
for i := range numRoutes { for i := range numRoutes {
@ -51,6 +53,7 @@ func TestRequestLatency(t *testing.T) {
env.Start() env.Start()
snippets.WaitStartupComplete(env) snippets.WaitStartupComplete(env)
runtime.MemProfileRate = 512 * 1024
out := testing.Benchmark(func(b *testing.B) { out := testing.Benchmark(func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View file

@ -1,9 +1,13 @@
// Package hashutil provides NON-CRYPTOGRAPHIC utility functions for hashing. // Package hashutil provides NON-CRYPTOGRAPHIC utility functions for hashing.
// //
// http://cyan4973.github.io/xxHash/ // http://cyan4973.github.io/xxHash/
//
//nolint:errcheck
package hashutil package hashutil
import ( import (
"encoding/binary"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
"github.com/mitchellh/hashstructure/v2" "github.com/mitchellh/hashstructure/v2"
) )
@ -27,3 +31,176 @@ func Hash(v any) (uint64, error) {
} }
return hashstructure.Hash(v, hashstructure.FormatV2, opts) return hashstructure.Hash(v, hashstructure.FormatV2, opts)
} }
type Digest struct {
xxhash.Digest
}
func NewDigest() *Digest {
var d Digest
d.Reset()
return &d
}
// WriteStringWithLen writes the string's length, then its contents to the hash.
func (d *Digest) WriteStringWithLen(s string) {
d.WriteInt32(int32(len(s)))
d.WriteString(s)
}
// WriteStringWithLen writes the byte array's length, then its contents to
// the hash.
func (d *Digest) WriteWithLen(b []byte) {
d.WriteInt32(int32(len(b)))
d.Write(b)
}
// WriteBool writes a single byte (1 or 0) to the hash.
func (d *Digest) WriteBool(b bool) {
if b {
d.Write([]byte{1})
} else {
d.Write([]byte{0})
}
}
// WriteUint32 writes a uint16 to the hash.
func (d *Digest) WriteUint16(t uint16) {
var buf [2]byte
binary.LittleEndian.PutUint16(buf[:], t)
d.Write(buf[:])
}
// WriteUint32 writes a uint32 to the hash.
func (d *Digest) WriteUint32(t uint32) {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], t)
d.Write(buf[:])
}
// WriteUint32 writes a uint64 to the hash.
func (d *Digest) WriteUint64(t uint64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], t)
d.Write(buf[:])
}
// WriteInt16 writes an int16 to the hash.
func (d *Digest) WriteInt16(t int16) {
var buf [2]byte
binary.LittleEndian.PutUint16(buf[:], uint16(t))
d.Write(buf[:])
}
// WriteInt32 writes an int32 to the hash.
func (d *Digest) WriteInt32(t int32) {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], uint32(t))
d.Write(buf[:])
}
// WriteInt64 writes an int64 to the hash.
func (d *Digest) WriteInt64(t int64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(t))
d.Write(buf[:])
}
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteStringPtr(t *string) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteString(*t)
}
}
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the string's length and value, if present.
func (d *Digest) WriteStringPtrWithLen(t *string) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteStringWithLen(*t)
}
}
// WriteBoolPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteBoolPtr(t *bool) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteBool(*t)
}
}
// WriteUint16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint16Ptr(t *uint16) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint16(*t)
}
}
// WriteUint32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint32Ptr(t *uint32) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint32(*t)
}
}
// WriteUint64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint64Ptr(t *uint64) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint64(*t)
}
}
// WriteInt16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt16Ptr(t *int16) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt16(*t)
}
}
// WriteInt32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt32Ptr(t *int32) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt32(*t)
}
}
// WriteInt64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt64Ptr(t *int64) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt64(*t)
}
}