github.com/iotexproject/iotex-core@v1.14.1-rc1/pkg/recovery/recovery.go (about)

     1  // Copyright (c) 2022 IoTeX
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package recovery
     7  
     8  import (
     9  	"os"
    10  	"path/filepath"
    11  	"runtime/debug"
    12  	"runtime/pprof"
    13  	"time"
    14  
    15  	"github.com/shirou/gopsutil/v3/cpu"
    16  	"github.com/shirou/gopsutil/v3/disk"
    17  	"github.com/shirou/gopsutil/v3/load"
    18  	"github.com/shirou/gopsutil/v3/mem"
    19  
    20  	"github.com/iotexproject/iotex-core/pkg/log"
    21  	"github.com/iotexproject/iotex-core/pkg/util/fileutil"
    22  )
    23  
    24  type (
    25  	// CPUInfo stat cpu infos
    26  	CPUInfo struct {
    27  		PhysicalCount int             `json:"physical_count"`
    28  		LogicalCount  int             `json:"logical_count"`
    29  		TotalPercent  []float64       `json:"total_use_percent"`
    30  		PerPercent    []float64       `json:"per_use_percent"`
    31  		Loads         *load.AvgStat   `json:"average_loads"`
    32  		Times         []cpu.TimesStat `json:"running_times"`
    33  		Infos         []cpu.InfoStat  `json:"infos"`
    34  	}
    35  	// MemInfo stat memory infos
    36  	MemInfo struct {
    37  		Virtual *mem.VirtualMemoryStat `json:"virtaul"`
    38  		Swap    *mem.SwapMemoryStat    `json:"swap"`
    39  	}
    40  	// DiskInfo stat disk infos
    41  	DiskInfo struct {
    42  		IOCounters map[string]disk.IOCountersStat `json:"io_counters"`
    43  		Partitions []disk.PartitionStat           `json:"partitions"`
    44  	}
    45  )
    46  
    47  // _crashlogDir saves the directory of crashlog
    48  var _crashlogDir string = "/"
    49  
    50  // Recover catchs the crashing goroutine
    51  func Recover() {
    52  	if r := recover(); r != nil {
    53  		LogCrash(r)
    54  	}
    55  }
    56  
    57  // SetCrashlogDir set the directory of crashlog
    58  func SetCrashlogDir(dir string) error {
    59  	if !fileutil.FileExists(dir) {
    60  		if err := os.MkdirAll(dir, 0744); err != nil {
    61  			log.S().Error(err.Error())
    62  			return err
    63  		}
    64  	}
    65  	_crashlogDir = dir
    66  	return nil
    67  }
    68  
    69  // LogCrash write down the current memory and stack info and the cpu/mem/disk infos into log dir
    70  func LogCrash(r interface{}) {
    71  	log.S().Errorf("crashlog: %v", r)
    72  	writeHeapProfile(filepath.Join(_crashlogDir,
    73  		"heapdump_"+time.Now().String()+".out"))
    74  	log.S().Infow("crashlog", "stack", string(debug.Stack()))
    75  	printInfo("cpu", cpuInfo)
    76  	printInfo("memory", memInfo)
    77  	printInfo("disk", diskInfo)
    78  }
    79  
    80  func writeHeapProfile(path string) {
    81  	f, err := os.OpenFile(filepath.Clean(path), os.O_CREATE|os.O_RDWR, 0600)
    82  	if err != nil {
    83  		log.S().Errorf("crashlog: open heap profile error: %v", err)
    84  		return
    85  	}
    86  	defer func() {
    87  		if err = f.Close(); err != nil {
    88  			log.S().Errorf("crashlog: close heap profile error: %v", err)
    89  			return
    90  		}
    91  	}()
    92  	if err := pprof.WriteHeapProfile(f); err != nil {
    93  		log.S().Errorf("crashlog: write heap profile error: %v", err)
    94  		return
    95  	}
    96  }
    97  
    98  func printInfo(name string, info func() (interface{}, error)) {
    99  	v, err := info()
   100  	if err != nil {
   101  		log.S().Errorw("crashlog: get %s info occur error: %v", name, err)
   102  		return
   103  	}
   104  	log.S().Infow("crashlog", name, v)
   105  }
   106  
   107  func cpuInfo() (interface{}, error) {
   108  	// cpu cores
   109  	physicalCnt, err := cpu.Counts(false)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	logicalCnt, err := cpu.Counts(true)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// cpu use percent during past 3 seconds
   119  	totalPercent, err := cpu.Percent(100*time.Millisecond, false)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	perPercents, err := cpu.Percent(100*time.Millisecond, true)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	// cpu load stats
   129  	// load1 respect avrage loads of tasks during past 1 minute
   130  	// load5 respect avrage loads of tasks during past 5 minute
   131  	// load15 respect avrage loads of tasks during past 15 minute
   132  	loads, err := load.Avg()
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	// cpu run time from startup
   138  	times, err := cpu.Times(false)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	pertimes, err := cpu.Times(true)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	times = append(times, pertimes...)
   147  
   148  	// cpu info
   149  	infos, err := cpu.Info()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return &CPUInfo{
   155  		PhysicalCount: physicalCnt,
   156  		LogicalCount:  logicalCnt,
   157  		TotalPercent:  totalPercent,
   158  		PerPercent:    perPercents,
   159  		Loads:         loads,
   160  		Times:         times,
   161  		Infos:         infos,
   162  	}, nil
   163  }
   164  
   165  func memInfo() (interface{}, error) {
   166  	virtual, err := mem.VirtualMemory()
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	swap, err := mem.SwapMemory()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return &MemInfo{
   175  		Virtual: virtual,
   176  		Swap:    swap,
   177  	}, nil
   178  }
   179  
   180  func diskInfo() (interface{}, error) {
   181  	ioCounters, err := disk.IOCounters()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	partitions, err := disk.Partitions(false)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	return &DiskInfo{
   190  		IOCounters: ioCounters,
   191  		Partitions: partitions,
   192  	}, nil
   193  }