7
7
"sync"
8
8
9
9
"github.com/google/uuid"
10
+ "github.com/prometheus/client_golang/prometheus"
11
+ "github.com/prometheus/client_golang/prometheus/promauto"
10
12
"golang.org/x/xerrors"
11
13
12
14
archivefs "github.com/coder/coder/v2/archive/fs"
@@ -16,22 +18,78 @@ import (
16
18
17
19
// NewFromStore returns a file cache that will fetch files from the provided
18
20
// database.
19
- func NewFromStore (store database.Store ) * Cache {
20
- fetcher := func (ctx context.Context , fileID uuid.UUID ) (fs. FS , error ) {
21
+ func NewFromStore (store database.Store , registerer prometheus. Registerer ) * Cache {
22
+ fetch := func (ctx context.Context , fileID uuid.UUID ) (cacheEntryValue , error ) {
21
23
file , err := store .GetFileByID (ctx , fileID )
22
24
if err != nil {
23
- return nil , xerrors .Errorf ("failed to read file from database: %w" , err )
25
+ return cacheEntryValue {} , xerrors .Errorf ("failed to read file from database: %w" , err )
24
26
}
25
27
26
28
content := bytes .NewBuffer (file .Data )
27
- return archivefs .FromTarReader (content ), nil
29
+ return cacheEntryValue {
30
+ FS : archivefs .FromTarReader (content ),
31
+ size : int64 (content .Len ()),
32
+ }, nil
28
33
}
29
34
30
- return & Cache {
35
+ return New (fetch , registerer )
36
+ }
37
+
38
+ func New (fetch fetcher , registerer prometheus.Registerer ) * Cache {
39
+ return (& Cache {
31
40
lock : sync.Mutex {},
32
41
data : make (map [uuid.UUID ]* cacheEntry ),
33
- fetcher : fetcher ,
34
- }
42
+ fetcher : fetch ,
43
+ }).registerMetrics (registerer )
44
+ }
45
+
46
+ func (c * Cache ) registerMetrics (registerer prometheus.Registerer ) * Cache {
47
+ subsystem := "file_cache"
48
+ f := promauto .With (registerer )
49
+
50
+ c .currentCacheSize = f .NewGauge (prometheus.GaugeOpts {
51
+ Namespace : "coderd" ,
52
+ Subsystem : subsystem ,
53
+ Name : "open_files_size_bytes_current" ,
54
+ Help : "The current amount of memory of all files currently open in the file cache." ,
55
+ })
56
+
57
+ c .totalCacheSize = f .NewCounter (prometheus.CounterOpts {
58
+ Namespace : "coderd" ,
59
+ Subsystem : subsystem ,
60
+ Name : "open_files_size_bytes_total" ,
61
+ Help : "The total amount of memory ever opened in the file cache. This number never decrements." ,
62
+ })
63
+
64
+ c .currentOpenFiles = f .NewGauge (prometheus.GaugeOpts {
65
+ Namespace : "coderd" ,
66
+ Subsystem : subsystem ,
67
+ Name : "open_files_current" ,
68
+ Help : "The count of unique files currently open in the file cache." ,
69
+ })
70
+
71
+ c .totalOpenedFiles = f .NewCounter (prometheus.CounterOpts {
72
+ Namespace : "coderd" ,
73
+ Subsystem : subsystem ,
74
+ Name : "open_files_total" ,
75
+ Help : "The total count of unique files ever opened in the file cache." ,
76
+ })
77
+
78
+ c .currentOpenFileReferences = f .NewGauge (prometheus.GaugeOpts {
79
+ Namespace : "coderd" ,
80
+ Subsystem : subsystem ,
81
+ Name : "open_file_refs_current" ,
82
+ Help : "The count of file references currently open in the file cache. Multiple references can be held for the same file." ,
83
+ })
84
+
85
+ c .totalOpenFileReferences = f .NewCounter (prometheus.CounterOpts {
86
+ Namespace : "coderd" ,
87
+ Subsystem : subsystem ,
88
+ Name : "open_file_refs_total" ,
89
+ Help : "The total number of file references ever opened in the file cache." ,
90
+ })
91
+
92
+ return c
35
93
}
36
94
37
95
// Cache persists the files for template versions, and is used by dynamic
@@ -43,15 +101,34 @@ type Cache struct {
43
101
lock sync.Mutex
44
102
data map [uuid.UUID ]* cacheEntry
45
103
fetcher
104
+
105
+ // metrics
106
+ cacheMetrics
107
+ }
108
+
109
+ type cacheMetrics struct {
110
+ currentOpenFileReferences prometheus.Gauge
111
+ totalOpenFileReferences prometheus.Counter
112
+
113
+ currentOpenFiles prometheus.Gauge
114
+ totalOpenedFiles prometheus.Counter
115
+
116
+ currentCacheSize prometheus.Gauge
117
+ totalCacheSize prometheus.Counter
118
+ }
119
+
120
+ type cacheEntryValue struct {
121
+ fs.FS
122
+ size int64
46
123
}
47
124
48
125
type cacheEntry struct {
49
126
// refCount must only be accessed while the Cache lock is held.
50
127
refCount int
51
- value * lazy.ValueWithError [fs. FS ]
128
+ value * lazy.ValueWithError [cacheEntryValue ]
52
129
}
53
130
54
- type fetcher func (context.Context , uuid.UUID ) (fs. FS , error )
131
+ type fetcher func (context.Context , uuid.UUID ) (cacheEntryValue , error )
55
132
56
133
// Acquire will load the fs.FS for the given file. It guarantees that parallel
57
134
// calls for the same fileID will only result in one fetch, and that parallel
@@ -66,27 +143,40 @@ func (c *Cache) Acquire(ctx context.Context, fileID uuid.UUID) (fs.FS, error) {
66
143
it , err := c .prepare (ctx , fileID ).Load ()
67
144
if err != nil {
68
145
c .Release (fileID )
146
+ return nil , err
69
147
}
70
- return it , err
148
+ return it . FS , err
71
149
}
72
150
73
- func (c * Cache ) prepare (ctx context.Context , fileID uuid.UUID ) * lazy.ValueWithError [fs. FS ] {
151
+ func (c * Cache ) prepare (ctx context.Context , fileID uuid.UUID ) * lazy.ValueWithError [cacheEntryValue ] {
74
152
c .lock .Lock ()
75
153
defer c .lock .Unlock ()
76
154
77
155
entry , ok := c .data [fileID ]
78
156
if ! ok {
79
- value := lazy .NewWithError (func () (fs.FS , error ) {
80
- return c .fetcher (ctx , fileID )
157
+ value := lazy .NewWithError (func () (cacheEntryValue , error ) {
158
+ val , err := c .fetcher (ctx , fileID )
159
+
160
+ // Always add to the cache size the bytes of the file loaded.
161
+ if err == nil {
162
+ c .currentCacheSize .Add (float64 (val .size ))
163
+ c .totalCacheSize .Add (float64 (val .size ))
164
+ }
165
+
166
+ return val , err
81
167
})
82
168
83
169
entry = & cacheEntry {
84
170
value : value ,
85
171
refCount : 0 ,
86
172
}
87
173
c .data [fileID ] = entry
174
+ c .currentOpenFiles .Inc ()
175
+ c .totalOpenedFiles .Inc ()
88
176
}
89
177
178
+ c .currentOpenFileReferences .Inc ()
179
+ c .totalOpenFileReferences .Inc ()
90
180
entry .refCount ++
91
181
return entry .value
92
182
}
@@ -105,11 +195,19 @@ func (c *Cache) Release(fileID uuid.UUID) {
105
195
return
106
196
}
107
197
198
+ c .currentOpenFileReferences .Dec ()
108
199
entry .refCount --
109
200
if entry .refCount > 0 {
110
201
return
111
202
}
112
203
204
+ c .currentOpenFiles .Dec ()
205
+
206
+ ev , err := entry .value .Load ()
207
+ if err == nil {
208
+ c .currentCacheSize .Add (- 1 * float64 (ev .size ))
209
+ }
210
+
113
211
delete (c .data , fileID )
114
212
}
115
213
0 commit comments