Files
tidb/executor/plan_recreator.go

179 lines
5.2 KiB
Go

// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package executor
import (
"archive/zip"
"context"
"crypto/md5" // #nosec G501
"encoding/hex"
"fmt"
"math/rand"
"os"
"time"
"github.com/pingcap/errors"
"github.com/pingcap/parser/ast"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/logutil"
)
const recreatorPath string = "/tmp/recreator"
// TTL of plan recreator files
const remainedInterval float64 = 3
// PlanRecreatorInfo saves the information of plan recreator operation.
type PlanRecreatorInfo interface {
// Process dose the export/import work for reproducing sql queries.
Process() (string, error)
}
// PlanRecreatorSingleExec represents a plan recreator executor.
type PlanRecreatorSingleExec struct {
baseExecutor
info *PlanRecreatorSingleInfo
}
// PlanRecreatorSingleInfo saves the information of plan recreator operation.
type PlanRecreatorSingleInfo struct {
ExecStmt ast.StmtNode
Analyze bool
Load bool
File string
Ctx sessionctx.Context
}
type fileInfo struct {
StartTime time.Time
Token [16]byte
}
type fileList struct {
FileInfo map[string]fileInfo
TokenMap map[[16]byte]string
}
// planRecreatorVarKeyType is a dummy type to avoid naming collision in context.
type planRecreatorVarKeyType int
// String defines a Stringer function for debugging and pretty printing.
func (k planRecreatorVarKeyType) String() string {
return "plan_recreator_var"
}
// planRecreatorFileListType is a dummy type to avoid naming collision in context.
type planRecreatorFileListType int
// String defines a Stringer function for debugging and pretty printing.
func (k planRecreatorFileListType) String() string {
return "plan_recreator_file_list"
}
// PlanRecreatorVarKey is a variable key for plan recreator.
const PlanRecreatorVarKey planRecreatorVarKeyType = 0
// PlanRecreatorFileList is a variable key for plan recreator's file list.
const PlanRecreatorFileList planRecreatorFileListType = 0
// Next implements the Executor Next interface.
func (e *PlanRecreatorSingleExec) Next(ctx context.Context, req *chunk.Chunk) error {
req.GrowAndReset(e.maxChunkSize)
if e.info.ExecStmt == nil {
return errors.New("plan Recreator: sql is empty")
}
val := e.ctx.Value(PlanRecreatorVarKey)
if val != nil {
e.ctx.SetValue(PlanRecreatorVarKey, nil)
return errors.New("plan Recreator: previous plan recreator option isn't closed normally")
}
e.ctx.SetValue(PlanRecreatorVarKey, e.info)
return nil
}
// Close implements the Executor Close interface.
func (e *PlanRecreatorSingleExec) Close() error {
return nil
}
// Open implements the Executor Open interface.
func (e *PlanRecreatorSingleExec) Open(ctx context.Context) error {
return nil
}
// Process dose the export/import work for reproducing sql queries.
func (e *PlanRecreatorSingleInfo) Process() (string, error) {
// TODO: plan recreator load will be developed later
if e.Load {
return "", nil
}
return e.dumpSingle()
}
func (e *PlanRecreatorSingleInfo) dumpSingle() (string, error) {
// Create path
err := os.MkdirAll(recreatorPath, os.ModePerm)
if err != nil {
return "", errors.New("plan Recreator: cannot create plan recreator path")
}
// Create zip file
startTime := time.Now()
fileName := fmt.Sprintf("recreator_single_%v.zip", startTime.UnixNano())
zf, err := os.Create(recreatorPath + "/" + fileName)
if err != nil {
return "", errors.New("plan Recreator: cannot create zip file")
}
val := e.Ctx.Value(PlanRecreatorFileList)
if val == nil {
e.Ctx.SetValue(PlanRecreatorFileList, fileList{FileInfo: make(map[string]fileInfo), TokenMap: make(map[[16]byte]string)})
} else {
// Clean outdated files
Flist := val.(fileList).FileInfo
TList := val.(fileList).TokenMap
for k, v := range Flist {
if time.Since(v.StartTime).Minutes() > remainedInterval {
err := os.Remove(recreatorPath + "/" + k)
if err != nil {
logutil.BgLogger().Warn(fmt.Sprintf("Cleaning outdated file %s failed.", k))
}
delete(Flist, k)
delete(TList, v.Token)
}
}
}
// Generate Token
token := md5.Sum([]byte(fmt.Sprintf("%s%d", fileName, rand.Int63()))) // #nosec G401 G404
e.Ctx.Value(PlanRecreatorFileList).(fileList).FileInfo[fileName] = fileInfo{StartTime: startTime, Token: token}
e.Ctx.Value(PlanRecreatorFileList).(fileList).TokenMap[token] = fileName
// Create zip writer
zw := zip.NewWriter(zf)
defer func() {
err := zw.Close()
if err != nil {
logutil.BgLogger().Warn("Closing zip writer failed.")
}
err = zf.Close()
if err != nil {
logutil.BgLogger().Warn("Closing zip file failed.")
}
}()
// TODO: DUMP PLAN RECREATOR FILES IN ZIP WRITER
return hex.EncodeToString(token[:]), nil
}