Skip to content

Commit

Permalink
Merge 3849347 into 0c90f84
Browse files Browse the repository at this point in the history
  • Loading branch information
srstack authored Apr 18, 2022
2 parents 0c90f84 + 3849347 commit c0614bb
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 0 deletions.
107 changes: 107 additions & 0 deletions cmd/history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2022 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"encoding/json"
"fmt"
"strconv"

"github.com/pingcap/errors"
"github.com/pingcap/tiup/pkg/environment"
"github.com/pingcap/tiup/pkg/tui"
"github.com/spf13/cobra"
)

// newHistoryCmd history
func newHistoryCmd() *cobra.Command {
rows := 100
var displayMode string
var all bool
cmd := &cobra.Command{
Use: "history <rows>",
Short: "Display the historical execution record of TiUP, displays 100 lines by default",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
r, err := strconv.Atoi(args[0])
if err == nil {
rows = r
} else {
return fmt.Errorf("%s: numeric argument required", args[0])
}
}

env := environment.GlobalEnv()
rows, err := env.GetHistory(rows, all)
if err != nil {
return err
}

if displayMode == "json" {
for _, r := range rows {
rBytes, err := json.Marshal(r)
if err != nil {
continue
}
fmt.Println(string(rBytes))
}
return nil
}
var table [][]string
table = append(table, []string{"Date", "Command", "Code"})

for _, r := range rows {
table = append(table, []string{
r.Date.Format("2006-01-02T15:04:05"),
r.Command,
strconv.Itoa(r.Code),
})
}
tui.PrintTable(table, true)
fmt.Printf("history log save path: %s\n", env.LocalPath(environment.HistoryDir))
return nil
},
}
cmd.Flags().StringVar(&displayMode, "format", "default", "The format of output, available values are [default, json]")
cmd.Flags().BoolVar(&all, "all", false, "Display all execution history")
cmd.AddCommand(newHistoryCleanupCmd())
return cmd
}

func newHistoryCleanupCmd() *cobra.Command {
var retainDays int
var all bool
var skipConfirm bool
cmd := &cobra.Command{
Use: "cleanup",
Short: "delete all execution history",
RunE: func(cmd *cobra.Command, args []string) error {
if retainDays < 0 {
return errors.Errorf("retain-days cannot be less than 0")
}

if all {
retainDays = 0
}

env := environment.GlobalEnv()
return env.DeleteHistory(retainDays, skipConfirm)
},
}

cmd.Flags().IntVar(&retainDays, "retain-days", 60, "Number of days to keep history for deletion")
cmd.Flags().BoolVar(&all, "all", false, "Delete all history")
cmd.Flags().BoolVarP(&skipConfirm, "yes", "y", false, "Skip all confirmations and assumes 'yes'")
return cmd
}
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ the latest stable version will be downloaded from the repository.`,
newMirrorCmd(),
newTelemetryCmd(),
newEnvCmd(),
newHistoryCmd(),
)

originHelpFunc := rootCmd.HelpFunc()
Expand Down Expand Up @@ -248,6 +249,12 @@ func Execute() {
// us a dedicated package for that
reportEnabled = false
} else {
// record TiUP execution history
err := environment.HistoryRecord(env, os.Args, start, code)
if err != nil {
log.Warnf("Record TiUP execution history log failed: %v", err)
}

teleMeta, _, err := telemetry.GetMeta(env)
if err == nil {
reportEnabled = teleMeta.Status == telemetry.EnableStatus
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/pingcap/kvproto v0.0.0-20220125073028-58f2ac94aa38
github.com/pingcap/log v0.0.0-20211215031037-e024ba4eb0ee // indirect
github.com/pingcap/tidb-insight/collector v0.0.0-20220111101533-227008e9835b
github.com/pkg/errors v0.9.1
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.32.1
github.com/prometheus/prom2json v1.3.0
Expand Down
244 changes: 244 additions & 0 deletions pkg/environment/history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
// Copyright 2022 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package environment

import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"

"github.com/fatih/color"
"github.com/pingcap/tiup/pkg/tui"
"github.com/pingcap/tiup/pkg/utils"
"github.com/pkg/errors"
)

const (
// HistoryDir history save path
HistoryDir = "history"
historyPrefix = "tiup-history-"
historySize int64 = 1024 * 64 // history file default size is 64k
)

// commandRow type of command history row
type historyRow struct {
Date time.Time `json:"time"`
Command string `json:"command"`
Code int `json:"exit_code"`
}

// historyItem record history row file item
type historyItem struct {
path string
info fs.FileInfo
index int
}

// HistoryRecord record tiup exec cmd
func HistoryRecord(env *Environment, command []string, date time.Time, code int) error {
if env == nil {
return nil
}

historyPath := env.LocalPath(HistoryDir)
if utils.IsNotExist(historyPath) {
err := os.MkdirAll(historyPath, 0755)
if err != nil {
return err
}
}

h := &historyRow{
Command: strings.Join(command, " "),
Date: date,
Code: code,
}

return h.save(historyPath)
}

// save save commandRow to file
func (r *historyRow) save(dir string) error {
rBytes, err := json.Marshal(r)
if err != nil {
return err
}

historyFile := getLatestHistoryFile(dir)

f, err := os.OpenFile(historyFile.path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(append(rBytes, []byte("\n")...))
return err
}

// GetHistory get tiup history
func (env *Environment) GetHistory(count int, all bool) ([]*historyRow, error) {
fList, err := getHistoryFileList(env.LocalPath(HistoryDir))
if err != nil {
return nil, err
}
rows := []*historyRow{}
for _, f := range fList {
rs, err := f.getHistory()
if err != nil {
return rows, err
}
if (len(rows)+len(rs)) > count && !all {
i := len(rows) + len(rs) - count
rows = append(rs[i:], rows...)
break
}

rows = append(rs, rows...)
}
return rows, nil
}

// DeleteHistory delete history file
func (env *Environment) DeleteHistory(retainDays int, skipConfirm bool) error {
if retainDays < 0 {
return errors.Errorf("retainDays cannot be less than 0")
}

// history file before `DelBeforeTime` will be deleted
oneDayDuration, _ := time.ParseDuration("-24h")
delBeforeTime := time.Now().Add(oneDayDuration * time.Duration(retainDays))

if !skipConfirm {
fmt.Printf("History logs before %s will be %s!\n",
color.HiYellowString(delBeforeTime.Format("2006-01-02T15:04:05")),
color.HiYellowString("deleted"),
)
if err := tui.PromptForConfirmOrAbortError("Do you want to continue? [y/N]:"); err != nil {
return err
}
}

fList, err := getHistoryFileList(env.LocalPath(HistoryDir))
if err != nil {
return err
}

if len(fList) == 0 {
return nil
}

for _, f := range fList {
if f.info.ModTime().Before(delBeforeTime) {
err := os.Remove(f.path)
if err != nil {
return err
}
continue
}
}
return nil
}

// getHistory get tiup history execution row
func (i *historyItem) getHistory() ([]*historyRow, error) {
rows := []*historyRow{}

fi, err := os.Open(i.path)
if err != nil {
return rows, err
}
defer fi.Close()

br := bufio.NewReader(fi)
for {
a, _, c := br.ReadLine()
if c == io.EOF {
break
}
r := &historyRow{}
// ignore
err := json.Unmarshal(a, r)
if err != nil {
continue
}
rows = append(rows, r)
}

return rows, nil
}

// getHistoryFileList get the history file list
func getHistoryFileList(dir string) ([]historyItem, error) {
fileInfos, err := os.ReadDir(dir)
if err != nil {
return nil, err
}

hfileList := []historyItem{}
for _, fi := range fileInfos {
if fi.IsDir() {
continue
}

// another suffix
// ex: tiup-history-0.bak
i, err := strconv.Atoi((strings.TrimPrefix(fi.Name(), historyPrefix)))
if err != nil {
continue
}

fInfo, _ := fi.Info()
hfileList = append(hfileList, historyItem{
path: filepath.Join(dir, fi.Name()),
index: i,
info: fInfo,
})
}

sort.Slice(hfileList, func(i, j int) bool {
return hfileList[i].index > hfileList[j].index
})

return hfileList, nil
}

// getLatestHistoryFile get the latest history file, use index 0 if it doesn't exist
func getLatestHistoryFile(dir string) (item historyItem) {
fileList, err := getHistoryFileList(dir)
// start from 0
if len(fileList) == 0 || err != nil {
item.index = 0
item.path = filepath.Join(dir, fmt.Sprintf("%s%s", historyPrefix, strconv.Itoa(item.index)))
return
}

latestItem := fileList[0]

if latestItem.info.Size() >= historySize {
item.index = latestItem.index + 1
item.path = filepath.Join(dir, fmt.Sprintf("%s%s", historyPrefix, strconv.Itoa(item.index)))
} else {
item = latestItem
}

return
}

0 comments on commit c0614bb

Please sign in to comment.