@@ -2,11 +2,16 @@ package encoding
2
2
3
3
import (
4
4
"bytes"
5
+ "context"
5
6
"fmt"
7
+ "reflect"
6
8
"sync"
9
+ "time"
7
10
8
11
"github.com/klauspost/compress/zstd"
9
12
cbg "github.com/whyrusleeping/cbor-gen"
13
+ "go.opentelemetry.io/otel/attribute"
14
+ "go.opentelemetry.io/otel/metric"
10
15
)
11
16
12
17
// maxDecompressedSize is the default maximum amount of memory allocated by the
@@ -37,15 +42,29 @@ func NewCBOR[T CBORMarshalUnmarshaler]() *CBOR[T] {
37
42
return & CBOR [T ]{}
38
43
}
39
44
40
- func (c * CBOR [T ]) Encode (m T ) ([]byte , error ) {
45
+ func (c * CBOR [T ]) Encode (m T ) (_ []byte , _err error ) {
46
+ defer func (start time.Time ) {
47
+ if _err != nil {
48
+ metrics .encodingTime .Record (context .Background (),
49
+ time .Since (start ).Seconds (),
50
+ metric .WithAttributeSet (attrSetCborEncode ))
51
+ }
52
+ }(time .Now ())
41
53
var out bytes.Buffer
42
54
if err := m .MarshalCBOR (& out ); err != nil {
43
55
return nil , err
44
56
}
45
57
return out .Bytes (), nil
46
58
}
47
59
48
- func (c * CBOR [T ]) Decode (v []byte , t T ) error {
60
+ func (c * CBOR [T ]) Decode (v []byte , t T ) (_err error ) {
61
+ defer func (start time.Time ) {
62
+ if _err != nil {
63
+ metrics .encodingTime .Record (context .Background (),
64
+ time .Since (start ).Seconds (),
65
+ metric .WithAttributeSet (attrSetCborDecode ))
66
+ }
67
+ }(time .Now ())
49
68
r := bytes .NewReader (v )
50
69
return t .UnmarshalCBOR (r )
51
70
}
@@ -54,6 +73,9 @@ type ZSTD[T CBORMarshalUnmarshaler] struct {
54
73
cborEncoding * CBOR [T ]
55
74
compressor * zstd.Encoder
56
75
decompressor * zstd.Decoder
76
+
77
+ metricAttr attribute.KeyValue
78
+ metricAttrLoader sync.Once
57
79
}
58
80
59
81
func NewZSTD [T CBORMarshalUnmarshaler ]() (* ZSTD [T ], error ) {
@@ -74,26 +96,57 @@ func NewZSTD[T CBORMarshalUnmarshaler]() (*ZSTD[T], error) {
74
96
}, nil
75
97
}
76
98
77
- func (c * ZSTD [T ]) Encode (m T ) ([]byte , error ) {
78
- cborEncoded , err := c .cborEncoding .Encode (m )
79
- if len (cborEncoded ) > maxDecompressedSize {
99
+ func (c * ZSTD [T ]) Encode (t T ) (_ []byte , _err error ) {
100
+ defer func (start time.Time ) {
101
+ metrics .encodingTime .Record (context .Background (),
102
+ time .Since (start ).Seconds (),
103
+ metric .WithAttributeSet (attrSetZstdEncode ))
104
+ }(time .Now ())
105
+ decompressed , err := c .cborEncoding .Encode (t )
106
+ if len (decompressed ) > maxDecompressedSize {
80
107
// Error out early if the encoded value is too large to be decompressed.
81
- return nil , fmt .Errorf ("encoded value cannot exceed maximum size: %d > %d" , len (cborEncoded ), maxDecompressedSize )
108
+ return nil , fmt .Errorf ("encoded value cannot exceed maximum size: %d > %d" , len (decompressed ), maxDecompressedSize )
82
109
}
83
110
if err != nil {
84
111
return nil , err
85
112
}
86
- compressed := c .compressor .EncodeAll (cborEncoded , make ([]byte , 0 , len (cborEncoded )))
113
+ compressed := c .compressor .EncodeAll (decompressed , make ([]byte , 0 , len (decompressed )))
114
+ c .meterCompressionRatio (len (decompressed ), len (compressed ))
87
115
return compressed , nil
88
116
}
89
117
90
- func (c * ZSTD [T ]) Decode (v []byte , t T ) error {
118
+ func (c * ZSTD [T ]) Decode (compressed []byte , t T ) (_err error ) {
119
+ defer func (start time.Time ) {
120
+ if _err != nil {
121
+ metrics .encodingTime .Record (context .Background (),
122
+ time .Since (start ).Seconds (),
123
+ metric .WithAttributeSet (attrSetZstdDecode ))
124
+ }
125
+ }(time .Now ())
91
126
buf := bufferPool .Get ().(* []byte )
92
127
defer bufferPool .Put (buf )
93
-
94
- cborEncoded , err := c .decompressor .DecodeAll (v , (* buf )[:0 ])
128
+ decompressed , err := c .decompressor .DecodeAll (compressed , (* buf )[:0 ])
95
129
if err != nil {
96
130
return err
97
131
}
98
- return c .cborEncoding .Decode (cborEncoded , t )
132
+ c .meterCompressionRatio (len (decompressed ), len (compressed ))
133
+ return c .cborEncoding .Decode (decompressed , t )
134
+ }
135
+
136
+ func (c * ZSTD [T ]) meterCompressionRatio (decompressedSize , compressedSize int ) {
137
+ compressionRatio := float64 (decompressedSize ) / float64 (compressedSize )
138
+ metrics .zstdCompressionRatio .Record (context .Background (), compressionRatio , metric .WithAttributes (c .getMetricAttribute ()))
139
+ }
140
+
141
+ func (c * ZSTD [T ]) getMetricAttribute () attribute.KeyValue {
142
+ c .metricAttrLoader .Do (func () {
143
+ const key = "type"
144
+ switch target := reflect .TypeFor [T ](); {
145
+ case target .Kind () == reflect .Ptr :
146
+ c .metricAttr = attribute .String (key , target .Elem ().Name ())
147
+ default :
148
+ c .metricAttr = attribute .String (key , target .Name ())
149
+ }
150
+ })
151
+ return c .metricAttr
99
152
}
0 commit comments