From 446a222199f31b9edf559f3ceb7b749448cc840d Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Wed, 15 Apr 2026 09:27:46 +0000 Subject: [PATCH] feat: add SQLServer performance insights support - CE components (#3237) - Fix empty RuleTemplateID handling in convertInstance to prevent parse errors - Add audit_enabled filter for instance_tips with view_sql_insight module - Fix slow log cursor timestamp format for MySQL STR_TO_DATE compatibility - Add SQLServer audit plan type locale strings (zh/en) --- sqle/api/controller/v2/instance_audit_plan.go | 6 ++++++ sqle/dms/instance.go | 19 +++++++++++++------ sqle/locale/active.en.toml | 6 ++++++ sqle/locale/active.zh.toml | 6 ++++++ sqle/locale/message_zh.go | 6 ++++++ sqle/model/instance_audit_plan.go | 9 +++++++-- 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/sqle/api/controller/v2/instance_audit_plan.go b/sqle/api/controller/v2/instance_audit_plan.go index d03491035d..0a1fb0a621 100644 --- a/sqle/api/controller/v2/instance_audit_plan.go +++ b/sqle/api/controller/v2/instance_audit_plan.go @@ -114,6 +114,12 @@ func GetInstanceTips(c echo.Context) error { instanceTipsResV1 := make([]InstanceTipResV2, 0, len(instances)) svc := server.BackupService{} for _, inst := range instances { + // When functional_module is view_sql_insight, only return instances + // that have SQL audit enabled. Instances without audit cannot provide + // performance insight data. + if req.FunctionalModule == v1.FunctionalModuleViewSQLInsight && !inst.SqlQueryConfig.AuditEnabled { + continue + } instanceTipRes := InstanceTipResV2{ ID: inst.GetIDStr(), Name: inst.Name, diff --git a/sqle/dms/instance.go b/sqle/dms/instance.go index 471f801112..e5e27338e2 100644 --- a/sqle/dms/instance.go +++ b/sqle/dms/instance.go @@ -35,7 +35,7 @@ func getInstances(ctx context.Context, req dmsV2.ListDBServiceReq) ([]*model.Ins } for _, item := range dbServices { - if item.SQLEConfig == nil || item.SQLEConfig.RuleTemplateID == "" { + if item.SQLEConfig == nil { continue } @@ -83,9 +83,16 @@ func convertInstance(instance *dmsV2.ListDBService) (*model.Instance, error) { return nil, err } - ruleTemplateId, err := strconv.ParseInt(instance.SQLEConfig.RuleTemplateID, 0, 64) - if err != nil { - return nil, err + var ruleTemplateId int64 + var ruleTemplateName string + if instance.SQLEConfig != nil { + ruleTemplateName = instance.SQLEConfig.RuleTemplateName + if instance.SQLEConfig.RuleTemplateID != "" { + ruleTemplateId, err = strconv.ParseInt(instance.SQLEConfig.RuleTemplateID, 0, 64) + if err != nil { + return nil, err + } + } } var maintenancePeriod = make(model.Periods, 0) @@ -114,7 +121,7 @@ func convertInstance(instance *dmsV2.ListDBService) (*model.Instance, error) { } sqlQueryConfig := model.SqlQueryConfig{} - if instance.SQLEConfig != nil { + if instance.SQLEConfig != nil && instance.SQLEConfig.SQLQueryConfig != nil { sqlQueryConfig = model.SqlQueryConfig{ MaxPreQueryRows: instance.SQLEConfig.SQLQueryConfig.MaxPreQueryRows, QueryTimeoutSecond: instance.SQLEConfig.SQLQueryConfig.QueryTimeoutSecond, @@ -132,7 +139,7 @@ func convertInstance(instance *dmsV2.ListDBService) (*model.Instance, error) { Name: instance.Name, DbType: instance.DBType, RuleTemplateId: uint64(ruleTemplateId), - RuleTemplateName: instance.SQLEConfig.RuleTemplateName, + RuleTemplateName: ruleTemplateName, ProjectId: instance.ProjectUID, MaintenancePeriod: maintenancePeriod, Host: instance.Host, diff --git a/sqle/locale/active.en.toml b/sqle/locale/active.en.toml index 4536b54606..28a7eceea7 100644 --- a/sqle/locale/active.en.toml +++ b/sqle/locale/active.en.toml @@ -36,6 +36,10 @@ ApMetaPerformanceCollectTips = "Performance metric collection will incur signifi ApMetaPostgreSQLTopSQL = "TOP SQL" ApMetaPostgreSQLSlowLog = "Slow log" ApMetaQueries = "QPS" +ApMetaSQLServerPerformanceCollect = "SQLServer performance collect" +ApMetaSQLServerPerformanceCollectTips = "Periodically collect performance metrics such as connections and QPS from SQLServer instances" +ApMetaSQLServerProcesslist = "SQLServer active sessions" +ApMetaSQLServerSlowLog = "SQLServer slow SQL collect" ApMetaSchemaMeta = "Database schema metadata" ApMetaSlowLog = "Slow log" ApMetaThreadsConnected = "ThreadsConnected" @@ -324,6 +328,8 @@ ParamProjectId = "Project ID" ParamRdsPath = "RDS Open API Address" ParamRegion = "Region of current RDS Instance (Example: cn-east-2)" ParamSQLMinSecond = "SQL Minimum Execution Time (Second)" +ParamSlowSqlThreshold = "Slow SQL Threshold (Second)" +ParamFirstSqlsScrappedHoursGeneric = "Time range for pulling slow logs on task startup (unit: hours)" ParamSlowLogCollectInput = "Collect Source" ParamTimeSpan = "Time span(hours)" ParamTopN = "Top N" diff --git a/sqle/locale/active.zh.toml b/sqle/locale/active.zh.toml index 5c49d8d2ad..d756ea5ab5 100644 --- a/sqle/locale/active.zh.toml +++ b/sqle/locale/active.zh.toml @@ -36,6 +36,10 @@ ApMetaPerformanceCollectTips = "性能指标采集将产生较大性能开销, ApMetaPostgreSQLTopSQL = "TOP SQL" ApMetaPostgreSQLSlowLog = "慢日志" ApMetaQueries = "QPS" +ApMetaSQLServerPerformanceCollect = "SQLServer性能采集" +ApMetaSQLServerPerformanceCollectTips = "定时采集SQLServer实例的连接数和QPS等性能指标" +ApMetaSQLServerProcesslist = "SQLServer活跃会话采集" +ApMetaSQLServerSlowLog = "SQLServer慢SQL采集" ApMetaSchemaMeta = "库表元数据" ApMetaSlowLog = "慢日志" ApMetaThreadsConnected = "线程数" @@ -324,6 +328,8 @@ ParamProjectId = "项目ID" ParamRdsPath = "RDS Open API地址" ParamRegion = "当前RDS实例所在的地区(示例:cn-east-2)" ParamSQLMinSecond = "SQL 最小执行时间(秒)" +ParamSlowSqlThreshold = "慢SQL判定时间阈值(秒)" +ParamFirstSqlsScrappedHoursGeneric = "启动任务时拉取慢日志时间范围(单位:小时)" ParamSlowLogCollectInput = "采集来源" ParamTimeSpan = "时间跨度(小时)" ParamTopN = "Top N" diff --git a/sqle/locale/message_zh.go b/sqle/locale/message_zh.go index 7fa1d52b8a..7ffd2045c1 100644 --- a/sqle/locale/message_zh.go +++ b/sqle/locale/message_zh.go @@ -425,6 +425,10 @@ var ( ApMetricRowExaminedAvg = &i18n.Message{ID: "ApMetricRowExaminedAvg", Other: "平均扫描行数"} ApMetaPerformanceCollect = &i18n.Message{ID: "ApMetaPerformanceCollect", Other: "数据源性能指标"} ApMetaPerformanceCollectTips = &i18n.Message{ID: "ApMetaPerformanceCollectTips", Other: "性能指标采集将产生较大性能开销,请谨慎开启。开启后,系统将持续采集该数据源的性能数据(如QPS、连接数等),并生成性能趋势图表,体现在性能洞察页面。"} + ApMetaSQLServerPerformanceCollect = &i18n.Message{ID: "ApMetaSQLServerPerformanceCollect", Other: "SQLServer性能采集"} + ApMetaSQLServerPerformanceCollectTips = &i18n.Message{ID: "ApMetaSQLServerPerformanceCollectTips", Other: "定时采集SQLServer实例的连接数和QPS等性能指标"} + ApMetaSQLServerSlowLog = &i18n.Message{ID: "ApMetaSQLServerSlowLog", Other: "SQLServer慢SQL采集"} + ApMetaSQLServerProcesslist = &i18n.Message{ID: "ApMetaSQLServerProcesslist", Other: "SQLServer活跃会话采集"} ApMetaCollectTime = &i18n.Message{ID: "ApMetaCollectTime", Other: "采集时间"} ApMetaThreadsConnected = &i18n.Message{ID: "ApMetaThreadsConnected", Other: "线程数"} ApMetaQPS = &i18n.Message{ID: "ApMetaQueries", Other: "QPS"} @@ -444,6 +448,8 @@ var ( ParamOrderByColumnGeneric = &i18n.Message{ID: "ParamOrderByColumnGeneric", Other: "排序字段"} ParamCollectIntervalSecond = &i18n.Message{ID: "ParamCollectIntervalSecond", Other: "采集周期(秒)"} ParamSQLMinSecond = &i18n.Message{ID: "ParamSQLMinSecond", Other: "SQL 最小执行时间(秒)"} + ParamSlowSqlThreshold = &i18n.Message{ID: "ParamSlowSqlThreshold", Other: "慢SQL判定时间阈值(秒)"} + ParamFirstSqlsScrappedHoursGeneric = &i18n.Message{ID: "ParamFirstSqlsScrappedHoursGeneric", Other: "启动任务时拉取慢日志时间范围(单位:小时)"} ParamCollectView = &i18n.Message{ID: "ParamCollectView", Other: "是否采集视图信息"} ParamDBInstanceId = &i18n.Message{ID: "ParamDBInstanceId", Other: "实例ID"} ParamAccessKeyId = &i18n.Message{ID: "ParamAccessKeyId", Other: "Access Key ID"} diff --git a/sqle/model/instance_audit_plan.go b/sqle/model/instance_audit_plan.go index f2aa9469e2..f29357c0c6 100644 --- a/sqle/model/instance_audit_plan.go +++ b/sqle/model/instance_audit_plan.go @@ -176,8 +176,13 @@ func (s *Storage) GetLatestStartTimeAuditPlanSQLV2(sourceId uint, typ string) (s info := struct { StartTime string `gorm:"column:max_start_time"` }{} - err := s.db.Raw(`SELECT MAX(STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%dT%H:%i:%s.%f')) - AS max_start_time FROM sql_manage_records WHERE source_id = ? AND source = ? AND deleted_at is NULL`, sourceId, typ).Scan(&info).Error + // Try multiple date formats: space-separated (new) and T-separated with/without timezone (legacy). + // COALESCE picks the first non-NULL parse result. The RFC3339 timezone suffix (Z/+HH:MM) + // is silently ignored by STR_TO_DATE, so '%Y-%m-%dT%H:%i:%s' handles it correctly. + err := s.db.Raw(`SELECT MAX(COALESCE( + STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%d %H:%i:%s'), + STR_TO_DATE(JSON_UNQUOTE(JSON_EXTRACT(info, '$.start_time_of_last_scraped_sql')), '%Y-%m-%dT%H:%i:%s') + )) AS max_start_time FROM sql_manage_records WHERE source_id = ? AND source = ? AND deleted_at is NULL`, sourceId, typ).Scan(&info).Error return info.StartTime, err }