1414 * limitations under the License.
1515 */
1616
17- import { AggregationData , AggregationType , Measure , Tags , View } from './types' ;
17+ import { Recorder } from './recorder' ;
18+ import { AggregationData , AggregationMetadata , AggregationType , Bucket , CountData , DistributionData , LastValueData , Measure , Measurement , MeasureType , SumData , Tags , View } from './types' ;
19+
20+ const RECORD_SEPARATOR = String . fromCharCode ( 30 ) ;
21+ const UNIT_SEPARATOR = String . fromCharCode ( 31 ) ;
22+
23+ // String that has only printable characters
24+ const invalidString = / [ ^ \u0020 - \u007e ] / ;
1825
1926export class BaseView implements View {
2027 /**
@@ -32,32 +39,164 @@ export class BaseView implements View {
3239 * If no Tags are provided, then, all data is recorded in a single
3340 * aggregation.
3441 */
35- private rows : { [ key : string ] : AggregationData } ;
42+ private rows : { [ key : string ] : AggregationData } = { } ;
43+ /**
44+ * A list of tag keys that represents the possible column labels
45+ */
46+ private columns : string [ ] ;
3647 /**
3748 * An Aggregation describes how data collected is aggregated.
3849 * There are four aggregation types: count, sum, lastValue and distirbution.
3950 */
4051 readonly aggregation : AggregationType ;
4152 /** The start time for this view */
4253 readonly startTime : number ;
54+ /** The bucket boundaries in a Distribution Aggregation */
55+ private bucketBoundaries ?: number [ ] ;
4356 /**
4457 * The end time for this view - represents the last time a value was recorded
4558 */
4659 endTime : number ;
4760 /** true if the view was registered */
48- registered : boolean ;
61+ registered = false ;
4962
63+ /**
64+ * Creates a new View instance. This constructor is used by Stats. User should
65+ * prefer using Stats.createView() instead.
66+ * @param name The view name
67+ * @param measure The view measure
68+ * @param aggregation The view aggregation type
69+ * @param tagsKeys The Tags' keys that view will have
70+ * @param description The view description
71+ * @param bucketBoundaries The view bucket boundaries for a distribution
72+ * aggregation type
73+ */
5074 constructor (
5175 name : string , measure : Measure , aggregation : AggregationType ,
52- tagKeys : string [ ] , description ?: string ) {
53- throw new Error ( 'Not Implemented' ) ;
76+ tagsKeys : string [ ] , description : string , bucketBoundaries ?: number [ ] ) {
77+ if ( aggregation === AggregationType . DISTRIBUTION && ! bucketBoundaries ) {
78+ throw new Error ( 'No bucketBoundaries specified' ) ;
79+ }
80+ this . name = name ;
81+ this . description = description ;
82+ this . measure = measure ;
83+ this . columns = tagsKeys ;
84+ this . aggregation = aggregation ;
85+ this . startTime = Date . now ( ) ;
86+ this . bucketBoundaries = bucketBoundaries ;
87+ }
88+
89+ /** Gets the view's tag keys */
90+ getColumns ( ) : string [ ] {
91+ return this . columns ;
92+ }
93+
94+ /**
95+ * Records a measurement in the proper view's row. This method is used by
96+ * Stats. User should prefer using Stats.record() instead.
97+ *
98+ * Measurements with measurement type INT64 will have its value truncated.
99+ * @param measurement The measurement to record
100+ */
101+ recordMeasurement ( measurement : Measurement ) {
102+ // Checks if measurement has valid tags
103+ if ( this . invalidTags ( measurement . tags ) ) {
104+ return ;
105+ }
106+
107+ // Checks if measurement has all tags in views
108+ for ( const tagKey of this . columns ) {
109+ if ( ! Object . keys ( measurement . tags ) . some ( ( key ) => key === tagKey ) ) {
110+ return ;
111+ }
112+ }
113+
114+ const encodedTags = this . encodeTags ( measurement . tags ) ;
115+ if ( ! this . rows [ encodedTags ] ) {
116+ this . rows [ encodedTags ] = this . createAggregationData ( measurement . tags ) ;
117+ }
118+ Recorder . addMeasurement ( this . rows [ encodedTags ] , measurement ) ;
119+ }
120+
121+ /**
122+ * Encodes a Tags object into a key sorted string.
123+ * @param tags The tags to encode
124+ */
125+ private encodeTags ( tags : Tags ) : string {
126+ return Object . keys ( tags )
127+ . sort ( )
128+ . map ( tagKey => {
129+ return tagKey + UNIT_SEPARATOR + tags [ tagKey ] ;
130+ } )
131+ . join ( RECORD_SEPARATOR ) ;
132+ }
133+
134+ /**
135+ * Checks if tag keys and values have only printable characters.
136+ * @param tags The tags to be checked
137+ */
138+ private invalidTags ( tags : Tags ) : boolean {
139+ return Object . keys ( tags ) . some ( tagKey => {
140+ return invalidString . test ( tagKey ) || invalidString . test ( tags [ tagKey ] ) ;
141+ } ) ;
142+ }
143+
144+ /**
145+ * Creates an empty aggregation data for a given tags.
146+ * @param tags The tags for that aggregation data
147+ */
148+ private createAggregationData ( tags : Tags ) : AggregationData {
149+ const aggregationMetadata = { tags, timestamp : Date . now ( ) } ;
150+
151+ switch ( this . aggregation ) {
152+ case AggregationType . DISTRIBUTION :
153+ return {
154+ ...aggregationMetadata ,
155+ type : AggregationType . DISTRIBUTION ,
156+ startTime : this . startTime ,
157+ count : 0 ,
158+ sum : 0 ,
159+ max : Number . MIN_SAFE_INTEGER ,
160+ min : Number . MAX_SAFE_INTEGER ,
161+ mean : null as number ,
162+ stdDeviation : null as number ,
163+ sumSquaredDeviations : null as number ,
164+ buckets : this . createBuckets ( this . bucketBoundaries )
165+ } ;
166+ case AggregationType . SUM :
167+ return { ...aggregationMetadata , type : AggregationType . SUM , value : 0 } ;
168+ case AggregationType . COUNT :
169+ return { ...aggregationMetadata , type : AggregationType . COUNT , value : 0 } ;
170+ default :
171+ return {
172+ ...aggregationMetadata ,
173+ type : AggregationType . LAST_VALUE ,
174+ value : undefined
175+ } ;
176+ }
177+ }
178+
179+ /**
180+ * Creates empty Buckets, given a list of bucket boundaries.
181+ * @param bucketBoundaries a list with the bucket boundaries
182+ */
183+ private createBuckets ( bucketBoundaries : number [ ] ) : Bucket [ ] {
184+ return bucketBoundaries . map ( ( boundary , boundaryIndex ) => {
185+ return {
186+ count : 0 ,
187+ lowBoundary : boundaryIndex ? boundary : - Infinity ,
188+ highBoundary : ( boundaryIndex === bucketBoundaries . length - 1 ) ?
189+ Infinity :
190+ bucketBoundaries [ boundaryIndex + 1 ]
191+ } ;
192+ } ) ;
54193 }
55194
56195 /**
57196 * Returns a snapshot of an AggregationData for that tags/labels values.
58197 * @param tags The desired data's tags
59198 */
60199 getSnapshot ( tags : Tags ) : AggregationData {
61- throw new Error ( 'Not Implemented' ) ;
200+ return this . rows [ this . encodeTags ( tags ) ] ;
62201 }
63202}
0 commit comments