github.com/grafana/pyroscope@v1.18.0/pkg/objstore/providers/memory/bucket_client.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package memory 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "io" 11 "sort" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/thanos-io/objstore" 17 ) 18 19 var errNotFound = errors.New("inmem: object not found") 20 21 var _ objstore.Bucket = &InMemBucket{} 22 23 // InMemBucket implements the objstore.Bucket interfaces against local memory. 24 // Methods from Bucket interface are thread-safe. Objects are assumed to be immutable. 25 type InMemBucket struct { 26 mtx sync.RWMutex 27 objects map[string][]byte 28 attrs map[string]objstore.ObjectAttributes 29 } 30 31 // NewInMemBucket returns a new in memory Bucket. 32 // NOTE: Returned bucket is just a naive in memory bucket implementation. For test use cases only. 33 func NewInMemBucket() *InMemBucket { 34 return &InMemBucket{ 35 objects: map[string][]byte{}, 36 attrs: map[string]objstore.ObjectAttributes{}, 37 } 38 } 39 40 func (b *InMemBucket) Provider() objstore.ObjProvider { return objstore.MEMORY } 41 42 // Objects returns a copy of the internally stored objects. 43 // NOTE: For assert purposes. 44 func (b *InMemBucket) Objects() map[string][]byte { 45 b.mtx.RLock() 46 defer b.mtx.RUnlock() 47 48 objs := make(map[string][]byte) 49 for k, v := range b.objects { 50 objs[k] = v 51 } 52 53 return objs 54 } 55 56 // Iter calls f for each entry in the given directory. The argument to f is the full 57 // object name including the prefix of the inspected directory. 58 func (b *InMemBucket) Iter(_ context.Context, dir string, f func(string) error, options ...objstore.IterOption) error { 59 unique := map[string]struct{}{} 60 params := objstore.ApplyIterOptions(options...) 61 62 var dirPartsCount int 63 dirParts := strings.SplitAfter(dir, objstore.DirDelim) 64 for _, p := range dirParts { 65 if p == "" { 66 continue 67 } 68 dirPartsCount++ 69 } 70 71 b.mtx.RLock() 72 for filename := range b.objects { 73 if !strings.HasPrefix(filename, dir) || dir == filename { 74 continue 75 } 76 77 if params.Recursive { 78 // Any object matching the prefix should be included. 79 unique[filename] = struct{}{} 80 continue 81 } 82 83 parts := strings.SplitAfter(filename, objstore.DirDelim) 84 unique[strings.Join(parts[:dirPartsCount+1], "")] = struct{}{} 85 } 86 b.mtx.RUnlock() 87 88 var keys []string 89 for n := range unique { 90 keys = append(keys, n) 91 } 92 sort.Slice(keys, func(i, j int) bool { 93 if strings.HasSuffix(keys[i], objstore.DirDelim) && strings.HasSuffix(keys[j], objstore.DirDelim) { 94 return strings.Compare(keys[i], keys[j]) < 0 95 } 96 if strings.HasSuffix(keys[i], objstore.DirDelim) { 97 return false 98 } 99 if strings.HasSuffix(keys[j], objstore.DirDelim) { 100 return true 101 } 102 103 return strings.Compare(keys[i], keys[j]) < 0 104 }) 105 106 for _, k := range keys { 107 if err := f(k); err != nil { 108 return err 109 } 110 } 111 return nil 112 } 113 114 func (i *InMemBucket) SupportedIterOptions() []objstore.IterOptionType { 115 return []objstore.IterOptionType{objstore.Recursive} 116 } 117 118 func (b *InMemBucket) IterWithAttributes(ctx context.Context, dir string, f func(attrs objstore.IterObjectAttributes) error, options ...objstore.IterOption) error { 119 if err := objstore.ValidateIterOptions(b.SupportedIterOptions(), options...); err != nil { 120 return err 121 } 122 123 return b.Iter(ctx, dir, func(name string) error { 124 return f(objstore.IterObjectAttributes{Name: name}) 125 126 }, options...) 127 } 128 129 // Get returns a reader for the given object name. 130 func (b *InMemBucket) Get(_ context.Context, name string) (io.ReadCloser, error) { 131 if name == "" { 132 return nil, errors.New("inmem: object name is empty") 133 } 134 135 b.mtx.RLock() 136 file, ok := b.objects[name] 137 b.mtx.RUnlock() 138 if !ok { 139 return nil, errNotFound 140 } 141 142 return io.NopCloser(bytes.NewReader(file)), nil 143 } 144 145 // GetRange returns a new range reader for the given object name and range. 146 func (b *InMemBucket) GetRange(_ context.Context, name string, off, length int64) (io.ReadCloser, error) { 147 if name == "" { 148 return nil, errors.New("inmem: object name is empty") 149 } 150 151 b.mtx.RLock() 152 file, ok := b.objects[name] 153 b.mtx.RUnlock() 154 if !ok { 155 return nil, errNotFound 156 } 157 158 if int64(len(file)) < off { 159 return io.NopCloser(bytes.NewReader(nil)), nil 160 } 161 162 if length == 0 { 163 return io.NopCloser(bytes.NewReader(nil)), nil 164 } 165 if length == -1 { 166 return io.NopCloser(bytes.NewReader(file[off:])), nil 167 } 168 169 if int64(len(file)) <= off+length { 170 // Just return maximum of what we have. 171 length = int64(len(file)) - off 172 } 173 174 return io.NopCloser(bytes.NewReader(file[off : off+length])), nil 175 } 176 177 // Exists checks if the given directory exists in memory. 178 func (b *InMemBucket) Exists(_ context.Context, name string) (bool, error) { 179 b.mtx.RLock() 180 defer b.mtx.RUnlock() 181 _, ok := b.objects[name] 182 return ok, nil 183 } 184 185 // Attributes returns information about the specified object. 186 func (b *InMemBucket) Attributes(_ context.Context, name string) (objstore.ObjectAttributes, error) { 187 b.mtx.RLock() 188 attrs, ok := b.attrs[name] 189 b.mtx.RUnlock() 190 if !ok { 191 return objstore.ObjectAttributes{}, errNotFound 192 } 193 return attrs, nil 194 } 195 196 // Upload writes the file specified in src to into the memory. 197 func (b *InMemBucket) Upload(_ context.Context, name string, r io.Reader, opts ...objstore.ObjectUploadOption) error { 198 b.mtx.Lock() 199 defer b.mtx.Unlock() 200 body, err := io.ReadAll(r) 201 if err != nil { 202 return err 203 } 204 b.objects[name] = body 205 b.attrs[name] = objstore.ObjectAttributes{ 206 Size: int64(len(body)), 207 LastModified: time.Now(), 208 } 209 return nil 210 } 211 212 // Delete removes all data prefixed with the dir. 213 func (b *InMemBucket) Delete(_ context.Context, name string) error { 214 b.mtx.Lock() 215 defer b.mtx.Unlock() 216 if _, ok := b.objects[name]; !ok { 217 return errNotFound 218 } 219 delete(b.objects, name) 220 delete(b.attrs, name) 221 return nil 222 } 223 224 // IsObjNotFoundErr returns true if error means that object is not found. Relevant to Get operations. 225 func (b *InMemBucket) IsObjNotFoundErr(err error) bool { 226 return errors.Is(err, errNotFound) 227 } 228 229 // IsAccessDeniedErr returns true if access to object is denied. 230 func (b *InMemBucket) IsAccessDeniedErr(err error) bool { 231 return false 232 } 233 234 func (b *InMemBucket) Close() error { return nil } 235 236 // Name returns the bucket name. 237 func (b *InMemBucket) Name() string { 238 return "inmem" 239 } 240 241 func (b *InMemBucket) Set(name string, data []byte) { 242 b.mtx.Lock() 243 defer b.mtx.Unlock() 244 b.objects[name] = data 245 b.attrs[name] = objstore.ObjectAttributes{ 246 Size: int64(len(data)), 247 LastModified: time.Now(), 248 } 249 }