Skip to content

Commit

Permalink
Breaking changes that add support to configure compression levels
Browse files Browse the repository at this point in the history
  • Loading branch information
rnishtala-sumo committed Sep 30, 2024
1 parent ebd7128 commit 9fe4614
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 292 deletions.
4 changes: 2 additions & 2 deletions .chloggen/configure-compression-levels.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement
change_type: breaking

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: confighttp
Expand All @@ -22,4 +22,4 @@ subtext:
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
change_logs: [user, api]
91 changes: 19 additions & 72 deletions config/configcompression/compressiontype.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ package configcompression // import "go.opentelemetry.io/collector/config/config

import (
"fmt"
"strconv"
"strings"

"github.com/klauspost/compress/zlib"
)

// Type represents a compression method
type Type string
type Level int

type TypeWithLevel struct {
Type Type `mapstructure:"type"`
Level Level `mapstructure:"level"`
}

const (
TypeGzip Type = "gzip"
Expand All @@ -30,82 +34,25 @@ func (ct *Type) IsCompressed() bool {
return *ct != typeEmpty && *ct != typeNone
}

func (ct *Type) UnmarshalText(in []byte) error {
typ := Type(in)
if typ == TypeGzip ||
typ == TypeZlib ||
typ == TypeDeflate ||
func (ct *TypeWithLevel) UnmarshalText() (TypeWithLevel, error) {
typ := ct.Type
if (typ == TypeGzip && isValidLevel(int(ct.Level))) ||
(typ == TypeZlib && isValidLevel(int(ct.Level))) ||
(typ == TypeDeflate && isValidLevel(int(ct.Level))) ||
typ == TypeSnappy ||
typ == TypeZstd ||
typ == typeNone ||
typ == typeEmpty {
*ct = typ
return nil
return TypeWithLevel{Type: typ, Level: ct.Level}, nil
}
return fmt.Errorf("unsupported compression type %q", typ)

}

// IsZstd returns true if the compression type is zstd.
// The specified compression level is not validated.
// Because zstd supports returning an encoder level that closest matches the compression ratio of a specific zstd compression level.
// Many input values will provide the same compression level.
func (ct *Type) IsZstd() bool {
parts := strings.Split(string(*ct), "/")
return parts[0] == string(TypeZstd)
return TypeWithLevel{Type: typ, Level: ct.Level}, fmt.Errorf("unsupported compression type/level %q/%q", typ, ct.Level)
}

// Compression level isn't supported for snappy.
func (ct *Type) IsSnappy() bool {
parts := strings.Split(string(*ct), "/")
if len(parts) > 1 {
return false
}
return parts[0] == string(TypeSnappy)
}

// IsGzip returns true if the compression type is gzip and the specified compression level is valid.
func (ct *Type) IsGzip() bool {
parts := strings.Split(string(*ct), "/")
if parts[0] == string(TypeGzip) {
if len(parts) > 1 {
levelStr, err := strconv.Atoi(parts[1])
if err != nil {
return false
}
if levelStr == zlib.BestSpeed ||
levelStr == zlib.BestCompression ||
levelStr == zlib.DefaultCompression ||
levelStr == zlib.HuffmanOnly ||
levelStr == zlib.NoCompression {
return true
}
return false
}
return true
}
return false
}

// IsZlib returns true if the compression type is zlib and the specified compression level is valid.
func (ct *Type) IsZlib() bool {
parts := strings.Split(string(*ct), "/")
if parts[0] == string(TypeZlib) || parts[0] == string(TypeDeflate) {
if len(parts) > 1 {
levelStr, err := strconv.Atoi(parts[1])
if err != nil {
return false
}
if levelStr == zlib.BestSpeed ||
levelStr == zlib.BestCompression ||
levelStr == zlib.DefaultCompression ||
levelStr == zlib.HuffmanOnly ||
levelStr == zlib.NoCompression {
return true
}
return false
}
return true
}
return false
func isValidLevel(level int) bool {
return level == zlib.BestSpeed ||
level == zlib.BestCompression ||
level == zlib.DefaultCompression ||
level == zlib.HuffmanOnly ||
level == zlib.NoCompression
}
146 changes: 4 additions & 142 deletions config/configcompression/compressiontype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,154 +65,16 @@ func TestUnmarshalText(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
temp := typeNone
err := temp.UnmarshalText(tt.compressionName)
temp := TypeWithLevel{Type(tt.compressionName), 0}
ct, err := temp.UnmarshalText()
if tt.shouldError {
assert.Error(t, err)
return
}
require.NoError(t, err)
ct := Type(tt.compressionName)
// ct := Type(tt.compressionName)
assert.Equal(t, temp, ct)
assert.Equal(t, tt.isCompressed, ct.IsCompressed())
})
}
}

func TestIsZstd(t *testing.T) {
tests := []struct {
name string
input Type
expected bool
}{
{
name: "ValidZstd",
input: TypeZstd,
expected: true,
},
{
name: "InvalidZstd",
input: TypeGzip,
expected: false,
},
{
name: "ValidZstdLevel",
input: "zstd/11",
expected: true,
},
{
name: "ValidZstdLevel",
input: "zstd/One",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.input.IsZstd())
})
}
}

func TestIsGzip(t *testing.T) {
tests := []struct {
name string
input Type
expected bool
}{
{
name: "ValidGzip",
input: TypeGzip,
expected: true,
},
{
name: "InvalidGzip",
input: TypeZlib,
expected: false,
},
{
name: "ValidZlibCompressionLevel",
input: "gzip/1",
expected: true,
},
{
name: "InvalidZlibCompressionLevel",
input: "gzip/10",
expected: false,
},
{
name: "InvalidZlibCompressionLevel",
input: "gzip/one", // Uses the default compression level
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.input.IsGzip())
})
}
}

func TestIsZlib(t *testing.T) {
tests := []struct {
name string
input Type
expected bool
err bool
}{
{
name: "ValidZlib",
input: TypeZlib,
expected: true,
},
{
name: "InvalidZlib",
input: TypeGzip,
expected: false,
},
{
name: "ValidZlibCompressionLevel",
input: "zlib/1",
expected: true,
},
{
name: "InvalidZlibCompressionLevel",
input: "zlib/10",
expected: false,
},
{
name: "InvalidZlibCompressionLevel",
input: "zlib/one", // Uses the default compression level
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.input.IsZlib())
})
}
}

func TestIsSnappy(t *testing.T) {
tests := []struct {
name string
input Type
expected bool
err bool
}{
{
name: "ValidSnappy",
input: "snappy",
expected: true,
},
{
name: "InvliaSnappy",
input: "snappy/1", // Uses the default compression level
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, tt.input.IsSnappy())
assert.Equal(t, tt.isCompressed, ct.Type.IsCompressed())
})
}
}
38 changes: 21 additions & 17 deletions config/confighttp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,27 @@ README](../configtls/README.md).
- `none` will be treated as uncompressed, and any other inputs will cause an error.
- Compression levels can now be configured as part of the compression type like below. Not specifying any compression level will result in the default.
- `gzip`
- NoCompression: `gzip/0`
- BestSpeed: `gzip/1`
- BestCompression: `gzip/9`
- DefaultCompression: `gzip/-1`
- NoCompression: `0`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `zlib`
- NoCompression: `zlib/0`
- BestSpeed: `zlib/1`
- BestCompression: `zlib/9`
- DefaultCompression: `zlib/-1`
- NoCompression: `0`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `deflate`
- NoCompression: `deflate/0`
- BestSpeed: `deflate/1`
- BestCompression: `deflate/9`
- DefaultCompression: `deflate/-1`
- NoCompression: `0`
- BestSpeed: `1`
- BestCompression: `9`
- DefaultCompression: `-1`
- `zstd`
- SpeedFastest: `zstd/1`
- SpeedDefault: `zstd/3`
- SpeedBetterCompression: `zstd/6`
- SpeedBestCompression: `zstd/11`
- SpeedFastest: `1`
- SpeedDefault: `3`
- SpeedBetterCompression: `6`
- SpeedBestCompression: `11`
- `snappy`
No compression levels supported
- [`max_idle_conns`](https://golang.org/pkg/net/http/#Transport)
- [`max_idle_conns_per_host`](https://golang.org/pkg/net/http/#Transport)
- [`max_conns_per_host`](https://golang.org/pkg/net/http/#Transport)
Expand All @@ -73,7 +75,9 @@ exporter:
headers:
test1: "value1"
"test 2": "value 2"
compression: zstd
compression:
type: zstd
level: 11
cookies:
enabled: true
```
Expand Down
6 changes: 3 additions & 3 deletions config/confighttp/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

type compressRoundTripper struct {
rt http.RoundTripper
compressionType configcompression.Type
compressionType configcompression.TypeWithLevel
compressor *compressor
}

Expand Down Expand Up @@ -68,7 +68,7 @@ var availableDecoders = map[string]func(body io.ReadCloser) (io.ReadCloser, erro
},
}

func newCompressRoundTripper(rt http.RoundTripper, compressionType configcompression.Type) (*compressRoundTripper, error) {
func newCompressRoundTripper(rt http.RoundTripper, compressionType configcompression.TypeWithLevel) (*compressRoundTripper, error) {
encoder, err := newCompressor(compressionType)
if err != nil {
return nil, err
Expand Down Expand Up @@ -104,7 +104,7 @@ func (r *compressRoundTripper) RoundTrip(req *http.Request) (*http.Response, err

// Clone the headers and add the encoding header.
cReq.Header = req.Header.Clone()
cReq.Header.Add(headerContentEncoding, string(r.compressionType))
cReq.Header.Add(headerContentEncoding, string(r.compressionType.Type))

return r.rt.RoundTrip(cReq)
}
Expand Down
Loading

0 comments on commit 9fe4614

Please sign in to comment.