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  }