mirror of https://github.com/k3s-io/k3s
Merge pull request #75699 from smarterclayton/fast_encode
Avoid allocations when building SelfLinks and fast path escapek3s-v1.15.3
commit
1514bb2141
|
@ -34,6 +34,7 @@ go_test(
|
||||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||||
|
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||||
"//vendor/k8s.io/utils/trace:go_default_library",
|
"//vendor/k8s.io/utils/trace:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -86,6 +87,21 @@ func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err
|
||||||
return ns, requestInfo.Name, nil
|
return ns, requestInfo.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fastURLPathEncode encodes the provided path as a URL path
|
||||||
|
func fastURLPathEncode(path string) string {
|
||||||
|
for _, r := range []byte(path) {
|
||||||
|
switch {
|
||||||
|
case r >= '-' && r <= '9', r >= 'A' && r <= 'Z', r >= 'a' && r <= 'z':
|
||||||
|
// characters within this range do not require escaping
|
||||||
|
default:
|
||||||
|
var u url.URL
|
||||||
|
u.Path = path
|
||||||
|
return u.EscapedPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
|
func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
|
||||||
namespace, name, err := n.ObjectName(obj)
|
namespace, name, err := n.ObjectName(obj)
|
||||||
if err == errEmptyName && len(requestInfo.Name) > 0 {
|
if err == errEmptyName && len(requestInfo.Name) > 0 {
|
||||||
|
@ -101,19 +117,23 @@ func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj r
|
||||||
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
|
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.SelfLinkPathPrefix +
|
builder := strings.Builder{}
|
||||||
url.QueryEscape(namespace) +
|
builder.Grow(len(n.SelfLinkPathPrefix) + len(namespace) + len(requestInfo.Resource) + len(name) + len(n.SelfLinkPathSuffix) + 8)
|
||||||
"/" + url.QueryEscape(requestInfo.Resource) + "/" +
|
builder.WriteString(n.SelfLinkPathPrefix)
|
||||||
url.QueryEscape(name) +
|
builder.WriteString(namespace)
|
||||||
n.SelfLinkPathSuffix,
|
builder.WriteByte('/')
|
||||||
nil
|
builder.WriteString(requestInfo.Resource)
|
||||||
|
builder.WriteByte('/')
|
||||||
|
builder.WriteString(name)
|
||||||
|
builder.WriteString(n.SelfLinkPathSuffix)
|
||||||
|
return fastURLPathEncode(builder.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
|
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
|
||||||
if len(req.URL.RawPath) > 0 {
|
if len(req.URL.RawPath) > 0 {
|
||||||
return req.URL.RawPath, nil
|
return req.URL.RawPath, nil
|
||||||
}
|
}
|
||||||
return req.URL.EscapedPath(), nil
|
return fastURLPathEncode(req.URL.Path), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
|
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
|
||||||
|
|
|
@ -17,9 +17,13 @@ limitations under the License.
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
fuzz "github.com/google/gofuzz"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -114,3 +118,62 @@ func TestGenerateLink(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_fastURLPathEncode_fuzz(t *testing.T) {
|
||||||
|
specialCases := []string{"/", "//", ".", "*", "/abc%"}
|
||||||
|
for _, test := range specialCases {
|
||||||
|
got := fastURLPathEncode(test)
|
||||||
|
u := url.URL{Path: test}
|
||||||
|
expected := u.EscapedPath()
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("%q did not match %q", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := fuzz.New().Funcs(
|
||||||
|
func(s *string, c fuzz.Continue) {
|
||||||
|
*s = randString(c.Rand)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for i := 0; i < 2000; i++ {
|
||||||
|
var test string
|
||||||
|
f.Fuzz(&test)
|
||||||
|
|
||||||
|
got := fastURLPathEncode(test)
|
||||||
|
u := url.URL{Path: test}
|
||||||
|
expected := u.EscapedPath()
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("%q did not match %q", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unicode range fuzzer from github.com/google/gofuzz/fuzz.go
|
||||||
|
|
||||||
|
type charRange struct {
|
||||||
|
first, last rune
|
||||||
|
}
|
||||||
|
|
||||||
|
var unicodeRanges = []charRange{
|
||||||
|
{0x00, 0x255},
|
||||||
|
{' ', '~'}, // ASCII characters
|
||||||
|
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
|
||||||
|
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// randString makes a random string up to 20 characters long. The returned string
|
||||||
|
// may include a variety of (valid) UTF-8 encodings.
|
||||||
|
func randString(r *rand.Rand) string {
|
||||||
|
n := r.Intn(20)
|
||||||
|
runes := make([]rune, n)
|
||||||
|
for i := range runes {
|
||||||
|
runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r)
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose returns a random unicode character from the given range, using the
|
||||||
|
// given randomness source.
|
||||||
|
func (r *charRange) choose(rand *rand.Rand) rune {
|
||||||
|
count := int64(r.last - r.first)
|
||||||
|
return r.first + rune(rand.Int63n(count))
|
||||||
|
}
|
||||||
|
|
|
@ -757,6 +757,30 @@ func TestWatchHTTPTimeout(t *testing.T) {
|
||||||
func BenchmarkWatchHTTP(b *testing.B) {
|
func BenchmarkWatchHTTP(b *testing.B) {
|
||||||
items := benchmarkItems(b)
|
items := benchmarkItems(b)
|
||||||
|
|
||||||
|
// use ASCII names to capture the cost of handling ASCII only self-links
|
||||||
|
for i := range items {
|
||||||
|
item := &items[i]
|
||||||
|
item.Namespace = fmt.Sprintf("namespace-%d", i)
|
||||||
|
item.Name = fmt.Sprintf("reasonable-name-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
runWatchHTTPBenchmark(b, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWatchHTTP_UTF8(b *testing.B) {
|
||||||
|
items := benchmarkItems(b)
|
||||||
|
|
||||||
|
// use UTF names to capture the cost of handling UTF-8 escaping in self-links
|
||||||
|
for i := range items {
|
||||||
|
item := &items[i]
|
||||||
|
item.Namespace = fmt.Sprintf("躀痢疈蜧í柢-%d", i)
|
||||||
|
item.Name = fmt.Sprintf("翏Ŏ熡韐-%d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
runWatchHTTPBenchmark(b, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWatchHTTPBenchmark(b *testing.B, items []example.Pod) {
|
||||||
simpleStorage := &SimpleRESTStorage{}
|
simpleStorage := &SimpleRESTStorage{}
|
||||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
|
|
Loading…
Reference in New Issue