55import Store from "../storage" ;
66import { MetricType , Lifetime , Metric } from "./" ;
77import { createMetric , validateMetricInternalRepresentation } from "./utils" ;
8- import { isObject , isUndefined , JSONValue } from "../utils" ;
8+ import { isObject , isUndefined , JSONObject , JSONValue } from "../utils" ;
99import Glean from "../glean" ;
1010
1111export interface Metrics {
@@ -133,13 +133,50 @@ class MetricsDatabase {
133133 }
134134
135135 const store = this . _chooseStore ( metric . lifetime ) ;
136- const storageKey = metric . identifier ;
136+ const storageKey = await metric . getAsyncIdentifier ( ) ;
137137 for ( const ping of metric . sendInPings ) {
138138 const finalTransformFn = ( v ?: JSONValue ) : JSONValue => transformFn ( v ) . get ( ) ;
139139 await store . update ( [ ping , metric . type , storageKey ] , finalTransformFn ) ;
140140 }
141141 }
142142
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+
143180 /**
144181 * Gets and validates the persisted payload of a given metric in a given ping.
145182 *
@@ -168,10 +205,10 @@ class MetricsDatabase {
168205 metric : MetricType
169206 ) : Promise < T | undefined > {
170207 const store = this . _chooseStore ( metric . lifetime ) ;
171- const storageKey = metric . identifier ;
208+ const storageKey = await metric . getAsyncIdentifier ( ) ;
172209 const value = await store . get ( [ ping , metric . type , storageKey ] ) ;
173210 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.` ) ;
175212 await store . delete ( [ ping , metric . type , storageKey ] ) ;
176213 return ;
177214 } else {
@@ -210,6 +247,30 @@ class MetricsDatabase {
210247 return data ;
211248 }
212249
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+
213274 /**
214275 * Gets all of the persisted metrics related to a given ping.
215276 *
@@ -228,13 +289,21 @@ class MetricsDatabase {
228289 await this . clear ( Lifetime . Ping , ping ) ;
229290 }
230291
231- const response : Metrics = { ... pingData } ;
232- for ( const data of [ userData , appData ] ) {
292+ const response : Metrics = { } ;
293+ for ( const data of [ userData , pingData , appData ] ) {
233294 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+ }
238307 }
239308 }
240309
0 commit comments