github.com/matrixorigin/matrixone@v1.2.0/pkg/util/metric/mometric/cron_task.go (about) 1 // Copyright 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mometric 16 17 import ( 18 "context" 19 "fmt" 20 "path" 21 "strings" 22 "sync/atomic" 23 "time" 24 25 "github.com/matrixorigin/matrixone/pkg/catalog" 26 "github.com/matrixorigin/matrixone/pkg/defines" 27 28 "github.com/matrixorigin/matrixone/pkg/common/log" 29 "github.com/matrixorigin/matrixone/pkg/util/export/table" 30 "github.com/matrixorigin/matrixone/pkg/util/metric" 31 32 "github.com/matrixorigin/matrixone/pkg/common/runtime" 33 "github.com/matrixorigin/matrixone/pkg/pb/task" 34 "github.com/matrixorigin/matrixone/pkg/taskservice" 35 ie "github.com/matrixorigin/matrixone/pkg/util/internalExecutor" 36 "github.com/matrixorigin/matrixone/pkg/util/trace" 37 38 "go.uber.org/zap" 39 ) 40 41 const ( 42 LoggerName = "MetricTask" 43 LoggerNameMetricStorage = "MetricStorage" 44 45 StorageUsageCronTask = "StorageUsage" 46 StorageUsageTaskCronExpr = ExprEvery05Min 47 48 ExprEvery05Min = "0 */1 * * * *" 49 ParamSeparator = " " 50 ) 51 52 // TaskMetadata handle args like: "{db_tbl_name} [date, default: today]" 53 func TaskMetadata(jobName string, id task.TaskCode, args ...string) task.TaskMetadata { 54 return task.TaskMetadata{ 55 ID: path.Join(jobName, path.Join(args...)), 56 Executor: id, 57 Context: []byte(strings.Join(args, ParamSeparator)), 58 Options: task.TaskOptions{Concurrency: 1}, 59 } 60 } 61 62 // CreateCronTask should init once in/with schema-init. 63 func CreateCronTask(ctx context.Context, executorID task.TaskCode, taskService taskservice.TaskService) error { 64 var err error 65 ctx, span := trace.Start(ctx, "MetricCreateCronTask") 66 defer span.End() 67 ctx = defines.AttachAccount(ctx, catalog.System_Account, catalog.System_User, catalog.System_Role) 68 logger := runtime.ProcessLevelRuntime().Logger().WithContext(ctx).Named(LoggerName) 69 logger.Debug(fmt.Sprintf("init metric task with CronExpr: %s", StorageUsageTaskCronExpr)) 70 if err = taskService.CreateCronTask(ctx, TaskMetadata(StorageUsageCronTask, executorID), StorageUsageTaskCronExpr); err != nil { 71 return err 72 } 73 return nil 74 } 75 76 // GetMetricStorageUsageExecutor collect metric server_storage_usage 77 func GetMetricStorageUsageExecutor(sqlExecutor func() ie.InternalExecutor) func(ctx context.Context, task task.Task) error { 78 return func(ctx context.Context, task task.Task) error { 79 return CalculateStorageUsage(ctx, sqlExecutor) 80 } 81 } 82 83 const ( 84 ShowAllAccountSQL = "SHOW ACCOUNTS;" 85 ShowAccountSQL = "SHOW ACCOUNTS like %q;" 86 ColumnAccountName = "account_name" // result column in `show accounts`, or column in table mo_catalog.mo_account 87 ColumnSize = "size" // result column in `show accounts`, or column in table mo_catalog.mo_account 88 ColumnCreatedTime = "created_time" // column in table mo_catalog.mo_account 89 ColumnStatus = "status" // column in table mo_catalog.mo_account 90 ) 91 92 var ( 93 gUpdateStorageUsageInterval atomic.Int64 94 gCheckNewInterval atomic.Int64 95 ) 96 97 func init() { 98 gUpdateStorageUsageInterval.Store(int64(time.Minute)) 99 gCheckNewInterval.Store(int64(time.Minute)) 100 } 101 102 func SetUpdateStorageUsageInterval(interval time.Duration) { 103 gUpdateStorageUsageInterval.Store(int64(interval)) 104 } 105 106 func GetUpdateStorageUsageInterval() time.Duration { 107 return time.Duration(gUpdateStorageUsageInterval.Load()) 108 } 109 110 func cleanStorageUsageMetric(logger *log.MOLogger, actor string) { 111 // clean metric data for next cron task. 112 metric.StorageUsageFactory.Reset() 113 logger.Info("clean storage usage metric", zap.String("actor", actor)) 114 } 115 116 func CalculateStorageUsage(ctx context.Context, sqlExecutor func() ie.InternalExecutor) (err error) { 117 ctx, span := trace.Start(ctx, "MetricStorageUsage") 118 defer span.End() 119 ctx = defines.AttachAccount(ctx, catalog.System_Account, catalog.System_User, catalog.System_Role) 120 ctx, cancel := context.WithCancel(ctx) 121 logger := runtime.ProcessLevelRuntime().Logger().WithContext(ctx).Named(LoggerNameMetricStorage) 122 logger.Info("started") 123 defer func() { 124 logger.Info("finished", zap.Error(err)) 125 cleanStorageUsageMetric(logger, "CalculateStorageUsage") 126 // quit CheckNewAccountSize goroutine 127 cancel() 128 }() 129 130 // start background task to check new account 131 go checkNewAccountSize(ctx, logger, sqlExecutor) 132 133 ticker := time.NewTicker(time.Second) 134 defer ticker.Stop() 135 136 queryOpts := ie.NewOptsBuilder().Database(MetricDBConst).Internal(true).Finish() 137 for { 138 select { 139 case <-ctx.Done(): 140 logger.Info("receive context signal", zap.Error(ctx.Err())) 141 return ctx.Err() 142 case <-ticker.C: 143 logger.Info("start next round") 144 } 145 146 if !IsEnable() { 147 logger.Debug("mometric is disable.") 148 continue 149 } 150 151 // mysql> show accounts; 152 // +-----------------+------------+---------------------+--------+----------------+----------+-------------+-----------+-------+----------------+ 153 // | account_name | admin_name | created | status | suspended_time | db_count | table_count | row_count | size | comment | 154 // +-----------------+------------+---------------------+--------+----------------+----------+-------------+-----------+-------+----------------+ 155 // | sys | root | 2023-01-17 09:56:10 | open | NULL | 6 | 56 | 2082 | 0.341 | system account | 156 // | query_tae_table | admin | 2023-01-17 09:56:26 | open | NULL | 6 | 34 | 792 | 0.036 | | 157 // +-----------------+------------+---------------------+--------+----------------+----------+-------------+-----------+-------+----------------+ 158 logger.Debug("query storage size") 159 showAccounts := func(ctx context.Context) ie.InternalExecResult { 160 ctx, spanQ := trace.Start(ctx, "QueryStorageStorage", trace.WithHungThreshold(time.Minute)) 161 defer spanQ.End() 162 return sqlExecutor().Query(ctx, ShowAllAccountSQL, queryOpts) 163 } 164 result := showAccounts(ctx) 165 err = result.Error() 166 if err != nil { 167 return err 168 } 169 170 cnt := result.RowCount() 171 if cnt == 0 { 172 ticker.Reset(time.Minute) 173 logger.Warn("got empty account info, wait shortly") 174 continue 175 } 176 logger.Debug("collect storage_usage cnt", zap.Uint64("cnt", cnt)) 177 metric.StorageUsageFactory.Reset() 178 for rowIdx := uint64(0); rowIdx < cnt; rowIdx++ { 179 account, err := result.StringValueByName(ctx, rowIdx, ColumnAccountName) 180 if err != nil { 181 return err 182 } 183 sizeMB, err := result.Float64ValueByName(ctx, rowIdx, ColumnSize) 184 if err != nil { 185 return err 186 } 187 logger.Debug("storage_usage", zap.String("account", account), zap.Float64("sizeMB", sizeMB)) 188 189 metric.StorageUsage(account).Set(sizeMB) 190 } 191 192 // next round 193 ticker.Reset(GetUpdateStorageUsageInterval()) 194 logger.Info("wait next round") 195 } 196 } 197 198 func SetStorageUsageCheckNewInterval(interval time.Duration) { 199 gCheckNewInterval.Store(int64(interval)) 200 } 201 202 func GetStorageUsageCheckNewInterval() time.Duration { 203 return time.Duration(gCheckNewInterval.Load()) 204 } 205 206 func checkNewAccountSize(ctx context.Context, logger *log.MOLogger, sqlExecutor func() ie.InternalExecutor) { 207 var err error 208 ctx, span := trace.Start(ctx, "checkNewAccountSize") 209 defer span.End() 210 logger = logger.WithContext(ctx) 211 logger.Info("checkNewAccountSize started") 212 defer func() { 213 logger.Info("checkNewAccountSize exit", zap.Error(err)) 214 }() 215 216 if !IsEnable() { 217 logger.Info("mometric is disable.") 218 return 219 } 220 opts := ie.NewOptsBuilder().Database(MetricDBConst).Internal(true).Finish() 221 222 var now time.Time 223 var interval = GetStorageUsageCheckNewInterval() 224 var next = time.NewTicker(interval) 225 var lastCheckTime = time.Now().Add(-time.Second) 226 var newAccountCnt uint64 227 for { 228 select { 229 case <-ctx.Done(): 230 logger.Info("receive context signal", zap.Error(ctx.Err())) 231 cleanStorageUsageMetric(logger, "checkNewAccountSize") 232 return 233 case now = <-next.C: 234 logger.Debug("start check new account") 235 } 236 237 // mysql> select * from mo_catalog.mo_account; 238 // +------------+--------------+--------+---------------------+----------------+---------+----------------+ 239 // | account_id | account_name | status | created_time | comments | version | suspended_time | 240 // +------------+--------------+--------+---------------------+----------------+---------+----------------+ 241 // | 0 | sys | open | 2023-05-09 04:34:57 | system account | 1 | NULL | 242 // +------------+--------------+--------+---------------------+----------------+---------+----------------+ 243 executor := sqlExecutor() 244 // tips: created_time column, in table mo_catalog.mo_account, always use UTC timestamp. 245 // more details in pkg/frontend/authenticate.go, function frontend.createTablesInMoCatalog 246 sql := fmt.Sprintf("select account_name, created_time from mo_catalog.mo_account where created_time >= %q;", 247 table.Time2DatetimeString(lastCheckTime.UTC())) 248 getNewAccounts := func(ctx context.Context, sql string, lastCheck, now time.Time) ie.InternalExecResult { 249 ctx, spanQ := trace.Start(ctx, "QueryStorageStorage.getNewAccounts") 250 defer spanQ.End() 251 spanQ.AddExtraFields(zap.Time("last_check_time", lastCheck)) 252 spanQ.AddExtraFields(zap.Time("now", now)) 253 logger.Debug("query new account", zap.String("sql", sql)) 254 return executor.Query(ctx, sql, opts) 255 } 256 result := getNewAccounts(ctx, sql, lastCheckTime, now) 257 lastCheckTime = now 258 err = result.Error() 259 if err != nil { 260 logger.Error("failed to fetch new created account", zap.Error(err), zap.String("sql", sql)) 261 goto nextL 262 } 263 264 newAccountCnt = result.RowCount() 265 if newAccountCnt == 0 { 266 logger.Debug("got empty new account info, wait next round") 267 goto nextL 268 } 269 logger.Debug("collect new account cnt", zap.Uint64("cnt", newAccountCnt)) 270 for rowIdx := uint64(0); rowIdx < result.RowCount(); rowIdx++ { 271 272 account, err := result.StringValueByName(ctx, rowIdx, ColumnAccountName) 273 if err != nil { 274 continue 275 } 276 277 createdTime, err := result.StringValueByName(ctx, rowIdx, ColumnCreatedTime) 278 if err != nil { 279 continue 280 } 281 282 // query single account's storage 283 showSql := fmt.Sprintf(ShowAccountSQL, account) 284 getOneAccount := func(ctx context.Context, sql string) ie.InternalExecResult { 285 ctx, spanQ := trace.Start(ctx, "QueryStorageStorage.getOneAccount") 286 defer spanQ.End() 287 spanQ.AddExtraFields(zap.String("account", account)) 288 logger.Debug("query one account", zap.String("sql", sql)) 289 return executor.Query(ctx, sql, opts) 290 } 291 showRet := getOneAccount(ctx, showSql) 292 err = showRet.Error() 293 if err != nil { 294 logger.Error("failed to exec query sql", 295 zap.Error(err), zap.String("account", account), zap.String("sql", showSql)) 296 continue 297 } 298 299 if result.RowCount() == 0 { 300 logger.Warn("failed to fetch new account size, not exist.") 301 continue 302 } 303 sizeMB, err := showRet.Float64ValueByName(ctx, 0, ColumnSize) 304 if err != nil { 305 logger.Error("failed to fetch new account size", zap.Error(err), zap.String("account", account)) 306 continue 307 } 308 // done query. 309 310 // update new accounts metric 311 logger.Info("new account storage_usage", zap.String("account", account), zap.Float64("sizeMB", sizeMB), 312 zap.String("created_time", createdTime)) 313 314 metric.StorageUsage(account).Set(sizeMB) 315 } 316 317 nextL: 318 // reset next Round 319 next.Reset(GetStorageUsageCheckNewInterval()) 320 logger.Debug("wait next round, check new account") 321 } 322 }