@@ -292,7 +292,7 @@ const aggFnExpr = ({
292
292
293
293
async function renderSelectList (
294
294
selectList : SelectList ,
295
- chartConfig : ChartConfigWithOptDateRateAndCte ,
295
+ chartConfig : ChartConfigWithOptDateRangeEx ,
296
296
metadata : Metadata ,
297
297
) {
298
298
if ( typeof selectList === 'string' ) {
@@ -467,7 +467,7 @@ async function timeFilterExpr({
467
467
}
468
468
469
469
async function renderSelect (
470
- chartConfig : ChartConfigWithOptDateRateAndCte ,
470
+ chartConfig : ChartConfigWithOptDateRangeEx ,
471
471
metadata : Metadata ,
472
472
) : Promise < ChSql > {
473
473
/**
@@ -565,7 +565,7 @@ async function renderWhereExpression({
565
565
}
566
566
567
567
async function renderWhere (
568
- chartConfig : ChartConfigWithOptDateRateAndCte ,
568
+ chartConfig : ChartConfigWithOptDateRangeEx ,
569
569
metadata : Metadata ,
570
570
) : Promise < ChSql > {
571
571
let whereSearchCondition : ChSql | [ ] = [ ] ;
@@ -729,28 +729,64 @@ function renderLimit(
729
729
730
730
// CTE (Common Table Expressions) isn't exported at this time. It's only used internally
731
731
// for metric SQL generation.
732
- type ChartConfigWithOptDateRateAndCte = ChartConfigWithOptDateRange & {
732
+ type ChartConfigWithOptDateRangeEx = ChartConfigWithOptDateRange & {
733
733
with ?: { name : string ; sql : ChSql } [ ] ;
734
734
} ;
735
735
736
736
function renderWith (
737
- chartConfig : ChartConfigWithOptDateRateAndCte ,
737
+ chartConfig : ChartConfigWithOptDateRangeEx ,
738
738
metadata : Metadata ,
739
739
) : ChSql | undefined {
740
740
const { with : withClauses } = chartConfig ;
741
741
if ( withClauses ) {
742
742
return concatChSql (
743
- '' ,
744
- withClauses . map ( clause => chSql `WITH ${ clause . name } AS (${ clause . sql } )` ) ,
743
+ ', ' ,
744
+ withClauses . map ( clause => chSql `${ clause . name } AS (${ clause . sql } )` ) ,
745
745
) ;
746
746
}
747
747
748
748
return undefined ;
749
749
}
750
750
751
+ function intervalToSeconds ( interval : SQLInterval ) : number {
752
+ // Parse interval string like "15 second" into number of seconds
753
+ const [ amount , unit ] = interval . split ( ' ' ) ;
754
+ const value = parseInt ( amount , 10 ) ;
755
+ switch ( unit ) {
756
+ case 'second' :
757
+ return value ;
758
+ case 'minute' :
759
+ return value * 60 ;
760
+ case 'hour' :
761
+ return value * 60 * 60 ;
762
+ case 'day' :
763
+ return value * 24 * 60 * 60 ;
764
+ default :
765
+ throw new Error ( `Invalid interval unit ${ unit } in interval ${ interval } ` ) ;
766
+ }
767
+ }
768
+
769
+ function renderFill (
770
+ chartConfig : ChartConfigWithOptDateRangeEx ,
771
+ ) : ChSql | undefined {
772
+ const { granularity, dateRange } = chartConfig ;
773
+ if ( dateRange && granularity && granularity !== 'auto' ) {
774
+ const [ start , end ] = dateRange ;
775
+ const step = intervalToSeconds ( granularity ) ;
776
+
777
+ return concatChSql ( ' ' , [
778
+ chSql `FROM toUnixTimestamp(toStartOfInterval(fromUnixTimestamp64Milli(${ { Int64 : start . getTime ( ) } } ), INTERVAL ${ granularity } ))
779
+ TO toUnixTimestamp(toStartOfInterval(fromUnixTimestamp64Milli(${ { Int64 : end . getTime ( ) } } ), INTERVAL ${ granularity } ))
780
+ STEP ${ { Int32 : step } } ` ,
781
+ ] ) ;
782
+ }
783
+
784
+ return undefined ;
785
+ }
786
+
751
787
function translateMetricChartConfig (
752
788
chartConfig : ChartConfigWithOptDateRange ,
753
- ) : ChartConfigWithOptDateRateAndCte {
789
+ ) : ChartConfigWithOptDateRangeEx {
754
790
const metricTables = chartConfig . metricTables ;
755
791
if ( ! metricTables ) {
756
792
return chartConfig ;
@@ -810,8 +846,67 @@ function translateMetricChartConfig(
810
846
databaseName : '' ,
811
847
tableName : 'RawSum' ,
812
848
} ,
813
- where : `MetricName = '${ metricName } '` ,
814
- whereLanguage : 'sql' ,
849
+ } ;
850
+ } else if ( metricType === MetricsDataType . Histogram && metricName ) {
851
+ // histograms are only valid for quantile selections
852
+ const { aggFn, level, ..._selectRest } = _select as {
853
+ aggFn : string ;
854
+ level ?: number ;
855
+ } ;
856
+
857
+ if ( aggFn !== 'quantile' || level == null ) {
858
+ throw new Error ( 'quantile must be specified for histogram metrics' ) ;
859
+ }
860
+
861
+ return {
862
+ ...restChartConfig ,
863
+ with : [
864
+ {
865
+ name : 'HistRate' ,
866
+ sql : chSql `SELECT *, any(BucketCounts) OVER (ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PrevBucketCounts,
867
+ any(CountLength) OVER (ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PrevCountLength,
868
+ any(AttributesHash) OVER (ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS PrevAttributesHash,
869
+ IF(AggregationTemporality = 1,
870
+ BucketCounts,
871
+ IF(AttributesHash = PrevAttributesHash AND CountLength = PrevCountLength,
872
+ arrayMap((prev, curr) -> IF(curr < prev, curr, toUInt64(toInt64(curr) - toInt64(prev))), PrevBucketCounts, BucketCounts),
873
+ BucketCounts)) as BucketRates
874
+ FROM (
875
+ SELECT *, cityHash64(mapConcat(ScopeAttributes, ResourceAttributes, Attributes)) AS AttributesHash,
876
+ length(BucketCounts) as CountLength
877
+ FROM ${ renderFrom ( { from : { ...from , tableName : metricTables [ MetricsDataType . Histogram ] } } ) } )
878
+ WHERE MetricName = '${ metricName } '
879
+ ORDER BY Attributes, TimeUnix ASC
880
+ ` ,
881
+ } ,
882
+ {
883
+ name : 'RawHist' ,
884
+ sql : chSql `
885
+ SELECT *, toUInt64( ${ { Float64 : level } } * arraySum(BucketRates)) AS Rank,
886
+ arrayCumSum(BucketRates) as CumRates,
887
+ arrayFirstIndex(x -> if(x > Rank, 1, 0), CumRates) AS BucketLowIdx,
888
+ IF(BucketLowIdx = length(BucketRates),
889
+ ExplicitBounds[length(ExplicitBounds)], -- if the low bound is the last bucket, use the last bound value
890
+ IF(BucketLowIdx > 1, -- indexes are 1-based
891
+ ExplicitBounds[BucketLowIdx] + (ExplicitBounds[BucketLowIdx + 1] - ExplicitBounds[BucketLowIdx]) *
892
+ intDivOrZero(
893
+ Rank - CumRates[BucketLowIdx - 1],
894
+ CumRates[BucketLowIdx] - CumRates[BucketLowIdx - 1]),
895
+ arrayElement(ExplicitBounds, BucketLowIdx + 1) * intDivOrZero(Rank, CumRates[BucketLowIdx]))) as Rate
896
+ FROM HistRate` ,
897
+ } ,
898
+ ] ,
899
+ select : [
900
+ {
901
+ ..._selectRest ,
902
+ aggFn : 'sum' ,
903
+ valueExpression : 'Rate' ,
904
+ } ,
905
+ ] ,
906
+ from : {
907
+ databaseName : '' ,
908
+ tableName : 'RawHist' ,
909
+ } ,
815
910
} ;
816
911
}
817
912
@@ -835,15 +930,19 @@ export async function renderChartConfig(
835
930
const where = await renderWhere ( chartConfig , metadata ) ;
836
931
const groupBy = await renderGroupBy ( chartConfig , metadata ) ;
837
932
const orderBy = renderOrderBy ( chartConfig ) ;
933
+ const fill = renderFill ( chartConfig ) ;
838
934
const limit = renderLimit ( chartConfig ) ;
839
935
840
- return chSql `${
841
- withClauses ?. sql ? chSql `${ withClauses } ` : ''
842
- } SELECT ${ select } FROM ${ from } ${ where ?. sql ? chSql `WHERE ${ where } ` : '' } ${
843
- groupBy ?. sql ? chSql `GROUP BY ${ groupBy } ` : ''
844
- } ${ orderBy ?. sql ? chSql `ORDER BY ${ orderBy } ` : '' } ${
845
- limit ?. sql ? chSql `LIMIT ${ limit } ` : ''
846
- } `;
936
+ return concatChSql ( ' ' , [
937
+ chSql `${ withClauses ?. sql ? chSql `WITH ${ withClauses } ` : '' } ` ,
938
+ chSql `SELECT ${ select } ` ,
939
+ chSql `FROM ${ from } ` ,
940
+ chSql `${ where . sql ? chSql `WHERE ${ where } ` : '' } ` ,
941
+ chSql `${ groupBy ?. sql ? chSql `GROUP BY ${ groupBy } ` : '' } ` ,
942
+ chSql `${ orderBy ?. sql ? chSql `ORDER BY ${ orderBy } ` : '' } ` ,
943
+ chSql `${ fill ?. sql ? chSql `WITH FILL ${ fill } ` : '' } ` ,
944
+ chSql `${ limit ?. sql ? chSql `LIMIT ${ limit } ` : '' } ` ,
945
+ ] ) ;
847
946
}
848
947
849
948
// EditForm -> translateToQueriedChartConfig -> QueriedChartConfig
0 commit comments