2021-05-22 13:35:30 +05:30
package clickhouseReader
import (
"context"
2023-04-06 13:32:24 +05:30
"database/sql"
2021-11-22 16:15:58 +05:30
"encoding/json"
2021-05-22 13:35:30 +05:30
"fmt"
2023-10-19 14:16:20 +05:30
"math"
2022-05-03 11:20:57 +05:30
"math/rand"
2021-05-27 12:52:34 +05:30
"os"
2022-06-24 14:52:11 +05:30
"reflect"
2022-04-01 11:22:25 +05:30
"regexp"
2021-11-22 16:15:58 +05:30
"sort"
2021-05-29 16:32:11 +05:30
"strconv"
2021-10-20 13:18:19 +05:30
"strings"
2021-11-22 16:15:58 +05:30
"sync"
2021-05-22 13:35:30 +05:30
"time"
2021-05-27 12:52:34 +05:30
2025-02-20 13:49:44 +05:30
"go.signoz.io/signoz/pkg/query-service/model/metrics_explorer"
2021-08-29 10:28:40 +05:30
"github.com/go-kit/log"
2021-11-22 16:15:58 +05:30
"github.com/go-kit/log/level"
2022-05-25 16:55:30 +05:30
"github.com/google/uuid"
2022-11-24 18:18:19 +05:30
"github.com/mailru/easyjson"
2021-11-22 16:15:58 +05:30
"github.com/oklog/oklog/pkg/group"
2022-05-03 11:20:57 +05:30
"github.com/pkg/errors"
2021-08-29 10:28:40 +05:30
"github.com/prometheus/common/promlog"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/promql"
2022-07-14 11:59:06 +05:30
2021-11-22 16:15:58 +05:30
"github.com/prometheus/prometheus/storage"
2021-08-29 10:28:40 +05:30
"github.com/prometheus/prometheus/storage/remote"
"github.com/prometheus/prometheus/util/stats"
2022-05-03 11:20:57 +05:30
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/jmoiron/sqlx"
2025-01-24 00:16:38 +05:30
"go.signoz.io/signoz/pkg/cache"
2025-02-17 18:16:41 +05:30
"go.signoz.io/signoz/pkg/types/authtypes"
2021-05-27 12:52:34 +05:30
2022-05-03 11:20:57 +05:30
promModel "github.com/prometheus/common/model"
2023-10-19 14:16:20 +05:30
"go.uber.org/zap"
2024-08-14 19:53:36 +05:30
queryprogress "go.signoz.io/signoz/pkg/query-service/app/clickhouseReader/query_progress"
2022-10-06 20:13:30 +05:30
"go.signoz.io/signoz/pkg/query-service/app/logs"
2024-11-22 21:57:25 +05:30
"go.signoz.io/signoz/pkg/query-service/app/resource"
2023-03-28 22:15:46 +05:30
"go.signoz.io/signoz/pkg/query-service/app/services"
2025-01-24 00:16:38 +05:30
"go.signoz.io/signoz/pkg/query-service/app/traces/tracedetail"
2024-03-01 14:51:50 +05:30
"go.signoz.io/signoz/pkg/query-service/common"
2022-10-06 20:13:30 +05:30
"go.signoz.io/signoz/pkg/query-service/constants"
2024-05-15 18:52:01 +05:30
chErrors "go.signoz.io/signoz/pkg/query-service/errors"
2022-10-06 20:13:30 +05:30
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
2022-11-24 18:18:19 +05:30
"go.signoz.io/signoz/pkg/query-service/interfaces"
2025-02-06 17:26:58 +05:30
"go.signoz.io/signoz/pkg/query-service/metrics"
2022-10-06 20:13:30 +05:30
"go.signoz.io/signoz/pkg/query-service/model"
2023-03-04 00:05:16 +05:30
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
2022-12-28 02:16:46 +05:30
"go.signoz.io/signoz/pkg/query-service/telemetry"
2022-10-06 20:13:30 +05:30
"go.signoz.io/signoz/pkg/query-service/utils"
2021-05-22 13:35:30 +05:30
)
const (
2024-10-08 19:35:38 +05:30
primaryNamespace = "clickhouse"
archiveNamespace = "clickhouse-archive"
signozTraceDBName = "signoz_traces"
signozHistoryDBName = "signoz_analytics"
ruleStateHistoryTableName = "distributed_rule_state_history_v0"
signozDurationMVTable = "distributed_durationSort"
signozUsageExplorerTable = "distributed_usage_explorer"
signozSpansTable = "distributed_signoz_spans"
signozErrorIndexTable = "distributed_signoz_error_index_v2"
signozTraceTableName = "distributed_signoz_index_v2"
signozTraceLocalTableName = "signoz_index_v2"
signozMetricDBName = "signoz_metrics"
2024-05-21 12:01:21 +05:30
signozSampleLocalTableName = "samples_v4"
signozSampleTableName = "distributed_samples_v4"
2024-10-08 19:35:38 +05:30
signozSamplesAgg5mLocalTableName = "samples_v4_agg_5m"
signozSamplesAgg5mTableName = "distributed_samples_v4_agg_5m"
signozSamplesAgg30mLocalTableName = "samples_v4_agg_30m"
signozSamplesAgg30mTableName = "distributed_samples_v4_agg_30m"
signozExpHistLocalTableName = "exp_hist"
signozExpHistTableName = "distributed_exp_hist"
2024-05-21 12:01:21 +05:30
signozTSLocalTableNameV4 = "time_series_v4"
signozTSTableNameV4 = "distributed_time_series_v4"
signozTSLocalTableNameV46Hrs = "time_series_v4_6hrs"
signozTSTableNameV46Hrs = "distributed_time_series_v4_6hrs"
signozTSLocalTableNameV41Day = "time_series_v4_1day"
signozTSTableNameV41Day = "distributed_time_series_v4_1day"
2022-05-03 11:20:57 +05:30
2024-10-08 19:35:38 +05:30
signozTSLocalTableNameV41Week = "time_series_v4_1week"
signozTSTableNameV41Week = "distributed_time_series_v4_1week"
2022-05-03 11:20:57 +05:30
minTimespanForProgressiveSearch = time . Hour
minTimespanForProgressiveSearchMargin = time . Minute
maxProgressiveSteps = 4
charset = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
2024-09-13 17:04:22 +05:30
NANOSECOND = 1000000000
2021-05-22 13:35:30 +05:30
)
var (
2022-05-03 11:20:57 +05:30
ErrNoOperationsTable = errors . New ( "no operations table supplied" )
ErrNoIndexTable = errors . New ( "no index table supplied" )
ErrStartTimeRequired = errors . New ( "start time is required for search queries" )
seededRand * rand . Rand = rand . New (
rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
2021-05-22 13:35:30 +05:30
)
// SpanWriter for reading spans from ClickHouse
2021-05-27 12:52:34 +05:30
type ClickHouseReader struct {
2022-08-04 11:57:05 +05:30
db clickhouse . Conn
localDB * sqlx . DB
2022-11-24 18:18:19 +05:30
TraceDB string
2022-08-04 11:57:05 +05:30
operationsTable string
durationTable string
indexTable string
errorTable string
2022-08-04 17:32:45 +05:30
usageExplorerTable string
2022-11-24 18:18:19 +05:30
SpansTable string
2024-12-16 15:29:16 +05:30
spanAttributeTableV2 string
2023-04-25 21:53:46 +05:30
spanAttributesKeysTable string
2022-08-04 12:38:53 +05:30
dependencyGraphTable string
2022-08-04 11:57:05 +05:30
topLevelOperationsTable string
2022-08-04 17:32:45 +05:30
logsDB string
logsTable string
2022-12-02 12:30:28 +05:30
logsLocalTable string
2022-08-04 17:32:45 +05:30
logsAttributeKeys string
logsResourceKeys string
2024-12-16 15:29:16 +05:30
logsTagAttributeTableV2 string
2022-08-04 11:57:05 +05:30
queryEngine * promql . Engine
remoteStorage * remote . Storage
2022-09-12 12:30:36 +05:30
fanoutStorage * storage . Storage
2024-08-14 19:53:36 +05:30
queryProgressTracker queryprogress . QueryProgressTracker
2022-07-14 11:59:06 +05:30
2024-09-13 17:04:22 +05:30
logsTableV2 string
logsLocalTableV2 string
logsResourceTableV2 string
logsResourceLocalTableV2 string
2022-07-14 11:59:06 +05:30
promConfigFile string
promConfig * config . Config
alertManager am . Manager
2022-11-24 18:18:19 +05:30
featureFlags interfaces . FeatureLookup
2022-07-25 14:42:58 +05:30
liveTailRefreshSeconds int
2023-10-20 12:37:45 +05:30
cluster string
2024-09-12 10:58:07 +05:30
2024-11-20 23:35:44 +05:30
useLogsNewSchema bool
useTraceNewSchema bool
2024-09-16 14:30:31 +05:30
logsTableName string
logsLocalTableName string
2024-11-20 23:35:44 +05:30
traceTableName string
traceLocalTableName string
traceResourceTableV3 string
traceSummaryTable string
2025-01-24 00:16:38 +05:30
fluxIntervalForTraceDetail time . Duration
cache cache . Cache
2021-05-22 13:35:30 +05:30
}
// NewTraceReader returns a TraceReader for the database
2023-08-10 17:20:34 +05:30
func NewReader (
localDB * sqlx . DB ,
2025-01-30 15:51:55 +05:30
db driver . Conn ,
2023-08-10 17:20:34 +05:30
configFile string ,
featureFlag interfaces . FeatureLookup ,
2023-10-20 12:37:45 +05:30
cluster string ,
2024-09-12 10:58:07 +05:30
useLogsNewSchema bool ,
2024-11-22 12:00:29 +05:30
useTraceNewSchema bool ,
2025-01-24 00:16:38 +05:30
fluxIntervalForTraceDetail time . Duration ,
cache cache . Cache ,
2023-08-10 17:20:34 +05:30
) * ClickHouseReader {
2025-01-30 15:51:55 +05:30
options := NewOptions ( primaryNamespace , archiveNamespace )
2025-01-24 00:16:38 +05:30
return NewReaderFromClickhouseConnection ( db , options , localDB , configFile , featureFlag , cluster , useLogsNewSchema , useTraceNewSchema , fluxIntervalForTraceDetail , cache )
2024-03-05 15:23:56 +05:30
}
func NewReaderFromClickhouseConnection (
db driver . Conn ,
options * Options ,
localDB * sqlx . DB ,
configFile string ,
featureFlag interfaces . FeatureLookup ,
cluster string ,
2024-09-12 10:58:07 +05:30
useLogsNewSchema bool ,
2024-11-22 12:00:29 +05:30
useTraceNewSchema bool ,
2025-01-24 00:16:38 +05:30
fluxIntervalForTraceDetail time . Duration ,
cache cache . Cache ,
2024-03-05 15:23:56 +05:30
) * ClickHouseReader {
2024-09-17 11:41:46 +05:30
alertManager , err := am . New ( )
2022-07-14 11:59:06 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to initialize alert manager" , zap . Error ( err ) )
zap . L ( ) . Error ( "check if the alert manager URL is correctly set and valid" )
2022-07-14 11:59:06 +05:30
os . Exit ( 1 )
}
2022-03-28 21:01:57 +05:30
2024-09-13 17:04:22 +05:30
logsTableName := options . primary . LogsTable
2024-09-16 14:30:31 +05:30
logsLocalTableName := options . primary . LogsLocalTable
2024-09-13 17:04:22 +05:30
if useLogsNewSchema {
logsTableName = options . primary . LogsTableV2
2024-09-16 14:30:31 +05:30
logsLocalTableName = options . primary . LogsLocalTableV2
2024-09-13 17:04:22 +05:30
}
2024-11-20 23:35:44 +05:30
traceTableName := options . primary . IndexTable
traceLocalTableName := options . primary . LocalIndexTable
2024-11-22 12:00:29 +05:30
if useTraceNewSchema {
traceTableName = options . primary . TraceIndexTableV3
traceLocalTableName = options . primary . TraceLocalTableNameV3
}
2024-11-20 23:35:44 +05:30
2021-11-22 16:15:58 +05:30
return & ClickHouseReader {
2025-01-30 15:51:55 +05:30
db : db ,
2022-08-04 11:57:05 +05:30
localDB : localDB ,
2022-11-24 18:18:19 +05:30
TraceDB : options . primary . TraceDB ,
2022-08-04 11:57:05 +05:30
alertManager : alertManager ,
operationsTable : options . primary . OperationsTable ,
indexTable : options . primary . IndexTable ,
errorTable : options . primary . ErrorTable ,
2022-08-04 12:55:21 +05:30
usageExplorerTable : options . primary . UsageExplorerTable ,
2022-08-04 11:57:05 +05:30
durationTable : options . primary . DurationTable ,
2022-11-24 18:18:19 +05:30
SpansTable : options . primary . SpansTable ,
2024-12-16 15:29:16 +05:30
spanAttributeTableV2 : options . primary . SpanAttributeTableV2 ,
2023-04-25 21:53:46 +05:30
spanAttributesKeysTable : options . primary . SpanAttributeKeysTable ,
2022-08-04 12:38:53 +05:30
dependencyGraphTable : options . primary . DependencyGraphTable ,
2022-08-04 11:57:05 +05:30
topLevelOperationsTable : options . primary . TopLevelOperationsTable ,
2022-08-04 17:32:45 +05:30
logsDB : options . primary . LogsDB ,
logsTable : options . primary . LogsTable ,
2022-12-02 12:30:28 +05:30
logsLocalTable : options . primary . LogsLocalTable ,
2022-08-04 17:32:45 +05:30
logsAttributeKeys : options . primary . LogsAttributeKeysTable ,
logsResourceKeys : options . primary . LogsResourceKeysTable ,
2024-12-16 15:29:16 +05:30
logsTagAttributeTableV2 : options . primary . LogsTagAttributeTableV2 ,
2022-08-04 17:32:45 +05:30
liveTailRefreshSeconds : options . primary . LiveTailRefreshSeconds ,
2022-08-04 11:57:05 +05:30
promConfigFile : configFile ,
2022-11-24 18:18:19 +05:30
featureFlags : featureFlag ,
2023-10-20 12:37:45 +05:30
cluster : cluster ,
2024-08-14 19:53:36 +05:30
queryProgressTracker : queryprogress . NewQueryProgressTracker ( ) ,
2024-09-13 17:04:22 +05:30
2024-11-22 12:00:29 +05:30
useLogsNewSchema : useLogsNewSchema ,
useTraceNewSchema : useTraceNewSchema ,
2024-09-13 17:04:22 +05:30
logsTableV2 : options . primary . LogsTableV2 ,
logsLocalTableV2 : options . primary . LogsLocalTableV2 ,
logsResourceTableV2 : options . primary . LogsResourceTableV2 ,
logsResourceLocalTableV2 : options . primary . LogsResourceLocalTableV2 ,
logsTableName : logsTableName ,
2024-09-16 14:30:31 +05:30
logsLocalTableName : logsLocalTableName ,
2024-11-20 23:35:44 +05:30
traceLocalTableName : traceLocalTableName ,
traceTableName : traceTableName ,
traceResourceTableV3 : options . primary . TraceResourceTableV3 ,
traceSummaryTable : options . primary . TraceSummaryTable ,
2025-01-24 00:16:38 +05:30
fluxIntervalForTraceDetail : fluxIntervalForTraceDetail ,
cache : cache ,
2021-11-22 16:15:58 +05:30
}
}
2022-09-12 12:30:36 +05:30
func ( r * ClickHouseReader ) Start ( readerReady chan bool ) {
2021-08-29 10:28:40 +05:30
logLevel := promlog . AllowedLevel { }
logLevel . Set ( "debug" )
2023-03-07 13:37:31 +05:30
allowedFormat := promlog . AllowedFormat { }
allowedFormat . Set ( "logfmt" )
2021-08-29 10:28:40 +05:30
2023-03-07 13:37:31 +05:30
promlogConfig := promlog . Config {
Level : & logLevel ,
Format : & allowedFormat ,
}
2021-08-29 10:28:40 +05:30
2023-03-07 13:37:31 +05:30
logger := promlog . New ( & promlogConfig )
2021-08-29 10:28:40 +05:30
2021-11-22 16:15:58 +05:30
startTime := func ( ) ( int64 , error ) {
return int64 ( promModel . Latest ) , nil
}
2023-03-07 13:37:31 +05:30
remoteStorage := remote . NewStorage (
log . With ( logger , "component" , "remote" ) ,
nil ,
startTime ,
"" ,
time . Duration ( 1 * time . Minute ) ,
nil ,
2024-10-10 14:10:28 +05:30
false ,
2023-03-07 13:37:31 +05:30
)
2021-11-22 16:15:58 +05:30
cfg := struct {
configFile string
localStoragePath string
lookbackDelta promModel . Duration
webTimeout promModel . Duration
queryTimeout promModel . Duration
queryConcurrency int
queryMaxSamples int
RemoteFlushDeadline promModel . Duration
prometheusURL string
logLevel promlog . AllowedLevel
} {
2022-07-14 11:59:06 +05:30
configFile : r . promConfigFile ,
2021-11-22 16:15:58 +05:30
}
fanoutStorage := storage . NewFanout ( logger , remoteStorage )
2021-08-29 10:28:40 +05:30
opts := promql . EngineOpts {
2023-03-07 13:37:31 +05:30
Logger : log . With ( logger , "component" , "query engine" ) ,
Reg : nil ,
MaxSamples : 50000000 ,
Timeout : time . Duration ( 2 * time . Minute ) ,
ActiveQueryTracker : promql . NewActiveQueryTracker (
"" ,
20 ,
log . With ( logger , "component" , "activeQueryTracker" ) ,
) ,
2021-08-29 10:28:40 +05:30
}
queryEngine := promql . NewEngine ( opts )
2021-11-22 16:15:58 +05:30
reloaders := [ ] func ( cfg * config . Config ) error {
remoteStorage . ApplyConfig ,
2021-08-29 10:28:40 +05:30
}
2021-11-22 16:15:58 +05:30
// sync.Once is used to make sure we can close the channel at different execution stages(SIGTERM or when the config is loaded).
type closeOnce struct {
C chan struct { }
once sync . Once
Close func ( )
}
// Wait until the server is ready to handle reloading.
reloadReady := & closeOnce {
C : make ( chan struct { } ) ,
}
reloadReady . Close = func ( ) {
reloadReady . once . Do ( func ( ) {
close ( reloadReady . C )
} )
}
2021-08-29 10:28:40 +05:30
2021-11-22 16:15:58 +05:30
var g group . Group
{
// Initial configuration loading.
cancel := make ( chan struct { } )
g . Add (
func ( ) error {
2022-07-14 11:59:06 +05:30
var err error
2021-11-22 16:15:58 +05:30
r . promConfig , err = reloadConfig ( cfg . configFile , logger , reloaders ... )
if err != nil {
return fmt . Errorf ( "error loading config from %q: %s" , cfg . configFile , err )
}
reloadReady . Close ( )
<- cancel
return nil
} ,
func ( err error ) {
close ( cancel )
} ,
)
}
r . queryEngine = queryEngine
r . remoteStorage = remoteStorage
2022-09-12 12:30:36 +05:30
r . fanoutStorage = & fanoutStorage
readerReady <- true
2021-11-22 16:15:58 +05:30
if err := g . Run ( ) ; err != nil {
level . Error ( logger ) . Log ( "err" , err )
os . Exit ( 1 )
}
}
2022-09-12 12:30:36 +05:30
func ( r * ClickHouseReader ) GetQueryEngine ( ) * promql . Engine {
return r . queryEngine
}
func ( r * ClickHouseReader ) GetFanoutStorage ( ) * storage . Storage {
return r . fanoutStorage
}
2021-11-22 16:15:58 +05:30
func reloadConfig ( filename string , logger log . Logger , rls ... func ( * config . Config ) error ) ( promConfig * config . Config , err error ) {
level . Info ( logger ) . Log ( "msg" , "Loading configuration file" , "filename" , filename )
2023-03-07 13:37:31 +05:30
conf , err := config . LoadFile ( filename , false , false , logger )
2021-08-29 10:28:40 +05:30
if err != nil {
2021-11-22 16:15:58 +05:30
return nil , fmt . Errorf ( "couldn't load configuration (--config.file=%q): %v" , filename , err )
}
failed := false
for _ , rl := range rls {
if err := rl ( conf ) ; err != nil {
level . Error ( logger ) . Log ( "msg" , "Failed to apply configuration" , "err" , err )
failed = true
}
}
if failed {
return nil , fmt . Errorf ( "one or more errors occurred while applying the new configuration (--config.file=%q)" , filename )
}
level . Info ( logger ) . Log ( "msg" , "Completed loading of configuration file" , "filename" , filename )
return conf , nil
}
2021-08-29 10:28:40 +05:30
func ( r * ClickHouseReader ) GetInstantQueryMetricsResult ( ctx context . Context , queryParams * model . InstantQueryMetricsParams ) ( * promql . Result , * stats . QueryStats , * model . ApiError ) {
2023-09-05 18:17:32 +05:30
qry , err := r . queryEngine . NewInstantQuery ( ctx , r . remoteStorage , nil , queryParams . Query , queryParams . Time )
2021-08-29 10:28:40 +05:30
if err != nil {
2022-05-03 11:20:57 +05:30
return nil , nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
res := qry . Exec ( ctx )
// Optional stats field in response if parameter "stats" is not empty.
2023-03-07 13:37:31 +05:30
var qs stats . QueryStats
2021-08-29 10:28:40 +05:30
if queryParams . Stats != "" {
qs = stats . NewQueryStats ( qry . Stats ( ) )
}
qry . Close ( )
2023-03-07 13:37:31 +05:30
return res , & qs , nil
2021-08-29 10:28:40 +05:30
}
func ( r * ClickHouseReader ) GetQueryRangeResult ( ctx context . Context , query * model . QueryRangeParams ) ( * promql . Result , * stats . QueryStats , * model . ApiError ) {
2023-09-05 18:17:32 +05:30
qry , err := r . queryEngine . NewRangeQuery ( ctx , r . remoteStorage , nil , query . Query , query . Start , query . End , query . Step )
2021-08-29 10:28:40 +05:30
if err != nil {
2022-05-03 11:20:57 +05:30
return nil , nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
res := qry . Exec ( ctx )
// Optional stats field in response if parameter "stats" is not empty.
2023-03-07 13:37:31 +05:30
var qs stats . QueryStats
2021-08-29 10:28:40 +05:30
if query . Stats != "" {
qs = stats . NewQueryStats ( qry . Stats ( ) )
}
qry . Close ( )
2023-03-07 13:37:31 +05:30
return res , & qs , nil
2021-08-29 10:28:40 +05:30
}
2022-05-03 11:20:57 +05:30
func ( r * ClickHouseReader ) GetServicesList ( ctx context . Context ) ( * [ ] string , error ) {
services := [ ] string { }
2024-11-20 23:35:44 +05:30
query := fmt . Sprintf ( ` SELECT DISTINCT serviceName FROM %s.%s WHERE toDate(timestamp) > now() - INTERVAL 1 DAY ` , r . TraceDB , r . traceTableName )
if r . useTraceNewSchema {
query = fmt . Sprintf ( ` SELECT DISTINCT serviceName FROM %s.%s WHERE ts_bucket_start > (toUnixTimestamp(now() - INTERVAL 1 DAY) - 1800) AND toDate(timestamp) > now() - INTERVAL 1 DAY ` , r . TraceDB , r . traceTableName )
}
2022-05-03 11:20:57 +05:30
rows , err := r . db . Query ( ctx , query )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-05-03 11:20:57 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "error in processing sql query" )
2022-05-03 11:20:57 +05:30
}
defer rows . Close ( )
for rows . Next ( ) {
var serviceName string
if err := rows . Scan ( & serviceName ) ; err != nil {
return & services , err
}
services = append ( services , serviceName )
}
return & services , nil
}
2024-07-30 02:02:50 +05:30
func ( r * ClickHouseReader ) GetTopLevelOperations ( ctx context . Context , skipConfig * model . SkipConfig , start , end time . Time , services [ ] string ) ( * map [ string ] [ ] string , * model . ApiError ) {
2021-05-22 13:35:30 +05:30
2024-03-12 17:22:48 +05:30
start = start . In ( time . UTC )
// The `top_level_operations` that have `time` >= start
2022-08-04 11:57:05 +05:30
operations := map [ string ] [ ] string { }
2024-07-30 02:02:50 +05:30
// We can't use the `end` because the `top_level_operations` table has the most recent instances of the operations
// We can only use the `start` time to filter the operations
query := fmt . Sprintf ( ` SELECT name, serviceName, max(time) as ts FROM %s.%s WHERE time >= @start ` , r . TraceDB , r . topLevelOperationsTable )
if len ( services ) > 0 {
query += ` AND serviceName IN @services `
}
query += ` GROUP BY name, serviceName ORDER BY ts DESC LIMIT 5000 `
2021-05-22 13:35:30 +05:30
2024-07-30 02:02:50 +05:30
rows , err := r . db . Query ( ctx , query , clickhouse . Named ( "start" , start ) , clickhouse . Named ( "services" , services ) )
2021-05-22 13:35:30 +05:30
2021-05-31 18:05:54 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-07-30 02:02:50 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2021-05-22 13:35:30 +05:30
}
2022-08-04 11:57:05 +05:30
defer rows . Close ( )
for rows . Next ( ) {
var name , serviceName string
2024-03-12 17:22:48 +05:30
var t time . Time
if err := rows . Scan ( & name , & serviceName , & t ) ; err != nil {
2024-07-30 02:02:50 +05:30
return nil , & model . ApiError { Typ : model . ErrorInternal , Err : fmt . Errorf ( "error in reading data" ) }
2022-08-04 11:57:05 +05:30
}
if _ , ok := operations [ serviceName ] ; ! ok {
2024-07-30 02:02:50 +05:30
operations [ serviceName ] = [ ] string { "overflow_operation" }
2024-03-12 17:22:48 +05:30
}
2023-06-30 06:58:22 +05:30
if skipConfig . ShouldSkip ( serviceName , name ) {
continue
}
2024-07-30 02:02:50 +05:30
operations [ serviceName ] = append ( operations [ serviceName ] , name )
2022-05-03 11:20:57 +05:30
}
2024-07-30 02:02:50 +05:30
return & operations , nil
2022-08-04 11:57:05 +05:30
}
2021-05-31 18:05:54 +05:30
2024-11-22 21:57:25 +05:30
func ( r * ClickHouseReader ) buildResourceSubQuery ( tags [ ] model . TagQueryParam , svc string , start , end time . Time ) ( string , error ) {
// assuming all will be resource attributes.
// and resource attributes are string for traces
filterSet := v3 . FilterSet { }
for _ , tag := range tags {
// skip the collector id as we don't add it to traces
if tag . Key == "signoz.collector.id" {
continue
}
key := v3 . AttributeKey {
Key : tag . Key ,
DataType : v3 . AttributeKeyDataTypeString ,
Type : v3 . AttributeKeyTypeResource ,
}
it := v3 . FilterItem {
Key : key ,
}
// as of now only in and not in are supported
switch tag . Operator {
case model . NotInOperator :
it . Operator = v3 . FilterOperatorNotIn
it . Value = tag . StringValues
case model . InOperator :
it . Operator = v3 . FilterOperatorIn
it . Value = tag . StringValues
default :
return "" , fmt . Errorf ( "operator %s not supported" , tag . Operator )
}
filterSet . Items = append ( filterSet . Items , it )
}
filterSet . Items = append ( filterSet . Items , v3 . FilterItem {
Key : v3 . AttributeKey {
Key : "service.name" ,
DataType : v3 . AttributeKeyDataTypeString ,
Type : v3 . AttributeKeyTypeResource ,
} ,
Operator : v3 . FilterOperatorEqual ,
Value : svc ,
} )
resourceSubQuery , err := resource . BuildResourceSubQuery (
r . TraceDB ,
r . traceResourceTableV3 ,
start . Unix ( ) - 1800 ,
end . Unix ( ) ,
& filterSet ,
[ ] v3 . AttributeKey { } ,
v3 . AttributeKey { } ,
false )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return "" , err
}
return resourceSubQuery , nil
}
func ( r * ClickHouseReader ) GetServicesV2 ( ctx context . Context , queryParams * model . GetServicesParams , skipConfig * model . SkipConfig ) ( * [ ] model . ServiceItem , * model . ApiError ) {
2021-05-31 18:05:54 +05:30
2022-08-04 11:57:05 +05:30
if r . indexTable == "" {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : ErrNoIndexTable }
2021-05-22 13:35:30 +05:30
}
2024-07-30 02:02:50 +05:30
topLevelOps , apiErr := r . GetTopLevelOperations ( ctx , skipConfig , * queryParams . Start , * queryParams . End , nil )
2022-08-04 11:57:05 +05:30
if apiErr != nil {
return nil , apiErr
2021-05-31 18:05:54 +05:30
}
2022-08-04 11:57:05 +05:30
serviceItems := [ ] model . ServiceItem { }
var wg sync . WaitGroup
// limit the number of concurrent queries to not overload the clickhouse server
sem := make ( chan struct { } , 10 )
var mtx sync . RWMutex
for svc , ops := range * topLevelOps {
sem <- struct { } { }
wg . Add ( 1 )
go func ( svc string , ops [ ] string ) {
defer wg . Done ( )
defer func ( ) { <- sem } ( )
var serviceItem model . ServiceItem
var numErrors uint64
2024-03-12 17:22:48 +05:30
// Even if the total number of operations within the time range is less and the all
// the top level operations are high, we want to warn to let user know the issue
// with the instrumentation
serviceItem . DataWarning = model . DataWarning {
2024-07-30 02:02:50 +05:30
TopLevelOps : ( * topLevelOps ) [ svc ] ,
2024-03-12 17:22:48 +05:30
}
// default max_query_size = 262144
// Let's assume the average size of the item in `ops` is 50 bytes
// We can have 262144/50 = 5242 items in the `ops` array
// Although we have make it as big as 5k, We cap the number of items
// in the `ops` array to 1500
ops = ops [ : int ( math . Min ( 1500 , float64 ( len ( ops ) ) ) ) ]
2022-08-04 11:57:05 +05:30
query := fmt . Sprintf (
` SELECT
quantile ( 0.99 ) ( durationNano ) as p99 ,
avg ( durationNano ) as avgDuration ,
count ( * ) as numCalls
FROM % s . % s
2023-09-07 15:20:14 +05:30
WHERE serviceName = @ serviceName AND name In @ names AND timestamp >= @ start AND timestamp <= @ end ` ,
2024-11-20 23:35:44 +05:30
r . TraceDB , r . traceTableName ,
2022-08-04 11:57:05 +05:30
)
errorQuery := fmt . Sprintf (
` SELECT
count ( * ) as numErrors
FROM % s . % s
2023-09-07 15:20:14 +05:30
WHERE serviceName = @ serviceName AND name In @ names AND timestamp >= @ start AND timestamp <= @ end AND statusCode = 2 ` ,
2024-11-20 23:35:44 +05:30
r . TraceDB , r . traceTableName ,
2022-08-04 11:57:05 +05:30
)
args := [ ] interface { } { }
args = append ( args ,
clickhouse . Named ( "start" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "end" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "serviceName" , svc ) ,
clickhouse . Named ( "names" , ops ) ,
)
2024-11-20 23:35:44 +05:30
2024-11-22 21:57:25 +05:30
resourceSubQuery , err := r . buildResourceSubQuery ( queryParams . Tags , svc , * queryParams . Start , * queryParams . End )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return
}
query += `
AND (
resource_fingerprint GLOBAL IN ` +
resourceSubQuery +
` ) AND ts_bucket_start >= @start_bucket AND ts_bucket_start <= @end_bucket `
args = append ( args ,
clickhouse . Named ( "start_bucket" , strconv . FormatInt ( queryParams . Start . Unix ( ) - 1800 , 10 ) ) ,
clickhouse . Named ( "end_bucket" , strconv . FormatInt ( queryParams . End . Unix ( ) , 10 ) ) ,
)
err = r . db . QueryRow (
ctx ,
query ,
args ... ,
) . ScanStruct ( & serviceItem )
if serviceItem . NumCalls == 0 {
return
}
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return
}
errorQuery += `
AND (
resource_fingerprint GLOBAL IN ` +
resourceSubQuery +
` ) AND ts_bucket_start >= @start_bucket AND ts_bucket_start <= @end_bucket `
err = r . db . QueryRow ( ctx , errorQuery , args ... ) . Scan ( & numErrors )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return
}
serviceItem . ServiceName = svc
serviceItem . NumErrors = numErrors
mtx . Lock ( )
serviceItems = append ( serviceItems , serviceItem )
mtx . Unlock ( )
} ( svc , ops )
}
wg . Wait ( )
for idx := range serviceItems {
serviceItems [ idx ] . CallRate = float64 ( serviceItems [ idx ] . NumCalls ) / float64 ( queryParams . Period )
serviceItems [ idx ] . ErrorRate = float64 ( serviceItems [ idx ] . NumErrors ) * 100 / float64 ( serviceItems [ idx ] . NumCalls )
}
return & serviceItems , nil
}
func ( r * ClickHouseReader ) GetServices ( ctx context . Context , queryParams * model . GetServicesParams , skipConfig * model . SkipConfig ) ( * [ ] model . ServiceItem , * model . ApiError ) {
if r . useTraceNewSchema {
return r . GetServicesV2 ( ctx , queryParams , skipConfig )
}
if r . indexTable == "" {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : ErrNoIndexTable }
}
topLevelOps , apiErr := r . GetTopLevelOperations ( ctx , skipConfig , * queryParams . Start , * queryParams . End , nil )
if apiErr != nil {
return nil , apiErr
}
serviceItems := [ ] model . ServiceItem { }
var wg sync . WaitGroup
// limit the number of concurrent queries to not overload the clickhouse server
sem := make ( chan struct { } , 10 )
var mtx sync . RWMutex
for svc , ops := range * topLevelOps {
sem <- struct { } { }
wg . Add ( 1 )
go func ( svc string , ops [ ] string ) {
defer wg . Done ( )
defer func ( ) { <- sem } ( )
var serviceItem model . ServiceItem
var numErrors uint64
// Even if the total number of operations within the time range is less and the all
// the top level operations are high, we want to warn to let user know the issue
// with the instrumentation
serviceItem . DataWarning = model . DataWarning {
TopLevelOps : ( * topLevelOps ) [ svc ] ,
2024-11-20 23:35:44 +05:30
}
2024-11-22 21:57:25 +05:30
// default max_query_size = 262144
// Let's assume the average size of the item in `ops` is 50 bytes
// We can have 262144/50 = 5242 items in the `ops` array
// Although we have make it as big as 5k, We cap the number of items
// in the `ops` array to 1500
ops = ops [ : int ( math . Min ( 1500 , float64 ( len ( ops ) ) ) ) ]
query := fmt . Sprintf (
` SELECT
quantile ( 0.99 ) ( durationNano ) as p99 ,
avg ( durationNano ) as avgDuration ,
count ( * ) as numCalls
FROM % s . % s
WHERE serviceName = @ serviceName AND name In @ names AND timestamp >= @ start AND timestamp <= @ end ` ,
r . TraceDB , r . indexTable ,
)
errorQuery := fmt . Sprintf (
` SELECT
count ( * ) as numErrors
FROM % s . % s
WHERE serviceName = @ serviceName AND name In @ names AND timestamp >= @ start AND timestamp <= @ end AND statusCode = 2 ` ,
r . TraceDB , r . indexTable ,
)
args := [ ] interface { } { }
args = append ( args ,
clickhouse . Named ( "start" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "end" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "serviceName" , svc ) ,
clickhouse . Named ( "names" , ops ) ,
)
2023-01-25 12:35:44 +05:30
// create TagQuery from TagQueryParams
tags := createTagQueryFromTagQueryParams ( queryParams . Tags )
subQuery , argsSubQuery , errStatus := buildQueryWithTagParams ( ctx , tags )
query += subQuery
args = append ( args , argsSubQuery ... )
2022-08-04 11:57:05 +05:30
if errStatus != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( errStatus ) )
2022-08-04 11:57:05 +05:30
return
}
err := r . db . QueryRow (
ctx ,
query ,
args ... ,
) . ScanStruct ( & serviceItem )
2021-05-29 16:32:11 +05:30
2022-08-17 15:11:08 +05:30
if serviceItem . NumCalls == 0 {
return
}
2022-08-04 11:57:05 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2022-08-04 11:57:05 +05:30
return
}
2023-01-25 12:35:44 +05:30
subQuery , argsSubQuery , errStatus = buildQueryWithTagParams ( ctx , tags )
2024-03-12 17:22:48 +05:30
if errStatus != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error building query with tag params" , zap . Error ( errStatus ) )
2024-03-12 17:22:48 +05:30
return
}
2024-04-03 16:42:00 +05:30
errorQuery += subQuery
2023-01-25 12:35:44 +05:30
args = append ( args , argsSubQuery ... )
2022-08-04 11:57:05 +05:30
err = r . db . QueryRow ( ctx , errorQuery , args ... ) . Scan ( & numErrors )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2022-08-04 11:57:05 +05:30
return
}
2021-05-31 18:05:54 +05:30
2022-08-04 11:57:05 +05:30
serviceItem . ServiceName = svc
serviceItem . NumErrors = numErrors
mtx . Lock ( )
serviceItems = append ( serviceItems , serviceItem )
mtx . Unlock ( )
} ( svc , ops )
2021-05-31 18:05:54 +05:30
}
2022-08-04 11:57:05 +05:30
wg . Wait ( )
2021-05-31 18:05:54 +05:30
2022-08-04 11:57:05 +05:30
for idx := range serviceItems {
serviceItems [ idx ] . CallRate = float64 ( serviceItems [ idx ] . NumCalls ) / float64 ( queryParams . Period )
serviceItems [ idx ] . ErrorRate = float64 ( serviceItems [ idx ] . NumErrors ) * 100 / float64 ( serviceItems [ idx ] . NumCalls )
2021-05-27 12:52:34 +05:30
}
return & serviceItems , nil
2021-05-22 13:35:30 +05:30
}
2024-11-20 23:35:44 +05:30
2022-02-08 13:28:56 +05:30
func getStatusFilters ( query string , statusParams [ ] string , excludeMap map [ string ] struct { } ) string {
// status can only be two and if both are selected than they are equivalent to none selected
if _ , ok := excludeMap [ "status" ] ; ok {
if len ( statusParams ) == 1 {
if statusParams [ 0 ] == "error" {
2022-05-03 11:20:57 +05:30
query += " AND hasError = false"
2022-02-08 13:28:56 +05:30
} else if statusParams [ 0 ] == "ok" {
2022-05-03 11:20:57 +05:30
query += " AND hasError = true"
2022-02-08 13:28:56 +05:30
}
}
} else if len ( statusParams ) == 1 {
if statusParams [ 0 ] == "error" {
2022-05-03 11:20:57 +05:30
query += " AND hasError = true"
2022-02-08 13:28:56 +05:30
} else if statusParams [ 0 ] == "ok" {
2022-05-03 11:20:57 +05:30
query += " AND hasError = false"
2022-02-08 13:28:56 +05:30
}
}
return query
}
2023-01-25 12:35:44 +05:30
func createTagQueryFromTagQueryParams ( queryParams [ ] model . TagQueryParam ) [ ] model . TagQuery {
tags := [ ] model . TagQuery { }
for _ , tag := range queryParams {
if len ( tag . StringValues ) > 0 {
2023-03-28 00:15:15 +05:30
tags = append ( tags , model . NewTagQueryString ( tag ) )
2023-01-25 12:35:44 +05:30
}
if len ( tag . NumberValues ) > 0 {
2023-03-28 00:15:15 +05:30
tags = append ( tags , model . NewTagQueryNumber ( tag ) )
2023-01-25 12:35:44 +05:30
}
if len ( tag . BoolValues ) > 0 {
2023-03-28 00:15:15 +05:30
tags = append ( tags , model . NewTagQueryBool ( tag ) )
2023-01-25 12:35:44 +05:30
}
}
return tags
}
2022-05-03 11:20:57 +05:30
func StringWithCharset ( length int , charset string ) string {
b := make ( [ ] byte , length )
for i := range b {
b [ i ] = charset [ seededRand . Intn ( len ( charset ) ) ]
}
return string ( b )
}
func String ( length int ) string {
return StringWithCharset ( length , charset )
}
2024-09-17 15:33:17 +05:30
func buildQueryWithTagParams ( _ context . Context , tags [ ] model . TagQuery ) ( string , [ ] interface { } , * model . ApiError ) {
2023-01-26 01:18:19 +05:30
query := ""
2023-01-25 12:35:44 +05:30
var args [ ] interface { }
2022-05-03 11:20:57 +05:30
for _ , item := range tags {
2023-01-26 01:18:19 +05:30
var subQuery string
var argsSubQuery [ ] interface { }
2023-03-28 00:15:15 +05:30
tagMapType := item . GetTagMapColumn ( )
2023-01-25 12:35:44 +05:30
switch item . GetOperator ( ) {
case model . EqualOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , "=" )
2023-01-25 12:35:44 +05:30
case model . NotEqualOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , "!=" )
2023-01-25 12:35:44 +05:30
case model . LessThanOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , "<" )
2023-01-25 12:35:44 +05:30
case model . GreaterThanOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , ">" )
2023-01-25 12:35:44 +05:30
case model . InOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addInOperator ( item , tagMapType , false )
2023-01-25 12:35:44 +05:30
case model . NotInOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addInOperator ( item , tagMapType , true )
2023-01-25 12:35:44 +05:30
case model . LessThanEqualOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , "<=" )
2023-01-25 12:35:44 +05:30
case model . GreaterThanEqualOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addArithmeticOperator ( item , tagMapType , ">=" )
2023-01-25 12:35:44 +05:30
case model . ContainsOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addContainsOperator ( item , tagMapType , false )
2023-01-25 12:35:44 +05:30
case model . NotContainsOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addContainsOperator ( item , tagMapType , true )
2023-01-25 12:35:44 +05:30
case model . StartsWithOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addStartsWithOperator ( item , tagMapType , false )
2023-01-25 12:35:44 +05:30
case model . NotStartsWithOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addStartsWithOperator ( item , tagMapType , true )
2023-01-25 12:35:44 +05:30
case model . ExistsOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addExistsOperator ( item , tagMapType , false )
2023-01-25 12:35:44 +05:30
case model . NotExistsOperator :
2023-01-26 01:18:19 +05:30
subQuery , argsSubQuery = addExistsOperator ( item , tagMapType , true )
2023-01-25 12:35:44 +05:30
default :
2024-03-12 17:22:48 +05:30
return "" , nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "filter operator %s not supported" , item . GetOperator ( ) ) }
2022-05-03 11:20:57 +05:30
}
2023-01-26 01:18:19 +05:30
query += subQuery
args = append ( args , argsSubQuery ... )
2022-05-03 11:20:57 +05:30
}
2023-01-26 01:18:19 +05:30
return query , args , nil
2023-01-25 12:35:44 +05:30
}
func addInOperator ( item model . TagQuery , tagMapType string , not bool ) ( string , [ ] interface { } ) {
values := item . GetValues ( )
args := [ ] interface { } { }
notStr := ""
if not {
notStr = "NOT"
}
tagValuePair := [ ] string { }
for _ , value := range values {
tagKey := "inTagKey" + String ( 5 )
tagValue := "inTagValue" + String ( 5 )
tagValuePair = append ( tagValuePair , fmt . Sprintf ( "%s[@%s] = @%s" , tagMapType , tagKey , tagValue ) )
args = append ( args , clickhouse . Named ( tagKey , item . GetKey ( ) ) )
args = append ( args , clickhouse . Named ( tagValue , value ) )
}
return fmt . Sprintf ( " AND %s (%s)" , notStr , strings . Join ( tagValuePair , " OR " ) ) , args
}
func addContainsOperator ( item model . TagQuery , tagMapType string , not bool ) ( string , [ ] interface { } ) {
values := item . GetValues ( )
args := [ ] interface { } { }
notStr := ""
if not {
notStr = "NOT"
}
tagValuePair := [ ] string { }
for _ , value := range values {
tagKey := "containsTagKey" + String ( 5 )
tagValue := "containsTagValue" + String ( 5 )
tagValuePair = append ( tagValuePair , fmt . Sprintf ( "%s[@%s] ILIKE @%s" , tagMapType , tagKey , tagValue ) )
args = append ( args , clickhouse . Named ( tagKey , item . GetKey ( ) ) )
args = append ( args , clickhouse . Named ( tagValue , "%" + fmt . Sprintf ( "%v" , value ) + "%" ) )
}
return fmt . Sprintf ( " AND %s (%s)" , notStr , strings . Join ( tagValuePair , " OR " ) ) , args
}
func addStartsWithOperator ( item model . TagQuery , tagMapType string , not bool ) ( string , [ ] interface { } ) {
values := item . GetValues ( )
args := [ ] interface { } { }
notStr := ""
if not {
notStr = "NOT"
}
tagValuePair := [ ] string { }
for _ , value := range values {
tagKey := "startsWithTagKey" + String ( 5 )
tagValue := "startsWithTagValue" + String ( 5 )
tagValuePair = append ( tagValuePair , fmt . Sprintf ( "%s[@%s] ILIKE @%s" , tagMapType , tagKey , tagValue ) )
args = append ( args , clickhouse . Named ( tagKey , item . GetKey ( ) ) )
args = append ( args , clickhouse . Named ( tagValue , "%" + fmt . Sprintf ( "%v" , value ) + "%" ) )
}
return fmt . Sprintf ( " AND %s (%s)" , notStr , strings . Join ( tagValuePair , " OR " ) ) , args
}
func addArithmeticOperator ( item model . TagQuery , tagMapType string , operator string ) ( string , [ ] interface { } ) {
values := item . GetValues ( )
args := [ ] interface { } { }
tagValuePair := [ ] string { }
for _ , value := range values {
tagKey := "arithmeticTagKey" + String ( 5 )
tagValue := "arithmeticTagValue" + String ( 5 )
tagValuePair = append ( tagValuePair , fmt . Sprintf ( "%s[@%s] %s @%s" , tagMapType , tagKey , operator , tagValue ) )
args = append ( args , clickhouse . Named ( tagKey , item . GetKey ( ) ) )
args = append ( args , clickhouse . Named ( tagValue , value ) )
}
return fmt . Sprintf ( " AND (%s)" , strings . Join ( tagValuePair , " OR " ) ) , args
}
func addExistsOperator ( item model . TagQuery , tagMapType string , not bool ) ( string , [ ] interface { } ) {
values := item . GetValues ( )
notStr := ""
if not {
notStr = "NOT"
}
args := [ ] interface { } { }
tagOperatorPair := [ ] string { }
for range values {
tagKey := "existsTagKey" + String ( 5 )
tagOperatorPair = append ( tagOperatorPair , fmt . Sprintf ( "mapContains(%s, @%s)" , tagMapType , tagKey ) )
args = append ( args , clickhouse . Named ( tagKey , item . GetKey ( ) ) )
}
return fmt . Sprintf ( " AND %s (%s)" , notStr , strings . Join ( tagOperatorPair , " OR " ) ) , args
2022-05-03 11:20:57 +05:30
}
2024-11-22 21:57:25 +05:30
func ( r * ClickHouseReader ) GetTopOperationsV2 ( ctx context . Context , queryParams * model . GetTopOperationsParams ) ( * [ ] model . TopOperationsItem , * model . ApiError ) {
2021-05-31 11:14:11 +05:30
2022-08-04 11:57:05 +05:30
namedArgs := [ ] interface { } {
clickhouse . Named ( "start" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "end" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "serviceName" , queryParams . ServiceName ) ,
2024-11-22 21:57:25 +05:30
clickhouse . Named ( "start_bucket" , strconv . FormatInt ( queryParams . Start . Unix ( ) - 1800 , 10 ) ) ,
clickhouse . Named ( "end_bucket" , strconv . FormatInt ( queryParams . End . Unix ( ) , 10 ) ) ,
2022-08-04 11:57:05 +05:30
}
var topOperationsItems [ ] model . TopOperationsItem
2021-05-31 11:14:11 +05:30
2022-08-04 11:57:05 +05:30
query := fmt . Sprintf ( `
SELECT
quantile ( 0.5 ) ( durationNano ) as p50 ,
quantile ( 0.95 ) ( durationNano ) as p95 ,
quantile ( 0.99 ) ( durationNano ) as p99 ,
COUNT ( * ) as numCalls ,
2023-04-26 18:23:54 +05:30
countIf ( statusCode = 2 ) as errorCount ,
2022-08-04 11:57:05 +05:30
name
FROM % s . % s
WHERE serviceName = @ serviceName AND timestamp >= @ start AND timestamp <= @ end ` ,
2024-11-20 23:35:44 +05:30
r . TraceDB , r . traceTableName ,
2022-08-04 11:57:05 +05:30
)
2024-11-20 23:35:44 +05:30
2024-11-22 21:57:25 +05:30
resourceSubQuery , err := r . buildResourceSubQuery ( queryParams . Tags , queryParams . ServiceName , * queryParams . Start , * queryParams . End )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
}
query += `
AND (
resource_fingerprint GLOBAL IN ` +
resourceSubQuery +
` ) AND ts_bucket_start >= @start_bucket AND ts_bucket_start <= @end_bucket `
query += " GROUP BY name ORDER BY p99 DESC"
if queryParams . Limit > 0 {
query += " LIMIT @limit"
namedArgs = append ( namedArgs , clickhouse . Named ( "limit" , queryParams . Limit ) )
}
err = r . db . Select ( ctx , & topOperationsItems , query , namedArgs ... )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
}
if topOperationsItems == nil {
topOperationsItems = [ ] model . TopOperationsItem { }
}
return & topOperationsItems , nil
}
func ( r * ClickHouseReader ) GetTopOperations ( ctx context . Context , queryParams * model . GetTopOperationsParams ) ( * [ ] model . TopOperationsItem , * model . ApiError ) {
2024-11-20 23:35:44 +05:30
if r . useTraceNewSchema {
2024-11-22 21:57:25 +05:30
return r . GetTopOperationsV2 ( ctx , queryParams )
2024-11-20 23:35:44 +05:30
}
2024-11-22 21:57:25 +05:30
namedArgs := [ ] interface { } {
clickhouse . Named ( "start" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "end" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "serviceName" , queryParams . ServiceName ) ,
}
var topOperationsItems [ ] model . TopOperationsItem
query := fmt . Sprintf ( `
SELECT
quantile ( 0.5 ) ( durationNano ) as p50 ,
quantile ( 0.95 ) ( durationNano ) as p95 ,
quantile ( 0.99 ) ( durationNano ) as p99 ,
COUNT ( * ) as numCalls ,
countIf ( statusCode = 2 ) as errorCount ,
name
FROM % s . % s
WHERE serviceName = @ serviceName AND timestamp >= @ start AND timestamp <= @ end ` ,
r . TraceDB , r . indexTable ,
)
2022-05-03 11:20:57 +05:30
args := [ ] interface { } { }
2022-08-04 11:57:05 +05:30
args = append ( args , namedArgs ... )
2023-01-25 12:35:44 +05:30
// create TagQuery from TagQueryParams
tags := createTagQueryFromTagQueryParams ( queryParams . Tags )
subQuery , argsSubQuery , errStatus := buildQueryWithTagParams ( ctx , tags )
query += subQuery
args = append ( args , argsSubQuery ... )
2022-05-03 11:20:57 +05:30
if errStatus != nil {
return nil , errStatus
}
2023-06-06 17:25:53 +05:30
query += " GROUP BY name ORDER BY p99 DESC"
if queryParams . Limit > 0 {
query += " LIMIT @limit"
args = append ( args , clickhouse . Named ( "limit" , queryParams . Limit ) )
}
2022-08-04 11:57:05 +05:30
err := r . db . Select ( ctx , & topOperationsItems , query , args ... )
2021-05-31 11:14:11 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2023-06-06 17:25:53 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2021-05-31 11:14:11 +05:30
}
2022-08-04 11:57:05 +05:30
if topOperationsItems == nil {
topOperationsItems = [ ] model . TopOperationsItem { }
2021-05-31 11:14:11 +05:30
}
2022-08-04 11:57:05 +05:30
return & topOperationsItems , nil
2021-05-31 11:14:11 +05:30
}
func ( r * ClickHouseReader ) GetUsage ( ctx context . Context , queryParams * model . GetUsageParams ) ( * [ ] model . UsageItem , error ) {
var usageItems [ ] model . UsageItem
2022-08-04 12:55:21 +05:30
namedArgs := [ ] interface { } {
clickhouse . Named ( "interval" , queryParams . StepHour ) ,
clickhouse . Named ( "start" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) ,
clickhouse . Named ( "end" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) ,
}
2021-05-31 11:14:11 +05:30
var query string
if len ( queryParams . ServiceName ) != 0 {
2022-08-04 12:55:21 +05:30
namedArgs = append ( namedArgs , clickhouse . Named ( "serviceName" , queryParams . ServiceName ) )
2022-11-24 18:18:19 +05:30
query = fmt . Sprintf ( "SELECT toStartOfInterval(timestamp, INTERVAL @interval HOUR) as time, sum(count) as count FROM %s.%s WHERE service_name=@serviceName AND timestamp>=@start AND timestamp<=@end GROUP BY time ORDER BY time ASC" , r . TraceDB , r . usageExplorerTable )
2021-05-31 11:14:11 +05:30
} else {
2022-11-24 18:18:19 +05:30
query = fmt . Sprintf ( "SELECT toStartOfInterval(timestamp, INTERVAL @interval HOUR) as time, sum(count) as count FROM %s.%s WHERE timestamp>=@start AND timestamp<=@end GROUP BY time ORDER BY time ASC" , r . TraceDB , r . usageExplorerTable )
2021-05-31 11:14:11 +05:30
}
2022-08-04 12:55:21 +05:30
err := r . db . Select ( ctx , & usageItems , query , namedArgs ... )
2021-05-31 11:14:11 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2021-05-31 11:14:11 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "error in processing sql query" )
2021-05-31 11:14:11 +05:30
}
2022-05-03 11:20:57 +05:30
for i := range usageItems {
usageItems [ i ] . Timestamp = uint64 ( usageItems [ i ] . Time . UnixNano ( ) )
2021-05-31 11:14:11 +05:30
}
if usageItems == nil {
usageItems = [ ] model . UsageItem { }
}
return & usageItems , nil
}
2024-12-10 13:43:51 +05:30
func ( r * ClickHouseReader ) SearchTracesV2 ( ctx context . Context , params * model . SearchTracesParams ,
smartTraceAlgorithm func ( payload [ ] model . SearchSpanResponseItem , targetSpanId string ,
2025-02-20 13:49:44 +05:30
levelUp int , levelDown int , spanLimit int ) ( [ ] model . SearchSpansResult , error ) ) ( * [ ] model . SearchSpansResult , error ) {
2024-11-20 23:35:44 +05:30
searchSpansResult := [ ] model . SearchSpansResult {
{
Columns : [ ] string { "__time" , "SpanId" , "TraceId" , "ServiceName" , "Name" , "Kind" , "DurationNano" , "TagsKeys" , "TagsValues" , "References" , "Events" , "HasError" , "StatusMessage" , "StatusCodeString" , "SpanKind" } ,
IsSubTree : false ,
Events : make ( [ ] [ ] interface { } , 0 ) ,
} ,
}
var traceSummary model . TraceSummary
summaryQuery := fmt . Sprintf ( "SELECT * from %s.%s WHERE trace_id=$1" , r . TraceDB , r . traceSummaryTable )
err := r . db . QueryRow ( ctx , summaryQuery , params . TraceID ) . Scan ( & traceSummary . TraceID , & traceSummary . Start , & traceSummary . End , & traceSummary . NumSpans )
if err != nil {
if err == sql . ErrNoRows {
return & searchSpansResult , nil
}
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error in processing sql query" )
}
if traceSummary . NumSpans > uint64 ( params . MaxSpansInTrace ) {
zap . L ( ) . Error ( "Max spans allowed in a trace limit reached" , zap . Int ( "MaxSpansInTrace" , params . MaxSpansInTrace ) ,
zap . Uint64 ( "Count" , traceSummary . NumSpans ) )
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-11-20 23:35:44 +05:30
data := map [ string ] interface { } {
"traceSize" : traceSummary . NumSpans ,
"maxSpansInTraceLimit" : params . MaxSpansInTrace ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED , data , claims . Email , true , false )
2024-11-20 23:35:44 +05:30
}
return nil , fmt . Errorf ( "max spans allowed in trace limit reached, please contact support for more details" )
}
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-11-20 23:35:44 +05:30
data := map [ string ] interface { } {
"traceSize" : traceSummary . NumSpans ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_TRACE_DETAIL_API , data , claims . Email , true , false )
2024-11-20 23:35:44 +05:30
}
var startTime , endTime , durationNano uint64
var searchScanResponses [ ] model . SpanItemV2
query := fmt . Sprintf ( "SELECT timestamp, duration_nano, span_id, trace_id, has_error, kind, resource_string_service$$name, name, references, attributes_string, attributes_number, attributes_bool, resources_string, events, status_message, status_code_string, kind_string FROM %s.%s WHERE trace_id=$1 and ts_bucket_start>=$2 and ts_bucket_start<=$3" , r . TraceDB , r . traceTableName )
start := time . Now ( )
err = r . db . Select ( ctx , & searchScanResponses , query , params . TraceID , strconv . FormatInt ( traceSummary . Start . Unix ( ) - 1800 , 10 ) , strconv . FormatInt ( traceSummary . End . Unix ( ) , 10 ) )
zap . L ( ) . Info ( query )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error in processing sql query" )
}
end := time . Now ( )
zap . L ( ) . Debug ( "getTraceSQLQuery took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
searchSpansResult [ 0 ] . Events = make ( [ ] [ ] interface { } , len ( searchScanResponses ) )
searchSpanResponses := [ ] model . SearchSpanResponseItem { }
start = time . Now ( )
for _ , item := range searchScanResponses {
ref := [ ] model . OtelSpanRef { }
err := json . Unmarshal ( [ ] byte ( item . References ) , & ref )
if err != nil {
zap . L ( ) . Error ( "Error unmarshalling references" , zap . Error ( err ) )
return nil , err
}
// merge attributes_number and attributes_bool to attributes_string
for k , v := range item . Attributes_bool {
item . Attributes_string [ k ] = fmt . Sprintf ( "%v" , v )
}
for k , v := range item . Attributes_number {
item . Attributes_string [ k ] = fmt . Sprintf ( "%v" , v )
}
for k , v := range item . Resources_string {
item . Attributes_string [ k ] = v
}
jsonItem := model . SearchSpanResponseItem {
SpanID : item . SpanID ,
TraceID : item . TraceID ,
ServiceName : item . ServiceName ,
Name : item . Name ,
Kind : int32 ( item . Kind ) ,
DurationNano : int64 ( item . DurationNano ) ,
HasError : item . HasError ,
StatusMessage : item . StatusMessage ,
StatusCodeString : item . StatusCodeString ,
SpanKind : item . SpanKind ,
References : ref ,
Events : item . Events ,
TagMap : item . Attributes_string ,
}
jsonItem . TimeUnixNano = uint64 ( item . TimeUnixNano . UnixNano ( ) / 1000000 )
searchSpanResponses = append ( searchSpanResponses , jsonItem )
if startTime == 0 || jsonItem . TimeUnixNano < startTime {
startTime = jsonItem . TimeUnixNano
}
if endTime == 0 || jsonItem . TimeUnixNano > endTime {
endTime = jsonItem . TimeUnixNano
}
if durationNano == 0 || uint64 ( jsonItem . DurationNano ) > durationNano {
durationNano = uint64 ( jsonItem . DurationNano )
}
}
end = time . Now ( )
zap . L ( ) . Debug ( "getTraceSQLQuery unmarshal took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
2024-12-10 13:43:51 +05:30
err = r . featureFlags . CheckFeature ( model . SmartTraceDetail )
smartAlgoEnabled := err == nil
if len ( searchScanResponses ) > params . SpansRenderLimit && smartAlgoEnabled {
start = time . Now ( )
searchSpansResult , err = smartTraceAlgorithm ( searchSpanResponses , params . SpanID , params . LevelUp , params . LevelDown , params . SpansRenderLimit )
if err != nil {
return nil , err
}
end = time . Now ( )
zap . L ( ) . Debug ( "smartTraceAlgo took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-12-10 13:43:51 +05:30
data := map [ string ] interface { } {
"traceSize" : len ( searchScanResponses ) ,
"spansRenderLimit" : params . SpansRenderLimit ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_LARGE_TRACE_OPENED , data , claims . Email , true , false )
2024-12-10 13:43:51 +05:30
}
} else {
for i , item := range searchSpanResponses {
spanEvents := item . GetValues ( )
searchSpansResult [ 0 ] . Events [ i ] = spanEvents
}
2024-11-20 23:35:44 +05:30
}
searchSpansResult [ 0 ] . StartTimestampMillis = startTime - ( durationNano / 1000000 )
searchSpansResult [ 0 ] . EndTimestampMillis = endTime + ( durationNano / 1000000 )
return & searchSpansResult , nil
}
2024-05-27 17:20:45 +05:30
func ( r * ClickHouseReader ) SearchTraces ( ctx context . Context , params * model . SearchTracesParams ,
smartTraceAlgorithm func ( payload [ ] model . SearchSpanResponseItem , targetSpanId string ,
2025-02-20 13:49:44 +05:30
levelUp int , levelDown int , spanLimit int ) ( [ ] model . SearchSpansResult , error ) ) ( * [ ] model . SearchSpansResult , error ) {
2024-05-27 17:20:45 +05:30
2024-11-20 23:35:44 +05:30
if r . useTraceNewSchema {
2024-12-10 13:43:51 +05:30
return r . SearchTracesV2 ( ctx , params , smartTraceAlgorithm )
2024-11-20 23:35:44 +05:30
}
2024-05-27 17:20:45 +05:30
var countSpans uint64
countQuery := fmt . Sprintf ( "SELECT count() as count from %s.%s WHERE traceID=$1" , r . TraceDB , r . SpansTable )
err := r . db . QueryRow ( ctx , countQuery , params . TraceID ) . Scan ( & countSpans )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error in processing sql query" )
}
if countSpans > uint64 ( params . MaxSpansInTrace ) {
zap . L ( ) . Error ( "Max spans allowed in a trace limit reached" , zap . Int ( "MaxSpansInTrace" , params . MaxSpansInTrace ) ,
zap . Uint64 ( "Count" , countSpans ) )
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-05-27 17:20:45 +05:30
data := map [ string ] interface { } {
"traceSize" : countSpans ,
"maxSpansInTraceLimit" : params . MaxSpansInTrace ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_MAX_SPANS_ALLOWED_LIMIT_REACHED , data , claims . Email , true , false )
2024-05-27 17:20:45 +05:30
}
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "max spans allowed in trace limit reached, please contact support for more details" )
2024-05-27 17:20:45 +05:30
}
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-05-27 17:20:45 +05:30
data := map [ string ] interface { } {
"traceSize" : countSpans ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_TRACE_DETAIL_API , data , claims . Email , true , false )
2024-05-27 17:20:45 +05:30
}
2021-05-31 11:14:11 +05:30
2024-06-24 14:45:26 +05:30
var startTime , endTime , durationNano uint64
2022-11-24 18:18:19 +05:30
var searchScanResponses [ ] model . SearchSpanDBResponseItem
2021-05-31 11:14:11 +05:30
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT timestamp, traceID, model FROM %s.%s WHERE traceID=$1" , r . TraceDB , r . SpansTable )
2021-05-31 11:14:11 +05:30
2022-11-24 18:18:19 +05:30
start := time . Now ( )
2024-05-27 17:20:45 +05:30
err = r . db . Select ( ctx , & searchScanResponses , query , params . TraceID )
2021-05-31 11:14:11 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2021-05-31 11:14:11 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error in processing sql query" )
2021-05-31 11:14:11 +05:30
}
2022-11-24 18:18:19 +05:30
end := time . Now ( )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "getTraceSQLQuery took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
2022-05-03 11:20:57 +05:30
searchSpansResult := [ ] model . SearchSpansResult { {
2024-06-27 12:34:23 +05:30
Columns : [ ] string { "__time" , "SpanId" , "TraceId" , "ServiceName" , "Name" , "Kind" , "DurationNano" , "TagsKeys" , "TagsValues" , "References" , "Events" , "HasError" , "StatusMessage" , "StatusCodeString" , "SpanKind" } ,
2024-05-27 17:20:45 +05:30
Events : make ( [ ] [ ] interface { } , len ( searchScanResponses ) ) ,
IsSubTree : false ,
2022-05-03 11:20:57 +05:30
} ,
2021-05-31 11:14:11 +05:30
}
2022-11-24 18:18:19 +05:30
searchSpanResponses := [ ] model . SearchSpanResponseItem { }
start = time . Now ( )
for _ , item := range searchScanResponses {
var jsonItem model . SearchSpanResponseItem
easyjson . Unmarshal ( [ ] byte ( item . Model ) , & jsonItem )
2022-05-03 11:20:57 +05:30
jsonItem . TimeUnixNano = uint64 ( item . Timestamp . UnixNano ( ) / 1000000 )
2022-11-24 18:18:19 +05:30
searchSpanResponses = append ( searchSpanResponses , jsonItem )
2024-06-24 14:45:26 +05:30
if startTime == 0 || jsonItem . TimeUnixNano < startTime {
startTime = jsonItem . TimeUnixNano
}
if endTime == 0 || jsonItem . TimeUnixNano > endTime {
endTime = jsonItem . TimeUnixNano
}
if durationNano == 0 || uint64 ( jsonItem . DurationNano ) > durationNano {
durationNano = uint64 ( jsonItem . DurationNano )
}
2021-05-31 11:14:11 +05:30
}
2022-11-24 18:18:19 +05:30
end = time . Now ( )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "getTraceSQLQuery unmarshal took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
2021-05-31 11:14:11 +05:30
2022-11-24 18:18:19 +05:30
err = r . featureFlags . CheckFeature ( model . SmartTraceDetail )
smartAlgoEnabled := err == nil
2024-05-27 17:20:45 +05:30
if len ( searchScanResponses ) > params . SpansRenderLimit && smartAlgoEnabled {
2022-11-24 18:18:19 +05:30
start = time . Now ( )
2024-05-27 17:20:45 +05:30
searchSpansResult , err = smartTraceAlgorithm ( searchSpanResponses , params . SpanID , params . LevelUp , params . LevelDown , params . SpansRenderLimit )
2022-11-24 18:18:19 +05:30
if err != nil {
return nil , err
}
end = time . Now ( )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "smartTraceAlgo took: " , zap . Duration ( "duration" , end . Sub ( start ) ) )
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
2024-05-27 17:20:45 +05:30
data := map [ string ] interface { } {
"traceSize" : len ( searchScanResponses ) ,
"spansRenderLimit" : params . SpansRenderLimit ,
}
2025-02-17 18:16:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_LARGE_TRACE_OPENED , data , claims . Email , true , false )
2024-05-27 17:20:45 +05:30
}
2022-11-24 18:18:19 +05:30
} else {
for i , item := range searchSpanResponses {
spanEvents := item . GetValues ( )
searchSpansResult [ 0 ] . Events [ i ] = spanEvents
}
2022-05-03 11:20:57 +05:30
}
2022-11-24 18:18:19 +05:30
2024-06-26 14:34:27 +05:30
searchSpansResult [ 0 ] . StartTimestampMillis = startTime - ( durationNano / 1000000 )
searchSpansResult [ 0 ] . EndTimestampMillis = endTime + ( durationNano / 1000000 )
2024-06-24 14:45:26 +05:30
2022-11-24 18:18:19 +05:30
return & searchSpansResult , nil
2022-05-03 11:20:57 +05:30
}
2025-01-24 00:16:38 +05:30
func ( r * ClickHouseReader ) GetSpansForTrace ( ctx context . Context , traceID string , traceDetailsQuery string ) ( [ ] model . SpanItemV2 , * model . ApiError ) {
var traceSummary model . TraceSummary
summaryQuery := fmt . Sprintf ( "SELECT * from %s.%s WHERE trace_id=$1" , r . TraceDB , r . traceSummaryTable )
err := r . db . QueryRow ( ctx , summaryQuery , traceID ) . Scan ( & traceSummary . TraceID , & traceSummary . Start , & traceSummary . End , & traceSummary . NumSpans )
if err != nil {
if err == sql . ErrNoRows {
return [ ] model . SpanItemV2 { } , nil
}
zap . L ( ) . Error ( "Error in processing trace summary sql query" , zap . Error ( err ) )
return nil , model . ExecutionError ( fmt . Errorf ( "error in processing trace summary sql query: %w" , err ) )
}
var searchScanResponses [ ] model . SpanItemV2
queryStartTime := time . Now ( )
err = r . db . Select ( ctx , & searchScanResponses , traceDetailsQuery , traceID , strconv . FormatInt ( traceSummary . Start . Unix ( ) - 1800 , 10 ) , strconv . FormatInt ( traceSummary . End . Unix ( ) , 10 ) )
zap . L ( ) . Info ( traceDetailsQuery )
if err != nil {
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
return nil , model . ExecutionError ( fmt . Errorf ( "error in processing trace data sql query: %w" , err ) )
}
zap . L ( ) . Info ( "trace details query took: " , zap . Duration ( "duration" , time . Since ( queryStartTime ) ) , zap . String ( "traceID" , traceID ) )
return searchScanResponses , nil
}
func ( r * ClickHouseReader ) GetWaterfallSpansForTraceWithMetadataCache ( ctx context . Context , traceID string ) ( * model . GetWaterfallSpansForTraceWithMetadataCache , error ) {
cachedTraceData := new ( model . GetWaterfallSpansForTraceWithMetadataCache )
cacheStatus , err := r . cache . Retrieve ( ctx , fmt . Sprintf ( "getWaterfallSpansForTraceWithMetadata-%v" , traceID ) , cachedTraceData , false )
if err != nil {
zap . L ( ) . Debug ( "error in retrieving getWaterfallSpansForTraceWithMetadata cache" , zap . Error ( err ) , zap . String ( "traceID" , traceID ) )
return nil , err
}
if cacheStatus != cache . RetrieveStatusHit {
return nil , errors . Errorf ( "cache status for getWaterfallSpansForTraceWithMetadata : %s, traceID: %s" , cacheStatus , traceID )
}
if time . Since ( time . UnixMilli ( int64 ( cachedTraceData . EndTime ) ) ) < r . fluxIntervalForTraceDetail {
zap . L ( ) . Info ( "the trace end time falls under the flux interval, skipping getWaterfallSpansForTraceWithMetadata cache" , zap . String ( "traceID" , traceID ) )
return nil , errors . Errorf ( "the trace end time falls under the flux interval, skipping getWaterfallSpansForTraceWithMetadata cache, traceID: %s" , traceID )
}
zap . L ( ) . Info ( "cache is successfully hit, applying cache for getWaterfallSpansForTraceWithMetadata" , zap . String ( "traceID" , traceID ) )
return cachedTraceData , nil
}
func ( r * ClickHouseReader ) GetWaterfallSpansForTraceWithMetadata ( ctx context . Context , traceID string , req * model . GetWaterfallSpansForTraceWithMetadataParams ) ( * model . GetWaterfallSpansForTraceWithMetadataResponse , * model . ApiError ) {
response := new ( model . GetWaterfallSpansForTraceWithMetadataResponse )
var startTime , endTime , durationNano , totalErrorSpans , totalSpans uint64
var spanIdToSpanNodeMap = map [ string ] * model . Span { }
var traceRoots [ ] * model . Span
var serviceNameToTotalDurationMap = map [ string ] uint64 { }
var serviceNameIntervalMap = map [ string ] [ ] tracedetail . Interval { }
2025-01-31 15:04:21 +05:30
var hasMissingSpans bool
2025-01-24 00:16:38 +05:30
2025-02-17 18:16:41 +05:30
claims , claimsPresent := authtypes . ClaimsFromContext ( ctx )
2025-01-24 00:16:38 +05:30
cachedTraceData , err := r . GetWaterfallSpansForTraceWithMetadataCache ( ctx , traceID )
if err == nil {
startTime = cachedTraceData . StartTime
endTime = cachedTraceData . EndTime
durationNano = cachedTraceData . DurationNano
spanIdToSpanNodeMap = cachedTraceData . SpanIdToSpanNodeMap
serviceNameToTotalDurationMap = cachedTraceData . ServiceNameToTotalDurationMap
traceRoots = cachedTraceData . TraceRoots
totalSpans = cachedTraceData . TotalSpans
totalErrorSpans = cachedTraceData . TotalErrorSpans
2025-01-31 15:04:21 +05:30
hasMissingSpans = cachedTraceData . HasMissingSpans
2025-02-17 18:16:41 +05:30
if claimsPresent {
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_TRACE_DETAIL_API , map [ string ] interface { } { "traceSize" : totalSpans } , claims . Email , true , false )
2025-01-31 15:04:21 +05:30
}
2025-01-24 00:16:38 +05:30
}
if err != nil {
zap . L ( ) . Info ( "cache miss for getWaterfallSpansForTraceWithMetadata" , zap . String ( "traceID" , traceID ) )
searchScanResponses , err := r . GetSpansForTrace ( ctx , traceID , fmt . Sprintf ( "SELECT timestamp, duration_nano, span_id, trace_id, has_error, kind, resource_string_service$$name, name, references, attributes_string, attributes_number, attributes_bool, resources_string, events, status_message, status_code_string, kind_string FROM %s.%s WHERE trace_id=$1 and ts_bucket_start>=$2 and ts_bucket_start<=$3 ORDER BY timestamp ASC, name ASC" , r . TraceDB , r . traceTableName ) )
if err != nil {
return nil , err
}
if len ( searchScanResponses ) == 0 {
return response , nil
}
totalSpans = uint64 ( len ( searchScanResponses ) )
2025-02-17 18:16:41 +05:30
if claimsPresent {
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_TRACE_DETAIL_API , map [ string ] interface { } { "traceSize" : totalSpans } , claims . Email , true , false )
2025-01-31 15:04:21 +05:30
}
2025-01-24 00:16:38 +05:30
processingBeforeCache := time . Now ( )
for _ , item := range searchScanResponses {
ref := [ ] model . OtelSpanRef { }
err := json . Unmarshal ( [ ] byte ( item . References ) , & ref )
if err != nil {
zap . L ( ) . Error ( "getWaterfallSpansForTraceWithMetadata: error unmarshalling references" , zap . Error ( err ) , zap . String ( "traceID" , traceID ) )
return nil , model . BadRequest ( fmt . Errorf ( "getWaterfallSpansForTraceWithMetadata: error unmarshalling references %w" , err ) )
}
// merge attributes_number and attributes_bool to attributes_string
for k , v := range item . Attributes_bool {
item . Attributes_string [ k ] = fmt . Sprintf ( "%v" , v )
}
for k , v := range item . Attributes_number {
item . Attributes_string [ k ] = fmt . Sprintf ( "%v" , v )
}
for k , v := range item . Resources_string {
item . Attributes_string [ k ] = v
}
jsonItem := model . Span {
SpanID : item . SpanID ,
TraceID : item . TraceID ,
ServiceName : item . ServiceName ,
Name : item . Name ,
Kind : int32 ( item . Kind ) ,
DurationNano : item . DurationNano ,
HasError : item . HasError ,
StatusMessage : item . StatusMessage ,
StatusCodeString : item . StatusCodeString ,
SpanKind : item . SpanKind ,
References : ref ,
Events : item . Events ,
TagMap : item . Attributes_string ,
Children : make ( [ ] * model . Span , 0 ) ,
}
// metadata calculation
2025-01-31 15:04:21 +05:30
startTimeUnixNano := uint64 ( item . TimeUnixNano . UnixNano ( ) )
if startTime == 0 || startTimeUnixNano < startTime {
startTime = startTimeUnixNano
2025-01-24 00:16:38 +05:30
}
2025-02-17 18:16:41 +05:30
if endTime == 0 || ( startTimeUnixNano + jsonItem . DurationNano ) > endTime {
endTime = ( startTimeUnixNano + jsonItem . DurationNano )
2025-01-24 00:16:38 +05:30
}
if durationNano == 0 || jsonItem . DurationNano > durationNano {
durationNano = jsonItem . DurationNano
}
2025-01-31 15:04:21 +05:30
2025-01-24 00:16:38 +05:30
if jsonItem . HasError {
totalErrorSpans = totalErrorSpans + 1
}
2025-01-31 15:04:21 +05:30
// convert start timestamp to millis because right now frontend is expecting it in millis
jsonItem . TimeUnixNano = uint64 ( item . TimeUnixNano . UnixNano ( ) / 1000000 )
// collect the intervals for service for execution time calculation
serviceNameIntervalMap [ jsonItem . ServiceName ] =
append ( serviceNameIntervalMap [ jsonItem . ServiceName ] , tracedetail . Interval { StartTime : jsonItem . TimeUnixNano , Duration : jsonItem . DurationNano / 1000000 , Service : jsonItem . ServiceName } )
// append to the span node map
spanIdToSpanNodeMap [ jsonItem . SpanID ] = & jsonItem
2025-01-24 00:16:38 +05:30
}
// traverse through the map and append each node to the children array of the parent node
// and add the missing spans
for _ , spanNode := range spanIdToSpanNodeMap {
hasParentSpanNode := false
for _ , reference := range spanNode . References {
if reference . RefType == "CHILD_OF" && reference . SpanId != "" {
hasParentSpanNode = true
if parentNode , exists := spanIdToSpanNodeMap [ reference . SpanId ] ; exists {
parentNode . Children = append ( parentNode . Children , spanNode )
} else {
// insert the missing span
missingSpan := model . Span {
SpanID : reference . SpanId ,
TraceID : spanNode . TraceID ,
ServiceName : "" ,
Name : "Missing Span" ,
TimeUnixNano : spanNode . TimeUnixNano ,
Kind : 0 ,
DurationNano : spanNode . DurationNano ,
HasError : false ,
StatusMessage : "" ,
StatusCodeString : "" ,
SpanKind : "" ,
Children : make ( [ ] * model . Span , 0 ) ,
}
missingSpan . Children = append ( missingSpan . Children , spanNode )
spanIdToSpanNodeMap [ missingSpan . SpanID ] = & missingSpan
traceRoots = append ( traceRoots , & missingSpan )
2025-01-31 15:04:21 +05:30
hasMissingSpans = true
2025-01-24 00:16:38 +05:30
}
}
}
if ! hasParentSpanNode && ! tracedetail . ContainsWaterfallSpan ( traceRoots , spanNode ) {
traceRoots = append ( traceRoots , spanNode )
}
}
// sort the trace roots to add missing spans at the right order
sort . Slice ( traceRoots , func ( i , j int ) bool {
if traceRoots [ i ] . TimeUnixNano == traceRoots [ j ] . TimeUnixNano {
return traceRoots [ i ] . Name < traceRoots [ j ] . Name
}
return traceRoots [ i ] . TimeUnixNano < traceRoots [ j ] . TimeUnixNano
} )
serviceNameToTotalDurationMap = tracedetail . CalculateServiceTime ( serviceNameIntervalMap )
traceCache := model . GetWaterfallSpansForTraceWithMetadataCache {
StartTime : startTime ,
EndTime : endTime ,
DurationNano : durationNano ,
TotalSpans : totalSpans ,
TotalErrorSpans : totalErrorSpans ,
SpanIdToSpanNodeMap : spanIdToSpanNodeMap ,
ServiceNameToTotalDurationMap : serviceNameToTotalDurationMap ,
TraceRoots : traceRoots ,
2025-01-31 15:04:21 +05:30
HasMissingSpans : hasMissingSpans ,
2025-01-24 00:16:38 +05:30
}
zap . L ( ) . Info ( "getWaterfallSpansForTraceWithMetadata: processing pre cache" , zap . Duration ( "duration" , time . Since ( processingBeforeCache ) ) , zap . String ( "traceID" , traceID ) )
cacheErr := r . cache . Store ( ctx , fmt . Sprintf ( "getWaterfallSpansForTraceWithMetadata-%v" , traceID ) , & traceCache , time . Minute * 5 )
if cacheErr != nil {
zap . L ( ) . Debug ( "failed to store cache for getWaterfallSpansForTraceWithMetadata" , zap . String ( "traceID" , traceID ) , zap . Error ( err ) )
}
}
processingPostCache := time . Now ( )
selectedSpans , uncollapsedSpans , rootServiceName , rootServiceEntryPoint := tracedetail . GetSelectedSpans ( req . UncollapsedSpans , req . SelectedSpanID , traceRoots , spanIdToSpanNodeMap , req . IsSelectedSpanIDUnCollapsed )
zap . L ( ) . Info ( "getWaterfallSpansForTraceWithMetadata: processing post cache" , zap . Duration ( "duration" , time . Since ( processingPostCache ) ) , zap . String ( "traceID" , traceID ) )
response . Spans = selectedSpans
response . UncollapsedSpans = uncollapsedSpans
2025-01-31 15:04:21 +05:30
response . StartTimestampMillis = startTime / 1000000
response . EndTimestampMillis = endTime / 1000000
2025-01-24 00:16:38 +05:30
response . TotalSpansCount = totalSpans
response . TotalErrorSpansCount = totalErrorSpans
response . RootServiceName = rootServiceName
response . RootServiceEntryPoint = rootServiceEntryPoint
response . ServiceNameToTotalDurationMap = serviceNameToTotalDurationMap
2025-01-31 15:04:21 +05:30
response . HasMissingSpans = hasMissingSpans
2025-01-24 00:16:38 +05:30
return response , nil
}
func ( r * ClickHouseReader ) GetFlamegraphSpansForTraceCache ( ctx context . Context , traceID string ) ( * model . GetFlamegraphSpansForTraceCache , error ) {
cachedTraceData := new ( model . GetFlamegraphSpansForTraceCache )
cacheStatus , err := r . cache . Retrieve ( ctx , fmt . Sprintf ( "getFlamegraphSpansForTrace-%v" , traceID ) , cachedTraceData , false )
if err != nil {
zap . L ( ) . Debug ( "error in retrieving getFlamegraphSpansForTrace cache" , zap . Error ( err ) , zap . String ( "traceID" , traceID ) )
return nil , err
}
if cacheStatus != cache . RetrieveStatusHit {
return nil , errors . Errorf ( "cache status for getFlamegraphSpansForTrace : %s, traceID: %s" , cacheStatus , traceID )
}
if time . Since ( time . UnixMilli ( int64 ( cachedTraceData . EndTime ) ) ) < r . fluxIntervalForTraceDetail {
zap . L ( ) . Info ( "the trace end time falls under the flux interval, skipping getFlamegraphSpansForTrace cache" , zap . String ( "traceID" , traceID ) )
return nil , errors . Errorf ( "the trace end time falls under the flux interval, skipping getFlamegraphSpansForTrace cache, traceID: %s" , traceID )
}
zap . L ( ) . Info ( "cache is successfully hit, applying cache for getFlamegraphSpansForTrace" , zap . String ( "traceID" , traceID ) )
return cachedTraceData , nil
}
func ( r * ClickHouseReader ) GetFlamegraphSpansForTrace ( ctx context . Context , traceID string , req * model . GetFlamegraphSpansForTraceParams ) ( * model . GetFlamegraphSpansForTraceResponse , * model . ApiError ) {
trace := new ( model . GetFlamegraphSpansForTraceResponse )
var startTime , endTime , durationNano uint64
var spanIdToSpanNodeMap = map [ string ] * model . FlamegraphSpan { }
// map[traceID][level]span
var selectedSpans = [ ] [ ] * model . FlamegraphSpan { }
var traceRoots [ ] * model . FlamegraphSpan
// get the trace tree from cache!
cachedTraceData , err := r . GetFlamegraphSpansForTraceCache ( ctx , traceID )
if err == nil {
startTime = cachedTraceData . StartTime
endTime = cachedTraceData . EndTime
durationNano = cachedTraceData . DurationNano
selectedSpans = cachedTraceData . SelectedSpans
traceRoots = cachedTraceData . TraceRoots
}
if err != nil {
zap . L ( ) . Info ( "cache miss for getFlamegraphSpansForTrace" , zap . String ( "traceID" , traceID ) )
searchScanResponses , err := r . GetSpansForTrace ( ctx , traceID , fmt . Sprintf ( "SELECT timestamp, duration_nano, span_id, trace_id, has_error,references, resource_string_service$$name, name FROM %s.%s WHERE trace_id=$1 and ts_bucket_start>=$2 and ts_bucket_start<=$3 ORDER BY timestamp ASC, name ASC" , r . TraceDB , r . traceTableName ) )
if err != nil {
return nil , err
}
if len ( searchScanResponses ) == 0 {
return trace , nil
}
processingBeforeCache := time . Now ( )
for _ , item := range searchScanResponses {
ref := [ ] model . OtelSpanRef { }
err := json . Unmarshal ( [ ] byte ( item . References ) , & ref )
if err != nil {
zap . L ( ) . Error ( "Error unmarshalling references" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "error in unmarshalling references: %w" , err ) }
}
jsonItem := model . FlamegraphSpan {
SpanID : item . SpanID ,
TraceID : item . TraceID ,
ServiceName : item . ServiceName ,
Name : item . Name ,
DurationNano : item . DurationNano ,
HasError : item . HasError ,
References : ref ,
Children : make ( [ ] * model . FlamegraphSpan , 0 ) ,
}
// metadata calculation
2025-02-17 18:16:41 +05:30
startTimeUnixNano := uint64 ( item . TimeUnixNano . UnixNano ( ) )
2025-01-31 15:04:21 +05:30
if startTime == 0 || startTimeUnixNano < startTime {
startTime = startTimeUnixNano
2025-01-24 00:16:38 +05:30
}
2025-02-17 18:16:41 +05:30
if endTime == 0 || ( startTimeUnixNano + jsonItem . DurationNano ) > endTime {
endTime = ( startTimeUnixNano + jsonItem . DurationNano )
2025-01-24 00:16:38 +05:30
}
if durationNano == 0 || jsonItem . DurationNano > durationNano {
durationNano = jsonItem . DurationNano
}
2025-01-31 15:04:21 +05:30
jsonItem . TimeUnixNano = uint64 ( item . TimeUnixNano . UnixNano ( ) / 1000000 )
spanIdToSpanNodeMap [ jsonItem . SpanID ] = & jsonItem
2025-01-24 00:16:38 +05:30
}
// traverse through the map and append each node to the children array of the parent node
// and add missing spans
for _ , spanNode := range spanIdToSpanNodeMap {
hasParentSpanNode := false
for _ , reference := range spanNode . References {
if reference . RefType == "CHILD_OF" && reference . SpanId != "" {
hasParentSpanNode = true
if parentNode , exists := spanIdToSpanNodeMap [ reference . SpanId ] ; exists {
parentNode . Children = append ( parentNode . Children , spanNode )
} else {
// insert the missing spans
missingSpan := model . FlamegraphSpan {
SpanID : reference . SpanId ,
TraceID : spanNode . TraceID ,
ServiceName : "" ,
Name : "Missing Span" ,
TimeUnixNano : spanNode . TimeUnixNano ,
DurationNano : spanNode . DurationNano ,
HasError : false ,
Children : make ( [ ] * model . FlamegraphSpan , 0 ) ,
}
missingSpan . Children = append ( missingSpan . Children , spanNode )
spanIdToSpanNodeMap [ missingSpan . SpanID ] = & missingSpan
traceRoots = append ( traceRoots , & missingSpan )
}
}
}
if ! hasParentSpanNode && ! tracedetail . ContainsFlamegraphSpan ( traceRoots , spanNode ) {
traceRoots = append ( traceRoots , spanNode )
}
}
selectedSpans = tracedetail . GetSelectedSpansForFlamegraph ( traceRoots , spanIdToSpanNodeMap )
traceCache := model . GetFlamegraphSpansForTraceCache {
StartTime : startTime ,
EndTime : endTime ,
DurationNano : durationNano ,
SelectedSpans : selectedSpans ,
TraceRoots : traceRoots ,
}
zap . L ( ) . Info ( "getFlamegraphSpansForTrace: processing pre cache" , zap . Duration ( "duration" , time . Since ( processingBeforeCache ) ) , zap . String ( "traceID" , traceID ) )
cacheErr := r . cache . Store ( ctx , fmt . Sprintf ( "getFlamegraphSpansForTrace-%v" , traceID ) , & traceCache , time . Minute * 5 )
if cacheErr != nil {
zap . L ( ) . Debug ( "failed to store cache for getFlamegraphSpansForTrace" , zap . String ( "traceID" , traceID ) , zap . Error ( err ) )
}
}
processingPostCache := time . Now ( )
2025-01-28 22:04:24 +05:30
selectedSpansForRequest := tracedetail . GetSelectedSpansForFlamegraphForRequest ( req . SelectedSpanID , selectedSpans , startTime , endTime )
2025-01-24 00:16:38 +05:30
zap . L ( ) . Info ( "getFlamegraphSpansForTrace: processing post cache" , zap . Duration ( "duration" , time . Since ( processingPostCache ) ) , zap . String ( "traceID" , traceID ) )
trace . Spans = selectedSpansForRequest
2025-01-31 15:04:21 +05:30
trace . StartTimestampMillis = startTime / 1000000
2025-02-17 18:16:41 +05:30
trace . EndTimestampMillis = endTime / 1000000
2025-01-24 00:16:38 +05:30
return trace , nil
}
2022-08-04 12:38:53 +05:30
func ( r * ClickHouseReader ) GetDependencyGraph ( ctx context . Context , queryParams * model . GetServicesParams ) ( * [ ] model . ServiceMapDependencyResponseItem , error ) {
2021-05-31 11:14:11 +05:30
2022-08-04 12:38:53 +05:30
response := [ ] model . ServiceMapDependencyResponseItem { }
2021-05-31 11:14:11 +05:30
2022-08-04 12:38:53 +05:30
args := [ ] interface { } { }
args = append ( args ,
clickhouse . Named ( "start" , uint64 ( queryParams . Start . Unix ( ) ) ) ,
clickhouse . Named ( "end" , uint64 ( queryParams . End . Unix ( ) ) ) ,
clickhouse . Named ( "duration" , uint64 ( queryParams . End . Unix ( ) - queryParams . Start . Unix ( ) ) ) ,
)
2021-05-31 11:14:11 +05:30
2022-08-04 12:38:53 +05:30
query := fmt . Sprintf ( `
WITH
quantilesMergeState ( 0.5 , 0.75 , 0.9 , 0.95 , 0.99 ) ( duration_quantiles_state ) AS duration_quantiles_state ,
finalizeAggregation ( duration_quantiles_state ) AS result
SELECT
src as parent ,
dest as child ,
result [ 1 ] AS p50 ,
result [ 2 ] AS p75 ,
result [ 3 ] AS p90 ,
result [ 4 ] AS p95 ,
result [ 5 ] AS p99 ,
sum ( total_count ) as callCount ,
sum ( total_count ) / @ duration AS callRate ,
2023-02-23 11:15:14 +05:30
sum ( error_count ) / sum ( total_count ) * 100 as errorRate
2022-08-04 12:38:53 +05:30
FROM % s . % s
2023-03-28 22:15:46 +05:30
WHERE toUInt64 ( toDateTime ( timestamp ) ) >= @ start AND toUInt64 ( toDateTime ( timestamp ) ) <= @ end ` ,
2022-11-24 18:18:19 +05:30
r . TraceDB , r . dependencyGraphTable ,
2022-08-04 12:38:53 +05:30
)
2021-05-31 11:14:11 +05:30
2023-03-28 22:15:46 +05:30
tags := createTagQueryFromTagQueryParams ( queryParams . Tags )
filterQuery , filterArgs := services . BuildServiceMapQuery ( tags )
query += filterQuery + " GROUP BY src, dest;"
args = append ( args , filterArgs ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "GetDependencyGraph query" , zap . String ( "query" , query ) , zap . Any ( "args" , args ) )
2021-05-31 11:14:11 +05:30
2022-08-04 12:38:53 +05:30
err := r . db . Select ( ctx , & response , query , args ... )
2021-05-31 11:14:11 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2023-03-28 22:15:46 +05:30
return nil , fmt . Errorf ( "error in processing sql query %w" , err )
2021-05-31 11:14:11 +05:30
}
2022-08-04 12:38:53 +05:30
return & response , nil
2021-05-31 11:14:11 +05:30
}
2021-06-01 15:13:48 +05:30
2022-12-02 12:30:28 +05:30
func getLocalTableName ( tableName string ) string {
tableNameSplit := strings . Split ( tableName , "." )
return tableNameSplit [ 0 ] + "." + strings . Split ( tableNameSplit [ 1 ] , "distributed_" ) [ 1 ]
}
2024-09-23 20:12:38 +05:30
func ( r * ClickHouseReader ) SetTTLLogsV2 ( ctx context . Context , params * model . TTLParams ) ( * model . SetTTLResponseItem , * model . ApiError ) {
// Keep only latest 100 transactions/requests
r . deleteTtlTransactions ( ctx , 100 )
// uuid is used as transaction id
uuidWithHyphen := uuid . New ( )
uuid := strings . Replace ( uuidWithHyphen . String ( ) , "-" , "" , - 1 )
coldStorageDuration := - 1
if len ( params . ColdStorageVolume ) > 0 {
coldStorageDuration = int ( params . ToColdStorageDuration )
}
tableNameArray := [ ] string { r . logsDB + "." + r . logsLocalTableV2 , r . logsDB + "." + r . logsResourceLocalTableV2 }
// check if there is existing things to be done
for _ , tableName := range tableNameArray {
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
}
if statusItem . Status == constants . StatusPending {
return nil , & model . ApiError { Typ : model . ErrorConflict , Err : fmt . Errorf ( "TTL is already running" ) }
}
}
// TTL query for logs_v2 table
ttlLogsV2 := fmt . Sprintf (
"ALTER TABLE %v ON CLUSTER %s MODIFY TTL toDateTime(timestamp / 1000000000) + " +
"INTERVAL %v SECOND DELETE" , tableNameArray [ 0 ] , r . cluster , params . DelDuration )
if len ( params . ColdStorageVolume ) > 0 {
ttlLogsV2 += fmt . Sprintf ( ", toDateTime(timestamp / 1000000000)" +
" + INTERVAL %v SECOND TO VOLUME '%s'" ,
params . ToColdStorageDuration , params . ColdStorageVolume )
}
// TTL query for logs_v2_resource table
// adding 1800 as our bucket size is 1800 seconds
ttlLogsV2Resource := fmt . Sprintf (
"ALTER TABLE %v ON CLUSTER %s MODIFY TTL toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + " +
"INTERVAL %v SECOND DELETE" , tableNameArray [ 1 ] , r . cluster , params . DelDuration )
if len ( params . ColdStorageVolume ) > 0 {
ttlLogsV2Resource += fmt . Sprintf ( ", toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + " +
"INTERVAL %v SECOND TO VOLUME '%s'" ,
params . ToColdStorageDuration , params . ColdStorageVolume )
}
ttlPayload := map [ string ] string {
tableNameArray [ 0 ] : ttlLogsV2 ,
tableNameArray [ 1 ] : ttlLogsV2Resource ,
}
// set the ttl if nothing is pending/ no errors
go func ( ttlPayload map [ string ] string ) {
for tableName , query := range ttlPayload {
// https://github.com/SigNoz/signoz/issues/5470
// we will change ttl for only the new parts and not the old ones
query += " SETTINGS materialize_ttl_after_modify=0"
_ , dbErr := r . localDB . Exec ( "INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)" , uuid , time . Now ( ) , time . Now ( ) , tableName , params . DelDuration , constants . StatusPending , coldStorageDuration )
if dbErr != nil {
zap . L ( ) . Error ( "error in inserting to ttl_status table" , zap . Error ( dbErr ) )
return
}
err := r . setColdStorage ( context . Background ( ) , tableName , params . ColdStorageVolume )
if err != nil {
zap . L ( ) . Error ( "error in setting cold storage" , zap . Error ( err ) )
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err == nil {
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
}
return
}
zap . L ( ) . Info ( "Executing TTL request: " , zap . String ( "request" , query ) )
statusItem , _ := r . checkTTLStatusItem ( ctx , tableName )
if err := r . db . Exec ( ctx , query ) ; err != nil {
zap . L ( ) . Error ( "error while setting ttl" , zap . Error ( err ) )
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
return
}
_ , dbErr = r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusSuccess , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
}
} ( ttlPayload )
return & model . SetTTLResponseItem { Message : "move ttl has been successfully set up" } , nil
}
2024-11-21 22:37:28 +05:30
func ( r * ClickHouseReader ) SetTTLTracesV2 ( ctx context . Context , params * model . TTLParams ) ( * model . SetTTLResponseItem , * model . ApiError ) {
// uuid is used as transaction id
uuidWithHyphen := uuid . New ( )
uuid := strings . Replace ( uuidWithHyphen . String ( ) , "-" , "" , - 1 )
tableNames := [ ] string {
r . TraceDB + "." + r . traceTableName ,
r . TraceDB + "." + r . traceResourceTableV3 ,
r . TraceDB + "." + signozErrorIndexTable ,
r . TraceDB + "." + signozUsageExplorerTable ,
r . TraceDB + "." + defaultDependencyGraphTable ,
r . TraceDB + "." + r . traceSummaryTable ,
}
coldStorageDuration := - 1
if len ( params . ColdStorageVolume ) > 0 {
coldStorageDuration = int ( params . ToColdStorageDuration )
}
// check if there is existing things to be done
for _ , tableName := range tableNames {
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
}
if statusItem . Status == constants . StatusPending {
return nil , & model . ApiError { Typ : model . ErrorConflict , Err : fmt . Errorf ( "TTL is already running" ) }
}
}
// TTL query
ttlV2 := "ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(%s) + INTERVAL %v SECOND DELETE"
ttlV2ColdStorage := ", toDateTime(%s) + INTERVAL %v SECOND TO VOLUME '%s'"
// TTL query for resource table
ttlV2Resource := "ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + INTERVAL %v SECOND DELETE"
ttlTracesV2ResourceColdStorage := ", toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + INTERVAL %v SECOND TO VOLUME '%s'"
for _ , distributedTableName := range tableNames {
go func ( distributedTableName string ) {
tableName := getLocalTableName ( distributedTableName )
// for trace summary table, we need to use end instead of timestamp
timestamp := "timestamp"
if strings . HasSuffix ( distributedTableName , r . traceSummaryTable ) {
timestamp = "end"
}
_ , dbErr := r . localDB . Exec ( "INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)" , uuid , time . Now ( ) , time . Now ( ) , tableName , params . DelDuration , constants . StatusPending , coldStorageDuration )
if dbErr != nil {
zap . L ( ) . Error ( "Error in inserting to ttl_status table" , zap . Error ( dbErr ) )
return
}
req := fmt . Sprintf ( ttlV2 , tableName , r . cluster , timestamp , params . DelDuration )
if strings . HasSuffix ( distributedTableName , r . traceResourceTableV3 ) {
req = fmt . Sprintf ( ttlV2Resource , tableName , r . cluster , params . DelDuration )
}
if len ( params . ColdStorageVolume ) > 0 {
if strings . HasSuffix ( distributedTableName , r . traceResourceTableV3 ) {
req += fmt . Sprintf ( ttlTracesV2ResourceColdStorage , params . ToColdStorageDuration , params . ColdStorageVolume )
} else {
req += fmt . Sprintf ( ttlV2ColdStorage , timestamp , params . ToColdStorageDuration , params . ColdStorageVolume )
}
}
err := r . setColdStorage ( context . Background ( ) , tableName , params . ColdStorageVolume )
if err != nil {
zap . L ( ) . Error ( "Error in setting cold storage" , zap . Error ( err ) )
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err == nil {
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
}
return
}
req += " SETTINGS materialize_ttl_after_modify=0;"
zap . L ( ) . Error ( " ExecutingTTL request: " , zap . String ( "request" , req ) )
statusItem , _ := r . checkTTLStatusItem ( ctx , tableName )
if err := r . db . Exec ( ctx , req ) ; err != nil {
zap . L ( ) . Error ( "Error in executing set TTL query" , zap . Error ( err ) )
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
return
}
_ , dbErr = r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusSuccess , statusItem . Id )
if dbErr != nil {
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
return
}
} ( distributedTableName )
}
return & model . SetTTLResponseItem { Message : "move ttl has been successfully set up" } , nil
}
2024-09-23 20:12:38 +05:30
// SetTTL sets the TTL for traces or metrics or logs tables.
// This is an async API which creates goroutines to set TTL.
// Status of TTL update is tracked with ttl_status table in sqlite db.
2022-08-04 14:28:10 +05:30
// SetTTL sets the TTL for traces or metrics or logs tables.
2022-05-25 16:55:30 +05:30
// This is an async API which creates goroutines to set TTL.
// Status of TTL update is tracked with ttl_status table in sqlite db.
2022-03-21 23:58:56 +05:30
func ( r * ClickHouseReader ) SetTTL ( ctx context . Context ,
params * model . TTLParams ) ( * model . SetTTLResponseItem , * model . ApiError ) {
2022-05-25 16:55:30 +05:30
// Keep only latest 100 transactions/requests
r . deleteTtlTransactions ( ctx , 100 )
// uuid is used as transaction id
uuidWithHyphen := uuid . New ( )
uuid := strings . Replace ( uuidWithHyphen . String ( ) , "-" , "" , - 1 )
coldStorageDuration := - 1
if len ( params . ColdStorageVolume ) > 0 {
coldStorageDuration = int ( params . ToColdStorageDuration )
}
2022-03-21 23:58:56 +05:30
switch params . Type {
2021-10-20 13:18:19 +05:30
case constants . TraceTTL :
2024-11-21 22:37:28 +05:30
if r . useTraceNewSchema {
return r . SetTTLTracesV2 ( ctx , params )
}
2024-10-08 19:35:38 +05:30
tableNames := [ ] string {
signozTraceDBName + "." + signozTraceTableName ,
signozTraceDBName + "." + signozDurationMVTable ,
signozTraceDBName + "." + signozSpansTable ,
signozTraceDBName + "." + signozErrorIndexTable ,
signozTraceDBName + "." + signozUsageExplorerTable ,
signozTraceDBName + "." + defaultDependencyGraphTable ,
}
for _ , tableName := range tableNames {
2022-12-07 18:23:01 +05:30
tableName := getLocalTableName ( tableName )
2022-05-25 16:55:30 +05:30
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err != nil {
2024-06-11 20:10:38 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
2022-05-25 16:55:30 +05:30
}
if statusItem . Status == constants . StatusPending {
return nil , & model . ApiError { Typ : model . ErrorConflict , Err : fmt . Errorf ( "TTL is already running" ) }
}
}
2024-10-08 19:35:38 +05:30
for _ , tableName := range tableNames {
2022-12-07 18:23:01 +05:30
tableName := getLocalTableName ( tableName )
2022-05-25 16:55:30 +05:30
// TODO: DB queries should be implemented with transactional statements but currently clickhouse doesn't support them. Issue: https://github.com/ClickHouse/ClickHouse/issues/22086
go func ( tableName string ) {
_ , dbErr := r . localDB . Exec ( "INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)" , uuid , time . Now ( ) , time . Now ( ) , tableName , params . DelDuration , constants . StatusPending , coldStorageDuration )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in inserting to ttl_status table" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
2022-12-07 18:23:01 +05:30
req := fmt . Sprintf (
2022-12-02 12:30:28 +05:30
"ALTER TABLE %v ON CLUSTER %s MODIFY TTL toDateTime(timestamp) + INTERVAL %v SECOND DELETE" ,
2023-10-20 12:37:45 +05:30
tableName , r . cluster , params . DelDuration )
2022-05-25 16:55:30 +05:30
if len ( params . ColdStorageVolume ) > 0 {
req += fmt . Sprintf ( ", toDateTime(timestamp) + INTERVAL %v SECOND TO VOLUME '%s'" ,
params . ToColdStorageDuration , params . ColdStorageVolume )
}
err := r . setColdStorage ( context . Background ( ) , tableName , params . ColdStorageVolume )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in setting cold storage" , zap . Error ( err ) )
2022-05-25 16:55:30 +05:30
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err == nil {
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
}
return
}
2024-10-08 19:35:38 +05:30
req += " SETTINGS materialize_ttl_after_modify=0;"
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Executing TTL request: " , zap . String ( "request" , req ) )
2022-05-25 16:55:30 +05:30
statusItem , _ := r . checkTTLStatusItem ( ctx , tableName )
if err := r . db . Exec ( context . Background ( ) , req ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in executing set TTL query" , zap . Error ( err ) )
2022-05-25 16:55:30 +05:30
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
return
}
_ , dbErr = r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusSuccess , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
} ( tableName )
}
case constants . MetricsTTL :
2024-10-08 19:35:38 +05:30
tableNames := [ ] string {
signozMetricDBName + "." + signozSampleLocalTableName ,
signozMetricDBName + "." + signozSamplesAgg5mLocalTableName ,
signozMetricDBName + "." + signozSamplesAgg30mLocalTableName ,
signozMetricDBName + "." + signozExpHistLocalTableName ,
signozMetricDBName + "." + signozTSLocalTableNameV4 ,
signozMetricDBName + "." + signozTSLocalTableNameV46Hrs ,
signozMetricDBName + "." + signozTSLocalTableNameV41Day ,
signozMetricDBName + "." + signozTSLocalTableNameV41Week ,
}
2024-05-21 12:01:21 +05:30
for _ , tableName := range tableNames {
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
}
if statusItem . Status == constants . StatusPending {
return nil , & model . ApiError { Typ : model . ErrorConflict , Err : fmt . Errorf ( "TTL is already running" ) }
}
2022-05-25 16:55:30 +05:30
}
2024-05-21 12:01:21 +05:30
metricTTL := func ( tableName string ) {
2022-05-25 16:55:30 +05:30
_ , dbErr := r . localDB . Exec ( "INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)" , uuid , time . Now ( ) , time . Now ( ) , tableName , params . DelDuration , constants . StatusPending , coldStorageDuration )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in inserting to ttl_status table" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
2024-06-10 11:29:31 +05:30
timeColumn := "timestamp_ms"
2024-10-08 19:35:38 +05:30
if strings . Contains ( tableName , "v4" ) || strings . Contains ( tableName , "exp_hist" ) {
2024-06-10 11:29:31 +05:30
timeColumn = "unix_milli"
}
2022-12-07 18:23:01 +05:30
req := fmt . Sprintf (
2024-06-10 11:29:31 +05:30
"ALTER TABLE %v ON CLUSTER %s MODIFY TTL toDateTime(toUInt32(%s / 1000), 'UTC') + " +
"INTERVAL %v SECOND DELETE" , tableName , r . cluster , timeColumn , params . DelDuration )
2022-05-03 11:20:57 +05:30
if len ( params . ColdStorageVolume ) > 0 {
2024-06-10 11:29:31 +05:30
req += fmt . Sprintf ( ", toDateTime(toUInt32(%s / 1000), 'UTC')" +
2022-05-25 16:55:30 +05:30
" + INTERVAL %v SECOND TO VOLUME '%s'" ,
2024-06-10 11:29:31 +05:30
timeColumn , params . ToColdStorageDuration , params . ColdStorageVolume )
2022-05-03 11:20:57 +05:30
}
2022-05-25 16:55:30 +05:30
err := r . setColdStorage ( context . Background ( ) , tableName , params . ColdStorageVolume )
2022-05-03 11:20:57 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in setting cold storage" , zap . Error ( err ) )
2022-05-25 16:55:30 +05:30
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err == nil {
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
}
return
2022-05-03 11:20:57 +05:30
}
2024-10-08 19:35:38 +05:30
req += " SETTINGS materialize_ttl_after_modify=0"
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "Executing TTL request: " , zap . String ( "request" , req ) )
2022-05-25 16:55:30 +05:30
statusItem , _ := r . checkTTLStatusItem ( ctx , tableName )
2022-05-03 11:20:57 +05:30
if err := r . db . Exec ( ctx , req ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while setting ttl." , zap . Error ( err ) )
2022-05-25 16:55:30 +05:30
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
return
2022-05-03 11:20:57 +05:30
}
2022-05-25 16:55:30 +05:30
_ , dbErr = r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusSuccess , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-05-25 16:55:30 +05:30
return
}
2024-05-21 12:01:21 +05:30
}
for _ , tableName := range tableNames {
go metricTTL ( tableName )
}
2022-08-04 14:28:10 +05:30
case constants . LogsTTL :
2024-09-23 20:12:38 +05:30
if r . useLogsNewSchema {
return r . SetTTLLogsV2 ( ctx , params )
}
2022-12-07 18:23:01 +05:30
tableName := r . logsDB + "." + r . logsLocalTable
2022-08-04 14:28:10 +05:30
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
}
if statusItem . Status == constants . StatusPending {
return nil , & model . ApiError { Typ : model . ErrorConflict , Err : fmt . Errorf ( "TTL is already running" ) }
}
go func ( tableName string ) {
_ , dbErr := r . localDB . Exec ( "INSERT INTO ttl_status (transaction_id, created_at, updated_at, table_name, ttl, status, cold_storage_ttl) VALUES (?, ?, ?, ?, ?, ?, ?)" , uuid , time . Now ( ) , time . Now ( ) , tableName , params . DelDuration , constants . StatusPending , coldStorageDuration )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error in inserting to ttl_status table" , zap . Error ( dbErr ) )
2022-08-04 14:28:10 +05:30
return
}
2022-12-07 18:23:01 +05:30
req := fmt . Sprintf (
2022-12-02 12:30:28 +05:30
"ALTER TABLE %v ON CLUSTER %s MODIFY TTL toDateTime(timestamp / 1000000000) + " +
2023-10-20 12:37:45 +05:30
"INTERVAL %v SECOND DELETE" , tableName , r . cluster , params . DelDuration )
2022-08-04 14:28:10 +05:30
if len ( params . ColdStorageVolume ) > 0 {
req += fmt . Sprintf ( ", toDateTime(timestamp / 1000000000)" +
" + INTERVAL %v SECOND TO VOLUME '%s'" ,
params . ToColdStorageDuration , params . ColdStorageVolume )
}
err := r . setColdStorage ( context . Background ( ) , tableName , params . ColdStorageVolume )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error in setting cold storage" , zap . Error ( err ) )
2022-08-04 14:28:10 +05:30
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
if err == nil {
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-08-04 14:28:10 +05:30
return
}
}
return
}
2024-10-08 19:35:38 +05:30
req += " SETTINGS materialize_ttl_after_modify=0"
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "Executing TTL request: " , zap . String ( "request" , req ) )
2022-08-04 14:28:10 +05:30
statusItem , _ := r . checkTTLStatusItem ( ctx , tableName )
if err := r . db . Exec ( ctx , req ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while setting ttl" , zap . Error ( err ) )
2022-08-04 14:28:10 +05:30
_ , dbErr := r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusFailed , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-08-04 14:28:10 +05:30
return
}
return
}
_ , dbErr = r . localDB . Exec ( "UPDATE ttl_status SET updated_at = ?, status = ? WHERE id = ?" , time . Now ( ) , constants . StatusSuccess , statusItem . Id )
if dbErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status update sql query" , zap . Error ( dbErr ) )
2022-08-04 14:28:10 +05:30
return
}
} ( tableName )
2021-10-20 13:18:19 +05:30
default :
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while setting ttl. ttl type should be <metrics|traces>, got %v" ,
params . Type ) }
2022-03-21 23:58:56 +05:30
}
2022-05-03 11:20:57 +05:30
return & model . SetTTLResponseItem { Message : "move ttl has been successfully set up" } , nil
}
2024-09-17 15:33:17 +05:30
func ( r * ClickHouseReader ) deleteTtlTransactions ( _ context . Context , numberOfTransactionsStore int ) {
2022-05-25 16:55:30 +05:30
_ , err := r . localDB . Exec ( "DELETE FROM ttl_status WHERE transaction_id NOT IN (SELECT distinct transaction_id FROM ttl_status ORDER BY created_at DESC LIMIT ?)" , numberOfTransactionsStore )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing ttl_status delete sql query" , zap . Error ( err ) )
2022-05-25 16:55:30 +05:30
}
}
// checkTTLStatusItem checks if ttl_status table has an entry for the given table name
2024-09-17 15:33:17 +05:30
func ( r * ClickHouseReader ) checkTTLStatusItem ( _ context . Context , tableName string ) ( model . TTLStatusItem , * model . ApiError ) {
2022-05-25 16:55:30 +05:30
statusItem := [ ] model . TTLStatusItem { }
2023-11-18 10:32:19 +05:30
query := ` SELECT id, status, ttl, cold_storage_ttl FROM ttl_status WHERE table_name = ? ORDER BY created_at DESC `
2022-05-25 16:55:30 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "checkTTLStatusItem query" , zap . String ( "query" , query ) , zap . String ( "tableName" , tableName ) )
2022-05-25 16:55:30 +05:30
2023-11-18 10:32:19 +05:30
stmt , err := r . localDB . Preparex ( query )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error preparing query for checkTTLStatusItem" , zap . Error ( err ) )
2023-11-18 10:32:19 +05:30
return model . TTLStatusItem { } , & model . ApiError { Typ : model . ErrorInternal , Err : err }
}
err = stmt . Select ( & statusItem , tableName )
2022-05-25 16:55:30 +05:30
if len ( statusItem ) == 0 {
return model . TTLStatusItem { } , nil
}
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return model . TTLStatusItem { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
2022-05-25 16:55:30 +05:30
}
return statusItem [ 0 ] , nil
}
// setTTLQueryStatus fetches ttl_status table status from DB
func ( r * ClickHouseReader ) setTTLQueryStatus ( ctx context . Context , tableNameArray [ ] string ) ( string , * model . ApiError ) {
failFlag := false
status := constants . StatusSuccess
for _ , tableName := range tableNameArray {
statusItem , err := r . checkTTLStatusItem ( ctx , tableName )
emptyStatusStruct := model . TTLStatusItem { }
if statusItem == emptyStatusStruct {
return "" , nil
}
if err != nil {
2024-06-11 20:10:38 +05:30
return "" , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing ttl_status check sql query" ) }
2022-05-25 16:55:30 +05:30
}
if statusItem . Status == constants . StatusPending && statusItem . UpdatedAt . Unix ( ) - time . Now ( ) . Unix ( ) < 3600 {
status = constants . StatusPending
return status , nil
}
if statusItem . Status == constants . StatusFailed {
failFlag = true
}
}
if failFlag {
status = constants . StatusFailed
}
return status , nil
}
2022-05-03 11:20:57 +05:30
func ( r * ClickHouseReader ) setColdStorage ( ctx context . Context , tableName string , coldStorageVolume string ) * model . ApiError {
2022-03-21 23:58:56 +05:30
// Set the storage policy for the required table. If it is already set, then setting it again
// will not a problem.
2022-05-03 11:20:57 +05:30
if len ( coldStorageVolume ) > 0 {
2023-10-20 12:37:45 +05:30
policyReq := fmt . Sprintf ( "ALTER TABLE %s ON CLUSTER %s MODIFY SETTING storage_policy='tiered'" , tableName , r . cluster )
2022-03-21 23:58:56 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "Executing Storage policy request: " , zap . String ( "request" , policyReq ) )
2022-05-03 11:20:57 +05:30
if err := r . db . Exec ( ctx , policyReq ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while setting storage policy" , zap . Error ( err ) )
2022-05-03 11:20:57 +05:30
return & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while setting storage policy. Err=%v" , err ) }
2022-03-21 23:58:56 +05:30
}
}
2022-05-03 11:20:57 +05:30
return nil
2022-03-21 23:58:56 +05:30
}
// GetDisks returns a list of disks {name, type} configured in clickhouse DB.
func ( r * ClickHouseReader ) GetDisks ( ctx context . Context ) ( * [ ] model . DiskItem , * model . ApiError ) {
diskItems := [ ] model . DiskItem { }
query := "SELECT name,type FROM system.disks"
2022-05-03 11:20:57 +05:30
if err := r . db . Select ( ctx , & diskItems , query ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while getting disks. Err=%v" , err ) }
2021-10-20 13:18:19 +05:30
}
2022-03-21 23:58:56 +05:30
return & diskItems , nil
2021-10-20 13:18:19 +05:30
}
2022-12-02 12:30:28 +05:30
func getLocalTableNameArray ( tableNames [ ] string ) [ ] string {
var localTableNames [ ] string
for _ , name := range tableNames {
tableNameSplit := strings . Split ( name , "." )
localTableNames = append ( localTableNames , tableNameSplit [ 0 ] + "." + strings . Split ( tableNameSplit [ 1 ] , "distributed_" ) [ 1 ] )
}
return localTableNames
}
2022-05-25 16:55:30 +05:30
// GetTTL returns current ttl, expected ttl and past setTTL status for metrics/traces.
2021-10-20 13:18:19 +05:30
func ( r * ClickHouseReader ) GetTTL ( ctx context . Context , ttlParams * model . GetTTLParams ) ( * model . GetTTLResponseItem , * model . ApiError ) {
2022-04-01 11:22:25 +05:30
parseTTL := func ( queryResp string ) ( int , int ) {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "Parsing TTL from: " , zap . String ( "queryResp" , queryResp ) )
2022-04-01 11:22:25 +05:30
deleteTTLExp := regexp . MustCompile ( ` toIntervalSecond\(([0-9]*)\) ` )
moveTTLExp := regexp . MustCompile ( ` toIntervalSecond\(([0-9]*)\) TO VOLUME ` )
var delTTL , moveTTL int = - 1 , - 1
2021-10-20 13:18:19 +05:30
2022-04-01 11:22:25 +05:30
m := deleteTTLExp . FindStringSubmatch ( queryResp )
if len ( m ) > 1 {
seconds_int , err := strconv . Atoi ( m [ 1 ] )
if err != nil {
return - 1 , - 1
2021-10-20 13:18:19 +05:30
}
2022-04-01 11:22:25 +05:30
delTTL = seconds_int / 3600
2021-10-20 13:18:19 +05:30
}
2022-04-01 11:22:25 +05:30
m = moveTTLExp . FindStringSubmatch ( queryResp )
if len ( m ) > 1 {
seconds_int , err := strconv . Atoi ( m [ 1 ] )
if err != nil {
return - 1 , - 1
}
moveTTL = seconds_int / 3600
2021-10-22 17:15:20 +05:30
}
2022-04-01 11:22:25 +05:30
return delTTL , moveTTL
2021-10-20 13:18:19 +05:30
}
getMetricsTTL := func ( ) ( * model . DBResponseTTL , * model . ApiError ) {
2022-05-03 11:20:57 +05:30
var dbResp [ ] model . DBResponseTTL
2021-10-20 13:18:19 +05:30
2022-12-02 12:30:28 +05:30
query := fmt . Sprintf ( "SELECT engine_full FROM system.tables WHERE name='%v'" , signozSampleLocalTableName )
2021-10-20 13:18:19 +05:30
2022-05-03 11:20:57 +05:30
err := r . db . Select ( ctx , & dbResp , query )
2021-10-20 13:18:19 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while getting ttl" , zap . Error ( err ) )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while getting ttl. Err=%v" , err ) }
}
if len ( dbResp ) == 0 {
return nil , nil
} else {
return & dbResp [ 0 ] , nil
2021-10-20 13:18:19 +05:30
}
}
getTracesTTL := func ( ) ( * model . DBResponseTTL , * model . ApiError ) {
2022-05-03 11:20:57 +05:30
var dbResp [ ] model . DBResponseTTL
2021-10-20 13:18:19 +05:30
2024-11-21 22:37:28 +05:30
query := fmt . Sprintf ( "SELECT engine_full FROM system.tables WHERE name='%v' AND database='%v'" , r . traceLocalTableName , signozTraceDBName )
2021-10-20 13:18:19 +05:30
2022-05-03 11:20:57 +05:30
err := r . db . Select ( ctx , & dbResp , query )
2021-10-20 13:18:19 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while getting ttl" , zap . Error ( err ) )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while getting ttl. Err=%v" , err ) }
}
if len ( dbResp ) == 0 {
return nil , nil
} else {
return & dbResp [ 0 ] , nil
2021-10-20 13:18:19 +05:30
}
}
2022-08-04 14:28:10 +05:30
getLogsTTL := func ( ) ( * model . DBResponseTTL , * model . ApiError ) {
var dbResp [ ] model . DBResponseTTL
2024-09-23 20:12:38 +05:30
query := fmt . Sprintf ( "SELECT engine_full FROM system.tables WHERE name='%v' AND database='%v'" , r . logsLocalTableName , r . logsDB )
2022-08-04 14:28:10 +05:30
err := r . db . Select ( ctx , & dbResp , query )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while getting ttl" , zap . Error ( err ) )
2022-08-04 14:28:10 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while getting ttl. Err=%v" , err ) }
}
if len ( dbResp ) == 0 {
return nil , nil
} else {
return & dbResp [ 0 ] , nil
}
}
2021-10-20 13:18:19 +05:30
switch ttlParams . Type {
case constants . TraceTTL :
2022-08-04 13:41:25 +05:30
tableNameArray := [ ] string { signozTraceDBName + "." + signozTraceTableName , signozTraceDBName + "." + signozDurationMVTable , signozTraceDBName + "." + signozSpansTable , signozTraceDBName + "." + signozErrorIndexTable , signozTraceDBName + "." + signozUsageExplorerTable , signozTraceDBName + "." + defaultDependencyGraphTable }
2022-12-02 12:30:28 +05:30
tableNameArray = getLocalTableNameArray ( tableNameArray )
2022-05-25 16:55:30 +05:30
status , err := r . setTTLQueryStatus ( ctx , tableNameArray )
if err != nil {
return nil , err
}
2021-10-20 13:18:19 +05:30
dbResp , err := getTracesTTL ( )
if err != nil {
return nil , err
}
2022-05-25 16:55:30 +05:30
ttlQuery , err := r . checkTTLStatusItem ( ctx , tableNameArray [ 0 ] )
if err != nil {
return nil , err
}
ttlQuery . TTL = ttlQuery . TTL / 3600 // convert to hours
if ttlQuery . ColdStorageTtl != - 1 {
ttlQuery . ColdStorageTtl = ttlQuery . ColdStorageTtl / 3600 // convert to hours
}
2021-10-20 13:18:19 +05:30
2022-04-01 11:22:25 +05:30
delTTL , moveTTL := parseTTL ( dbResp . EngineFull )
2022-05-25 16:55:30 +05:30
return & model . GetTTLResponseItem { TracesTime : delTTL , TracesMoveTime : moveTTL , ExpectedTracesTime : ttlQuery . TTL , ExpectedTracesMoveTime : ttlQuery . ColdStorageTtl , Status : status } , nil
2021-10-20 13:18:19 +05:30
case constants . MetricsTTL :
2022-06-24 14:52:11 +05:30
tableNameArray := [ ] string { signozMetricDBName + "." + signozSampleTableName }
2022-12-02 12:30:28 +05:30
tableNameArray = getLocalTableNameArray ( tableNameArray )
2022-05-25 16:55:30 +05:30
status , err := r . setTTLQueryStatus ( ctx , tableNameArray )
if err != nil {
return nil , err
}
2021-10-20 13:18:19 +05:30
dbResp , err := getMetricsTTL ( )
if err != nil {
return nil , err
}
2022-05-25 16:55:30 +05:30
ttlQuery , err := r . checkTTLStatusItem ( ctx , tableNameArray [ 0 ] )
if err != nil {
return nil , err
}
ttlQuery . TTL = ttlQuery . TTL / 3600 // convert to hours
if ttlQuery . ColdStorageTtl != - 1 {
ttlQuery . ColdStorageTtl = ttlQuery . ColdStorageTtl / 3600 // convert to hours
}
2021-10-20 13:18:19 +05:30
2022-04-01 11:22:25 +05:30
delTTL , moveTTL := parseTTL ( dbResp . EngineFull )
2022-05-25 16:55:30 +05:30
return & model . GetTTLResponseItem { MetricsTime : delTTL , MetricsMoveTime : moveTTL , ExpectedMetricsTime : ttlQuery . TTL , ExpectedMetricsMoveTime : ttlQuery . ColdStorageTtl , Status : status } , nil
2022-08-04 14:28:10 +05:30
case constants . LogsTTL :
tableNameArray := [ ] string { r . logsDB + "." + r . logsTable }
2022-12-02 12:30:28 +05:30
tableNameArray = getLocalTableNameArray ( tableNameArray )
2022-08-04 14:28:10 +05:30
status , err := r . setTTLQueryStatus ( ctx , tableNameArray )
if err != nil {
return nil , err
}
dbResp , err := getLogsTTL ( )
if err != nil {
return nil , err
}
ttlQuery , err := r . checkTTLStatusItem ( ctx , tableNameArray [ 0 ] )
if err != nil {
return nil , err
}
ttlQuery . TTL = ttlQuery . TTL / 3600 // convert to hours
if ttlQuery . ColdStorageTtl != - 1 {
ttlQuery . ColdStorageTtl = ttlQuery . ColdStorageTtl / 3600 // convert to hours
}
delTTL , moveTTL := parseTTL ( dbResp . EngineFull )
return & model . GetTTLResponseItem { LogsTime : delTTL , LogsMoveTime : moveTTL , ExpectedLogsTime : ttlQuery . TTL , ExpectedLogsMoveTime : ttlQuery . ColdStorageTtl , Status : status } , nil
2022-05-25 16:55:30 +05:30
default :
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error while getting ttl. ttl type should be metrics|traces, got %v" ,
ttlParams . Type ) }
2021-10-20 13:18:19 +05:30
}
}
2022-01-21 00:31:58 +05:30
2022-07-13 15:55:43 +05:30
func ( r * ClickHouseReader ) ListErrors ( ctx context . Context , queryParams * model . ListErrorsParams ) ( * [ ] model . Error , * model . ApiError ) {
2022-01-21 00:31:58 +05:30
2022-07-13 15:55:43 +05:30
var getErrorResponses [ ] model . Error
2022-01-21 00:31:58 +05:30
2022-12-28 14:54:15 +05:30
query := "SELECT any(exceptionMessage) as exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, groupID"
if len ( queryParams . ServiceName ) != 0 {
query = query + ", serviceName"
} else {
query = query + ", any(serviceName) as serviceName"
}
if len ( queryParams . ExceptionType ) != 0 {
query = query + ", exceptionType"
} else {
query = query + ", any(exceptionType) as exceptionType"
}
query += fmt . Sprintf ( " FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU" , r . TraceDB , r . errorTable )
2022-05-03 11:20:57 +05:30
args := [ ] interface { } { clickhouse . Named ( "timestampL" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) , clickhouse . Named ( "timestampU" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) }
2022-12-28 14:54:15 +05:30
if len ( queryParams . ServiceName ) != 0 {
query = query + " AND serviceName ilike @serviceName"
args = append ( args , clickhouse . Named ( "serviceName" , "%" + queryParams . ServiceName + "%" ) )
}
if len ( queryParams . ExceptionType ) != 0 {
query = query + " AND exceptionType ilike @exceptionType"
args = append ( args , clickhouse . Named ( "exceptionType" , "%" + queryParams . ExceptionType + "%" ) )
}
2023-03-28 00:15:15 +05:30
// create TagQuery from TagQueryParams
tags := createTagQueryFromTagQueryParams ( queryParams . Tags )
subQuery , argsSubQuery , errStatus := buildQueryWithTagParams ( ctx , tags )
query += subQuery
args = append ( args , argsSubQuery ... )
if errStatus != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing tags" , zap . Error ( errStatus ) )
2023-03-28 00:15:15 +05:30
return nil , errStatus
}
2022-12-28 14:54:15 +05:30
query = query + " GROUP BY groupID"
if len ( queryParams . ServiceName ) != 0 {
query = query + ", serviceName"
}
if len ( queryParams . ExceptionType ) != 0 {
query = query + ", exceptionType"
}
2022-07-13 15:55:43 +05:30
if len ( queryParams . OrderParam ) != 0 {
if queryParams . Order == constants . Descending {
query = query + " ORDER BY " + queryParams . OrderParam + " DESC"
} else if queryParams . Order == constants . Ascending {
query = query + " ORDER BY " + queryParams . OrderParam + " ASC"
}
}
if queryParams . Limit > 0 {
query = query + " LIMIT @limit"
args = append ( args , clickhouse . Named ( "limit" , queryParams . Limit ) )
}
2022-01-21 00:31:58 +05:30
2022-07-13 15:55:43 +05:30
if queryParams . Offset > 0 {
query = query + " OFFSET @offset"
args = append ( args , clickhouse . Named ( "offset" , queryParams . Offset ) )
}
2022-01-21 00:31:58 +05:30
2022-07-13 15:55:43 +05:30
err := r . db . Select ( ctx , & getErrorResponses , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-01-21 00:31:58 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-01-21 00:31:58 +05:30
}
2022-07-13 15:55:43 +05:30
return & getErrorResponses , nil
}
func ( r * ClickHouseReader ) CountErrors ( ctx context . Context , queryParams * model . CountErrorsParams ) ( uint64 , * model . ApiError ) {
2022-01-21 00:31:58 +05:30
2022-07-13 15:55:43 +05:30
var errorCount uint64
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT count(distinct(groupID)) FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "timestampL" , strconv . FormatInt ( queryParams . Start . UnixNano ( ) , 10 ) ) , clickhouse . Named ( "timestampU" , strconv . FormatInt ( queryParams . End . UnixNano ( ) , 10 ) ) }
2022-12-28 14:54:15 +05:30
if len ( queryParams . ServiceName ) != 0 {
2022-12-30 16:46:13 +05:30
query = query + " AND serviceName ilike @serviceName"
args = append ( args , clickhouse . Named ( "serviceName" , "%" + queryParams . ServiceName + "%" ) )
2022-12-28 14:54:15 +05:30
}
if len ( queryParams . ExceptionType ) != 0 {
2022-12-30 16:46:13 +05:30
query = query + " AND exceptionType ilike @exceptionType"
args = append ( args , clickhouse . Named ( "exceptionType" , "%" + queryParams . ExceptionType + "%" ) )
2022-12-28 14:54:15 +05:30
}
2023-03-28 00:15:15 +05:30
// create TagQuery from TagQueryParams
tags := createTagQueryFromTagQueryParams ( queryParams . Tags )
subQuery , argsSubQuery , errStatus := buildQueryWithTagParams ( ctx , tags )
query += subQuery
args = append ( args , argsSubQuery ... )
if errStatus != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing tags" , zap . Error ( errStatus ) )
2023-03-28 00:15:15 +05:30
return 0 , errStatus
}
2022-07-13 15:55:43 +05:30
err := r . db . QueryRow ( ctx , query , args ... ) . Scan ( & errorCount )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return 0 , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
return errorCount , nil
2022-01-21 00:31:58 +05:30
}
2022-07-13 15:55:43 +05:30
func ( r * ClickHouseReader ) GetErrorFromErrorID ( ctx context . Context , queryParams * model . GetErrorParams ) ( * model . ErrorWithSpan , * model . ApiError ) {
2022-01-21 00:31:58 +05:30
if queryParams . ErrorID == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "errorId missing from params" )
2022-07-13 15:55:43 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "ErrorID missing from params" ) }
2022-01-21 00:31:58 +05:30
}
2022-05-03 11:20:57 +05:30
var getErrorWithSpanReponse [ ] model . ErrorWithSpan
2022-01-21 00:31:58 +05:30
2023-03-29 07:32:47 +05:30
query := fmt . Sprintf ( "SELECT errorID, exceptionType, exceptionStacktrace, exceptionEscaped, exceptionMessage, timestamp, spanID, traceID, serviceName, groupID FROM %s.%s WHERE timestamp = @timestamp AND groupID = @groupID AND errorID = @errorID LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
2022-01-21 00:31:58 +05:30
2022-05-03 11:20:57 +05:30
err := r . db . Select ( ctx , & getErrorWithSpanReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-01-21 00:31:58 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-01-21 00:31:58 +05:30
}
2022-05-03 11:20:57 +05:30
if len ( getErrorWithSpanReponse ) > 0 {
return & getErrorWithSpanReponse [ 0 ] , nil
} else {
2022-07-13 15:55:43 +05:30
return nil , & model . ApiError { Typ : model . ErrorNotFound , Err : fmt . Errorf ( "Error/Exception not found" ) }
2022-05-03 11:20:57 +05:30
}
2022-01-21 00:31:58 +05:30
}
2022-07-13 15:55:43 +05:30
func ( r * ClickHouseReader ) GetErrorFromGroupID ( ctx context . Context , queryParams * model . GetErrorParams ) ( * model . ErrorWithSpan , * model . ApiError ) {
2022-01-21 00:31:58 +05:30
2022-05-03 11:20:57 +05:30
var getErrorWithSpanReponse [ ] model . ErrorWithSpan
2022-01-21 00:31:58 +05:30
2023-03-29 07:32:47 +05:30
query := fmt . Sprintf ( "SELECT errorID, exceptionType, exceptionStacktrace, exceptionEscaped, exceptionMessage, timestamp, spanID, traceID, serviceName, groupID FROM %s.%s WHERE timestamp = @timestamp AND groupID = @groupID LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
2022-01-21 00:31:58 +05:30
2022-05-03 11:20:57 +05:30
err := r . db . Select ( ctx , & getErrorWithSpanReponse , query , args ... )
2022-01-21 00:31:58 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-01-21 00:31:58 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return nil , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-05-03 11:20:57 +05:30
}
if len ( getErrorWithSpanReponse ) > 0 {
return & getErrorWithSpanReponse [ 0 ] , nil
} else {
2022-07-13 15:55:43 +05:30
return nil , & model . ApiError { Typ : model . ErrorNotFound , Err : fmt . Errorf ( "Error/Exception not found" ) }
}
}
func ( r * ClickHouseReader ) GetNextPrevErrorIDs ( ctx context . Context , queryParams * model . GetErrorParams ) ( * model . NextPrevErrorIDs , * model . ApiError ) {
if queryParams . ErrorID == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "errorId missing from params" )
2022-07-13 15:55:43 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "ErrorID missing from params" ) }
}
var err * model . ApiError
getNextPrevErrorIDsResponse := model . NextPrevErrorIDs {
GroupID : queryParams . GroupID ,
}
getNextPrevErrorIDsResponse . NextErrorID , getNextPrevErrorIDsResponse . NextTimestamp , err = r . getNextErrorID ( ctx , queryParams )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Unable to get next error ID due to err: " , zap . Error ( err ) )
2022-07-13 15:55:43 +05:30
return nil , err
}
getNextPrevErrorIDsResponse . PrevErrorID , getNextPrevErrorIDsResponse . PrevTimestamp , err = r . getPrevErrorID ( ctx , queryParams )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Unable to get prev error ID due to err: " , zap . Error ( err ) )
2022-07-13 15:55:43 +05:30
return nil , err
}
return & getNextPrevErrorIDsResponse , nil
}
func ( r * ClickHouseReader ) getNextErrorID ( ctx context . Context , queryParams * model . GetErrorParams ) ( string , time . Time , * model . ApiError ) {
var getNextErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as nextErrorID, timestamp as nextTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp >= @timestamp AND errorID != @errorID ORDER BY timestamp ASC LIMIT 2" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getNextErrorIDReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getNextErrorIDReponse ) == 0 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID not found" )
2022-07-13 15:55:43 +05:30
return "" , time . Time { } , nil
} else if len ( getNextErrorIDReponse ) == 1 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID found" )
2022-07-13 15:55:43 +05:30
return getNextErrorIDReponse [ 0 ] . NextErrorID , getNextErrorIDReponse [ 0 ] . NextTimestamp , nil
} else {
if getNextErrorIDReponse [ 0 ] . Timestamp . UnixNano ( ) == getNextErrorIDReponse [ 1 ] . Timestamp . UnixNano ( ) {
var getNextErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as nextErrorID, timestamp as nextTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp = @timestamp AND errorID > @errorID ORDER BY errorID ASC LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getNextErrorIDReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getNextErrorIDReponse ) == 0 {
var getNextErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as nextErrorID, timestamp as nextTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp > @timestamp ORDER BY timestamp ASC LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getNextErrorIDReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getNextErrorIDReponse ) == 0 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID not found" )
2022-07-13 15:55:43 +05:30
return "" , time . Time { } , nil
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID found" )
2022-07-13 15:55:43 +05:30
return getNextErrorIDReponse [ 0 ] . NextErrorID , getNextErrorIDReponse [ 0 ] . NextTimestamp , nil
}
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID found" )
2022-07-13 15:55:43 +05:30
return getNextErrorIDReponse [ 0 ] . NextErrorID , getNextErrorIDReponse [ 0 ] . NextTimestamp , nil
}
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "NextErrorID found" )
2022-07-13 15:55:43 +05:30
return getNextErrorIDReponse [ 0 ] . NextErrorID , getNextErrorIDReponse [ 0 ] . NextTimestamp , nil
}
2022-05-03 11:20:57 +05:30
}
2022-07-13 15:55:43 +05:30
}
func ( r * ClickHouseReader ) getPrevErrorID ( ctx context . Context , queryParams * model . GetErrorParams ) ( string , time . Time , * model . ApiError ) {
var getPrevErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as prevErrorID, timestamp as prevTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp <= @timestamp AND errorID != @errorID ORDER BY timestamp DESC LIMIT 2" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getPrevErrorIDReponse , query , args ... )
2022-05-03 11:20:57 +05:30
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getPrevErrorIDReponse ) == 0 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID not found" )
2022-07-13 15:55:43 +05:30
return "" , time . Time { } , nil
} else if len ( getPrevErrorIDReponse ) == 1 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID found" )
2022-07-13 15:55:43 +05:30
return getPrevErrorIDReponse [ 0 ] . PrevErrorID , getPrevErrorIDReponse [ 0 ] . PrevTimestamp , nil
} else {
if getPrevErrorIDReponse [ 0 ] . Timestamp . UnixNano ( ) == getPrevErrorIDReponse [ 1 ] . Timestamp . UnixNano ( ) {
var getPrevErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as prevErrorID, timestamp as prevTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp = @timestamp AND errorID < @errorID ORDER BY errorID DESC LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getPrevErrorIDReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getPrevErrorIDReponse ) == 0 {
var getPrevErrorIDReponse [ ] model . NextPrevErrorIDsDBResponse
2022-11-24 18:18:19 +05:30
query := fmt . Sprintf ( "SELECT errorID as prevErrorID, timestamp as prevTimestamp FROM %s.%s WHERE groupID = @groupID AND timestamp < @timestamp ORDER BY timestamp DESC LIMIT 1" , r . TraceDB , r . errorTable )
2022-07-13 15:55:43 +05:30
args := [ ] interface { } { clickhouse . Named ( "errorID" , queryParams . ErrorID ) , clickhouse . Named ( "groupID" , queryParams . GroupID ) , clickhouse . Named ( "timestamp" , strconv . FormatInt ( queryParams . Timestamp . UnixNano ( ) , 10 ) ) }
err := r . db . Select ( ctx , & getPrevErrorIDReponse , query , args ... )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-07-13 15:55:43 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2024-06-11 20:10:38 +05:30
return "" , time . Time { } , & model . ApiError { Typ : model . ErrorExec , Err : fmt . Errorf ( "error in processing sql query" ) }
2022-07-13 15:55:43 +05:30
}
if len ( getPrevErrorIDReponse ) == 0 {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID not found" )
2022-07-13 15:55:43 +05:30
return "" , time . Time { } , nil
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID found" )
2022-07-13 15:55:43 +05:30
return getPrevErrorIDReponse [ 0 ] . PrevErrorID , getPrevErrorIDReponse [ 0 ] . PrevTimestamp , nil
}
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID found" )
2022-07-13 15:55:43 +05:30
return getPrevErrorIDReponse [ 0 ] . PrevErrorID , getPrevErrorIDReponse [ 0 ] . PrevTimestamp , nil
}
} else {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( "PrevErrorID found" )
2022-07-13 15:55:43 +05:30
return getPrevErrorIDReponse [ 0 ] . PrevErrorID , getPrevErrorIDReponse [ 0 ] . PrevTimestamp , nil
}
}
2022-05-03 11:20:57 +05:30
}
2022-07-04 17:13:36 +05:30
func ( r * ClickHouseReader ) GetTotalSpans ( ctx context . Context ) ( uint64 , error ) {
var totalSpans uint64
2024-11-20 23:35:44 +05:30
queryStr := fmt . Sprintf ( "SELECT count() from %s.%s;" , signozTraceDBName , r . traceTableName )
2022-07-04 17:13:36 +05:30
r . db . QueryRow ( ctx , queryStr ) . Scan ( & totalSpans )
return totalSpans , nil
}
2024-02-21 14:49:33 +05:30
func ( r * ClickHouseReader ) GetSpansInLastHeartBeatInterval ( ctx context . Context , interval time . Duration ) ( uint64 , error ) {
2022-07-04 17:13:36 +05:30
var spansInLastHeartBeatInterval uint64
2024-02-21 14:49:33 +05:30
queryStr := fmt . Sprintf ( "SELECT count() from %s.%s where timestamp > toUnixTimestamp(now()-toIntervalMinute(%d));" , signozTraceDBName , signozSpansTable , int ( interval . Minutes ( ) ) )
2024-11-20 23:35:44 +05:30
if r . useTraceNewSchema {
queryStr = fmt . Sprintf ( "SELECT count() from %s.%s where ts_bucket_start >= toUInt64(toUnixTimestamp(now() - toIntervalMinute(%d))) - 1800 and timestamp > toUnixTimestamp(now()-toIntervalMinute(%d));" , signozTraceDBName , r . traceTableName , int ( interval . Minutes ( ) ) , int ( interval . Minutes ( ) ) )
}
2022-07-04 17:13:36 +05:30
r . db . QueryRow ( ctx , queryStr ) . Scan ( & spansInLastHeartBeatInterval )
return spansInLastHeartBeatInterval , nil
}
2024-02-21 14:49:33 +05:30
func ( r * ClickHouseReader ) GetTotalLogs ( ctx context . Context ) ( uint64 , error ) {
var totalLogs uint64
2024-11-28 20:46:06 +05:30
queryStr := fmt . Sprintf ( "SELECT count() from %s.%s;" , r . logsDB , r . logsTableName )
2024-02-21 14:49:33 +05:30
r . db . QueryRow ( ctx , queryStr ) . Scan ( & totalLogs )
return totalLogs , nil
}
2023-07-13 18:50:19 +05:30
func ( r * ClickHouseReader ) FetchTemporality ( ctx context . Context , metricNames [ ] string ) ( map [ string ] map [ v3 . Temporality ] bool , error ) {
metricNameToTemporality := make ( map [ string ] map [ v3 . Temporality ] bool )
2024-02-11 00:31:47 +05:30
query := fmt . Sprintf ( ` SELECT DISTINCT metric_name, temporality FROM %s.%s WHERE metric_name IN $1 ` , signozMetricDBName , signozTSTableNameV41Day )
2023-07-13 18:50:19 +05:30
rows , err := r . db . Query ( ctx , query , metricNames )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
var metricName , temporality string
err := rows . Scan ( & metricName , & temporality )
if err != nil {
return nil , err
}
if _ , ok := metricNameToTemporality [ metricName ] ; ! ok {
metricNameToTemporality [ metricName ] = make ( map [ v3 . Temporality ] bool )
}
metricNameToTemporality [ metricName ] [ v3 . Temporality ( temporality ) ] = true
}
return metricNameToTemporality , nil
}
2022-07-04 17:13:36 +05:30
func ( r * ClickHouseReader ) GetTimeSeriesInfo ( ctx context . Context ) ( map [ string ] interface { } , error ) {
2024-05-21 12:01:21 +05:30
queryStr := fmt . Sprintf ( "SELECT countDistinct(fingerprint) as count from %s.%s where metric_name not like 'signoz_%%' group by metric_name order by count desc;" , signozMetricDBName , signozTSTableNameV41Day )
2022-07-04 17:13:36 +05:30
rows , _ := r . db . Query ( ctx , queryStr )
var totalTS uint64
totalTS = 0
var maxTS uint64
maxTS = 0
count := 0
for rows . Next ( ) {
var value uint64
rows . Scan ( & value )
totalTS += value
if count == 0 {
maxTS = value
}
count += 1
}
timeSeriesData := map [ string ] interface { } { }
timeSeriesData [ "totalTS" ] = totalTS
timeSeriesData [ "maxTS" ] = maxTS
return timeSeriesData , nil
}
2024-02-21 14:49:33 +05:30
func ( r * ClickHouseReader ) GetSamplesInfoInLastHeartBeatInterval ( ctx context . Context , interval time . Duration ) ( uint64 , error ) {
2022-07-04 17:13:36 +05:30
var totalSamples uint64
2024-06-24 09:23:18 +05:30
queryStr := fmt . Sprintf ( "select count() from %s.%s where metric_name not like 'signoz_%%' and unix_milli > toUnixTimestamp(now()-toIntervalMinute(%d))*1000;" , signozMetricDBName , signozSampleTableName , int ( interval . Minutes ( ) ) )
2024-02-21 14:49:33 +05:30
r . db . QueryRow ( ctx , queryStr ) . Scan ( & totalSamples )
return totalSamples , nil
}
func ( r * ClickHouseReader ) GetTotalSamples ( ctx context . Context ) ( uint64 , error ) {
var totalSamples uint64
queryStr := fmt . Sprintf ( "select count() from %s.%s where metric_name not like 'signoz_%%';" , signozMetricDBName , signozSampleTableName )
2022-07-04 17:13:36 +05:30
r . db . QueryRow ( ctx , queryStr ) . Scan ( & totalSamples )
return totalSamples , nil
}
2022-12-28 02:16:46 +05:30
func ( r * ClickHouseReader ) GetDistributedInfoInLastHeartBeatInterval ( ctx context . Context ) ( map [ string ] interface { } , error ) {
clusterInfo := [ ] model . ClusterInfo { }
queryStr := ` SELECT shard_num, shard_weight, replica_num, errors_count, slowdowns_count, estimated_recovery_time FROM system.clusters where cluster='cluster'; `
r . db . Select ( ctx , & clusterInfo , queryStr )
if len ( clusterInfo ) == 1 {
return clusterInfo [ 0 ] . GetMapFromStruct ( ) , nil
}
return nil , nil
}
2024-02-21 14:49:33 +05:30
func ( r * ClickHouseReader ) GetLogsInfoInLastHeartBeatInterval ( ctx context . Context , interval time . Duration ) ( uint64 , error ) {
2022-08-11 14:27:19 +05:30
var totalLogLines uint64
2025-01-14 01:03:12 +05:30
queryStr := fmt . Sprintf ( "select count() from %s.%s where timestamp > toUnixTimestamp(now()-toIntervalMinute(%d))*1000000000;" , r . logsDB , r . logsTableV2 , int ( interval . Minutes ( ) ) )
2022-08-11 14:27:19 +05:30
2023-12-21 19:05:21 +05:30
err := r . db . QueryRow ( ctx , queryStr ) . Scan ( & totalLogLines )
2022-08-11 14:27:19 +05:30
2023-12-21 19:05:21 +05:30
return totalLogLines , err
2022-08-11 14:27:19 +05:30
}
2022-07-12 16:38:26 +05:30
2024-02-21 14:49:33 +05:30
func ( r * ClickHouseReader ) GetTagsInfoInLastHeartBeatInterval ( ctx context . Context , interval time . Duration ) ( * model . TagsInfo , error ) {
2023-11-17 16:18:31 +05:30
queryStr := fmt . Sprintf ( ` select serviceName , stringTagMap [ ' deployment . environment ' ] as env ,
stringTagMap [ ' telemetry . sdk . language ' ] as language from % s . % s
where timestamp > toUnixTimestamp ( now ( ) - toIntervalMinute ( % d ) )
2024-11-20 23:35:44 +05:30
group by serviceName , env , language ; ` , r . TraceDB , r . traceTableName , int ( interval . Minutes ( ) ) )
if r . useTraceNewSchema {
queryStr = fmt . Sprintf ( ` select serviceName , resources_string [ ' deployment . environment ' ] as env ,
resources_string [ ' telemetry . sdk . language ' ] as language from % s . % s
where timestamp > toUnixTimestamp ( now ( ) - toIntervalMinute ( % d ) )
group by serviceName , env , language ; ` , r . TraceDB , r . traceTableName , int ( interval . Minutes ( ) ) )
}
2022-10-11 00:43:54 +05:30
tagTelemetryDataList := [ ] model . TagTelemetryData { }
err := r . db . Select ( ctx , & tagTelemetryDataList , queryStr )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query: " , zap . Error ( err ) )
2022-10-11 00:43:54 +05:30
return nil , err
}
tagsInfo := model . TagsInfo {
Languages : make ( map [ string ] interface { } ) ,
2024-02-21 14:49:33 +05:30
Services : make ( map [ string ] interface { } ) ,
2022-10-11 00:43:54 +05:30
}
for _ , tagTelemetryData := range tagTelemetryDataList {
if len ( tagTelemetryData . ServiceName ) != 0 && strings . Contains ( tagTelemetryData . ServiceName , "prod" ) {
tagsInfo . Env = tagTelemetryData . ServiceName
}
if len ( tagTelemetryData . Env ) != 0 && strings . Contains ( tagTelemetryData . Env , "prod" ) {
tagsInfo . Env = tagTelemetryData . Env
}
if len ( tagTelemetryData . Language ) != 0 {
tagsInfo . Languages [ tagTelemetryData . Language ] = struct { } { }
}
2024-02-21 14:49:33 +05:30
if len ( tagTelemetryData . ServiceName ) != 0 {
tagsInfo . Services [ tagTelemetryData . ServiceName ] = struct { } { }
}
2022-10-11 00:43:54 +05:30
}
return & tagsInfo , nil
}
2023-12-21 12:11:35 +05:30
// remove this after sometime
2024-12-19 11:52:20 +07:00
func removeUnderscoreDuplicateFields ( fields [ ] model . Field ) [ ] model . Field {
lookup := map [ string ] model . Field { }
2023-12-21 12:11:35 +05:30
for _ , v := range fields {
lookup [ v . Name + v . DataType ] = v
}
for k := range lookup {
if strings . Contains ( k , "." ) {
delete ( lookup , strings . ReplaceAll ( k , "." , "_" ) )
}
}
2024-12-19 11:52:20 +07:00
updatedFields := [ ] model . Field { }
2023-12-21 12:11:35 +05:30
for _ , v := range lookup {
updatedFields = append ( updatedFields , v )
}
return updatedFields
}
2022-07-12 16:38:26 +05:30
func ( r * ClickHouseReader ) GetLogFields ( ctx context . Context ) ( * model . GetFieldsResponse , * model . ApiError ) {
// response will contain top level fields from the otel log model
response := model . GetFieldsResponse {
Selected : constants . StaticSelectedLogFields ,
2024-12-19 11:52:20 +07:00
Interesting : [ ] model . Field { } ,
2022-07-12 16:38:26 +05:30
}
// get attribute keys
2024-12-19 11:52:20 +07:00
attributes := [ ] model . Field { }
2022-07-12 16:38:26 +05:30
query := fmt . Sprintf ( "SELECT DISTINCT name, datatype from %s.%s group by name, datatype" , r . logsDB , r . logsAttributeKeys )
2022-07-22 15:27:52 +05:30
err := r . db . Select ( ctx , & attributes , query )
2022-07-12 16:38:26 +05:30
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
// get resource keys
2024-12-19 11:52:20 +07:00
resources := [ ] model . Field { }
2022-07-12 16:38:26 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT name, datatype from %s.%s group by name, datatype" , r . logsDB , r . logsResourceKeys )
2022-07-22 15:27:52 +05:30
err = r . db . Select ( ctx , & resources , query )
2022-07-12 16:38:26 +05:30
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2023-12-21 12:11:35 +05:30
//remove this code after sometime
attributes = removeUnderscoreDuplicateFields ( attributes )
resources = removeUnderscoreDuplicateFields ( resources )
2022-07-22 15:27:52 +05:30
statements := [ ] model . ShowCreateTableStatement { }
2024-09-16 14:30:31 +05:30
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . logsDB , r . logsLocalTableName )
2022-07-12 16:38:26 +05:30
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2024-09-16 14:30:31 +05:30
r . extractSelectedAndInterestingFields ( statements [ 0 ] . Statement , constants . Attributes , & attributes , & response )
r . extractSelectedAndInterestingFields ( statements [ 0 ] . Statement , constants . Resources , & resources , & response )
2022-07-12 16:38:26 +05:30
return & response , nil
}
2024-12-19 11:52:20 +07:00
func ( r * ClickHouseReader ) extractSelectedAndInterestingFields ( tableStatement string , overrideFieldType string , fields * [ ] model . Field , response * model . GetFieldsResponse ) {
2022-07-12 16:38:26 +05:30
for _ , field := range * fields {
2024-12-19 11:52:20 +07:00
if overrideFieldType != "" {
field . Type = overrideFieldType
}
2023-08-23 15:03:24 +05:30
// all static fields are assumed to be selected as we don't allow changing them
2024-09-16 14:30:31 +05:30
if isColumn ( r . useLogsNewSchema , tableStatement , field . Type , field . Name , field . DataType ) {
2022-07-12 16:38:26 +05:30
response . Selected = append ( response . Selected , field )
} else {
response . Interesting = append ( response . Interesting , field )
}
}
}
2024-09-13 17:04:22 +05:30
func ( r * ClickHouseReader ) UpdateLogFieldV2 ( ctx context . Context , field * model . UpdateField ) * model . ApiError {
if ! field . Selected {
return model . ForbiddenError ( errors . New ( "removing a selected field is not allowed, please reach out to support." ) )
}
colname := utils . GetClickhouseColumnNameV2 ( field . Type , field . DataType , field . Name )
2025-02-20 20:09:58 +05:30
field . DataType = strings . ToLower ( field . DataType )
dataType := constants . MaterializedDataTypeMap [ field . DataType ]
chDataType := constants . ChDataTypeMap [ field . DataType ]
2024-09-13 17:04:22 +05:30
attrColName := fmt . Sprintf ( "%s_%s" , field . Type , dataType )
for _ , table := range [ ] string { r . logsLocalTableV2 , r . logsTableV2 } {
q := "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS `%s` %s DEFAULT %s['%s'] CODEC(ZSTD(1))"
query := fmt . Sprintf ( q ,
r . logsDB , table ,
r . cluster ,
2025-02-20 20:09:58 +05:30
colname , chDataType ,
2024-09-13 17:04:22 +05:30
attrColName ,
field . Name ,
)
err := r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
query = fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS `%s_exists` bool DEFAULT if(mapContains(%s, '%s') != 0, true, false) CODEC(ZSTD(1))" ,
r . logsDB , table ,
r . cluster ,
colname ,
attrColName ,
field . Name ,
)
err = r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
}
// create the index
if strings . ToLower ( field . DataType ) == "bool" {
// there is no point in creating index for bool attributes as the cardinality is just 2
return nil
}
if field . IndexType == "" {
field . IndexType = constants . DefaultLogSkipIndexType
}
if field . IndexGranularity == 0 {
field . IndexGranularity = constants . DefaultLogSkipIndexGranularity
}
query := fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD INDEX IF NOT EXISTS `%s_idx` (`%s`) TYPE %s GRANULARITY %d" ,
r . logsDB , r . logsLocalTableV2 ,
r . cluster ,
colname ,
colname ,
field . IndexType ,
field . IndexGranularity ,
)
err := r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
return nil
}
2022-07-12 16:38:26 +05:30
func ( r * ClickHouseReader ) UpdateLogField ( ctx context . Context , field * model . UpdateField ) * model . ApiError {
2023-08-23 15:03:24 +05:30
// don't allow updating static fields
if field . Type == constants . Static {
err := errors . New ( "cannot update static fields" )
return & model . ApiError { Err : err , Typ : model . ErrorBadData }
}
2024-09-13 17:04:22 +05:30
if r . useLogsNewSchema {
return r . UpdateLogFieldV2 ( ctx , field )
}
2023-08-23 15:03:24 +05:30
2022-07-22 15:27:52 +05:30
// if a field is selected it means that the field needs to be indexed
2022-07-12 16:38:26 +05:30
if field . Selected {
2024-09-13 17:04:22 +05:30
colname := utils . GetClickhouseColumnName ( field . Type , field . DataType , field . Name )
2023-08-23 15:03:24 +05:30
keyColName := fmt . Sprintf ( "%s_%s_key" , field . Type , strings . ToLower ( field . DataType ) )
valueColName := fmt . Sprintf ( "%s_%s_value" , field . Type , strings . ToLower ( field . DataType ) )
// create materialized column
2022-12-02 12:30:28 +05:30
2023-12-21 12:11:35 +05:30
for _ , table := range [ ] string { r . logsLocalTable , r . logsTable } {
q := "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS %s %s DEFAULT %s[indexOf(%s, '%s')] CODEC(ZSTD(1))"
query := fmt . Sprintf ( q ,
r . logsDB , table ,
r . cluster ,
colname , field . DataType ,
valueColName ,
keyColName ,
field . Name ,
)
err := r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2022-12-02 12:30:28 +05:30
2024-03-30 17:57:01 +05:30
query = fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS %s_exists` bool DEFAULT if(indexOf(%s, '%s') != 0, true, false) CODEC(ZSTD(1))" ,
2023-12-21 12:11:35 +05:30
r . logsDB , table ,
r . cluster ,
2024-03-30 17:57:01 +05:30
strings . TrimSuffix ( colname , "`" ) ,
2023-12-21 12:11:35 +05:30
keyColName ,
field . Name ,
)
err = r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2023-08-23 15:03:24 +05:30
}
2023-12-21 12:11:35 +05:30
// create the index
if strings . ToLower ( field . DataType ) == "bool" {
// there is no point in creating index for bool attributes as the cardinality is just 2
return nil
2022-07-12 16:38:26 +05:30
}
2022-07-22 16:49:40 +05:30
if field . IndexType == "" {
field . IndexType = constants . DefaultLogSkipIndexType
2022-07-12 16:38:26 +05:30
}
2022-07-22 16:49:40 +05:30
if field . IndexGranularity == 0 {
field . IndexGranularity = constants . DefaultLogSkipIndexGranularity
2022-07-12 16:38:26 +05:30
}
2024-03-30 17:57:01 +05:30
query := fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD INDEX IF NOT EXISTS %s_idx` (%s) TYPE %s GRANULARITY %d" ,
2023-08-23 15:03:24 +05:30
r . logsDB , r . logsLocalTable ,
2023-10-20 12:37:45 +05:30
r . cluster ,
2024-03-30 17:57:01 +05:30
strings . TrimSuffix ( colname , "`" ) ,
2023-08-23 15:03:24 +05:30
colname ,
field . IndexType ,
field . IndexGranularity ,
)
2023-12-21 12:11:35 +05:30
err := r . db . Exec ( ctx , query )
2022-07-12 16:38:26 +05:30
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2022-12-02 12:30:28 +05:30
2022-07-12 16:38:26 +05:30
} else {
2024-07-10 11:23:29 +05:30
// We are not allowing to delete a materialized column
// For more details please check https://github.com/SigNoz/signoz/issues/4566
return model . ForbiddenError ( errors . New ( "Removing a selected field is not allowed, please reach out to support." ) )
2023-08-23 15:03:24 +05:30
2024-07-10 11:23:29 +05:30
// Delete the index first
// query := fmt.Sprintf("ALTER TABLE %s.%s ON CLUSTER %s DROP INDEX IF EXISTS %s_idx`", r.logsDB, r.logsLocalTable, r.cluster, strings.TrimSuffix(colname, "`"))
// err := r.db.Exec(ctx, query)
// if err != nil {
// return &model.ApiError{Err: err, Typ: model.ErrorInternal}
// }
// for _, table := range []string{r.logsTable, r.logsLocalTable} {
// // drop materialized column from logs table
// query := "ALTER TABLE %s.%s ON CLUSTER %s DROP COLUMN IF EXISTS %s "
// err := r.db.Exec(ctx, fmt.Sprintf(query,
// r.logsDB, table,
// r.cluster,
// colname,
// ),
// )
// if err != nil {
// return &model.ApiError{Err: err, Typ: model.ErrorInternal}
// }
// // drop exists column on logs table
// query = "ALTER TABLE %s.%s ON CLUSTER %s DROP COLUMN IF EXISTS %s_exists` "
// err = r.db.Exec(ctx, fmt.Sprintf(query,
// r.logsDB, table,
// r.cluster,
// strings.TrimSuffix(colname, "`"),
// ),
// )
// if err != nil {
// return &model.ApiError{Err: err, Typ: model.ErrorInternal}
// }
// }
2022-07-12 16:38:26 +05:30
}
return nil
}
2022-07-13 15:42:13 +05:30
2024-12-19 11:52:20 +07:00
func ( r * ClickHouseReader ) GetTraceFields ( ctx context . Context ) ( * model . GetFieldsResponse , * model . ApiError ) {
// response will contain top level fields from the otel trace model
response := model . GetFieldsResponse {
Selected : [ ] model . Field { } ,
Interesting : [ ] model . Field { } ,
}
// get the top level selected fields
for _ , field := range constants . NewStaticFieldsTraces {
if ( v3 . AttributeKey { } == field ) {
continue
}
response . Selected = append ( response . Selected , model . Field {
Name : field . Key ,
DataType : field . DataType . String ( ) ,
Type : constants . Static ,
} )
}
// get attribute keys
attributes := [ ] model . Field { }
query := fmt . Sprintf ( "SELECT tagKey, tagType, dataType from %s.%s group by tagKey, tagType, dataType" , r . TraceDB , r . spanAttributesKeysTable )
rows , err := r . db . Query ( ctx , query )
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
defer rows . Close ( )
var tagKey string
var dataType string
var tagType string
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & tagType , & dataType ) ; err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
attributes = append ( attributes , model . Field {
Name : tagKey ,
DataType : dataType ,
Type : tagType ,
} )
}
statements := [ ] model . ShowCreateTableStatement { }
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . TraceDB , r . traceLocalTableName )
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
r . extractSelectedAndInterestingFields ( statements [ 0 ] . Statement , "" , & attributes , & response )
return & response , nil
}
func ( r * ClickHouseReader ) UpdateTraceField ( ctx context . Context , field * model . UpdateField ) * model . ApiError {
if ! field . Selected {
return model . ForbiddenError ( errors . New ( "removing a selected field is not allowed, please reach out to support." ) )
}
// name of the materialized column
colname := utils . GetClickhouseColumnNameV2 ( field . Type , field . DataType , field . Name )
field . DataType = strings . ToLower ( field . DataType )
// dataType and chDataType of the materialized column
2025-02-20 20:09:58 +05:30
chDataType := constants . ChDataTypeMap [ field . DataType ]
dataType := constants . MaterializedDataTypeMap [ field . DataType ]
2024-12-19 11:52:20 +07:00
// typeName: tag => attributes, resource => resources
typeName := field . Type
if field . Type == string ( v3 . AttributeKeyTypeTag ) {
typeName = constants . Attributes
} else if field . Type == string ( v3 . AttributeKeyTypeResource ) {
typeName = constants . Resources
}
attrColName := fmt . Sprintf ( "%s_%s" , typeName , dataType )
for _ , table := range [ ] string { r . traceLocalTableName , r . traceTableName } {
q := "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS `%s` %s DEFAULT %s['%s'] CODEC(ZSTD(1))"
query := fmt . Sprintf ( q ,
r . TraceDB , table ,
r . cluster ,
colname , chDataType ,
attrColName ,
field . Name ,
)
err := r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
query = fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD COLUMN IF NOT EXISTS `%s_exists` bool DEFAULT if(mapContains(%s, '%s') != 0, true, false) CODEC(ZSTD(1))" ,
r . TraceDB , table ,
r . cluster ,
colname ,
attrColName ,
field . Name ,
)
err = r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
}
// create the index
if strings . ToLower ( field . DataType ) == "bool" {
// there is no point in creating index for bool attributes as the cardinality is just 2
return nil
}
if field . IndexType == "" {
field . IndexType = constants . DefaultLogSkipIndexType
}
if field . IndexGranularity == 0 {
field . IndexGranularity = constants . DefaultLogSkipIndexGranularity
}
query := fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD INDEX IF NOT EXISTS `%s_idx` (`%s`) TYPE %s GRANULARITY %d" ,
r . TraceDB , r . traceLocalTableName ,
r . cluster ,
colname ,
colname ,
field . IndexType ,
field . IndexGranularity ,
)
err := r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
// add a default minmax index for numbers
if dataType == "number" {
query = fmt . Sprintf ( "ALTER TABLE %s.%s ON CLUSTER %s ADD INDEX IF NOT EXISTS `%s_minmax_idx` (`%s`) TYPE minmax GRANULARITY 1" ,
r . TraceDB , r . traceLocalTableName ,
r . cluster ,
colname ,
colname ,
)
err = r . db . Exec ( ctx , query )
if err != nil {
return & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
}
return nil
}
2023-10-09 15:25:13 +05:30
func ( r * ClickHouseReader ) GetLogs ( ctx context . Context , params * model . LogsFilterParams ) ( * [ ] model . SignozLog , * model . ApiError ) {
response := [ ] model . SignozLog { }
2022-07-13 15:42:13 +05:30
fields , apiErr := r . GetLogFields ( ctx )
if apiErr != nil {
return nil , apiErr
}
2022-08-10 14:27:46 +05:30
isPaginatePrev := logs . CheckIfPrevousPaginateAndModifyOrder ( params )
2022-12-28 02:16:46 +05:30
filterSql , lenFilters , err := logs . GenerateSQLWhere ( fields , params )
2022-07-13 15:42:13 +05:30
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorBadData }
}
2022-12-28 02:16:46 +05:30
data := map [ string ] interface { } {
"lenFilters" : lenFilters ,
}
2022-12-29 01:14:57 +05:30
if lenFilters != 0 {
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_LOGS_FILTERS , data , claims . Email , true , false )
2023-11-16 15:11:38 +05:30
}
2022-12-29 01:14:57 +05:30
}
2022-12-28 02:16:46 +05:30
2022-07-22 16:07:19 +05:30
query := fmt . Sprintf ( "%s from %s.%s" , constants . LogsSQLSelect , r . logsDB , r . logsTable )
2022-07-13 15:42:13 +05:30
2022-07-22 15:39:43 +05:30
if filterSql != "" {
2022-07-27 10:46:33 +05:30
query = fmt . Sprintf ( "%s where %s" , query , filterSql )
2022-07-13 15:42:13 +05:30
}
query = fmt . Sprintf ( "%s order by %s %s limit %d" , query , params . OrderBy , params . Order , params . Limit )
2022-07-22 15:27:52 +05:30
err = r . db . Select ( ctx , & response , query )
2022-07-13 15:42:13 +05:30
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
2022-08-10 14:27:46 +05:30
if isPaginatePrev {
// rever the results from db
for i , j := 0 , len ( response ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
response [ i ] , response [ j ] = response [ j ] , response [ i ]
}
}
2022-07-22 15:27:52 +05:30
return & response , nil
2022-07-13 15:42:13 +05:30
}
2022-07-18 16:37:46 +05:30
2022-07-18 18:55:52 +05:30
func ( r * ClickHouseReader ) TailLogs ( ctx context . Context , client * model . LogsTailClient ) {
2022-07-19 16:34:33 +05:30
2022-07-18 18:55:52 +05:30
fields , apiErr := r . GetLogFields ( ctx )
if apiErr != nil {
client . Error <- apiErr . Err
return
}
2022-12-28 02:16:46 +05:30
filterSql , lenFilters , err := logs . GenerateSQLWhere ( fields , & model . LogsFilterParams {
2022-07-18 18:55:52 +05:30
Query : client . Filter . Query ,
} )
2022-12-28 02:16:46 +05:30
data := map [ string ] interface { } {
"lenFilters" : lenFilters ,
}
2022-12-29 01:14:57 +05:30
if lenFilters != 0 {
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_LOGS_FILTERS , data , claims . Email , true , false )
2023-11-16 15:11:38 +05:30
}
2022-12-29 01:14:57 +05:30
}
2022-12-28 02:16:46 +05:30
2022-07-18 18:55:52 +05:30
if err != nil {
client . Error <- err
return
}
2022-07-22 16:07:19 +05:30
query := fmt . Sprintf ( "%s from %s.%s" , constants . LogsSQLSelect , r . logsDB , r . logsTable )
2022-07-18 18:55:52 +05:30
2022-07-22 15:49:50 +05:30
tsStart := uint64 ( time . Now ( ) . UnixNano ( ) )
2022-07-22 16:49:40 +05:30
if client . Filter . TimestampStart != 0 {
tsStart = client . Filter . TimestampStart
2022-07-18 18:55:52 +05:30
}
2022-07-22 15:44:07 +05:30
var idStart string
2022-08-10 14:27:46 +05:30
if client . Filter . IdGt != "" {
idStart = client . Filter . IdGt
2022-07-18 18:55:52 +05:30
}
2022-07-27 11:47:35 +05:30
ticker := time . NewTicker ( time . Duration ( r . liveTailRefreshSeconds ) * time . Second )
defer ticker . Stop ( )
2022-07-18 18:55:52 +05:30
for {
2022-07-18 16:37:46 +05:30
select {
case <- ctx . Done ( ) :
2022-07-18 18:55:52 +05:30
done := true
client . Done <- & done
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "closing go routine : " + client . Name )
2022-07-18 18:55:52 +05:30
return
2022-07-27 11:47:35 +05:30
case <- ticker . C :
2022-07-25 14:42:58 +05:30
// get the new 100 logs as anything more older won't make sense
2022-07-22 15:44:07 +05:30
tmpQuery := fmt . Sprintf ( "%s where timestamp >='%d'" , query , tsStart )
2022-07-22 15:39:43 +05:30
if filterSql != "" {
2022-07-27 10:46:33 +05:30
tmpQuery = fmt . Sprintf ( "%s and %s" , tmpQuery , filterSql )
2022-07-18 18:55:52 +05:30
}
2022-07-22 15:44:07 +05:30
if idStart != "" {
2022-07-27 10:46:33 +05:30
tmpQuery = fmt . Sprintf ( "%s and id > '%s'" , tmpQuery , idStart )
2022-07-18 18:55:52 +05:30
}
2022-07-25 14:42:58 +05:30
tmpQuery = fmt . Sprintf ( "%s order by timestamp desc, id desc limit 100" , tmpQuery )
2023-10-09 15:25:13 +05:30
response := [ ] model . SignozLog { }
2022-07-22 15:44:07 +05:30
err := r . db . Select ( ctx , & response , tmpQuery )
2022-07-18 18:55:52 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while getting logs" , zap . Error ( err ) )
2022-07-18 18:55:52 +05:30
client . Error <- err
return
}
2022-07-27 10:46:33 +05:30
for i := len ( response ) - 1 ; i >= 0 ; i -- {
2022-07-18 18:55:52 +05:30
select {
case <- ctx . Done ( ) :
done := true
client . Done <- & done
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "closing go routine while sending logs : " + client . Name )
2022-07-18 18:55:52 +05:30
return
default :
2022-07-22 15:44:07 +05:30
client . Logs <- & response [ i ]
2022-07-25 14:42:58 +05:30
if i == 0 {
2022-07-22 15:44:07 +05:30
tsStart = response [ i ] . Timestamp
idStart = response [ i ] . ID
2022-07-18 18:55:52 +05:30
}
}
}
2022-07-18 16:37:46 +05:30
}
}
}
2022-07-20 12:11:03 +05:30
func ( r * ClickHouseReader ) AggregateLogs ( ctx context . Context , params * model . LogsAggregateParams ) ( * model . GetLogsAggregatesResponse , * model . ApiError ) {
2022-07-22 15:27:52 +05:30
logAggregatesDBResponseItems := [ ] model . LogsAggregatesDBResponseItem { }
2022-07-20 12:11:03 +05:30
function := "toFloat64(count()) as value"
2022-07-22 16:49:40 +05:30
if params . Function != "" {
function = fmt . Sprintf ( "toFloat64(%s) as value" , params . Function )
2022-07-20 12:11:03 +05:30
}
fields , apiErr := r . GetLogFields ( ctx )
if apiErr != nil {
return nil , apiErr
}
2022-12-28 02:16:46 +05:30
filterSql , lenFilters , err := logs . GenerateSQLWhere ( fields , & model . LogsFilterParams {
2022-07-20 12:11:03 +05:30
Query : params . Query ,
} )
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorBadData }
}
2022-12-28 02:16:46 +05:30
data := map [ string ] interface { } {
"lenFilters" : lenFilters ,
}
2022-12-29 01:14:57 +05:30
if lenFilters != 0 {
2025-02-17 18:16:41 +05:30
claims , ok := authtypes . ClaimsFromContext ( ctx )
if ok {
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_LOGS_FILTERS , data , claims . Email , true , false )
2023-11-16 15:11:38 +05:30
}
2022-12-29 01:14:57 +05:30
}
2022-12-28 02:16:46 +05:30
2022-07-20 12:11:03 +05:30
query := ""
2022-07-22 16:49:40 +05:30
if params . GroupBy != "" {
2022-08-11 13:53:33 +05:30
query = fmt . Sprintf ( "SELECT toInt64(toUnixTimestamp(toStartOfInterval(toDateTime(timestamp/1000000000), INTERVAL %d minute))*1000000000) as ts_start_interval, toString(%s) as groupBy, " +
2022-07-20 12:11:03 +05:30
"%s " +
2022-11-28 18:16:21 +05:30
"FROM %s.%s WHERE (timestamp >= '%d' AND timestamp <= '%d' )" ,
2022-07-22 16:49:40 +05:30
params . StepSeconds / 60 , params . GroupBy , function , r . logsDB , r . logsTable , params . TimestampStart , params . TimestampEnd )
2022-07-20 12:11:03 +05:30
} else {
2022-08-11 13:53:33 +05:30
query = fmt . Sprintf ( "SELECT toInt64(toUnixTimestamp(toStartOfInterval(toDateTime(timestamp/1000000000), INTERVAL %d minute))*1000000000) as ts_start_interval, " +
2022-07-20 12:11:03 +05:30
"%s " +
2022-11-28 18:16:21 +05:30
"FROM %s.%s WHERE (timestamp >= '%d' AND timestamp <= '%d' )" ,
2022-07-22 16:49:40 +05:30
params . StepSeconds / 60 , function , r . logsDB , r . logsTable , params . TimestampStart , params . TimestampEnd )
2022-07-20 12:11:03 +05:30
}
2022-07-22 15:39:43 +05:30
if filterSql != "" {
2022-11-28 18:16:21 +05:30
query = fmt . Sprintf ( "%s AND ( %s ) " , query , filterSql )
2022-07-20 12:11:03 +05:30
}
2022-07-22 16:49:40 +05:30
if params . GroupBy != "" {
2022-08-11 13:53:33 +05:30
query = fmt . Sprintf ( "%s GROUP BY ts_start_interval, toString(%s) as groupBy ORDER BY ts_start_interval" , query , params . GroupBy )
2022-07-20 12:11:03 +05:30
} else {
2022-08-11 13:53:33 +05:30
query = fmt . Sprintf ( "%s GROUP BY ts_start_interval ORDER BY ts_start_interval" , query )
2022-07-20 12:11:03 +05:30
}
2022-07-22 15:27:52 +05:30
err = r . db . Select ( ctx , & logAggregatesDBResponseItems , query )
2022-07-20 12:11:03 +05:30
if err != nil {
return nil , & model . ApiError { Err : err , Typ : model . ErrorInternal }
}
aggregateResponse := model . GetLogsAggregatesResponse {
Items : make ( map [ int64 ] model . LogsAggregatesResponseItem ) ,
}
2022-07-22 15:27:52 +05:30
for i := range logAggregatesDBResponseItems {
if elem , ok := aggregateResponse . Items [ int64 ( logAggregatesDBResponseItems [ i ] . Timestamp ) ] ; ok {
2022-07-22 16:49:40 +05:30
if params . GroupBy != "" && logAggregatesDBResponseItems [ i ] . GroupBy != "" {
2022-07-22 15:27:52 +05:30
elem . GroupBy [ logAggregatesDBResponseItems [ i ] . GroupBy ] = logAggregatesDBResponseItems [ i ] . Value
2022-07-20 12:11:03 +05:30
}
2022-07-22 15:27:52 +05:30
aggregateResponse . Items [ logAggregatesDBResponseItems [ i ] . Timestamp ] = elem
2022-07-20 12:11:03 +05:30
} else {
2022-07-22 16:49:40 +05:30
if params . GroupBy != "" && logAggregatesDBResponseItems [ i ] . GroupBy != "" {
2022-07-22 15:27:52 +05:30
aggregateResponse . Items [ logAggregatesDBResponseItems [ i ] . Timestamp ] = model . LogsAggregatesResponseItem {
Timestamp : logAggregatesDBResponseItems [ i ] . Timestamp ,
2022-07-22 15:39:43 +05:30
GroupBy : map [ string ] interface { } { logAggregatesDBResponseItems [ i ] . GroupBy : logAggregatesDBResponseItems [ i ] . Value } ,
2022-07-20 12:11:03 +05:30
}
2022-07-22 16:49:40 +05:30
} else if params . GroupBy == "" {
2022-07-22 15:27:52 +05:30
aggregateResponse . Items [ logAggregatesDBResponseItems [ i ] . Timestamp ] = model . LogsAggregatesResponseItem {
Timestamp : logAggregatesDBResponseItems [ i ] . Timestamp ,
Value : logAggregatesDBResponseItems [ i ] . Value ,
2022-07-20 12:11:03 +05:30
}
}
}
}
return & aggregateResponse , nil
}
2022-09-11 03:34:02 +05:30
func ( r * ClickHouseReader ) QueryDashboardVars ( ctx context . Context , query string ) ( * model . DashboardVar , error ) {
2024-10-24 15:03:35 +05:30
var result = model . DashboardVar { VariableValues : make ( [ ] interface { } , 0 ) }
2022-09-11 03:34:02 +05:30
rows , err := r . db . Query ( ctx , query )
2024-03-27 00:07:29 +05:30
zap . L ( ) . Info ( query )
2022-09-11 03:34:02 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error in processing sql query" , zap . Error ( err ) )
2022-09-11 03:34:02 +05:30
return nil , err
}
var (
columnTypes = rows . ColumnTypes ( )
vars = make ( [ ] interface { } , len ( columnTypes ) )
)
for i := range columnTypes {
vars [ i ] = reflect . New ( columnTypes [ i ] . ScanType ( ) ) . Interface ( )
}
defer rows . Close ( )
for rows . Next ( ) {
if err := rows . Scan ( vars ... ) ; err != nil {
return nil , err
}
for _ , v := range vars {
switch v := v . ( type ) {
case * string , * int8 , * int16 , * int32 , * int64 , * uint8 , * uint16 , * uint32 , * uint64 , * float32 , * float64 , * time . Time , * bool :
result . VariableValues = append ( result . VariableValues , reflect . ValueOf ( v ) . Elem ( ) . Interface ( ) )
default :
return nil , fmt . Errorf ( "unsupported value type encountered" )
}
}
}
return & result , nil
}
2023-02-15 00:37:57 +05:30
2024-10-10 17:01:44 +05:30
func ( r * ClickHouseReader ) GetMetricAggregateAttributes (
ctx context . Context ,
req * v3 . AggregateAttributeRequest ,
skipDotNames bool ,
) ( * v3 . AggregateAttributeResponse , error ) {
2023-03-04 00:05:16 +05:30
var query string
var err error
var rows driver . Rows
var response v3 . AggregateAttributeResponse
2024-03-01 14:51:50 +05:30
query = fmt . Sprintf ( "SELECT metric_name, type, is_monotonic, temporality FROM %s.%s WHERE metric_name ILIKE $1 GROUP BY metric_name, type, is_monotonic, temporality" , signozMetricDBName , signozTSTableNameV41Day )
2023-03-04 00:05:16 +05:30
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err = r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-03-04 00:05:16 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
2024-03-01 14:51:50 +05:30
seen := make ( map [ string ] struct { } )
var metricName , typ , temporality string
var isMonotonic bool
2023-03-04 00:05:16 +05:30
for rows . Next ( ) {
2024-03-01 14:51:50 +05:30
if err := rows . Scan ( & metricName , & typ , & isMonotonic , & temporality ) ; err != nil {
2023-03-04 00:05:16 +05:30
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
2024-10-10 17:01:44 +05:30
if skipDotNames && strings . Contains ( metricName , "." ) {
continue
}
2024-03-01 14:51:50 +05:30
// Non-monotonic cumulative sums are treated as gauges
if typ == "Sum" && ! isMonotonic && temporality == string ( v3 . Cumulative ) {
typ = "Gauge"
}
2024-02-11 00:31:47 +05:30
// unlike traces/logs `tag`/`resource` type, the `Type` will be metric type
2023-03-04 00:05:16 +05:30
key := v3 . AttributeKey {
Key : metricName ,
2023-04-06 13:32:24 +05:30
DataType : v3 . AttributeKeyDataTypeFloat64 ,
2024-02-11 00:31:47 +05:30
Type : v3 . AttributeKeyType ( typ ) ,
2023-04-07 09:46:21 +05:30
IsColumn : true ,
2023-03-04 00:05:16 +05:30
}
2024-03-01 14:51:50 +05:30
// remove duplicates
if _ , ok := seen [ metricName + typ ] ; ok {
continue
}
seen [ metricName + typ ] = struct { } { }
2023-03-04 00:05:16 +05:30
response . AttributeKeys = append ( response . AttributeKeys , key )
}
return & response , nil
}
2023-03-10 11:22:34 +05:30
func ( r * ClickHouseReader ) GetMetricAttributeKeys ( ctx context . Context , req * v3 . FilterAttributeKeyRequest ) ( * v3 . FilterAttributeKeyResponse , error ) {
var query string
var err error
var rows driver . Rows
var response v3 . FilterAttributeKeyResponse
// skips the internal attributes i.e attributes starting with __
2024-03-01 14:51:50 +05:30
query = fmt . Sprintf ( "SELECT arrayJoin(tagKeys) AS distinctTagKey FROM (SELECT JSONExtractKeys(labels) AS tagKeys FROM %s.%s WHERE metric_name=$1 AND unix_milli >= $2 GROUP BY tagKeys) WHERE distinctTagKey ILIKE $3 AND distinctTagKey NOT LIKE '\\_\\_%%' GROUP BY distinctTagKey" , signozMetricDBName , signozTSTableNameV41Day )
2023-03-10 11:22:34 +05:30
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
2024-03-01 14:51:50 +05:30
rows , err = r . db . Query ( ctx , query , req . AggregateAttribute , common . PastDayRoundOff ( ) , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
2023-03-10 11:22:34 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-03-10 11:22:34 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var attributeKey string
for rows . Next ( ) {
if err := rows . Scan ( & attributeKey ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : attributeKey ,
DataType : v3 . AttributeKeyDataTypeString , // https://github.com/OpenObservability/OpenMetrics/blob/main/proto/openmetrics_data_model.proto#L64-L72.
Type : v3 . AttributeKeyTypeTag ,
2023-04-07 09:46:21 +05:30
IsColumn : false ,
2023-03-10 11:22:34 +05:30
}
response . AttributeKeys = append ( response . AttributeKeys , key )
}
return & response , nil
}
func ( r * ClickHouseReader ) GetMetricAttributeValues ( ctx context . Context , req * v3 . FilterAttributeValueRequest ) ( * v3 . FilterAttributeValueResponse , error ) {
var query string
var err error
var rows driver . Rows
var attributeValues v3 . FilterAttributeValueResponse
2025-02-06 17:26:58 +05:30
query = fmt . Sprintf ( "SELECT JSONExtractString(labels, $1) AS tagValue FROM %s.%s WHERE metric_name IN $2 AND JSONExtractString(labels, $3) ILIKE $4 AND unix_milli >= $5 GROUP BY tagValue" , signozMetricDBName , signozTSTableNameV41Day )
2023-03-10 11:22:34 +05:30
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
2025-02-06 17:26:58 +05:30
names := [ ] string { req . AggregateAttribute }
if _ , ok := metrics . MetricsUnderTransition [ req . AggregateAttribute ] ; ok {
names = append ( names , metrics . MetricsUnderTransition [ req . AggregateAttribute ] )
}
rows , err = r . db . Query ( ctx , query , req . FilterAttributeKey , names , req . FilterAttributeKey , fmt . Sprintf ( "%%%s%%" , req . SearchText ) , common . PastDayRoundOff ( ) )
2023-03-10 11:22:34 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-03-10 11:22:34 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var atrributeValue string
for rows . Next ( ) {
if err := rows . Scan ( & atrributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
// https://github.com/OpenObservability/OpenMetrics/blob/main/proto/openmetrics_data_model.proto#L64-L72
// this may change in future if we use OTLP as the data model
attributeValues . StringAttributeValues = append ( attributeValues . StringAttributeValues , atrributeValue )
}
return & attributeValues , nil
}
2024-04-15 13:37:08 +05:30
func ( r * ClickHouseReader ) GetMetricMetadata ( ctx context . Context , metricName , serviceName string ) ( * v3 . MetricMetadataResponse , error ) {
2023-07-26 12:27:46 +05:30
2024-04-15 13:37:08 +05:30
unixMilli := common . PastDayRoundOff ( )
2023-07-26 12:27:46 +05:30
2024-02-11 00:31:47 +05:30
// Note: metric metadata should be accessible regardless of the time range selection
// our standard retention period is 30 days, so we are querying the table v4_1_day to reduce the
// amount of data scanned
2024-04-15 13:37:08 +05:30
query := fmt . Sprintf ( "SELECT temporality, description, type, unit, is_monotonic from %s.%s WHERE metric_name=$1 AND unix_milli >= $2 GROUP BY temporality, description, type, unit, is_monotonic" , signozMetricDBName , signozTSTableNameV41Day )
rows , err := r . db . Query ( ctx , query , metricName , unixMilli )
2024-02-11 00:31:47 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while fetching metric metadata" , zap . Error ( err ) )
2024-02-11 00:31:47 +05:30
return nil , fmt . Errorf ( "error while fetching metric metadata: %s" , err . Error ( ) )
}
defer rows . Close ( )
var deltaExists , isMonotonic bool
var temporality , description , metricType , unit string
for rows . Next ( ) {
if err := rows . Scan ( & temporality , & description , & metricType , & unit , & isMonotonic ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
if temporality == string ( v3 . Delta ) {
deltaExists = true
}
}
2024-04-15 13:37:08 +05:30
query = fmt . Sprintf ( "SELECT JSONExtractString(labels, 'le') as le from %s.%s WHERE metric_name=$1 AND unix_milli >= $2 AND type = 'Histogram' AND JSONExtractString(labels, 'service_name') = $3 GROUP BY le ORDER BY le" , signozMetricDBName , signozTSTableNameV41Day )
rows , err = r . db . Query ( ctx , query , metricName , unixMilli , serviceName )
2024-02-11 00:31:47 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2024-02-11 00:31:47 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var leFloat64 [ ] float64
for rows . Next ( ) {
var leStr string
if err := rows . Scan ( & leStr ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
le , err := strconv . ParseFloat ( leStr , 64 )
// ignore the error and continue if the value is not a float
// ideally this should not happen but we have seen ClickHouse
// returning empty string for some values
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while parsing le value" , zap . Error ( err ) )
2024-02-11 00:31:47 +05:30
continue
}
if math . IsInf ( le , 0 ) {
continue
}
leFloat64 = append ( leFloat64 , le )
}
return & v3 . MetricMetadataResponse {
Delta : deltaExists ,
Le : leFloat64 ,
Description : description ,
Unit : unit ,
Type : metricType ,
IsMonotonic : isMonotonic ,
Temporality : temporality ,
} , nil
}
2024-11-25 23:23:42 +05:30
// GetCountOfThings returns the count of things in the query
// This is a generic function that can be used to check if any data exists for a given query
func ( r * ClickHouseReader ) GetCountOfThings ( ctx context . Context , query string ) ( uint64 , error ) {
var count uint64
err := r . db . QueryRow ( ctx , query ) . Scan ( & count )
if err != nil {
return 0 , err
}
return count , nil
}
2024-03-18 10:01:53 +05:30
func ( r * ClickHouseReader ) GetLatestReceivedMetric (
2025-02-06 13:08:47 +05:30
ctx context . Context , metricNames [ ] string , labelValues map [ string ] string ,
2024-03-18 10:01:53 +05:30
) ( * model . MetricStatus , * model . ApiError ) {
2025-02-06 13:08:47 +05:30
// at least 1 metric name must be specified.
// this query can be too slow otherwise.
2024-03-18 10:01:53 +05:30
if len ( metricNames ) < 1 {
2025-02-06 13:08:47 +05:30
return nil , model . BadRequest ( fmt . Errorf ( "atleast 1 metric name must be specified" ) )
2024-03-18 10:01:53 +05:30
}
quotedMetricNames := [ ] string { }
for _ , m := range metricNames {
2025-02-06 13:08:47 +05:30
quotedMetricNames = append ( quotedMetricNames , utils . ClickHouseFormattedValue ( m ) )
2024-03-18 10:01:53 +05:30
}
commaSeparatedMetricNames := strings . Join ( quotedMetricNames , ", " )
2025-02-06 13:08:47 +05:30
whereClauseParts := [ ] string {
fmt . Sprintf ( ` metric_name in (%s) ` , commaSeparatedMetricNames ) ,
}
if labelValues != nil {
for label , val := range labelValues {
whereClauseParts = append (
whereClauseParts ,
fmt . Sprintf ( ` JSONExtractString(labels, '%s') = '%s' ` , label , val ) ,
)
}
}
if len ( whereClauseParts ) < 1 {
return nil , nil
}
whereClause := strings . Join ( whereClauseParts , " AND " )
2024-03-18 10:01:53 +05:30
query := fmt . Sprintf ( `
2025-02-06 13:08:47 +05:30
SELECT metric_name , anyLast ( labels ) , max ( unix_milli )
2024-03-18 10:01:53 +05:30
from % s . % s
2025-02-06 13:08:47 +05:30
where % s
group by metric_name
2024-03-18 10:01:53 +05:30
limit 1
2025-02-06 13:08:47 +05:30
` , signozMetricDBName , signozTSTableNameV4 , whereClause ,
2024-03-18 10:01:53 +05:30
)
rows , err := r . db . Query ( ctx , query )
if err != nil {
return nil , model . InternalError ( fmt . Errorf (
"couldn't query clickhouse for received metrics status: %w" , err ,
) )
}
defer rows . Close ( )
var result * model . MetricStatus
if rows . Next ( ) {
result = & model . MetricStatus { }
var labelsJson string
err := rows . Scan (
& result . MetricName ,
& labelsJson ,
& result . LastReceivedTsMillis ,
)
if err != nil {
return nil , model . InternalError ( fmt . Errorf (
"couldn't scan metric status row: %w" , err ,
) )
}
err = json . Unmarshal ( [ ] byte ( labelsJson ) , & result . LastReceivedLabels )
if err != nil {
return nil , model . InternalError ( fmt . Errorf (
"couldn't unmarshal metric labels json: %w" , err ,
) )
}
}
return result , nil
}
2024-09-13 17:04:22 +05:30
func isColumn ( useLogsNewSchema bool , tableStatement , attrType , field , datType string ) bool {
2023-08-23 15:03:24 +05:30
// value of attrType will be `resource` or `tag`, if `tag` change it to `attribute`
2024-09-13 17:04:22 +05:30
var name string
if useLogsNewSchema {
2024-09-16 16:35:47 +05:30
// adding explict '`'
name = fmt . Sprintf ( "`%s`" , utils . GetClickhouseColumnNameV2 ( attrType , datType , field ) )
2024-09-13 17:04:22 +05:30
} else {
name = utils . GetClickhouseColumnName ( attrType , datType , field )
}
2024-03-30 17:57:01 +05:30
return strings . Contains ( tableStatement , fmt . Sprintf ( "%s " , name ) )
2023-04-06 13:32:24 +05:30
}
func ( r * ClickHouseReader ) GetLogAggregateAttributes ( ctx context . Context , req * v3 . AggregateAttributeRequest ) ( * v3 . AggregateAttributeResponse , error ) {
var query string
var err error
var rows driver . Rows
var response v3 . AggregateAttributeResponse
2023-04-20 13:09:32 +05:30
var stringAllowed bool
2023-04-06 13:32:24 +05:30
where := ""
switch req . Operator {
2023-04-18 16:38:52 +05:30
case
v3 . AggregateOperatorCountDistinct ,
2023-04-25 21:53:46 +05:30
v3 . AggregateOperatorCount :
2024-12-16 15:29:16 +05:30
where = "tag_key ILIKE $1"
2023-04-20 13:09:32 +05:30
stringAllowed = true
2023-04-06 13:32:24 +05:30
case
v3 . AggregateOperatorRateSum ,
v3 . AggregateOperatorRateMax ,
v3 . AggregateOperatorRateAvg ,
v3 . AggregateOperatorRate ,
v3 . AggregateOperatorRateMin ,
v3 . AggregateOperatorP05 ,
v3 . AggregateOperatorP10 ,
v3 . AggregateOperatorP20 ,
v3 . AggregateOperatorP25 ,
v3 . AggregateOperatorP50 ,
v3 . AggregateOperatorP75 ,
v3 . AggregateOperatorP90 ,
v3 . AggregateOperatorP95 ,
v3 . AggregateOperatorP99 ,
v3 . AggregateOperatorAvg ,
v3 . AggregateOperatorSum ,
v3 . AggregateOperatorMin ,
v3 . AggregateOperatorMax :
2024-12-16 15:29:16 +05:30
where = "tag_key ILIKE $1 AND (tag_data_type='int64' or tag_data_type='float64')"
2023-04-20 13:09:32 +05:30
stringAllowed = false
2023-04-06 13:32:24 +05:30
case
v3 . AggregateOperatorNoOp :
return & v3 . AggregateAttributeResponse { } , nil
default :
return nil , fmt . Errorf ( "unsupported aggregate operator" )
}
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT(tag_key), tag_type, tag_data_type from %s.%s WHERE %s limit $2" , r . logsDB , r . logsTagAttributeTableV2 , where )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) , req . Limit )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-04-06 13:32:24 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
statements := [ ] model . ShowCreateTableStatement { }
2024-09-16 14:30:31 +05:30
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . logsDB , r . logsLocalTableName )
2023-04-06 13:32:24 +05:30
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , fmt . Errorf ( "error while fetching logs schema: %s" , err . Error ( ) )
}
var tagKey string
var dataType string
var attType string
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & attType , & dataType ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : tagKey ,
DataType : v3 . AttributeKeyDataType ( dataType ) ,
Type : v3 . AttributeKeyType ( attType ) ,
2024-09-13 17:04:22 +05:30
IsColumn : isColumn ( r . useLogsNewSchema , statements [ 0 ] . Statement , attType , tagKey , dataType ) ,
2023-04-06 13:32:24 +05:30
}
response . AttributeKeys = append ( response . AttributeKeys , key )
}
// add other attributes
2023-04-20 13:09:32 +05:30
for _ , field := range constants . StaticFieldsLogsV3 {
2023-06-08 12:26:59 +05:30
if ( ! stringAllowed && field . DataType == v3 . AttributeKeyDataTypeString ) || ( v3 . AttributeKey { } == field ) {
2023-04-20 13:09:32 +05:30
continue
} else if len ( req . SearchText ) == 0 || strings . Contains ( field . Key , req . SearchText ) {
response . AttributeKeys = append ( response . AttributeKeys , field )
2023-04-06 13:32:24 +05:30
}
}
return & response , nil
}
func ( r * ClickHouseReader ) GetLogAttributeKeys ( ctx context . Context , req * v3 . FilterAttributeKeyRequest ) ( * v3 . FilterAttributeKeyResponse , error ) {
var query string
var err error
var rows driver . Rows
var response v3 . FilterAttributeKeyResponse
if len ( req . SearchText ) != 0 {
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "select distinct tag_key, tag_type, tag_data_type from %s.%s where tag_key ILIKE $1 limit $2" , r . logsDB , r . logsTagAttributeTableV2 )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) , req . Limit )
} else {
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "select distinct tag_key, tag_type, tag_data_type from %s.%s limit $1" , r . logsDB , r . logsTagAttributeTableV2 )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , req . Limit )
}
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-04-06 13:32:24 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
statements := [ ] model . ShowCreateTableStatement { }
2024-09-16 14:30:31 +05:30
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . logsDB , r . logsLocalTableName )
2023-04-06 13:32:24 +05:30
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , fmt . Errorf ( "error while fetching logs schema: %s" , err . Error ( ) )
}
var attributeKey string
var attributeDataType string
var tagType string
for rows . Next ( ) {
if err := rows . Scan ( & attributeKey , & tagType , & attributeDataType ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : attributeKey ,
DataType : v3 . AttributeKeyDataType ( attributeDataType ) ,
Type : v3 . AttributeKeyType ( tagType ) ,
2024-09-13 17:04:22 +05:30
IsColumn : isColumn ( r . useLogsNewSchema , statements [ 0 ] . Statement , tagType , attributeKey , attributeDataType ) ,
2023-04-06 13:32:24 +05:30
}
response . AttributeKeys = append ( response . AttributeKeys , key )
}
// add other attributes
for _ , f := range constants . StaticFieldsLogsV3 {
2023-05-25 09:58:32 +05:30
if ( v3 . AttributeKey { } == f ) {
continue
}
2023-04-06 13:32:24 +05:30
if len ( req . SearchText ) == 0 || strings . Contains ( f . Key , req . SearchText ) {
response . AttributeKeys = append ( response . AttributeKeys , f )
}
}
return & response , nil
}
func ( r * ClickHouseReader ) GetLogAttributeValues ( ctx context . Context , req * v3 . FilterAttributeValueRequest ) ( * v3 . FilterAttributeValueResponse , error ) {
var err error
var filterValueColumn string
var rows driver . Rows
var attributeValues v3 . FilterAttributeValueResponse
// if dataType or tagType is not present return empty response
2023-07-27 09:49:34 +05:30
if len ( req . FilterAttributeKeyDataType ) == 0 || len ( req . TagType ) == 0 {
2023-07-11 23:02:10 +05:30
// also check if it is not a top level key
if _ , ok := constants . StaticFieldsLogsV3 [ req . FilterAttributeKey ] ; ! ok {
return & v3 . FilterAttributeValueResponse { } , nil
}
2023-04-06 13:32:24 +05:30
}
2023-07-27 09:49:34 +05:30
// ignore autocomplete request for body
2024-09-13 17:04:22 +05:30
if req . FilterAttributeKey == "body" || req . FilterAttributeKey == "__attrs" {
2023-07-27 09:49:34 +05:30
return & v3 . FilterAttributeValueResponse { } , nil
}
2023-04-06 13:32:24 +05:30
// if data type is bool, return true and false
if req . FilterAttributeKeyDataType == v3 . AttributeKeyDataTypeBool {
return & v3 . FilterAttributeValueResponse {
BoolAttributeValues : [ ] bool { true , false } ,
} , nil
}
query := "select distinct"
switch req . FilterAttributeKeyDataType {
case v3 . AttributeKeyDataTypeInt64 :
2024-12-16 15:29:16 +05:30
filterValueColumn = "number_value"
2023-04-06 13:32:24 +05:30
case v3 . AttributeKeyDataTypeFloat64 :
2024-12-16 15:29:16 +05:30
filterValueColumn = "number_value"
2023-04-06 13:32:24 +05:30
case v3 . AttributeKeyDataTypeString :
2024-12-16 15:29:16 +05:30
filterValueColumn = "string_value"
2023-04-06 13:32:24 +05:30
}
searchText := fmt . Sprintf ( "%%%s%%" , req . SearchText )
// check if the tagKey is a topLevelColumn
2023-05-25 09:58:32 +05:30
if _ , ok := constants . StaticFieldsLogsV3 [ req . FilterAttributeKey ] ; ok {
2023-04-06 13:32:24 +05:30
// query the column for the last 48 hours
filterValueColumnWhere := req . FilterAttributeKey
selectKey := req . FilterAttributeKey
if req . FilterAttributeKeyDataType != v3 . AttributeKeyDataTypeString {
filterValueColumnWhere = fmt . Sprintf ( "toString(%s)" , req . FilterAttributeKey )
selectKey = fmt . Sprintf ( "toInt64(%s)" , req . FilterAttributeKey )
}
// prepare the query and run
if len ( req . SearchText ) != 0 {
2024-09-16 14:30:31 +05:30
query = fmt . Sprintf ( "select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) and %s ILIKE $1 limit $2" , selectKey , r . logsDB , r . logsLocalTableName , filterValueColumnWhere )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , searchText , req . Limit )
} else {
2024-09-16 14:30:31 +05:30
query = fmt . Sprintf ( "select distinct %s from %s.%s where timestamp >= toInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR)*1000000000) limit $1" , selectKey , r . logsDB , r . logsLocalTableName )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , req . Limit )
}
} else if len ( req . SearchText ) != 0 {
filterValueColumnWhere := filterValueColumn
if req . FilterAttributeKeyDataType != v3 . AttributeKeyDataTypeString {
filterValueColumnWhere = fmt . Sprintf ( "toString(%s)" , filterValueColumn )
}
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT %s FROM %s.%s WHERE tag_key=$1 AND %s ILIKE $2 AND tag_type=$3 LIMIT $4" , filterValueColumn , r . logsDB , r . logsTagAttributeTableV2 , filterValueColumnWhere )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , req . FilterAttributeKey , searchText , req . TagType , req . Limit )
} else {
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT %s FROM %s.%s WHERE tag_key=$1 AND tag_type=$2 LIMIT $3" , filterValueColumn , r . logsDB , r . logsTagAttributeTableV2 )
2023-04-06 13:32:24 +05:30
rows , err = r . db . Query ( ctx , query , req . FilterAttributeKey , req . TagType , req . Limit )
}
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-04-06 13:32:24 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var strAttributeValue string
var float64AttributeValue sql . NullFloat64
var int64AttributeValue sql . NullInt64
for rows . Next ( ) {
switch req . FilterAttributeKeyDataType {
case v3 . AttributeKeyDataTypeInt64 :
if err := rows . Scan ( & int64AttributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
if int64AttributeValue . Valid {
attributeValues . NumberAttributeValues = append ( attributeValues . NumberAttributeValues , int64AttributeValue . Int64 )
}
case v3 . AttributeKeyDataTypeFloat64 :
if err := rows . Scan ( & float64AttributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
if float64AttributeValue . Valid {
attributeValues . NumberAttributeValues = append ( attributeValues . NumberAttributeValues , float64AttributeValue . Float64 )
}
case v3 . AttributeKeyDataTypeString :
if err := rows . Scan ( & strAttributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
attributeValues . StringAttributeValues = append ( attributeValues . StringAttributeValues , strAttributeValue )
}
}
return & attributeValues , nil
}
2024-06-14 16:23:56 +05:30
func readRow ( vars [ ] interface { } , columnNames [ ] string , countOfNumberCols int ) ( [ ] string , map [ string ] string , [ ] map [ string ] string , * v3 . Point ) {
2023-03-23 19:45:15 +05:30
// Each row will have a value and a timestamp, and an optional list of label values
// example: {Timestamp: ..., Value: ...}
// The timestamp may also not present in some cases where the time series is reduced to single value
var point v3 . Point
// groupBy is a container to hold label values for the current point
// example: ["frontend", "/fetch"]
var groupBy [ ] string
2023-07-28 10:00:16 +05:30
var groupAttributesArray [ ] map [ string ] string
2023-03-23 19:45:15 +05:30
// groupAttributes is a container to hold the key-value pairs for the current
// metric point.
// example: {"serviceName": "frontend", "operation": "/fetch"}
groupAttributes := make ( map [ string ] string )
2024-06-14 16:23:56 +05:30
isValidPoint := false
2023-03-23 19:45:15 +05:30
for idx , v := range vars {
colName := columnNames [ idx ]
switch v := v . ( type ) {
case * string :
// special case for returning all labels in metrics datasource
if colName == "fullLabels" {
var metric map [ string ] string
err := json . Unmarshal ( [ ] byte ( * v ) , & metric )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "unexpected error encountered" , zap . Error ( err ) )
2023-03-23 19:45:15 +05:30
}
for key , val := range metric {
groupBy = append ( groupBy , val )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ key ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { key : val } )
}
2023-03-23 19:45:15 +05:30
groupAttributes [ key ] = val
}
} else {
groupBy = append ( groupBy , * v )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : * v } )
}
2023-03-23 19:45:15 +05:30
groupAttributes [ colName ] = * v
}
case * time . Time :
point . Timestamp = v . UnixMilli ( )
case * float64 , * float32 :
2024-06-05 19:33:45 +05:30
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2023-03-23 19:45:15 +05:30
point . Value = float64 ( reflect . ValueOf ( v ) . Elem ( ) . Float ( ) )
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Float ( ) ) )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Float ( ) ) } )
}
2023-03-23 19:45:15 +05:30
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Float ( ) )
}
2024-06-25 10:32:44 +05:30
case * * float64 , * * float32 :
val := reflect . ValueOf ( v )
if val . IsValid ( ) && ! val . IsNil ( ) && ! val . Elem ( ) . IsNil ( ) {
value := reflect . ValueOf ( v ) . Elem ( ) . Elem ( ) . Float ( )
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2024-06-25 10:32:44 +05:30
point . Value = value
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , value ) )
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , value ) } )
}
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , value )
}
}
2024-06-05 19:33:45 +05:30
case * uint , * uint8 , * uint64 , * uint16 , * uint32 :
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2023-03-23 19:45:15 +05:30
point . Value = float64 ( reflect . ValueOf ( v ) . Elem ( ) . Uint ( ) )
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Uint ( ) ) )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Uint ( ) ) } )
}
2023-03-23 19:45:15 +05:30
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Uint ( ) )
}
2024-06-25 10:32:44 +05:30
case * * uint , * * uint8 , * * uint64 , * * uint16 , * * uint32 :
val := reflect . ValueOf ( v )
if val . IsValid ( ) && ! val . IsNil ( ) && ! val . Elem ( ) . IsNil ( ) {
value := reflect . ValueOf ( v ) . Elem ( ) . Elem ( ) . Uint ( )
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2024-06-25 10:32:44 +05:30
point . Value = float64 ( value )
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , value ) )
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , value ) } )
}
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , value )
}
}
2024-06-05 19:33:45 +05:30
case * int , * int8 , * int16 , * int32 , * int64 :
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2023-03-23 19:45:15 +05:30
point . Value = float64 ( reflect . ValueOf ( v ) . Elem ( ) . Int ( ) )
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Int ( ) ) )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Int ( ) ) } )
}
2023-03-23 19:45:15 +05:30
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , reflect . ValueOf ( v ) . Elem ( ) . Int ( ) )
}
2024-06-25 10:32:44 +05:30
case * * int , * * int8 , * * int16 , * * int32 , * * int64 :
val := reflect . ValueOf ( v )
if val . IsValid ( ) && ! val . IsNil ( ) && ! val . Elem ( ) . IsNil ( ) {
value := reflect . ValueOf ( v ) . Elem ( ) . Elem ( ) . Int ( )
if _ , ok := constants . ReservedColumnTargetAliases [ colName ] ; ok || countOfNumberCols == 1 {
2024-06-26 14:34:27 +05:30
isValidPoint = true
2024-06-25 10:32:44 +05:30
point . Value = float64 ( value )
} else {
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , value ) )
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , value ) } )
}
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , value )
}
}
2023-05-18 14:08:32 +05:30
case * bool :
groupBy = append ( groupBy , fmt . Sprintf ( "%v" , * v ) )
2023-07-28 10:00:16 +05:30
if _ , ok := groupAttributes [ colName ] ; ! ok {
groupAttributesArray = append ( groupAttributesArray , map [ string ] string { colName : fmt . Sprintf ( "%v" , * v ) } )
}
2023-05-18 14:08:32 +05:30
groupAttributes [ colName ] = fmt . Sprintf ( "%v" , * v )
2023-03-23 19:45:15 +05:30
default :
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "unsupported var type found in query builder query result" , zap . Any ( "v" , v ) , zap . String ( "colName" , colName ) )
2023-03-23 19:45:15 +05:30
}
}
2024-06-14 16:23:56 +05:30
if isValidPoint {
return groupBy , groupAttributes , groupAttributesArray , & point
}
return groupBy , groupAttributes , groupAttributesArray , nil
2023-03-23 19:45:15 +05:30
}
2024-06-05 19:33:45 +05:30
func readRowsForTimeSeriesResult ( rows driver . Rows , vars [ ] interface { } , columnNames [ ] string , countOfNumberCols int ) ( [ ] * v3 . Series , error ) {
2023-03-23 19:45:15 +05:30
// when groupBy is applied, each combination of cartesian product
// of attribute values is a separate series. Each item in seriesToPoints
// represent a unique series where the key is sorted attribute values joined
// by "," and the value is the list of points for that series
// For instance, group by (serviceName, operation)
// with two services and three operations in each will result in (maximum of) 6 series
// ("frontend", "order") x ("/fetch", "/fetch/{Id}", "/order")
//
// ("frontend", "/fetch")
// ("frontend", "/fetch/{Id}")
// ("frontend", "/order")
// ("order", "/fetch")
// ("order", "/fetch/{Id}")
// ("order", "/order")
seriesToPoints := make ( map [ string ] [ ] v3 . Point )
2023-07-18 11:01:51 +05:30
var keys [ ] string
2023-03-23 19:45:15 +05:30
// seriesToAttrs is a mapping of key to a map of attribute key to attribute value
// for each series. This is used to populate the series' attributes
// For instance, for the above example, the seriesToAttrs will be
// {
// "frontend,/fetch": {"serviceName": "frontend", "operation": "/fetch"},
// "frontend,/fetch/{Id}": {"serviceName": "frontend", "operation": "/fetch/{Id}"},
// "frontend,/order": {"serviceName": "frontend", "operation": "/order"},
// "order,/fetch": {"serviceName": "order", "operation": "/fetch"},
// "order,/fetch/{Id}": {"serviceName": "order", "operation": "/fetch/{Id}"},
// "order,/order": {"serviceName": "order", "operation": "/order"},
// }
seriesToAttrs := make ( map [ string ] map [ string ] string )
2023-07-28 10:00:16 +05:30
labelsArray := make ( map [ string ] [ ] map [ string ] string )
2023-03-23 19:45:15 +05:30
for rows . Next ( ) {
if err := rows . Scan ( vars ... ) ; err != nil {
return nil , err
}
2024-06-05 19:33:45 +05:30
groupBy , groupAttributes , groupAttributesArray , metricPoint := readRow ( vars , columnNames , countOfNumberCols )
2024-05-15 18:52:01 +05:30
// skip the point if the value is NaN or Inf
// are they ever useful enough to be returned?
2024-06-14 16:23:56 +05:30
if metricPoint != nil && ( math . IsNaN ( metricPoint . Value ) || math . IsInf ( metricPoint . Value , 0 ) ) {
2024-05-15 18:52:01 +05:30
continue
}
2023-03-23 19:45:15 +05:30
sort . Strings ( groupBy )
key := strings . Join ( groupBy , "" )
2023-07-18 11:01:51 +05:30
if _ , exists := seriesToAttrs [ key ] ; ! exists {
keys = append ( keys , key )
}
2023-03-23 19:45:15 +05:30
seriesToAttrs [ key ] = groupAttributes
2023-07-28 10:00:16 +05:30
labelsArray [ key ] = groupAttributesArray
2024-06-14 16:23:56 +05:30
if metricPoint != nil {
seriesToPoints [ key ] = append ( seriesToPoints [ key ] , * metricPoint )
}
2023-03-23 19:45:15 +05:30
}
var seriesList [ ] * v3 . Series
2023-07-18 11:01:51 +05:30
for _ , key := range keys {
2023-07-13 14:22:30 +05:30
points := seriesToPoints [ key ]
2024-06-05 19:33:45 +05:30
series := v3 . Series { Labels : seriesToAttrs [ key ] , Points : points , LabelsArray : labelsArray [ key ] }
2023-03-23 19:45:15 +05:30
seriesList = append ( seriesList , & series )
}
2024-04-10 17:25:57 +05:30
return seriesList , getPersonalisedError ( rows . Err ( ) )
2023-03-23 19:45:15 +05:30
}
2024-06-17 09:00:55 +05:30
func logCommentKVs ( ctx context . Context ) map [ string ] string {
2024-06-11 20:10:38 +05:30
kv := ctx . Value ( common . LogCommentKey )
2024-03-12 18:39:28 +05:30
if kv == nil {
2024-06-17 09:00:55 +05:30
return nil
2024-03-12 18:39:28 +05:30
}
logCommentKVs , ok := kv . ( map [ string ] string )
if ! ok {
2024-06-17 09:00:55 +05:30
return nil
2024-03-12 18:39:28 +05:30
}
2024-06-17 09:00:55 +05:30
return logCommentKVs
2024-03-12 18:39:28 +05:30
}
2023-03-23 19:45:15 +05:30
// GetTimeSeriesResultV3 runs the query and returns list of time series
func ( r * ClickHouseReader ) GetTimeSeriesResultV3 ( ctx context . Context , query string ) ( [ ] * v3 . Series , error ) {
2024-06-17 09:00:55 +05:30
ctxArgs := map [ string ] interface { } { "query" : query }
for k , v := range logCommentKVs ( ctx ) {
ctxArgs [ k ] = v
}
defer utils . Elapsed ( "GetTimeSeriesResultV3" , ctxArgs ) ( )
2023-03-23 19:45:15 +05:30
2024-08-14 19:53:36 +05:30
// Hook up query progress reporting if requested.
queryId := ctx . Value ( "queryId" )
if queryId != nil {
qid , ok := queryId . ( string )
if ! ok {
zap . L ( ) . Error ( "GetTimeSeriesResultV3: queryId in ctx not a string as expected" , zap . Any ( "queryId" , queryId ) )
} else {
ctx = clickhouse . Context ( ctx , clickhouse . WithProgress (
func ( p * clickhouse . Progress ) {
go func ( ) {
err := r . queryProgressTracker . ReportQueryProgress ( qid , p )
if err != nil {
zap . L ( ) . Error (
"Couldn't report query progress" ,
zap . String ( "queryId" , qid ) , zap . Error ( err ) ,
)
}
} ( )
} ,
) )
}
}
2023-03-23 19:45:15 +05:30
rows , err := r . db . Query ( ctx , query )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while reading time series result" , zap . Error ( err ) )
2024-09-13 18:01:37 +05:30
return nil , errors . New ( err . Error ( ) )
2023-03-23 19:45:15 +05:30
}
defer rows . Close ( )
var (
columnTypes = rows . ColumnTypes ( )
columnNames = rows . Columns ( )
vars = make ( [ ] interface { } , len ( columnTypes ) )
)
2024-06-05 19:33:45 +05:30
var countOfNumberCols int
2023-03-23 19:45:15 +05:30
for i := range columnTypes {
vars [ i ] = reflect . New ( columnTypes [ i ] . ScanType ( ) ) . Interface ( )
2024-06-05 19:33:45 +05:30
switch columnTypes [ i ] . ScanType ( ) . Kind ( ) {
case reflect . Float32 ,
reflect . Float64 ,
reflect . Uint ,
reflect . Uint8 ,
reflect . Uint16 ,
reflect . Uint32 ,
reflect . Uint64 ,
reflect . Int ,
reflect . Int8 ,
reflect . Int16 ,
reflect . Int32 ,
reflect . Int64 :
countOfNumberCols ++
}
}
return readRowsForTimeSeriesResult ( rows , vars , columnNames , countOfNumberCols )
2023-03-23 19:45:15 +05:30
}
2023-04-10 19:36:13 +05:30
// GetListResultV3 runs the query and returns list of rows
func ( r * ClickHouseReader ) GetListResultV3 ( ctx context . Context , query string ) ( [ ] * v3 . Row , error ) {
2024-06-17 09:00:55 +05:30
ctxArgs := map [ string ] interface { } { "query" : query }
for k , v := range logCommentKVs ( ctx ) {
ctxArgs [ k ] = v
}
defer utils . Elapsed ( "GetListResultV3" , ctxArgs ) ( )
2023-04-10 19:36:13 +05:30
rows , err := r . db . Query ( ctx , query )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "error while reading time series result" , zap . Error ( err ) )
2024-09-13 18:01:37 +05:30
return nil , errors . New ( err . Error ( ) )
2023-04-10 19:36:13 +05:30
}
defer rows . Close ( )
var (
columnTypes = rows . ColumnTypes ( )
columnNames = rows . Columns ( )
)
var rowList [ ] * v3 . Row
for rows . Next ( ) {
2023-06-09 17:07:45 +05:30
var vars = make ( [ ] interface { } , len ( columnTypes ) )
for i := range columnTypes {
vars [ i ] = reflect . New ( columnTypes [ i ] . ScanType ( ) ) . Interface ( )
}
2023-04-10 19:36:13 +05:30
if err := rows . Scan ( vars ... ) ; err != nil {
return nil , err
}
row := map [ string ] interface { } { }
var t time . Time
for idx , v := range vars {
if columnNames [ idx ] == "timestamp" {
2025-01-21 09:39:40 +05:30
switch v := v . ( type ) {
case * uint64 :
t = time . Unix ( 0 , int64 ( * v ) )
case * time . Time :
t = * v
}
2023-07-05 06:57:39 +05:30
} else if columnNames [ idx ] == "timestamp_datetime" {
t = * v . ( * time . Time )
2025-01-21 09:39:40 +05:30
} else if columnNames [ idx ] == "events" {
var events [ ] map [ string ] interface { }
eventsFromDB , ok := v . ( * [ ] string )
if ! ok {
continue
}
for _ , event := range * eventsFromDB {
var eventMap map [ string ] interface { }
json . Unmarshal ( [ ] byte ( event ) , & eventMap )
events = append ( events , eventMap )
}
row [ columnNames [ idx ] ] = events
2023-07-05 06:57:39 +05:30
} else {
row [ columnNames [ idx ] ] = v
2023-04-10 19:36:13 +05:30
}
}
2023-12-21 12:11:35 +05:30
// remove duplicate _ attributes for logs.
// remove this function after a month
removeDuplicateUnderscoreAttributes ( row )
2023-04-10 19:36:13 +05:30
rowList = append ( rowList , & v3 . Row { Timestamp : t , Data : row } )
}
2024-04-10 17:25:57 +05:30
return rowList , getPersonalisedError ( rows . Err ( ) )
}
2023-04-10 19:36:13 +05:30
2024-04-10 17:25:57 +05:30
func getPersonalisedError ( err error ) error {
if err == nil {
return nil
}
zap . L ( ) . Error ( "error while reading result" , zap . Error ( err ) )
if strings . Contains ( err . Error ( ) , "code: 307" ) {
2024-05-15 18:52:01 +05:30
return chErrors . ErrResourceBytesLimitExceeded
2024-04-10 17:25:57 +05:30
}
if strings . Contains ( err . Error ( ) , "code: 159" ) {
2024-05-15 18:52:01 +05:30
return chErrors . ErrResourceTimeLimitExceeded
2024-04-10 17:25:57 +05:30
}
return err
2023-04-10 19:36:13 +05:30
}
2023-12-21 12:11:35 +05:30
func removeDuplicateUnderscoreAttributes ( row map [ string ] interface { } ) {
if val , ok := row [ "attributes_int64" ] ; ok {
attributes := val . ( * map [ string ] int64 )
for key := range * attributes {
if strings . Contains ( key , "." ) {
uKey := strings . ReplaceAll ( key , "." , "_" )
delete ( * attributes , uKey )
}
}
}
if val , ok := row [ "attributes_float64" ] ; ok {
attributes := val . ( * map [ string ] float64 )
for key := range * attributes {
if strings . Contains ( key , "." ) {
uKey := strings . ReplaceAll ( key , "." , "_" )
delete ( * attributes , uKey )
}
}
}
if val , ok := row [ "attributes_bool" ] ; ok {
attributes := val . ( * map [ string ] bool )
for key := range * attributes {
if strings . Contains ( key , "." ) {
uKey := strings . ReplaceAll ( key , "." , "_" )
delete ( * attributes , uKey )
}
}
}
for _ , k := range [ ] string { "attributes_string" , "resources_string" } {
if val , ok := row [ k ] ; ok {
attributes := val . ( * map [ string ] string )
for key := range * attributes {
if strings . Contains ( key , "." ) {
uKey := strings . ReplaceAll ( key , "." , "_" )
delete ( * attributes , uKey )
}
}
}
}
}
2023-02-15 00:37:57 +05:30
func ( r * ClickHouseReader ) CheckClickHouse ( ctx context . Context ) error {
rows , err := r . db . Query ( ctx , "SELECT 1" )
if err != nil {
return err
}
defer rows . Close ( )
return nil
}
2023-04-13 15:33:08 +05:30
2024-11-25 17:05:43 +05:30
func ( r * ClickHouseReader ) GetTraceAggregateAttributes ( ctx context . Context , req * v3 . AggregateAttributeRequest ) ( * v3 . AggregateAttributeResponse , error ) {
2024-11-20 23:35:44 +05:30
var query string
var err error
var rows driver . Rows
var response v3 . AggregateAttributeResponse
var stringAllowed bool
where := ""
switch req . Operator {
case
v3 . AggregateOperatorCountDistinct ,
v3 . AggregateOperatorCount :
2024-12-16 15:29:16 +05:30
where = "tag_key ILIKE $1"
2024-11-20 23:35:44 +05:30
stringAllowed = true
case
v3 . AggregateOperatorRateSum ,
v3 . AggregateOperatorRateMax ,
v3 . AggregateOperatorRateAvg ,
v3 . AggregateOperatorRate ,
v3 . AggregateOperatorRateMin ,
v3 . AggregateOperatorP05 ,
v3 . AggregateOperatorP10 ,
v3 . AggregateOperatorP20 ,
v3 . AggregateOperatorP25 ,
v3 . AggregateOperatorP50 ,
v3 . AggregateOperatorP75 ,
v3 . AggregateOperatorP90 ,
v3 . AggregateOperatorP95 ,
v3 . AggregateOperatorP99 ,
v3 . AggregateOperatorAvg ,
v3 . AggregateOperatorSum ,
v3 . AggregateOperatorMin ,
v3 . AggregateOperatorMax :
2024-12-16 15:29:16 +05:30
where = "tag_key ILIKE $1 AND tag_data_type='float64'"
2024-11-20 23:35:44 +05:30
stringAllowed = false
case
v3 . AggregateOperatorNoOp :
return & v3 . AggregateAttributeResponse { } , nil
default :
return nil , fmt . Errorf ( "unsupported aggregate operator" )
}
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT(tag_key), tag_type, tag_data_type FROM %s.%s WHERE %s" , r . TraceDB , r . spanAttributeTableV2 , where )
2024-11-20 23:35:44 +05:30
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err = r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
statements := [ ] model . ShowCreateTableStatement { }
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . TraceDB , r . traceLocalTableName )
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , fmt . Errorf ( "error while fetching trace schema: %s" , err . Error ( ) )
}
var tagKey string
var dataType string
var tagType string
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & tagType , & dataType ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : tagKey ,
DataType : v3 . AttributeKeyDataType ( dataType ) ,
Type : v3 . AttributeKeyType ( tagType ) ,
IsColumn : isColumn ( true , statements [ 0 ] . Statement , tagType , tagKey , dataType ) ,
}
if _ , ok := constants . DeprecatedStaticFieldsTraces [ tagKey ] ; ! ok {
response . AttributeKeys = append ( response . AttributeKeys , key )
}
}
2024-11-25 17:05:43 +05:30
fields := constants . NewStaticFieldsTraces
if ! r . useTraceNewSchema {
fields = constants . DeprecatedStaticFieldsTraces
}
2024-11-20 23:35:44 +05:30
// add the new static fields
2024-11-25 17:05:43 +05:30
for _ , field := range fields {
2024-11-20 23:35:44 +05:30
if ( ! stringAllowed && field . DataType == v3 . AttributeKeyDataTypeString ) || ( v3 . AttributeKey { } == field ) {
continue
} else if len ( req . SearchText ) == 0 || strings . Contains ( field . Key , req . SearchText ) {
response . AttributeKeys = append ( response . AttributeKeys , field )
}
}
return & response , nil
}
2024-11-25 17:05:43 +05:30
func ( r * ClickHouseReader ) GetTraceAttributeKeys ( ctx context . Context , req * v3 . FilterAttributeKeyRequest ) ( * v3 . FilterAttributeKeyResponse , error ) {
2024-11-20 23:35:44 +05:30
var query string
var err error
var rows driver . Rows
var response v3 . FilterAttributeKeyResponse
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT(tag_key), tag_type, tag_data_type FROM %s.%s WHERE tag_key ILIKE $1 LIMIT $2" , r . TraceDB , r . spanAttributeTableV2 )
2024-11-20 23:35:44 +05:30
rows , err = r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) , req . Limit )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
statements := [ ] model . ShowCreateTableStatement { }
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . TraceDB , r . traceLocalTableName )
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , fmt . Errorf ( "error while fetching trace schema: %s" , err . Error ( ) )
}
var tagKey string
var dataType string
var tagType string
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & tagType , & dataType ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : tagKey ,
DataType : v3 . AttributeKeyDataType ( dataType ) ,
Type : v3 . AttributeKeyType ( tagType ) ,
IsColumn : isColumn ( true , statements [ 0 ] . Statement , tagType , tagKey , dataType ) ,
}
// don't send deprecated static fields
// this is added so that once the old tenants are moved to new schema,
// they old attributes are not sent to the frontend autocomplete
if _ , ok := constants . DeprecatedStaticFieldsTraces [ tagKey ] ; ! ok {
response . AttributeKeys = append ( response . AttributeKeys , key )
}
}
2024-11-25 17:05:43 +05:30
// remove this later just to have NewStaticFieldsTraces in the response
fields := constants . NewStaticFieldsTraces
if ! r . useTraceNewSchema {
fields = constants . DeprecatedStaticFieldsTraces
}
2024-11-20 23:35:44 +05:30
// add the new static fields
2024-11-25 17:05:43 +05:30
for _ , f := range fields {
2024-11-20 23:35:44 +05:30
if ( v3 . AttributeKey { } == f ) {
continue
}
if len ( req . SearchText ) == 0 || strings . Contains ( f . Key , req . SearchText ) {
response . AttributeKeys = append ( response . AttributeKeys , f )
}
}
return & response , nil
}
2024-11-25 17:05:43 +05:30
func ( r * ClickHouseReader ) GetTraceAttributeValues ( ctx context . Context , req * v3 . FilterAttributeValueRequest ) ( * v3 . FilterAttributeValueResponse , error ) {
2024-11-20 23:35:44 +05:30
var query string
var filterValueColumn string
var err error
var rows driver . Rows
var attributeValues v3 . FilterAttributeValueResponse
// if dataType or tagType is not present return empty response
if len ( req . FilterAttributeKeyDataType ) == 0 || len ( req . TagType ) == 0 {
// add data type if it's a top level key
if k , ok := constants . StaticFieldsTraces [ req . FilterAttributeKey ] ; ok {
req . FilterAttributeKeyDataType = k . DataType
} else {
return & v3 . FilterAttributeValueResponse { } , nil
}
}
// if data type is bool, return true and false
if req . FilterAttributeKeyDataType == v3 . AttributeKeyDataTypeBool {
return & v3 . FilterAttributeValueResponse {
BoolAttributeValues : [ ] bool { true , false } ,
} , nil
}
2024-12-16 15:29:16 +05:30
query = "SELECT DISTINCT"
2024-11-20 23:35:44 +05:30
switch req . FilterAttributeKeyDataType {
case v3 . AttributeKeyDataTypeFloat64 :
2024-12-16 15:29:16 +05:30
filterValueColumn = "number_value"
2024-11-20 23:35:44 +05:30
case v3 . AttributeKeyDataTypeString :
2024-12-16 15:29:16 +05:30
filterValueColumn = "string_value"
2023-06-30 10:55:45 +05:30
}
2024-11-20 23:35:44 +05:30
searchText := fmt . Sprintf ( "%%%s%%" , req . SearchText )
// check if the tagKey is a topLevelColumn
// here we are using StaticFieldsTraces instead of NewStaticFieldsTraces as we want to consider old columns as well.
if _ , ok := constants . StaticFieldsTraces [ req . FilterAttributeKey ] ; ok {
// query the column for the last 48 hours
filterValueColumnWhere := req . FilterAttributeKey
selectKey := req . FilterAttributeKey
if req . FilterAttributeKeyDataType != v3 . AttributeKeyDataTypeString {
filterValueColumnWhere = fmt . Sprintf ( "toString(%s)" , req . FilterAttributeKey )
selectKey = fmt . Sprintf ( "toInt64(%s)" , req . FilterAttributeKey )
}
// TODO(nitya): remove 24 hour limit in future after checking the perf/resource implications
2024-11-25 17:05:43 +05:30
where := "timestamp >= toDateTime64(now() - INTERVAL 48 HOUR, 9)"
if r . useTraceNewSchema {
where += " AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR))"
}
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT %s FROM %s.%s WHERE %s AND %s ILIKE $1 LIMIT $2" , selectKey , r . TraceDB , r . traceTableName , where , filterValueColumnWhere )
2024-11-20 23:35:44 +05:30
rows , err = r . db . Query ( ctx , query , searchText , req . Limit )
} else {
filterValueColumnWhere := filterValueColumn
if req . FilterAttributeKeyDataType != v3 . AttributeKeyDataTypeString {
filterValueColumnWhere = fmt . Sprintf ( "toString(%s)" , filterValueColumn )
}
2024-12-16 15:29:16 +05:30
query = fmt . Sprintf ( "SELECT DISTINCT %s FROM %s.%s WHERE tag_key=$1 AND %s ILIKE $2 AND tag_type=$3 LIMIT $4" , filterValueColumn , r . TraceDB , r . spanAttributeTableV2 , filterValueColumnWhere )
2024-11-20 23:35:44 +05:30
rows , err = r . db . Query ( ctx , query , req . FilterAttributeKey , searchText , req . TagType , req . Limit )
}
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var strAttributeValue string
var float64AttributeValue sql . NullFloat64
for rows . Next ( ) {
switch req . FilterAttributeKeyDataType {
case v3 . AttributeKeyDataTypeFloat64 :
if err := rows . Scan ( & float64AttributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
if float64AttributeValue . Valid {
attributeValues . NumberAttributeValues = append ( attributeValues . NumberAttributeValues , float64AttributeValue . Float64 )
}
case v3 . AttributeKeyDataTypeString :
if err := rows . Scan ( & strAttributeValue ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
attributeValues . StringAttributeValues = append ( attributeValues . StringAttributeValues , strAttributeValue )
}
}
return & attributeValues , nil
2023-06-30 10:55:45 +05:30
}
2024-11-20 23:35:44 +05:30
func ( r * ClickHouseReader ) GetSpanAttributeKeysV2 ( ctx context . Context ) ( map [ string ] v3 . AttributeKey , error ) {
var query string
var err error
var rows driver . Rows
response := map [ string ] v3 . AttributeKey { }
query = fmt . Sprintf ( "SELECT DISTINCT(tagKey), tagType, dataType FROM %s.%s" , r . TraceDB , r . spanAttributesKeysTable )
rows , err = r . db . Query ( ctx , query )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
statements := [ ] model . ShowCreateTableStatement { }
query = fmt . Sprintf ( "SHOW CREATE TABLE %s.%s" , r . TraceDB , r . traceTableName )
err = r . db . Select ( ctx , & statements , query )
if err != nil {
return nil , fmt . Errorf ( "error while fetching trace schema: %s" , err . Error ( ) )
}
var tagKey string
var dataType string
var tagType string
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & tagType , & dataType ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : tagKey ,
DataType : v3 . AttributeKeyDataType ( dataType ) ,
Type : v3 . AttributeKeyType ( tagType ) ,
IsColumn : isColumn ( true , statements [ 0 ] . Statement , tagType , tagKey , dataType ) ,
}
name := tagKey + "##" + tagType + "##" + strings . ToLower ( dataType )
response [ name ] = key
}
for _ , key := range constants . StaticFieldsTraces {
name := key . Key + "##" + key . Type . String ( ) + "##" + strings . ToLower ( key . DataType . String ( ) )
response [ name ] = key
}
return response , nil
}
2023-04-25 21:53:46 +05:30
func ( r * ClickHouseReader ) GetSpanAttributeKeys ( ctx context . Context ) ( map [ string ] v3 . AttributeKey , error ) {
2024-11-20 23:35:44 +05:30
if r . useTraceNewSchema {
return r . GetSpanAttributeKeysV2 ( ctx )
}
2023-04-25 21:53:46 +05:30
var query string
var err error
var rows driver . Rows
response := map [ string ] v3 . AttributeKey { }
query = fmt . Sprintf ( "SELECT DISTINCT(tagKey), tagType, dataType, isColumn FROM %s.%s" , r . TraceDB , r . spanAttributesKeysTable )
rows , err = r . db . Query ( ctx , query )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
2023-04-25 21:53:46 +05:30
return nil , fmt . Errorf ( "error while executing query: %s" , err . Error ( ) )
}
defer rows . Close ( )
var tagKey string
var dataType string
var tagType string
var isColumn bool
for rows . Next ( ) {
if err := rows . Scan ( & tagKey , & tagType , & dataType , & isColumn ) ; err != nil {
return nil , fmt . Errorf ( "error while scanning rows: %s" , err . Error ( ) )
}
key := v3 . AttributeKey {
Key : tagKey ,
DataType : v3 . AttributeKeyDataType ( dataType ) ,
Type : v3 . AttributeKeyType ( tagType ) ,
IsColumn : isColumn ,
}
response [ tagKey ] = key
}
2024-11-25 17:05:43 +05:30
// add the deprecated static fields as they are not present in spanAttributeKeysTable
for _ , f := range constants . DeprecatedStaticFieldsTraces {
response [ f . Key ] = f
}
2023-04-25 21:53:46 +05:30
return response , nil
2023-05-18 14:08:32 +05:30
}
2023-07-20 17:53:55 +05:30
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) LiveTailLogsV4 ( ctx context . Context , query string , timestampStart uint64 , idStart string , client * model . LogsLiveTailClientV2 ) {
2024-09-13 17:04:22 +05:30
if timestampStart == 0 {
timestampStart = uint64 ( time . Now ( ) . UnixNano ( ) )
} else {
timestampStart = uint64 ( utils . GetEpochNanoSecs ( int64 ( timestampStart ) ) )
}
ticker := time . NewTicker ( time . Duration ( r . liveTailRefreshSeconds ) * time . Second )
defer ticker . Stop ( )
for {
select {
case <- ctx . Done ( ) :
done := true
client . Done <- & done
zap . L ( ) . Debug ( "closing go routine : " + client . Name )
return
case <- ticker . C :
// get the new 100 logs as anything more older won't make sense
var tmpQuery string
bucketStart := ( timestampStart / NANOSECOND ) - 1800
// we have to form the query differently if the resource filters are used
if strings . Contains ( query , r . logsResourceTableV2 ) {
tmpQuery = fmt . Sprintf ( "seen_at_ts_bucket_start >=%d)) AND ts_bucket_start >=%d AND timestamp >=%d" , bucketStart , bucketStart , timestampStart )
} else {
tmpQuery = fmt . Sprintf ( "ts_bucket_start >=%d AND timestamp >=%d" , bucketStart , timestampStart )
}
if idStart != "" {
tmpQuery = fmt . Sprintf ( "%s AND id > '%s'" , tmpQuery , idStart )
}
// the reason we are doing desc is that we need the latest logs first
tmpQuery = query + tmpQuery + " order by timestamp desc, id desc limit 100"
// using the old structure since we can directly read it to the struct as use it.
response := [ ] model . SignozLogV2 { }
err := r . db . Select ( ctx , & response , tmpQuery )
if err != nil {
zap . L ( ) . Error ( "Error while getting logs" , zap . Error ( err ) )
client . Error <- err
return
}
for i := len ( response ) - 1 ; i >= 0 ; i -- {
client . Logs <- & response [ i ]
if i == 0 {
timestampStart = response [ i ] . Timestamp
idStart = response [ i ] . ID
}
}
}
}
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) LiveTailLogsV3 ( ctx context . Context , query string , timestampStart uint64 , idStart string , client * model . LogsLiveTailClient ) {
2023-07-20 17:53:55 +05:30
if timestampStart == 0 {
timestampStart = uint64 ( time . Now ( ) . UnixNano ( ) )
2023-08-22 16:48:44 +05:30
} else {
timestampStart = uint64 ( utils . GetEpochNanoSecs ( int64 ( timestampStart ) ) )
2023-07-20 17:53:55 +05:30
}
ticker := time . NewTicker ( time . Duration ( r . liveTailRefreshSeconds ) * time . Second )
defer ticker . Stop ( )
for {
select {
case <- ctx . Done ( ) :
done := true
client . Done <- & done
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "closing go routine : " + client . Name )
2023-07-20 17:53:55 +05:30
return
case <- ticker . C :
// get the new 100 logs as anything more older won't make sense
tmpQuery := fmt . Sprintf ( "timestamp >='%d'" , timestampStart )
if idStart != "" {
tmpQuery = fmt . Sprintf ( "%s AND id > '%s'" , tmpQuery , idStart )
}
// the reason we are doing desc is that we need the latest logs first
2023-08-30 20:38:46 +05:30
tmpQuery = query + tmpQuery + " order by timestamp desc, id desc limit 100"
2023-07-20 17:53:55 +05:30
// using the old structure since we can directly read it to the struct as use it.
2023-10-09 15:25:13 +05:30
response := [ ] model . SignozLog { }
2023-07-20 17:53:55 +05:30
err := r . db . Select ( ctx , & response , tmpQuery )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Error while getting logs" , zap . Error ( err ) )
2023-07-20 17:53:55 +05:30
client . Error <- err
return
}
for i := len ( response ) - 1 ; i >= 0 ; i -- {
client . Logs <- & response [ i ]
if i == 0 {
timestampStart = response [ i ] . Timestamp
idStart = response [ i ] . ID
}
}
}
}
}
2024-07-31 16:00:57 +05:30
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) AddRuleStateHistory ( ctx context . Context , ruleStateHistory [ ] model . RuleStateHistory ) error {
2024-08-09 12:11:05 +05:30
var statement driver . Batch
var err error
defer func ( ) {
if statement != nil {
statement . Abort ( )
}
} ( )
statement , err = r . db . PrepareBatch ( ctx , fmt . Sprintf ( "INSERT INTO %s.%s (rule_id, rule_name, overall_state, overall_state_changed, state, state_changed, unix_milli, labels, fingerprint, value) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)" ,
signozHistoryDBName , ruleStateHistoryTableName ) )
if err != nil {
return err
}
for _ , history := range ruleStateHistory {
err = statement . Append ( history . RuleID , history . RuleName , history . OverallState , history . OverallStateChanged , history . State , history . StateChanged , history . UnixMilli , history . Labels , history . Fingerprint , history . Value )
if err != nil {
return err
}
}
err = statement . Send ( )
if err != nil {
return err
}
return nil
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetLastSavedRuleStateHistory ( ctx context . Context , ruleID string ) ( [ ] model . RuleStateHistory , error ) {
2024-09-09 13:06:09 +05:30
query := fmt . Sprintf ( "SELECT * FROM %s.%s WHERE rule_id = '%s' AND state_changed = true ORDER BY unix_milli DESC LIMIT 1 BY fingerprint" ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID )
2024-09-13 18:10:49 +05:30
history := [ ] model . RuleStateHistory { }
2024-09-09 13:06:09 +05:30
err := r . db . Select ( ctx , & history , query )
if err != nil {
return nil , err
}
return history , nil
}
2024-08-09 12:11:05 +05:30
func ( r * ClickHouseReader ) ReadRuleStateHistoryByRuleID (
2024-09-13 18:10:49 +05:30
ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( * model . RuleStateTimeline , error ) {
2024-08-09 12:11:05 +05:30
var conditions [ ] string
conditions = append ( conditions , fmt . Sprintf ( "rule_id = '%s'" , ruleID ) )
conditions = append ( conditions , fmt . Sprintf ( "unix_milli >= %d AND unix_milli < %d" , params . Start , params . End ) )
2024-08-23 21:13:00 +05:30
if params . State != "" {
conditions = append ( conditions , fmt . Sprintf ( "state = '%s'" , params . State ) )
}
2024-08-09 12:11:05 +05:30
if params . Filters != nil && len ( params . Filters . Items ) != 0 {
for _ , item := range params . Filters . Items {
toFormat := item . Value
op := v3 . FilterOperator ( strings . ToLower ( strings . TrimSpace ( string ( item . Operator ) ) ) )
if op == v3 . FilterOperatorContains || op == v3 . FilterOperatorNotContains {
toFormat = fmt . Sprintf ( "%%%s%%" , toFormat )
}
fmtVal := utils . ClickHouseFormattedValue ( toFormat )
switch op {
case v3 . FilterOperatorEqual :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') = %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorNotEqual :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') != %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorIn :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') IN %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorNotIn :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') NOT IN %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorLike :
conditions = append ( conditions , fmt . Sprintf ( "like(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorNotLike :
conditions = append ( conditions , fmt . Sprintf ( "notLike(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorRegex :
conditions = append ( conditions , fmt . Sprintf ( "match(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorNotRegex :
conditions = append ( conditions , fmt . Sprintf ( "not match(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorGreaterThan :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') > %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorGreaterThanOrEq :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') >= %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorLessThan :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') < %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorLessThanOrEq :
conditions = append ( conditions , fmt . Sprintf ( "JSONExtractString(labels, '%s') <= %s" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorContains :
conditions = append ( conditions , fmt . Sprintf ( "like(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorNotContains :
conditions = append ( conditions , fmt . Sprintf ( "notLike(JSONExtractString(labels, '%s'), %s)" , item . Key . Key , fmtVal ) )
case v3 . FilterOperatorExists :
conditions = append ( conditions , fmt . Sprintf ( "has(JSONExtractKeys(labels), '%s')" , item . Key . Key ) )
case v3 . FilterOperatorNotExists :
conditions = append ( conditions , fmt . Sprintf ( "not has(JSONExtractKeys(labels), '%s')" , item . Key . Key ) )
default :
return nil , fmt . Errorf ( "unsupported filter operator" )
}
}
}
whereClause := strings . Join ( conditions , " AND " )
query := fmt . Sprintf ( "SELECT * FROM %s.%s WHERE %s ORDER BY unix_milli %s LIMIT %d OFFSET %d" ,
signozHistoryDBName , ruleStateHistoryTableName , whereClause , params . Order , params . Limit , params . Offset )
2024-09-13 18:10:49 +05:30
history := [ ] model . RuleStateHistory { }
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "rule state history query" , zap . String ( "query" , query ) )
2024-08-09 12:11:05 +05:30
err := r . db . Select ( ctx , & history , query )
if err != nil {
zap . L ( ) . Error ( "Error while reading rule state history" , zap . Error ( err ) )
return nil , err
}
2024-08-23 21:13:00 +05:30
var total uint64
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "rule state history total query" , zap . String ( "query" , fmt . Sprintf ( "SELECT count(*) FROM %s.%s WHERE %s" ,
signozHistoryDBName , ruleStateHistoryTableName , whereClause ) ) )
2024-08-23 21:13:00 +05:30
err = r . db . QueryRow ( ctx , fmt . Sprintf ( "SELECT count(*) FROM %s.%s WHERE %s" ,
signozHistoryDBName , ruleStateHistoryTableName , whereClause ) ) . Scan ( & total )
if err != nil {
return nil , err
}
2024-09-09 13:06:09 +05:30
labelsQuery := fmt . Sprintf ( "SELECT DISTINCT labels FROM %s.%s WHERE rule_id = $1" ,
signozHistoryDBName , ruleStateHistoryTableName )
rows , err := r . db . Query ( ctx , labelsQuery , ruleID )
if err != nil {
return nil , err
}
defer rows . Close ( )
labelsMap := make ( map [ string ] [ ] string )
for rows . Next ( ) {
var rawLabel string
err = rows . Scan ( & rawLabel )
if err != nil {
return nil , err
}
label := map [ string ] string { }
err = json . Unmarshal ( [ ] byte ( rawLabel ) , & label )
if err != nil {
return nil , err
}
for k , v := range label {
labelsMap [ k ] = append ( labelsMap [ k ] , v )
}
}
2024-09-13 18:10:49 +05:30
timeline := & model . RuleStateTimeline {
2024-09-09 13:06:09 +05:30
Items : history ,
Total : total ,
Labels : labelsMap ,
2024-08-23 21:13:00 +05:30
}
return timeline , nil
2024-08-09 12:11:05 +05:30
}
func ( r * ClickHouseReader ) ReadRuleStateHistoryTopContributorsByRuleID (
2024-09-13 18:10:49 +05:30
ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( [ ] model . RuleStateHistoryContributor , error ) {
2024-08-09 12:11:05 +05:30
query := fmt . Sprintf ( ` SELECT
fingerprint ,
any ( labels ) as labels ,
count ( * ) as count
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE rule_id = ' % s ' AND ( state_changed = true ) AND ( state = ' % s ' ) AND unix_milli >= % d AND unix_milli <= % d
2024-08-09 12:11:05 +05:30
GROUP BY fingerprint
2024-09-09 13:06:09 +05:30
HAVING labels != ' { } '
2024-08-09 12:11:05 +05:30
ORDER BY count DESC ` ,
2024-09-09 13:06:09 +05:30
signozHistoryDBName , ruleStateHistoryTableName , ruleID , model . StateFiring . String ( ) , params . Start , params . End )
2024-08-09 12:11:05 +05:30
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "rule state history top contributors query" , zap . String ( "query" , query ) )
2024-09-13 18:10:49 +05:30
contributors := [ ] model . RuleStateHistoryContributor { }
2024-08-09 12:11:05 +05:30
err := r . db . Select ( ctx , & contributors , query )
if err != nil {
zap . L ( ) . Error ( "Error while reading rule state history" , zap . Error ( err ) )
return nil , err
}
return contributors , nil
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetOverallStateTransitions ( ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( [ ] model . ReleStateItem , error ) {
2024-08-09 12:11:05 +05:30
tmpl := ` WITH firing_events AS (
SELECT
rule_id ,
state ,
unix_milli AS firing_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateFiring.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
resolution_events AS (
SELECT
rule_id ,
state ,
unix_milli AS resolution_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateInactive.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
matched_events AS (
SELECT
f . rule_id ,
f . state ,
f . firing_time ,
MIN ( r . resolution_time ) AS resolution_time
FROM firing_events f
LEFT JOIN resolution_events r
ON f . rule_id = r . rule_id
WHERE r . resolution_time > f . firing_time
GROUP BY f . rule_id , f . state , f . firing_time
)
SELECT *
FROM matched_events
ORDER BY firing_time ASC ; `
query := fmt . Sprintf ( tmpl ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End )
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "overall state transitions query" , zap . String ( "query" , query ) )
2024-09-13 18:10:49 +05:30
transitions := [ ] model . RuleStateTransition { }
2024-08-09 12:11:05 +05:30
err := r . db . Select ( ctx , & transitions , query )
if err != nil {
return nil , err
}
2024-09-13 18:10:49 +05:30
stateItems := [ ] model . ReleStateItem { }
2024-09-09 13:06:09 +05:30
for idx , item := range transitions {
start := item . FiringTime
end := item . ResolutionTime
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : item . State ,
Start : start ,
End : end ,
} )
if idx < len ( transitions ) - 1 {
nextStart := transitions [ idx + 1 ] . FiringTime
if nextStart > end {
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : model . StateInactive ,
Start : end ,
End : nextStart ,
} )
}
}
}
// fetch the most recent overall_state from the table
var state model . AlertState
stateQuery := fmt . Sprintf ( "SELECT state FROM %s.%s WHERE rule_id = '%s' AND unix_milli <= %d ORDER BY unix_milli DESC LIMIT 1" ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . End )
if err := r . db . QueryRow ( ctx , stateQuery ) . Scan ( & state ) ; err != nil {
if err != sql . ErrNoRows {
return nil , err
}
state = model . StateInactive
}
if len ( transitions ) == 0 {
// no transitions found, it is either firing or inactive for whole time range
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : state ,
Start : params . Start ,
End : params . End ,
} )
} else {
// there were some transitions, we need to add the last state at the end
if state == model . StateInactive {
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : model . StateInactive ,
Start : transitions [ len ( transitions ) - 1 ] . ResolutionTime ,
End : params . End ,
} )
} else {
// fetch the most recent firing event from the table in the given time range
var firingTime int64
firingQuery := fmt . Sprintf ( `
SELECT
unix_milli
FROM % s . % s
WHERE rule_id = ' % s ' AND overall_state_changed = true AND overall_state = ' % s ' AND unix_milli <= % d
ORDER BY unix_milli DESC LIMIT 1 ` , signozHistoryDBName , ruleStateHistoryTableName , ruleID , model . StateFiring . String ( ) , params . End )
if err := r . db . QueryRow ( ctx , firingQuery ) . Scan ( & firingTime ) ; err != nil {
return nil , err
}
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : model . StateInactive ,
Start : transitions [ len ( transitions ) - 1 ] . ResolutionTime ,
End : firingTime ,
} )
2024-09-13 18:10:49 +05:30
stateItems = append ( stateItems , model . ReleStateItem {
2024-09-09 13:06:09 +05:30
State : model . StateFiring ,
Start : firingTime ,
End : params . End ,
} )
}
}
return stateItems , nil
2024-08-09 12:11:05 +05:30
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetAvgResolutionTime ( ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( float64 , error ) {
2024-08-09 12:11:05 +05:30
tmpl := `
WITH firing_events AS (
SELECT
rule_id ,
state ,
unix_milli AS firing_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateFiring.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
resolution_events AS (
SELECT
rule_id ,
state ,
unix_milli AS resolution_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateInactive.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
matched_events AS (
SELECT
f . rule_id ,
f . state ,
f . firing_time ,
MIN ( r . resolution_time ) AS resolution_time
FROM firing_events f
LEFT JOIN resolution_events r
ON f . rule_id = r . rule_id
WHERE r . resolution_time > f . firing_time
GROUP BY f . rule_id , f . state , f . firing_time
)
SELECT AVG ( resolution_time - firing_time ) / 1000 AS avg_resolution_time
FROM matched_events ;
`
query := fmt . Sprintf ( tmpl ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End )
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "avg resolution time query" , zap . String ( "query" , query ) )
2024-08-09 12:11:05 +05:30
var avgResolutionTime float64
err := r . db . QueryRow ( ctx , query ) . Scan ( & avgResolutionTime )
if err != nil {
return 0 , err
}
return avgResolutionTime , nil
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetAvgResolutionTimeByInterval ( ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( * v3 . Series , error ) {
2024-08-09 12:11:05 +05:30
step := common . MinAllowedStepInterval ( params . Start , params . End )
tmpl := `
WITH firing_events AS (
SELECT
rule_id ,
state ,
unix_milli AS firing_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateFiring.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
resolution_events AS (
SELECT
rule_id ,
state ,
unix_milli AS resolution_time
FROM % s . % s
2024-09-09 13:06:09 +05:30
WHERE overall_state = ' ` + model.StateInactive.String() + ` '
2024-08-09 12:11:05 +05:30
AND overall_state_changed = true
AND rule_id IN ( ' % s ' )
AND unix_milli >= % d AND unix_milli <= % d
) ,
matched_events AS (
SELECT
f . rule_id ,
f . state ,
f . firing_time ,
MIN ( r . resolution_time ) AS resolution_time
FROM firing_events f
LEFT JOIN resolution_events r
ON f . rule_id = r . rule_id
WHERE r . resolution_time > f . firing_time
GROUP BY f . rule_id , f . state , f . firing_time
)
SELECT toStartOfInterval ( toDateTime ( firing_time / 1000 ) , INTERVAL % d SECOND ) AS ts , AVG ( resolution_time - firing_time ) / 1000 AS avg_resolution_time
FROM matched_events
GROUP BY ts
ORDER BY ts ASC ; `
query := fmt . Sprintf ( tmpl ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , params . Start , params . End , step )
2024-09-09 13:06:09 +05:30
zap . L ( ) . Debug ( "avg resolution time by interval query" , zap . String ( "query" , query ) )
2024-08-09 12:11:05 +05:30
result , err := r . GetTimeSeriesResultV3 ( ctx , query )
if err != nil || len ( result ) == 0 {
return nil , err
}
return result [ 0 ] , nil
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetTotalTriggers ( ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( uint64 , error ) {
2024-09-09 13:06:09 +05:30
query := fmt . Sprintf ( "SELECT count(*) FROM %s.%s WHERE rule_id = '%s' AND (state_changed = true) AND (state = '%s') AND unix_milli >= %d AND unix_milli <= %d" ,
signozHistoryDBName , ruleStateHistoryTableName , ruleID , model . StateFiring . String ( ) , params . Start , params . End )
2024-08-09 12:11:05 +05:30
var totalTriggers uint64
2024-09-09 13:06:09 +05:30
2024-08-09 12:11:05 +05:30
err := r . db . QueryRow ( ctx , query ) . Scan ( & totalTriggers )
if err != nil {
return 0 , err
}
return totalTriggers , nil
}
2024-09-13 18:10:49 +05:30
func ( r * ClickHouseReader ) GetTriggersByInterval ( ctx context . Context , ruleID string , params * model . QueryRuleStateHistory ) ( * v3 . Series , error ) {
2024-08-09 12:11:05 +05:30
step := common . MinAllowedStepInterval ( params . Start , params . End )
2024-09-09 13:06:09 +05:30
query := fmt . Sprintf ( "SELECT count(*), toStartOfInterval(toDateTime(intDiv(unix_milli, 1000)), INTERVAL %d SECOND) as ts FROM %s.%s WHERE rule_id = '%s' AND (state_changed = true) AND (state = '%s') AND unix_milli >= %d AND unix_milli <= %d GROUP BY ts ORDER BY ts ASC" ,
step , signozHistoryDBName , ruleStateHistoryTableName , ruleID , model . StateFiring . String ( ) , params . Start , params . End )
2024-08-09 12:11:05 +05:30
result , err := r . GetTimeSeriesResultV3 ( ctx , query )
if err != nil || len ( result ) == 0 {
return nil , err
}
return result [ 0 ] , nil
}
2024-07-31 16:00:57 +05:30
func ( r * ClickHouseReader ) GetMinAndMaxTimestampForTraceID ( ctx context . Context , traceID [ ] string ) ( int64 , int64 , error ) {
var minTime , maxTime time . Time
query := fmt . Sprintf ( "SELECT min(timestamp), max(timestamp) FROM %s.%s WHERE traceID IN ('%s')" ,
r . TraceDB , r . SpansTable , strings . Join ( traceID , "','" ) )
zap . L ( ) . Debug ( "GetMinAndMaxTimestampForTraceID" , zap . String ( "query" , query ) )
err := r . db . QueryRow ( ctx , query ) . Scan ( & minTime , & maxTime )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return 0 , 0 , err
}
2024-08-08 08:32:11 +05:30
// return current time if traceID not found
2024-07-31 16:00:57 +05:30
if minTime . IsZero ( ) || maxTime . IsZero ( ) {
2024-08-08 08:32:11 +05:30
zap . L ( ) . Debug ( "minTime or maxTime is zero, traceID not found" )
return time . Now ( ) . UnixNano ( ) , time . Now ( ) . UnixNano ( ) , nil
2024-07-31 16:00:57 +05:30
}
zap . L ( ) . Debug ( "GetMinAndMaxTimestampForTraceID" , zap . Any ( "minTime" , minTime ) , zap . Any ( "maxTime" , maxTime ) )
return minTime . UnixNano ( ) , maxTime . UnixNano ( ) , nil
}
2024-08-14 19:53:36 +05:30
func ( r * ClickHouseReader ) ReportQueryStartForProgressTracking (
queryId string ,
) ( func ( ) , * model . ApiError ) {
return r . queryProgressTracker . ReportQueryStarted ( queryId )
}
func ( r * ClickHouseReader ) SubscribeToQueryProgress (
queryId string ,
2024-09-13 18:10:49 +05:30
) ( <- chan model . QueryProgress , func ( ) , * model . ApiError ) {
2024-08-14 19:53:36 +05:30
return r . queryProgressTracker . SubscribeToQueryProgress ( queryId )
}
2025-02-20 13:49:44 +05:30
func ( r * ClickHouseReader ) GetAllMetricFilterAttributeKeys ( ctx context . Context , req * metrics_explorer . FilterKeyRequest , skipDotNames bool ) ( * [ ] v3 . AttributeKey , * model . ApiError ) {
var rows driver . Rows
var response [ ] v3 . AttributeKey
query := fmt . Sprintf ( "SELECT arrayJoin(tagKeys) AS distinctTagKey FROM (SELECT JSONExtractKeys(labels) AS tagKeys FROM %s.%s WHERE unix_milli >= $1 GROUP BY tagKeys) WHERE distinctTagKey ILIKE $2 AND distinctTagKey NOT LIKE '\\_\\_%%' GROUP BY distinctTagKey" , signozMetricDBName , signozTSTableNameV41Day )
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err := r . db . Query ( ctx , query , common . PastDayRoundOff ( ) , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
var attributeKey string
for rows . Next ( ) {
if err := rows . Scan ( & attributeKey ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
if skipDotNames && strings . Contains ( attributeKey , "." ) {
continue
}
key := v3 . AttributeKey {
Key : attributeKey ,
DataType : v3 . AttributeKeyDataTypeString , // https://github.com/OpenObservability/OpenMetrics/blob/main/proto/openmetrics_data_model.proto#L64-L72.
Type : v3 . AttributeKeyTypeTag ,
IsColumn : false ,
}
response = append ( response , key )
}
return & response , nil
}
func ( r * ClickHouseReader ) GetAllMetricFilterAttributeValues ( ctx context . Context , req * metrics_explorer . FilterValueRequest ) ( [ ] string , * model . ApiError ) {
var query string
var err error
var rows driver . Rows
var attributeValues [ ] string
query = fmt . Sprintf ( "SELECT JSONExtractString(labels, $1) AS tagValue FROM %s.%s WHERE JSONExtractString(labels, $2) ILIKE $3 AND unix_milli >= $4 GROUP BY tagValue" , signozMetricDBName , signozTSTableNameV41Day )
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err = r . db . Query ( ctx , query , req . FilterKey , req . FilterKey , fmt . Sprintf ( "%%%s%%" , req . SearchText ) , common . PastDayRoundOff ( ) )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
var atrributeValue string
for rows . Next ( ) {
if err := rows . Scan ( & atrributeValue ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
attributeValues = append ( attributeValues , atrributeValue )
}
return attributeValues , nil
}
func ( r * ClickHouseReader ) GetAllMetricFilterUnits ( ctx context . Context , req * metrics_explorer . FilterValueRequest ) ( [ ] string , * model . ApiError ) {
var rows driver . Rows
var response [ ] string
query := fmt . Sprintf ( "SELECT DISTINCT unit FROM %s.%s WHERE unit ILIKE $1 AND unit IS NOT NULL ORDER BY unit" , signozMetricDBName , signozTSTableNameV41Day )
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err := r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
var attributeKey string
for rows . Next ( ) {
if err := rows . Scan ( & attributeKey ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
response = append ( response , attributeKey )
}
return response , nil
}
func ( r * ClickHouseReader ) GetAllMetricFilterTypes ( ctx context . Context , req * metrics_explorer . FilterValueRequest ) ( [ ] string , * model . ApiError ) {
var rows driver . Rows
var response [ ] string
query := fmt . Sprintf ( "SELECT DISTINCT type FROM %s.%s WHERE type ILIKE $1 AND type IS NOT NULL ORDER BY type" , signozMetricDBName , signozTSTableNameV41Day )
if req . Limit != 0 {
query = query + fmt . Sprintf ( " LIMIT %d;" , req . Limit )
}
rows , err := r . db . Query ( ctx , query , fmt . Sprintf ( "%%%s%%" , req . SearchText ) )
if err != nil {
zap . L ( ) . Error ( "Error while executing query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
var attributeKey string
for rows . Next ( ) {
if err := rows . Scan ( & attributeKey ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
response = append ( response , attributeKey )
}
return response , nil
}
2025-02-27 12:25:58 +05:30
func ( r * ClickHouseReader ) GetMetricsDataPoints ( ctx context . Context , metricName string ) ( uint64 , * model . ApiError ) {
query := fmt . Sprintf ( ` SELECT
sum ( count ) as data_points
FROM % s . % s
WHERE metric_name = ?
` , signozMetricDBName , constants . SIGNOZ_SAMPLES_V4_AGG_30M_TABLENAME )
2025-02-20 13:49:44 +05:30
var dataPoints uint64
2025-02-27 12:25:58 +05:30
err := r . db . QueryRow ( ctx , query , metricName ) . Scan ( & dataPoints )
if err != nil {
return 0 , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return dataPoints , nil // Convert to uint64 before returning
}
func ( r * ClickHouseReader ) GetMetricsLastReceived ( ctx context . Context , metricName string ) ( int64 , * model . ApiError ) {
query := fmt . Sprintf ( ` SELECT
MAX ( unix_milli ) AS last_received_time
FROM % s . % s
WHERE metric_name = ?
` , signozMetricDBName , signozSampleTableName )
var lastReceived int64
err := r . db . QueryRow ( ctx , query , metricName ) . Scan ( & lastReceived )
2025-02-20 13:49:44 +05:30
if err != nil {
2025-02-27 12:25:58 +05:30
return 0 , & model . ApiError { Typ : "ClickHouseError" , Err : err }
2025-02-20 13:49:44 +05:30
}
2025-02-27 12:25:58 +05:30
return lastReceived , nil // Convert to uint64 before returning
2025-02-20 13:49:44 +05:30
}
func ( r * ClickHouseReader ) GetTotalTimeSeriesForMetricName ( ctx context . Context , metricName string ) ( uint64 , * model . ApiError ) {
query := fmt . Sprintf ( ` SELECT
uniq ( fingerprint ) AS timeSeriesCount
FROM % s . % s
WHERE metric_name = ? ; ` , signozMetricDBName , signozTSTableNameV41Week )
var timeSeriesCount uint64
err := r . db . QueryRow ( ctx , query , metricName ) . Scan ( & timeSeriesCount )
if err != nil {
return 0 , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return timeSeriesCount , nil
}
func ( r * ClickHouseReader ) GetAttributesForMetricName ( ctx context . Context , metricName string ) ( * [ ] metrics_explorer . Attribute , * model . ApiError ) {
query := fmt . Sprintf ( `
SELECT
kv .1 AS key ,
arrayMap ( x - > trim ( BOTH '\"' FROM x ) , groupUniqArray ( 10000 ) ( kv .2 ) ) AS values ,
length ( groupUniqArray ( 10000 ) ( kv .2 ) ) AS valueCount
FROM % s . % s
ARRAY JOIN arrayFilter ( x - > NOT startsWith ( x .1 , ' __ ' ) , JSONExtractKeysAndValuesRaw ( labels ) ) AS kv
WHERE metric_name = ?
GROUP BY kv .1
ORDER BY valueCount DESC ;
` , signozMetricDBName , signozTSTableNameV41Week )
rows , err := r . db . Query ( ctx , query , metricName )
if err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( ) // Ensure the rows are closed
var attributesList [ ] metrics_explorer . Attribute
for rows . Next ( ) {
var key string
var values [ ] string
var valueCount uint64
// Manually scan each value into its corresponding variable
if err := rows . Scan ( & key , & values , & valueCount ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
// Append the scanned values into the struct
attributesList = append ( attributesList , metrics_explorer . Attribute {
Key : key ,
Value : values ,
ValueCount : valueCount ,
} )
}
// Handle any errors encountered while scanning rows
if err := rows . Err ( ) ; err != nil {
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return & attributesList , nil
}
func ( r * ClickHouseReader ) GetActiveTimeSeriesForMetricName ( ctx context . Context , metricName string , duration time . Duration ) ( uint64 , * model . ApiError ) {
milli := time . Now ( ) . Add ( - duration ) . UnixMilli ( )
query := fmt . Sprintf ( "SELECT uniq(fingerprint) FROM %s.%s WHERE metric_name = '%s' and unix_milli >= ?" , signozMetricDBName , signozTSTableNameV4 , metricName )
var timeSeries uint64
// Using QueryRow instead of Select since we're only expecting a single value
err := r . db . QueryRow ( ctx , query , milli ) . Scan ( & timeSeries )
if err != nil {
return 0 , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return timeSeries , nil
}
func ( r * ClickHouseReader ) ListSummaryMetrics ( ctx context . Context , req * metrics_explorer . SummaryListMetricsRequest ) ( * metrics_explorer . SummaryListMetricsResponse , * model . ApiError ) {
var args [ ] interface { }
conditions , _ := utils . BuildFilterConditions ( & req . Filters , "t" )
whereClause := ""
if conditions != nil {
whereClause = "AND " + strings . Join ( conditions , " AND " )
}
firstQueryLimit := req . Limit
dataPointsOrder := false
var orderByClauseFirstQuery string
if req . OrderBy . ColumnName == "samples" {
dataPointsOrder = true
orderByClauseFirstQuery = fmt . Sprintf ( "ORDER BY timeseries %s" , req . OrderBy . Order )
if req . Limit < 50 {
firstQueryLimit = 50
}
} else if req . OrderBy . ColumnName == "metric_type" {
orderByClauseFirstQuery = fmt . Sprintf ( "ORDER BY type %s" , req . OrderBy . Order )
} else {
orderByClauseFirstQuery = fmt . Sprintf ( "ORDER BY %s %s" , req . OrderBy . ColumnName , req . OrderBy . Order )
}
start , end , tsTable , localTsTable := utils . WhichTSTableToUse ( req . Start , req . EndD )
sampleTable , countExp := utils . WhichSampleTableToUse ( req . Start , req . EndD )
metricsQuery := fmt . Sprintf (
` SELECT
t . metric_name AS metric_name ,
ANY_VALUE ( t . description ) AS description ,
ANY_VALUE ( t . type ) AS type ,
ANY_VALUE ( t . unit ) ,
uniq ( t . fingerprint ) AS timeseries ,
uniq ( metric_name ) OVER ( ) AS total
FROM % s . % s AS t
WHERE unix_milli BETWEEN ? AND ?
% s
GROUP BY t . metric_name
% s
LIMIT % d OFFSET % d ; ` ,
signozMetricDBName , tsTable , whereClause , orderByClauseFirstQuery , firstQueryLimit , req . Offset )
args = append ( args , start , end )
valueCtx := context . WithValue ( ctx , "clickhouse_max_threads" , constants . MetricsExplorerClickhouseThreads )
rows , err := r . db . Query ( valueCtx , metricsQuery , args ... )
if err != nil {
zap . L ( ) . Error ( "Error executing metrics query" , zap . Error ( err ) )
return & metrics_explorer . SummaryListMetricsResponse { } , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
var response metrics_explorer . SummaryListMetricsResponse
var metricNames [ ] string
for rows . Next ( ) {
var metric metrics_explorer . MetricDetail
if err := rows . Scan ( & metric . MetricName , & metric . Description , & metric . Type , & metric . Unit , & metric . TimeSeries , & response . Total ) ; err != nil {
zap . L ( ) . Error ( "Error scanning metric row" , zap . Error ( err ) )
return & response , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
metricNames = append ( metricNames , metric . MetricName )
response . Metrics = append ( response . Metrics , metric )
}
if err := rows . Err ( ) ; err != nil {
zap . L ( ) . Error ( "Error iterating over metric rows" , zap . Error ( err ) )
return & response , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
if len ( metricNames ) == 0 {
return & response , nil
}
metricsList := "'" + strings . Join ( metricNames , "', '" ) + "'"
if dataPointsOrder {
orderByClauseFirstQuery = fmt . Sprintf ( "ORDER BY s.samples %s" , req . OrderBy . Order )
} else {
orderByClauseFirstQuery = ""
}
2025-02-27 12:25:58 +05:30
var sb strings . Builder
sb . WriteString ( fmt . Sprintf (
2025-02-20 13:49:44 +05:30
` SELECT
2025-02-27 12:25:58 +05:30
s . samples ,
s . metric_name ,
s . unix_milli AS lastReceived
FROM (
SELECT
metric_name ,
% s AS samples ,
max ( unix_milli ) as unix_milli
FROM % s . % s
` , countExp , signozMetricDBName , sampleTable ) )
// Conditionally add the fingerprint subquery if `whereClause` is present
if whereClause != "" {
sb . WriteString ( fmt . Sprintf (
` WHERE fingerprint IN (
SELECT fingerprint
FROM % s . % s
WHERE unix_milli BETWEEN ? AND ?
% s
AND metric_name IN ( % s )
GROUP BY fingerprint
)
AND metric_name IN ( % s ) ` ,
signozMetricDBName , localTsTable , whereClause , metricsList , metricsList ) )
} else {
sb . WriteString ( fmt . Sprintf (
` WHERE metric_name IN (%s) ` , metricsList ) )
}
sb . WriteString ( ` GROUP BY metric_name ) AS s ` )
if orderByClauseFirstQuery != "" {
sb . WriteString ( orderByClauseFirstQuery )
}
sb . WriteString ( fmt . Sprintf ( " LIMIT %d OFFSET %d;" , req . Limit , req . Offset ) )
sampleQuery := sb . String ( )
2025-02-20 13:49:44 +05:30
args = append ( args , start , end )
rows , err = r . db . Query ( valueCtx , sampleQuery , args ... )
if err != nil {
zap . L ( ) . Error ( "Error executing samples query" , zap . Error ( err ) )
return & response , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
samplesMap := make ( map [ string ] uint64 )
lastReceivedMap := make ( map [ string ] int64 )
for rows . Next ( ) {
var samples uint64
var metricName string
var lastReceived int64
if err := rows . Scan ( & samples , & metricName , & lastReceived ) ; err != nil {
zap . L ( ) . Error ( "Error scanning sample row" , zap . Error ( err ) )
return & response , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
samplesMap [ metricName ] = samples
lastReceivedMap [ metricName ] = lastReceived
}
if err := rows . Err ( ) ; err != nil {
zap . L ( ) . Error ( "Error iterating over sample rows" , zap . Error ( err ) )
return & response , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
var filteredMetrics [ ] metrics_explorer . MetricDetail
for i := range response . Metrics {
if samples , exists := samplesMap [ response . Metrics [ i ] . MetricName ] ; exists {
response . Metrics [ i ] . Samples = samples
if lastReceived , exists := lastReceivedMap [ response . Metrics [ i ] . MetricName ] ; exists {
response . Metrics [ i ] . LastReceived = lastReceived
}
filteredMetrics = append ( filteredMetrics , response . Metrics [ i ] )
}
}
response . Metrics = filteredMetrics
if dataPointsOrder {
sort . Slice ( response . Metrics , func ( i , j int ) bool {
return response . Metrics [ i ] . Samples > response . Metrics [ j ] . Samples
} )
}
return & response , nil
}
func ( r * ClickHouseReader ) GetMetricsTimeSeriesPercentage ( ctx context . Context , req * metrics_explorer . TreeMapMetricsRequest ) ( * [ ] metrics_explorer . TreeMapResponseItem , * model . ApiError ) {
var args [ ] interface { }
// Build filters dynamically
conditions , _ := utils . BuildFilterConditions ( & req . Filters , "" )
whereClause := ""
if len ( conditions ) > 0 {
whereClause = "AND " + strings . Join ( conditions , " AND " )
}
start , end , tsTable , _ := utils . WhichTSTableToUse ( req . Start , req . EndD )
// Construct the query without backticks
query := fmt . Sprintf ( `
SELECT
metric_name ,
total_value ,
( total_value * 100.0 / total_time_series ) AS percentage
FROM (
SELECT
metric_name ,
uniq ( fingerprint ) AS total_value ,
( SELECT uniq ( fingerprint )
FROM % s . % s
WHERE unix_milli BETWEEN ? AND ? ) AS total_time_series
FROM % s . % s
WHERE unix_milli BETWEEN ? AND ? % s
GROUP BY metric_name
)
ORDER BY percentage DESC
LIMIT % d ; ` ,
signozMetricDBName ,
tsTable ,
signozMetricDBName ,
tsTable ,
whereClause ,
req . Limit ,
)
args = append ( args ,
start , end , // For total_cardinality subquery
start , end , // For main query
)
valueCtx := context . WithValue ( ctx , "clickhouse_max_threads" , constants . MetricsExplorerClickhouseThreads )
rows , err := r . db . Query ( valueCtx , query , args ... )
if err != nil {
zap . L ( ) . Error ( "Error executing cardinality query" , zap . Error ( err ) , zap . String ( "query" , query ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
var heatmap [ ] metrics_explorer . TreeMapResponseItem
for rows . Next ( ) {
var item metrics_explorer . TreeMapResponseItem
if err := rows . Scan ( & item . MetricName , & item . TotalValue , & item . Percentage ) ; err != nil {
zap . L ( ) . Error ( "Error scanning row" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
heatmap = append ( heatmap , item )
}
if err := rows . Err ( ) ; err != nil {
zap . L ( ) . Error ( "Error iterating over rows" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return & heatmap , nil
}
func ( r * ClickHouseReader ) GetMetricsSamplesPercentage ( ctx context . Context , req * metrics_explorer . TreeMapMetricsRequest ) ( * [ ] metrics_explorer . TreeMapResponseItem , * model . ApiError ) {
var args [ ] interface { }
// Build the filter conditions
conditions , _ := utils . BuildFilterConditions ( & req . Filters , "t" )
whereClause := ""
if conditions != nil {
whereClause = "AND " + strings . Join ( conditions , " AND " )
}
// Determine time range and tables to use
start , end , tsTable , localTsTable := utils . WhichTSTableToUse ( req . Start , req . EndD )
sampleTable , countExp := utils . WhichSampleTableToUse ( req . Start , req . EndD )
// Construct the metrics query
queryLimit := 50 + req . Limit
metricsQuery := fmt . Sprintf (
` SELECT
t . metric_name AS metric_name ,
uniq ( t . fingerprint ) AS timeSeries
FROM % s . % s AS t
WHERE unix_milli BETWEEN ? AND ?
% s
GROUP BY t . metric_name
ORDER BY timeSeries DESC
LIMIT % d ; ` ,
signozMetricDBName , tsTable , whereClause , queryLimit ,
)
args = append ( args , start , end )
valueCtx := context . WithValue ( ctx , "clickhouse_max_threads" , constants . MetricsExplorerClickhouseThreads )
// Execute the metrics query
rows , err := r . db . Query ( valueCtx , metricsQuery , args ... )
if err != nil {
zap . L ( ) . Error ( "Error executing metrics query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
// Process the query results
var metricNames [ ] string
for rows . Next ( ) {
var metricName string
var timeSeries uint64
if err := rows . Scan ( & metricName , & timeSeries ) ; err != nil {
zap . L ( ) . Error ( "Error scanning metric row" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
metricNames = append ( metricNames , metricName )
}
if err := rows . Err ( ) ; err != nil {
zap . L ( ) . Error ( "Error iterating over metric rows" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
// If no metrics found, return early
if len ( metricNames ) == 0 {
return nil , nil
}
// Format metric names for query
metricsList := "'" + strings . Join ( metricNames , "', '" ) + "'"
2025-02-27 12:25:58 +05:30
// Build query using string builder for better performance
var sb strings . Builder
sb . WriteString ( fmt . Sprintf (
2025-02-20 13:49:44 +05:30
` WITH TotalSamples AS (
SELECT % s AS total_samples
FROM % s . % s
WHERE unix_milli BETWEEN ? AND ?
)
SELECT
s . samples ,
s . metric_name ,
COALESCE ( ( s . samples * 100.0 / t . total_samples ) , 0 ) AS percentage
FROM
(
SELECT
metric_name ,
% s AS samples
2025-02-27 12:25:58 +05:30
FROM % s . % s ` ,
countExp , signozMetricDBName , sampleTable , // Total samples
countExp , signozMetricDBName , sampleTable , // Inner select samples
) )
// Conditionally add the fingerprint subquery if whereClause is present
if whereClause != "" {
sb . WriteString ( fmt . Sprintf (
` WHERE fingerprint IN (
2025-02-20 13:49:44 +05:30
SELECT fingerprint
FROM % s . % s
WHERE unix_milli BETWEEN ? AND ?
% s
AND metric_name IN ( % s )
GROUP BY fingerprint
)
2025-02-27 12:25:58 +05:30
AND metric_name IN ( % s ) ` ,
signozMetricDBName , localTsTable , whereClause , metricsList ,
metricsList ,
) )
} else {
sb . WriteString ( fmt . Sprintf (
` WHERE metric_name IN (%s) ` ,
metricsList ,
) )
}
sb . WriteString ( `
2025-02-20 13:49:44 +05:30
GROUP BY metric_name
) AS s
JOIN TotalSamples t ON 1 = 1
ORDER BY percentage DESC
2025-02-27 12:25:58 +05:30
LIMIT ? ; ` )
sampleQuery := sb . String ( )
2025-02-20 13:49:44 +05:30
2025-02-27 12:25:58 +05:30
// Add start and end time to args
2025-02-20 13:49:44 +05:30
args = append ( args , start , end )
// Execute the sample percentage query
rows , err = r . db . Query ( valueCtx , sampleQuery , args ... )
if err != nil {
zap . L ( ) . Error ( "Error executing samples query" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
defer rows . Close ( )
// Process the results into a response slice
var heatmap [ ] metrics_explorer . TreeMapResponseItem
for rows . Next ( ) {
var item metrics_explorer . TreeMapResponseItem
if err := rows . Scan ( & item . TotalValue , & item . MetricName , & item . Percentage ) ; err != nil {
zap . L ( ) . Error ( "Error scanning row" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
heatmap = append ( heatmap , item )
}
if err := rows . Err ( ) ; err != nil {
zap . L ( ) . Error ( "Error iterating over sample rows" , zap . Error ( err ) )
return nil , & model . ApiError { Typ : "ClickHouseError" , Err : err }
}
return & heatmap , nil
}