mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-18 06:21:35 -06:00
48f76fd2aa
And the default inmem storage implementation(s) in particular.
- Avoid creating expensive references for data which isn't in need of that, like strings or booleans.
- Avoid expensive roundtrips of data which doesn't need it, for the same reasons.
- Never roundtrip AST store items being written — unknown values will be roundtripped by `ast.InterfaceToValue` already
- Convert numbers to json.Number directly and without a roundtrip
- Avoid AST -> Go type conversion which is done for triggers... when no triggers have been registered
- Greatly reduce cost of not found lookups by using a static NotFound error that does not include the path — the path is already known by the caller so that information adds nothing they can't add themselves.
- Improve interning to make it easier to get interned `Value`'s without a *Term
- Use interned numbers when possible in `ast.InterfaceToValue`
- Much improved benchmarking framework for inmem storage, and many new benchmarks added.
- A few general improvements of error handling
**Benchstat**
```
goos: darwin
goarch: arm64
pkg: github.com/open-policy-agent/opa/v1/storage/inmem
cpu: Apple M4 Max
│ v1.9.0 │ now │
│ sec/op │ sec/op vs base │
NewTransaction/write-16 44.60n ± 1% 17.12n ± 1% -61.61% (p=0.000 n=10)
NewTransaction/read-16 44.62n ± 0% 17.04n ± 0% -61.80% (p=0.000 n=10)
ReadOne/Go-16 126.95n ± 0% 98.93n ± 0% -22.07% (p=0.000 n=10)
ReadOne/Go_(roundtrip)-16 127.65n ± 0% 99.04n ± 0% -22.41% (p=0.000 n=10)
ReadOne/AST-16 151.1n ± 0% 121.7n ± 0% -19.40% (p=0.000 n=10)
ReadOneNested/Go-16 138.0n ± 0% 109.5n ± 0% -20.69% (p=0.000 n=10)
ReadOneNested/Go_(roundtrip)-16 139.1n ± 0% 110.1n ± 1% -20.85% (p=0.000 n=10)
ReadOneNested/AST-16 207.6n ± 1% 177.2n ± 0% -14.64% (p=0.000 n=10)
ReadNotFound/Go-16 127.70n ± 0% 73.22n ± 1% -42.67% (p=0.000 n=10)
ReadNotFound/Go_(roundtrip)-16 128.00n ± 0% 73.35n ± 1% -42.70% (p=0.000 n=10)
ReadNotFound/AST-16 148.90n ± 0% 99.38n ± 0% -33.26% (p=0.000 n=10)
WriteOneString/Go-16 240.1n ± 0% 184.5n ± 0% -23.16% (p=0.000 n=10)
WriteOneString/Go_(roundtrip)-16 655.2n ± 0% 185.3n ± 0% -71.72% (p=0.000 n=10)
WriteOneString/AST-16 803.4n ± 0% 325.4n ± 0% -59.49% (p=0.000 n=10)
WriteSameValue/Go-16 138.75n ± 1% 77.56n ± 2% -44.10% (p=0.000 n=10)
WriteSameValue/Go_(roundtrip)-16 557.95n ± 1% 77.86n ± 2% -86.05% (p=0.000 n=10)
WriteSameValue/AST-16 615.65n ± 1% 79.25n ± 1% -87.13% (p=0.000 n=10)
WriteIncrementingValueSamePath/Go-16 6.312µ ± 0% 6.462µ ± 0% +2.38% (p=0.000 n=10)
WriteIncrementingValueSamePath/Go_(roundtrip)-16 48.825µ ± 1% 7.860µ ± 0% -83.90% (p=0.000 n=10)
WriteIncrementingValueSamePath/AST-16 53.723µ ± 1% 8.718µ ± 1% -83.77% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/Go-16 6.351µ ± 1% 6.511µ ± 1% +2.52% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/Go_(roundtrip)-16 49.804µ ± 1% 7.907µ ± 0% -84.12% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/AST-16 51.806µ ± 2% 6.663µ ± 1% -87.14% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go-16 96.23µ ± 4% 43.83µ ± 4% -54.45% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go_(roundtrip)-16 135.08µ ± 1% 43.01µ ± 3% -68.16% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/AST-16 145.67µ ± 1% 43.19µ ± 3% -70.35% (p=0.000 n=10)
WriteCollection/Go-16 136.8n ± 1% 137.0n ± 1% ~ (p=0.643 n=10)
WriteCollection/Go_(roundtrip)-16 1.304µ ± 0% 1.304µ ± 2% ~ (p=0.629 n=10)
WriteCollection/AST-16 2034.0n ± 0% 772.5n ± 2% -62.02% (p=0.000 n=10)
WriteAndCommit/Go-16 54.00µ ± 3% 47.63µ ± 1% -11.80% (p=0.000 n=10)
WriteAndCommit/Go_(roundtrip)-16 94.63µ ± 1% 47.19µ ± 3% -50.13% (p=0.000 n=10)
WriteAndCommit/AST-16 239.6µ ± 1% 192.7µ ± 1% -19.59% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go-16 54.08µ ± 1% 47.87µ ± 2% -11.47% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go_(roundtrip)-16 94.75µ ± 1% 47.41µ ± 2% -49.97% (p=0.000 n=10)
WriteAndCommitWithTriggers/AST-16 247.2µ ± 0% 192.2µ ± 1% -22.27% (p=0.000 n=10)
│ v1.9.0 │ now │
│ B/op │ B/op vs base │
NewTransaction/write-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
NewTransaction/read-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
ReadOne/Go-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
ReadOne/Go_(roundtrip)-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
ReadOne/AST-16 160.00 ± 0% 64.00 ± 0% -60.00% (p=0.000 n=10)
ReadOneNested/Go-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
ReadOneNested/Go_(roundtrip)-16 144.00 ± 0% 48.00 ± 0% -66.67% (p=0.000 n=10)
ReadOneNested/AST-16 192.00 ± 0% 96.00 ± 0% -50.00% (p=0.000 n=10)
ReadNotFound/Go-16 72.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
ReadNotFound/Go_(roundtrip)-16 72.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
ReadNotFound/AST-16 88.00 ± 0% 16.00 ± 0% -81.82% (p=0.000 n=10)
WriteOneString/Go-16 368.0 ± 0% 240.0 ± 0% -34.78% (p=0.000 n=10)
WriteOneString/Go_(roundtrip)-16 2818.0 ± 0% 240.0 ± 0% -91.48% (p=0.000 n=10)
WriteOneString/AST-16 2938.0 ± 0% 360.0 ± 0% -87.75% (p=0.000 n=10)
WriteSameValue/Go-16 128.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10)
WriteSameValue/Go_(roundtrip)-16 2.518Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10)
WriteSameValue/AST-16 2.572Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10)
WriteIncrementingValueSamePath/Go-16 12.50Ki ± 0% 12.50Ki ± 0% ~ (p=1.000 n=10) ¹
WriteIncrementingValueSamePath/Go_(roundtrip)-16 251.73Ki ± 0% 14.06Ki ± 0% -94.41% (p=0.000 n=10)
WriteIncrementingValueSamePath/AST-16 255.63Ki ± 0% 13.28Ki ± 0% -94.80% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/Go-16 12.50Ki ± 0% 12.50Ki ± 0% ~ (p=1.000 n=10) ¹
WriteIncrementingValueSamePathInterned/Go_(roundtrip)-16 251.73Ki ± 0% 14.06Ki ± 0% -94.41% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/AST-16 251.722Ki ± 0% 9.375Ki ± 0% -96.28% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go-16 14.062Ki ± 0% 1.562Ki ± 0% -88.89% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go_(roundtrip)-16 253.267Ki ± 0% 1.562Ki ± 0% -99.38% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/AST-16 254.870Ki ± 0% 1.562Ki ± 0% -99.39% (p=0.000 n=10)
WriteCollection/Go-16 128.0 ± 0% 128.0 ± 0% ~ (p=1.000 n=10) ¹
WriteCollection/Go_(roundtrip)-16 2.243Ki ± 0% 2.243Ki ± 0% ~ (p=1.000 n=10) ¹
WriteCollection/AST-16 3.472Ki ± 0% 1.320Ki ± 0% -61.97% (p=0.000 n=10)
WriteAndCommit/Go-16 29.58Ki ± 0% 11.08Ki ± 0% -62.55% (p=0.000 n=10)
WriteAndCommit/Go_(roundtrip)-16 268.80Ki ± 0% 11.08Ki ± 0% -95.88% (p=0.000 n=10)
WriteAndCommit/AST-16 272.77Ki ± 0% 15.06Ki ± 0% -94.48% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go-16 29.58Ki ± 0% 15.83Ki ± 0% -46.49% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go_(roundtrip)-16 268.80Ki ± 0% 15.83Ki ± 0% -94.11% (p=0.000 n=10)
WriteAndCommitWithTriggers/AST-16 279.09Ki ± 0% 26.12Ki ± 0% -90.64% (p=0.000 n=10)
│ v1.9.0 │ now │
│ allocs/op │ allocs/op vs base │
NewTransaction/write-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
NewTransaction/read-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
ReadOne/Go-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
ReadOne/Go_(roundtrip)-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
ReadOne/AST-16 4.000 ± 0% 2.000 ± 0% -50.00% (p=0.000 n=10)
ReadOneNested/Go-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
ReadOneNested/Go_(roundtrip)-16 3.000 ± 0% 1.000 ± 0% -66.67% (p=0.000 n=10)
ReadOneNested/AST-16 6.000 ± 0% 4.000 ± 0% -33.33% (p=0.000 n=10)
ReadNotFound/Go-16 3.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)
ReadNotFound/Go_(roundtrip)-16 3.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)
ReadNotFound/AST-16 4.000 ± 0% 1.000 ± 0% -75.00% (p=0.000 n=10)
WriteOneString/Go-16 9.000 ± 0% 5.000 ± 0% -44.44% (p=0.000 n=10)
WriteOneString/Go_(roundtrip)-16 16.000 ± 0% 5.000 ± 0% -68.75% (p=0.000 n=10)
WriteOneString/AST-16 22.00 ± 0% 11.00 ± 0% -50.00% (p=0.000 n=10)
WriteSameValue/Go-16 4.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10)
WriteSameValue/Go_(roundtrip)-16 11.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
WriteSameValue/AST-16 14.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10)
WriteIncrementingValueSamePath/Go-16 400.0 ± 0% 400.0 ± 0% ~ (p=1.000 n=10) ¹
WriteIncrementingValueSamePath/Go_(roundtrip)-16 1090.0 ± 0% 500.0 ± 0% -54.13% (p=0.000 n=10)
WriteIncrementingValueSamePath/AST-16 1290.0 ± 0% 400.0 ± 0% -68.99% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/Go-16 400.0 ± 0% 400.0 ± 0% ~ (p=1.000 n=10) ¹
WriteIncrementingValueSamePathInterned/Go_(roundtrip)-16 1090.0 ± 0% 500.0 ± 0% -54.13% (p=0.000 n=10)
WriteIncrementingValueSamePathInterned/AST-16 1090.0 ± 0% 200.0 ± 0% -81.65% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go-16 500.0 ± 0% 100.0 ± 0% -80.00% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/Go_(roundtrip)-16 1190.0 ± 0% 100.0 ± 0% -91.60% (p=0.000 n=10)
PathAndValueInternedAndNoRoundtripRequired/AST-16 1292.0 ± 0% 100.0 ± 0% -92.26% (p=0.000 n=10)
WriteCollection/Go-16 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹
WriteCollection/Go_(roundtrip)-16 35.00 ± 0% 35.00 ± 0% ~ (p=1.000 n=10) ¹
WriteCollection/AST-16 63.00 ± 0% 30.00 ± 0% -52.38% (p=0.000 n=10)
WriteAndCommit/Go-16 512.0 ± 0% 303.0 ± 0% -40.82% (p=0.000 n=10)
WriteAndCommit/Go_(roundtrip)-16 1202.0 ± 0% 303.0 ± 0% -74.79% (p=0.000 n=10)
WriteAndCommit/AST-16 1406.0 ± 0% 507.0 ± 0% -63.94% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go-16 512.0 ± 0% 304.0 ± 0% -40.62% (p=0.000 n=10)
WriteAndCommitWithTriggers/Go_(roundtrip)-16 1202.0 ± 0% 304.0 ± 0% -74.71% (p=0.000 n=10)
WriteAndCommitWithTriggers/AST-16 1507.0 ± 0% 609.0 ± 0% -59.59% (p=0.000 n=10)
```
Signed-off-by: Anders Eknert <anders@eknert.com>
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
// Copyright 2016 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/open-policy-agent/opa/v1/loader/extension"
|
|
)
|
|
|
|
// UnmarshalJSON parses the JSON encoded data and stores the result in the value
|
|
// pointed to by x.
|
|
//
|
|
// This function is intended to be used in place of the standard [json.Marshal]
|
|
// function when [json.Number] is required.
|
|
func UnmarshalJSON(bs []byte, x any) error {
|
|
return unmarshalJSON(bs, x, true)
|
|
}
|
|
|
|
func unmarshalJSON(bs []byte, x any, ext bool) error {
|
|
decoder := NewJSONDecoder(bytes.NewBuffer(bs))
|
|
if err := decoder.Decode(x); err != nil {
|
|
if handler := extension.FindExtension(".json"); handler != nil && ext {
|
|
return handler(bs, x)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Since decoder.Decode validates only the first json structure in bytes,
|
|
// check if decoder has more bytes to consume to validate whole input bytes.
|
|
tok, err := decoder.Token()
|
|
if tok != nil {
|
|
return fmt.Errorf("error: invalid character '%s' after top-level value", tok)
|
|
}
|
|
if err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewJSONDecoder returns a new decoder that reads from r.
|
|
//
|
|
// This function is intended to be used in place of the standard [json.NewDecoder]
|
|
// when [json.Number] is required.
|
|
func NewJSONDecoder(r io.Reader) *json.Decoder {
|
|
decoder := json.NewDecoder(r)
|
|
decoder.UseNumber()
|
|
return decoder
|
|
}
|
|
|
|
// MustUnmarshalJSON parse the JSON encoded data and returns the result.
|
|
//
|
|
// If the data cannot be decoded, this function will panic. This function is for
|
|
// test purposes.
|
|
func MustUnmarshalJSON(bs []byte) any {
|
|
var x any
|
|
if err := UnmarshalJSON(bs, &x); err != nil {
|
|
panic(err)
|
|
}
|
|
return x
|
|
}
|
|
|
|
// MustMarshalJSON returns the JSON encoding of x
|
|
//
|
|
// If the data cannot be encoded, this function will panic. This function is for
|
|
// test purposes.
|
|
func MustMarshalJSON(x any) []byte {
|
|
bs, err := json.Marshal(x)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bs
|
|
}
|
|
|
|
// RoundTrip encodes to JSON, and decodes the result again.
|
|
//
|
|
// Thereby, it is converting its argument to the representation expected by
|
|
// rego.Input and inmem's Write operations. Works with both references and
|
|
// values.
|
|
func RoundTrip(x *any) error {
|
|
// Avoid round-tripping types that won't change as a result of
|
|
// marshalling/unmarshalling, as even for those values, round-tripping
|
|
// comes with a significant cost.
|
|
if x == nil || !NeedsRoundTrip(*x) {
|
|
return nil
|
|
}
|
|
|
|
// For number types, we can write the json.Number representation
|
|
// directly into x without marshalling to bytes and back.
|
|
a := *x
|
|
switch v := a.(type) {
|
|
case int:
|
|
*x = json.Number(strconv.Itoa(v))
|
|
return nil
|
|
case int8:
|
|
*x = json.Number(strconv.FormatInt(int64(v), 10))
|
|
return nil
|
|
case int16:
|
|
*x = json.Number(strconv.FormatInt(int64(v), 10))
|
|
return nil
|
|
case int32:
|
|
*x = json.Number(strconv.FormatInt(int64(v), 10))
|
|
return nil
|
|
case int64:
|
|
*x = json.Number(strconv.FormatInt(v, 10))
|
|
return nil
|
|
case uint:
|
|
*x = json.Number(strconv.FormatUint(uint64(v), 10))
|
|
return nil
|
|
case uint8:
|
|
*x = json.Number(strconv.FormatUint(uint64(v), 10))
|
|
return nil
|
|
case uint16:
|
|
*x = json.Number(strconv.FormatUint(uint64(v), 10))
|
|
return nil
|
|
case uint32:
|
|
*x = json.Number(strconv.FormatUint(uint64(v), 10))
|
|
return nil
|
|
case uint64:
|
|
*x = json.Number(strconv.FormatUint(v, 10))
|
|
return nil
|
|
case float32:
|
|
*x = json.Number(strconv.FormatFloat(float64(v), 'f', -1, 32))
|
|
return nil
|
|
case float64:
|
|
*x = json.Number(strconv.FormatFloat(v, 'f', -1, 64))
|
|
return nil
|
|
}
|
|
|
|
bs, err := json.Marshal(x)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return UnmarshalJSON(bs, x)
|
|
}
|
|
|
|
// NeedsRoundTrip returns true if the value won't change as a result of
|
|
// a marshalling/unmarshalling round-trip. Since [RoundTrip] itself calls
|
|
// this you normally don't need to call this function directly, unless you
|
|
// want to make decisions based on the round-tripability of a value without
|
|
// actually doing the round-trip.
|
|
func NeedsRoundTrip(x any) bool {
|
|
switch x.(type) {
|
|
case nil, bool, string, json.Number:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Reference returns a pointer to its argument unless the argument already is
|
|
// a pointer. If the argument is **t, or ***t, etc, it will return *t.
|
|
//
|
|
// Used for preparing Go types (including pointers to structs) into values to be
|
|
// put through [RoundTrip].
|
|
func Reference(x any) *any {
|
|
var y any
|
|
rv := reflect.ValueOf(x)
|
|
if rv.Kind() == reflect.Pointer {
|
|
return Reference(rv.Elem().Interface())
|
|
}
|
|
if rv.Kind() != reflect.Invalid {
|
|
y = rv.Interface()
|
|
return &y
|
|
}
|
|
return &x
|
|
}
|
|
|
|
// Unmarshal decodes a YAML, JSON or JSON extension value into the specified type.
|
|
func Unmarshal(bs []byte, v any) error {
|
|
if len(bs) > 2 && bs[0] == 0xef && bs[1] == 0xbb && bs[2] == 0xbf {
|
|
bs = bs[3:] // Strip UTF-8 BOM, see https://www.rfc-editor.org/rfc/rfc8259#section-8.1
|
|
}
|
|
|
|
if json.Valid(bs) {
|
|
return unmarshalJSON(bs, v, false)
|
|
}
|
|
nbs, err := yaml.YAMLToJSON(bs)
|
|
if err == nil {
|
|
return unmarshalJSON(nbs, v, false)
|
|
}
|
|
// not json or yaml: try extensions
|
|
if handler := extension.FindExtension(".json"); handler != nil {
|
|
return handler(bs, v)
|
|
}
|
|
return err
|
|
}
|