-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
executor: add remain time for showAnalyzeStatus #43866
Changes from all commits
e701cb7
47ce042
754ca11
4ac3fee
d7437c1
b7d7b2f
b94a4fa
5a80996
375c603
5b2a420
37a703d
9a4727f
3841ad5
906a5da
edd0939
1bf44aa
fdf73cd
1cc7d1d
c5aff05
4886b4f
7115c75
da40a2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ import ( | |
"github.com/pingcap/tidb/domain" | ||
"github.com/pingcap/tidb/domain/infosync" | ||
"github.com/pingcap/tidb/errno" | ||
internalutil "github.com/pingcap/tidb/executor/internal/util" | ||
"github.com/pingcap/tidb/expression" | ||
"github.com/pingcap/tidb/infoschema" | ||
"github.com/pingcap/tidb/kv" | ||
|
@@ -49,6 +50,7 @@ import ( | |
"github.com/pingcap/tidb/sessionctx" | ||
"github.com/pingcap/tidb/sessionctx/variable" | ||
"github.com/pingcap/tidb/sessiontxn" | ||
"github.com/pingcap/tidb/statistics" | ||
"github.com/pingcap/tidb/store/helper" | ||
"github.com/pingcap/tidb/table" | ||
"github.com/pingcap/tidb/tablecodec" | ||
|
@@ -58,7 +60,9 @@ import ( | |
"github.com/pingcap/tidb/util/codec" | ||
"github.com/pingcap/tidb/util/collate" | ||
"github.com/pingcap/tidb/util/deadlockhistory" | ||
"github.com/pingcap/tidb/util/execdetails" | ||
"github.com/pingcap/tidb/util/hint" | ||
"github.com/pingcap/tidb/util/intest" | ||
"github.com/pingcap/tidb/util/keydecoder" | ||
"github.com/pingcap/tidb/util/logutil" | ||
"github.com/pingcap/tidb/util/mathutil" | ||
|
@@ -120,7 +124,7 @@ func (e *memtableRetriever) retrieve(ctx context.Context, sctx sessionctx.Contex | |
case infoschema.TableClusterInfo: | ||
err = e.dataForTiDBClusterInfo(sctx) | ||
case infoschema.TableAnalyzeStatus: | ||
err = e.setDataForAnalyzeStatus(sctx) | ||
err = e.setDataForAnalyzeStatus(ctx, sctx) | ||
case infoschema.TableTiDBIndexes: | ||
e.setDataFromIndexes(sctx, dbs) | ||
case infoschema.TableViews: | ||
|
@@ -2130,16 +2134,17 @@ func (e *tableStorageStatsRetriever) setDataForTableStorageStats(ctx sessionctx. | |
} | ||
|
||
// dataForAnalyzeStatusHelper is a helper function which can be used in show_stats.go | ||
func dataForAnalyzeStatusHelper(sctx sessionctx.Context) (rows [][]types.Datum, err error) { | ||
func dataForAnalyzeStatusHelper(ctx context.Context, sctx sessionctx.Context, isShow bool) (rows [][]types.Datum, err error) { | ||
const maxAnalyzeJobs = 30 | ||
const sql = "SELECT table_schema, table_name, partition_name, job_info, processed_rows, CONVERT_TZ(start_time, @@TIME_ZONE, '+00:00'), CONVERT_TZ(end_time, @@TIME_ZONE, '+00:00'), state, fail_reason, instance, process_id FROM mysql.analyze_jobs ORDER BY update_time DESC LIMIT %?" | ||
exec := sctx.(sqlexec.RestrictedSQLExecutor) | ||
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnStats) | ||
chunkRows, _, err := exec.ExecRestrictedSQL(ctx, nil, sql, maxAnalyzeJobs) | ||
kctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnStats) | ||
chunkRows, _, err := exec.ExecRestrictedSQL(kctx, nil, sql, maxAnalyzeJobs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
checker := privilege.GetPrivilegeManager(sctx) | ||
|
||
for _, chunkRow := range chunkRows { | ||
dbName := chunkRow.GetString(0) | ||
tableName := chunkRow.GetString(1) | ||
|
@@ -2164,6 +2169,7 @@ func dataForAnalyzeStatusHelper(sctx sessionctx.Context) (rows [][]types.Datum, | |
} | ||
endTime = types.NewTime(types.FromGoTime(t.In(sctx.GetSessionVars().TimeZone)), mysql.TypeDatetime, 0) | ||
} | ||
|
||
state := chunkRow.GetEnum(7).String() | ||
var failReason interface{} | ||
if !chunkRow.IsNull(8) { | ||
|
@@ -2174,26 +2180,138 @@ func dataForAnalyzeStatusHelper(sctx sessionctx.Context) (rows [][]types.Datum, | |
if !chunkRow.IsNull(10) { | ||
procID = chunkRow.GetUint64(10) | ||
} | ||
rows = append(rows, types.MakeDatums( | ||
dbName, // TABLE_SCHEMA | ||
tableName, // TABLE_NAME | ||
partitionName, // PARTITION_NAME | ||
jobInfo, // JOB_INFO | ||
processedRows, // ROW_COUNT | ||
startTime, // START_TIME | ||
endTime, // END_TIME | ||
state, // STATE | ||
failReason, // FAIL_REASON | ||
instance, // INSTANCE | ||
procID, // PROCESS_ID | ||
)) | ||
|
||
var remainDurationStr, progressStr, estimatedRowCntStr interface{} | ||
if isShow && state == statistics.AnalyzeRunning { | ||
startTime, ok := startTime.(types.Time) | ||
if !ok { | ||
return nil, errors.New("invalid start time") | ||
} | ||
RemainingDuration, progress, estimatedRowCnt, RemainDurationErr := | ||
getRemainDurationForAnalyzeStatusHelper(ctx, sctx, &startTime, | ||
dbName, tableName, partitionName, processedRows) | ||
if RemainDurationErr != nil { | ||
logutil.BgLogger().Warn("get remaining duration failed", zap.Error(RemainDurationErr)) | ||
} | ||
if RemainingDuration != nil { | ||
remainDurationStr = execdetails.FormatDuration(*RemainingDuration) | ||
} | ||
progressStr = progress | ||
estimatedRowCntStr = int64(estimatedRowCnt) | ||
} | ||
var row []types.Datum | ||
if isShow { | ||
row = types.MakeDatums( | ||
dbName, // TABLE_SCHEMA | ||
tableName, // TABLE_NAME | ||
partitionName, // PARTITION_NAME | ||
jobInfo, // JOB_INFO | ||
processedRows, // ROW_COUNT | ||
startTime, // START_TIME | ||
endTime, // END_TIME | ||
state, // STATE | ||
failReason, // FAIL_REASON | ||
instance, // INSTANCE | ||
procID, // PROCESS_ID | ||
remainDurationStr, // REMAINING_SECONDS | ||
progressStr, // PROGRESS | ||
estimatedRowCntStr, // ESTIMATED_TOTAL_ROWS | ||
) | ||
} else { | ||
row = types.MakeDatums( | ||
dbName, // TABLE_SCHEMA | ||
tableName, // TABLE_NAME | ||
partitionName, // PARTITION_NAME | ||
jobInfo, // JOB_INFO | ||
processedRows, // ROW_COUNT | ||
startTime, // START_TIME | ||
endTime, // END_TIME | ||
state, // STATE | ||
failReason, // FAIL_REASON | ||
instance, // INSTANCE | ||
procID, // PROCESS_ID | ||
) | ||
} | ||
rows = append(rows, row) | ||
} | ||
return | ||
} | ||
|
||
func getRemainDurationForAnalyzeStatusHelper( | ||
ctx context.Context, | ||
sctx sessionctx.Context, startTime *types.Time, | ||
dbName, tableName, partitionName string, processedRows int64) (*time.Duration, float64, float64, error) { | ||
var RemainingDuration = time.Duration(0) | ||
var percentage = 0.0 | ||
var totalCnt = float64(0) | ||
if startTime != nil { | ||
start, err := startTime.GoTime(time.UTC) | ||
if err != nil { | ||
return nil, percentage, totalCnt, err | ||
} | ||
duration := time.Now().UTC().Sub(start) | ||
if intest.InTest { | ||
if val := ctx.Value(AnalyzeProgressTest); val != nil { | ||
RemainingDuration, percentage = calRemainInfoForAnalyzeStatus(ctx, int64(totalCnt), processedRows, duration) | ||
return &RemainingDuration, percentage, totalCnt, nil | ||
} | ||
} | ||
var tid int64 | ||
is := sessiontxn.GetTxnManager(sctx).GetTxnInfoSchema() | ||
tb, err := is.TableByName(model.NewCIStr(dbName), model.NewCIStr(tableName)) | ||
if err != nil { | ||
return nil, percentage, totalCnt, err | ||
} | ||
statsHandle := domain.GetDomain(sctx).StatsHandle() | ||
if statsHandle != nil { | ||
var statsTbl *statistics.Table | ||
meta := tb.Meta() | ||
if partitionName != "" { | ||
pt := meta.GetPartitionInfo() | ||
tid = pt.GetPartitionIDByName(partitionName) | ||
statsTbl = statsHandle.GetPartitionStats(meta, tid) | ||
} else { | ||
statsTbl = statsHandle.GetTableStats(meta) | ||
tid = meta.ID | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that we need to exchange the two branches. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
if statsTbl != nil && statsTbl.RealtimeCount != 0 { | ||
totalCnt = float64(statsTbl.RealtimeCount) | ||
} | ||
} | ||
if tid > 0 && totalCnt == 0 { | ||
totalCnt, _ = internalutil.GetApproximateTableCountFromStorage(sctx, tid, dbName, tableName, partitionName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This func will be called at getAdjustedSampleRate when we do the first analyze, and called again when show analyze status, right? Can we reuse, like persisting the ApproximateTableCount somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UI will call showAnalyzeStatus many times to get fresh status. I'm afraid there will be frequent calls to internalutil.GetApproximateTableCountFromStorage. |
||
} | ||
RemainingDuration, percentage = calRemainInfoForAnalyzeStatus(ctx, int64(totalCnt), processedRows, duration) | ||
} | ||
return &RemainingDuration, percentage, totalCnt, nil | ||
} | ||
|
||
func calRemainInfoForAnalyzeStatus(ctx context.Context, totalCnt int64, processedRows int64, duration time.Duration) (time.Duration, float64) { | ||
if intest.InTest { | ||
if val := ctx.Value(AnalyzeProgressTest); val != nil { | ||
totalCnt = 100 // But in final result, it is still 0. | ||
processedRows = 10 | ||
duration = 1 * time.Minute | ||
} | ||
} | ||
if totalCnt == 0 { | ||
return 0, 100.0 | ||
} | ||
remainLine := totalCnt - processedRows | ||
if processedRows == 0 { | ||
processedRows = 1 | ||
} | ||
if duration == 0 { | ||
duration = 1 * time.Second | ||
} | ||
i := float64(remainLine) * duration.Seconds() / float64(processedRows) | ||
persentage := float64(processedRows) / float64(totalCnt) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: persentage-> percentage |
||
return time.Duration(i) * time.Second, persentage | ||
} | ||
|
||
// setDataForAnalyzeStatus gets all the analyze jobs. | ||
func (e *memtableRetriever) setDataForAnalyzeStatus(sctx sessionctx.Context) (err error) { | ||
e.rows, err = dataForAnalyzeStatusHelper(sctx) | ||
func (e *memtableRetriever) setDataForAnalyzeStatus(ctx context.Context, sctx sessionctx.Context) (err error) { | ||
e.rows, err = dataForAnalyzeStatusHelper(ctx, sctx, false) | ||
return | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to be a numeric type for UI to easily parse and aggregate.