/**
 * Copyright 2018, OpenCensus Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import * as defaultLogger from '../common/console-logger' ; import { getTimestampWithProcessHRTime , timestampFromMillis , } from '../common/time-util' ; import * as loggerTypes from '../common/types' ; import { Bucket as metricBucket , DistributionValue , LabelValue , Metric , MetricDescriptor , MetricDescriptorType , Point , TimeSeries , Timestamp , } from '../metrics/export/types' ; import { TagMap } from '../tags/tag-map' ; import { TagKey , TagValue } from '../tags/types' ; import { isValidTagKey } from '../tags/validation' ; import { BucketBoundaries } from './bucket-boundaries' ; import { MetricUtils } from './metric-utils' ; import { Recorder } from './recorder' ; import { AggregationData , AggregationType , Measure , Measurement , StatsExemplar , View , } from './types' ; const RECORD_SEPARATOR = String . fromCharCode ( 30 ) ; /**
 * A View specifies an aggregation and a set of tag keys. The aggregation will
 * be broken down by the unique set of matching tag values for each measure.
 */ export class BaseView implements View { /**
     * A string by which the View will be referred to, e.g. "rpc_latency". Names
     * MUST be unique within the library.
     */ readonly name : string ; /** Describes the view, e.g. "RPC latency distribution" */ readonly description : string ; /** The Measure to which this view is applied. */ readonly measure : Measure ; /**
     * A map of stringified tags representing columns labels or tag keys, concept
     * similar to dimensions on multidimensional modeling, to AggregationData.
     * If no Tags are provided, then, all data is recorded in a single
     * aggregation.
     */ private tagValueAggregationMap : { [ key ] : AggregationData ; } = { } ; /**
     * A list of tag keys that represents the possible column labels
     */ private columns : TagKey [ ] ; /**
     * An Aggregation describes how data collected is aggregated.
     * There are four aggregation types: count, sum, lastValue and distirbution.
     */ readonly aggregation : AggregationType ; /** The start time for this view */ readonly startTime : number ; /** The bucket boundaries in a Distribution Aggregation */ private bucketBoundaries ? : BucketBoundaries ; /**
     * Cache a MetricDescriptor to avoid converting View to MetricDescriptor
     * in the future.
     */ private metricDescriptor : MetricDescriptor ; /**
     * The end time for this view - represents the last time a value was recorded
     */ endTime ? : number ; /** true if the view was registered */ registered = false ; /** An object to log information to */ logger : loggerTypes . Logger ; param name param measure param aggregation param tagsKeys param description param bucketBoundaries param logger constructor ( name , measure , aggregation , tagsKeys , description , bucketBoundaries ? , logger = defaultLogger ) { if ( aggregation === AggregationType . DISTRIBUTION && ! bucketBoundaries ) { throw new Error ( 'No bucketBoundaries specified' ) ; } this . logger = logger . logger ( ) ; this . name = name ; this . description = description ; this . measure = measure ; this . columns = this . validateTagKeys ( tagsKeys ) ; this . aggregation = aggregation ; this . startTime = Date . now ( ) ; if ( bucketBoundaries ) { this . bucketBoundaries = new BucketBoundaries ( bucketBoundaries ) ; } this . metricDescriptor = MetricUtils . viewToMetricDescriptor ( this ) ; } /** Gets the view's tag keys */ getColumns ( ) { return this . columns ; } param measurement param tags param attachments recordMeasurement ( measurement , tags , attachments ? ) { const tagValues = Recorder . getTagValues ( tags . tags , this . columns ) ; const encodedTags = this . encodeTagValues ( tagValues ) ; if ( ! this . tagValueAggregationMap [ encodedTags ] ) { this . tagValueAggregationMap [ encodedTags ] = this . createAggregationData ( tagValues ) ; } Recorder . addMeasurement ( this . tagValueAggregationMap [ encodedTags ] , measurement , attachments ) ; } param tagValues private encodeTagValues ( tagValues ) { return tagValues . map ( tagValue => ( tagValue ? tagValue . value : null ) ) . sort ( ) . join ( RECORD_SEPARATOR ) ; } param tagValues private createAggregationData ( tagValues ) { const aggregationMetadata = { tagValues , timestamp : Date . now ( ) } ; switch ( this . aggregation ) { case AggregationType . DISTRIBUTION : const { buckets , bucketCounts } = this . bucketBoundaries ! ; const bucketsCopy = Object . assign ( [ ] , buckets ) ; const bucketCountsCopy = Object . assign ( [ ] , bucketCounts ) ; const exemplars = new Array ( bucketCounts . length ) ; return { ... aggregationMetadata , type : AggregationType . DISTRIBUTION , startTime : this . startTime , count : 0 , sum : 0 , mean : 0 , stdDeviation : 0 , sumOfSquaredDeviation : 0 , buckets : bucketsCopy , bucketCounts : bucketCountsCopy , exemplars , } ; case AggregationType . SUM : return { ... aggregationMetadata , type : AggregationType . SUM , value : 0 } ; case AggregationType . COUNT : return { ... aggregationMetadata , type : AggregationType . COUNT , value : 0 , } ; default : return { ... aggregationMetadata , type : AggregationType . LAST_VALUE , value : 0 , } ; } } param start returns getMetric ( start ) { const { type } = this . metricDescriptor ; let startTimestamp ; const now = getTimestampWithProcessHRTime ( ) ; if ( type !== MetricDescriptorType . GAUGE_INT64 && type !== MetricDescriptorType . GAUGE_DOUBLE ) { startTimestamp = timestampFromMillis ( start ) ; } const timeseries = [ ] ; Object . keys ( this . tagValueAggregationMap ) . forEach ( key => { const { tagValues } = this . tagValueAggregationMap [ key ] ; const labelValues = MetricUtils . tagValuesToLabelValues ( tagValues ) ; const point = this . toPoint ( now , this . getSnapshot ( tagValues ) ) ; if ( startTimestamp ) { timeseries . push ( { startTimestamp , labelValues , points : [ point ] } ) ; } else { timeseries . push ( { labelValues , points : [ point ] } ) ; } } ) ; return { descriptor : this . metricDescriptor , timeseries } ; } param timestamp param data returns private toPoint ( timestamp , data ) { if ( data . type === AggregationType . DISTRIBUTION ) { const { count , sum , sumOfSquaredDeviation , exemplars } = data ; const buckets = [ ] ; if ( data . bucketCounts ) { for ( let bucket = 0 ; bucket < data . bucketCounts . length ; bucket ++ ) { const bucketCount = data . bucketCounts [ bucket ] ; const statsExemplar = exemplars ? exemplars [ bucket ] : undefined ; buckets . push ( this . getMetricBucket ( bucketCount , statsExemplar ) ) ; } } const value = { count , sum , sumOfSquaredDeviation , buckets , bucketOptions : { explicit : { bounds : data . buckets } } , } ; return { timestamp , value } ; } else { const value = data . value ; return { timestamp , value } ; } } param tags returns getSnapshot ( tagValues ) { return this . tagValueAggregationMap [ this . encodeTagValues ( tagValues ) ] ; } /** Returns a Bucket with count and examplar (if present) */ private getMetricBucket ( bucketCount , statsExemplar ? ) { if ( statsExemplar ) { return { count : bucketCount , exemplar : { value : statsExemplar . value , timestamp : timestampFromMillis ( statsExemplar . timestamp ) , attachments : statsExemplar . attachments , } , } ; } return { count : bucketCount } ; } /** Determines whether the given TagKeys are valid. */ private validateTagKeys ( tagKeys ) { const tagKeysCopy = Object . assign ( [ ] , tagKeys ) ; tagKeysCopy . forEach ( tagKey => { if ( ! isValidTagKey ( tagKey ) ) { throw new Error ( `Invalid TagKey name: ${ tagKey }` ) ; } } ) ; const tagKeysSet = new Set ( tagKeysCopy . map ( ( tagKey ) => tagKey . name ) ) ; if ( tagKeysSet . size !== tagKeysCopy . length ) { throw new Error ( 'Columns have duplicate' ) ; } return tagKeysCopy ; } }