5
5
import Store from "../storage" ;
6
6
import { MetricType , Lifetime , Metric } from "./" ;
7
7
import { createMetric , validateMetricInternalRepresentation } from "./utils" ;
8
- import { isObject , isUndefined , JSONValue } from "../utils" ;
8
+ import { isObject , isUndefined , JSONObject , JSONValue } from "../utils" ;
9
9
import Glean from "../glean" ;
10
10
11
11
export interface Metrics {
@@ -133,13 +133,50 @@ class MetricsDatabase {
133
133
}
134
134
135
135
const store = this . _chooseStore ( metric . lifetime ) ;
136
- const storageKey = metric . identifier ;
136
+ const storageKey = await metric . getAsyncIdentifier ( ) ;
137
137
for ( const ping of metric . sendInPings ) {
138
138
const finalTransformFn = ( v ?: JSONValue ) : JSONValue => transformFn ( v ) . get ( ) ;
139
139
await store . update ( [ ping , metric . type , storageKey ] , finalTransformFn ) ;
140
140
}
141
141
}
142
142
143
+ /**
144
+ * Checks if anything was stored for the provided metric.
145
+ *
146
+ * @param lifetime the metric `Lifetime`.
147
+ * @param ping the ping storage to search in.
148
+ * @param metricType the type of the metric.
149
+ * @param metricIdentifier the metric identifier.
150
+ *
151
+ * @returns `true` if the metric was found (regardless of the validity of the
152
+ * stored data), `false` otherwise.
153
+ */
154
+ async hasMetric ( lifetime : Lifetime , ping : string , metricType : string , metricIdentifier : string ) : Promise < boolean > {
155
+ const store = this . _chooseStore ( lifetime ) ;
156
+ const value = await store . get ( [ ping , metricType , metricIdentifier ] ) ;
157
+ return ! isUndefined ( value ) ;
158
+ }
159
+
160
+ /**
161
+ * Counts the number of stored metrics with an id starting with a specific identifier.
162
+ *
163
+ * @param lifetime the metric `Lifetime`.
164
+ * @param ping the ping storage to search in.
165
+ * @param metricType the type of the metric.
166
+ * @param metricIdentifier the metric identifier.
167
+ *
168
+ * @returns the number of stored metrics with their id starting with the given identifier.
169
+ */
170
+ async countByBaseIdentifier ( lifetime : Lifetime , ping : string , metricType : string , metricIdentifier : string ) : Promise < number > {
171
+ const store = this . _chooseStore ( lifetime ) ;
172
+ const pingStorage = await store . get ( [ ping , metricType ] ) ;
173
+ if ( isUndefined ( pingStorage ) ) {
174
+ return 0 ;
175
+ }
176
+
177
+ return Object . keys ( pingStorage ) . filter ( n => n . startsWith ( metricIdentifier ) ) . length ;
178
+ }
179
+
143
180
/**
144
181
* Gets and validates the persisted payload of a given metric in a given ping.
145
182
*
@@ -168,10 +205,10 @@ class MetricsDatabase {
168
205
metric : MetricType
169
206
) : Promise < T | undefined > {
170
207
const store = this . _chooseStore ( metric . lifetime ) ;
171
- const storageKey = metric . identifier ;
208
+ const storageKey = await metric . getAsyncIdentifier ( ) ;
172
209
const value = await store . get ( [ ping , metric . type , storageKey ] ) ;
173
210
if ( ! isUndefined ( value ) && ! validateMetricInternalRepresentation < T > ( metric . type , value ) ) {
174
- console . error ( `Unexpected value found for metric ${ metric . identifier } : ${ JSON . stringify ( value ) } . Clearing.` ) ;
211
+ console . error ( `Unexpected value found for metric ${ storageKey } : ${ JSON . stringify ( value ) } . Clearing.` ) ;
175
212
await store . delete ( [ ping , metric . type , storageKey ] ) ;
176
213
return ;
177
214
} else {
@@ -210,6 +247,30 @@ class MetricsDatabase {
210
247
return data ;
211
248
}
212
249
250
+ private processLabeledMetric ( snapshot : Metrics , metricType : string , metricId : string , metricData : JSONValue ) {
251
+ const newType = `labeled_${ metricType } ` ;
252
+ const idLabelSplit = metricId . split ( "/" , 2 ) ;
253
+ const newId = idLabelSplit [ 0 ] ;
254
+ const label = idLabelSplit [ 1 ] ;
255
+
256
+ if ( newType in snapshot && newId in snapshot [ newType ] ) {
257
+ // Other labels were found for this metric. Do not throw them away.
258
+ const existingData = snapshot [ newType ] [ newId ] ;
259
+ snapshot [ newType ] [ newId ] = {
260
+ ...( existingData as JSONObject ) ,
261
+ [ label ] : metricData
262
+ } ;
263
+ } else {
264
+ // This is the first label for this metric.
265
+ snapshot [ newType ] = {
266
+ ...snapshot [ newType ] ,
267
+ [ newId ] : {
268
+ [ label ] : metricData
269
+ }
270
+ } ;
271
+ }
272
+ }
273
+
213
274
/**
214
275
* Gets all of the persisted metrics related to a given ping.
215
276
*
@@ -228,13 +289,21 @@ class MetricsDatabase {
228
289
await this . clear ( Lifetime . Ping , ping ) ;
229
290
}
230
291
231
- const response : Metrics = { ... pingData } ;
232
- for ( const data of [ userData , appData ] ) {
292
+ const response : Metrics = { } ;
293
+ for ( const data of [ userData , pingData , appData ] ) {
233
294
for ( const metricType in data ) {
234
- response [ metricType ] = {
235
- ...response [ metricType ] ,
236
- ...data [ metricType ]
237
- } ;
295
+ for ( const metricId in data [ metricType ] ) {
296
+ if ( metricId . includes ( "/" ) ) {
297
+ // While labeled data is stored within the subtype storage (e.g. counter storage), it
298
+ // needs to live in a different section of the ping payload (e.g. `labeled_counter`).
299
+ this . processLabeledMetric ( response , metricType , metricId , data [ metricType ] [ metricId ] ) ;
300
+ } else {
301
+ response [ metricType ] = {
302
+ ...response [ metricType ] ,
303
+ [ metricId ] : data [ metricType ] [ metricId ]
304
+ } ;
305
+ }
306
+ }
238
307
}
239
308
}
240
309
0 commit comments