github.com/matrixorigin/matrixone@v1.2.0/pkg/common/system/system.go (about)

     1  // Copyright 2021 - 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 system
    16  
    17  import (
    18  	"context"
    19  	"math"
    20  	"os"
    21  	"runtime"
    22  	"sync/atomic"
    23  	"time"
    24  
    25  	"github.com/elastic/gosigar"
    26  	"github.com/elastic/gosigar/cgroup"
    27  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    28  	"github.com/matrixorigin/matrixone/pkg/logutil"
    29  )
    30  
    31  var (
    32  	// pid is the process ID.
    33  	pid int
    34  	// cpuNum is the total number of CPU of this node.
    35  	cpuNum int
    36  	// memoryTotal is the total memory of this node.
    37  	memoryTotal uint64
    38  	// cpuUsage is the CPU statistics updated every second.
    39  	cpuUsage atomic.Uint64
    40  )
    41  
    42  // InContainer returns if the process is running in a container.
    43  func InContainer() bool {
    44  	return pid == 1
    45  }
    46  
    47  // NumCPU return the total number of CPU of this node.
    48  func NumCPU() int {
    49  	return cpuNum
    50  }
    51  
    52  // CPUAvailable returns the available cpu of this node.
    53  func CPUAvailable() float64 {
    54  	usage := math.Float64frombits(cpuUsage.Load())
    55  	return math.Round((1 - usage) * float64(cpuNum))
    56  }
    57  
    58  // MemoryTotal returns the total size of memory of this node.
    59  func MemoryTotal() uint64 {
    60  	return memoryTotal
    61  }
    62  
    63  // MemoryAvailable returns the available size of memory of this node.
    64  func MemoryAvailable() uint64 {
    65  	if InContainer() {
    66  		used, err := cgroup.GetMemUsage(pid)
    67  		if err != nil {
    68  			logutil.Errorf("failed to get memory usage: %v", err)
    69  			return 0
    70  		}
    71  		return memoryTotal - uint64(used)
    72  	}
    73  	s := gosigar.ConcreteSigar{}
    74  	mem, err := s.GetMem()
    75  	if err != nil {
    76  		logutil.Errorf("failed to get memory stats: %v", err)
    77  	}
    78  	return mem.Free
    79  }
    80  
    81  func MemoryUsed() uint64 {
    82  	if InContainer() {
    83  		used, err := cgroup.GetMemUsage(pid)
    84  		if err != nil {
    85  			logutil.Errorf("failed to get memory usage: %v", err)
    86  			return 0
    87  		}
    88  		return uint64(used)
    89  	}
    90  	s := gosigar.ConcreteSigar{}
    91  	mem, err := s.GetMem()
    92  	if err != nil {
    93  		logutil.Errorf("failed to get memory stats: %v", err)
    94  	}
    95  	return mem.Used
    96  }
    97  
    98  // MemoryGolang returns the total size of golang's memory.
    99  func MemoryGolang() int {
   100  	var ms runtime.MemStats
   101  	runtime.ReadMemStats(&ms)
   102  	return int(ms.Alloc)
   103  }
   104  
   105  // GoRoutines returns the number of goroutine.
   106  func GoRoutines() int {
   107  	return runtime.NumGoroutine()
   108  }
   109  
   110  func runWithContainer(stopper *stopper.Stopper) {
   111  	if err := stopper.RunNamedTask("system runner", func(ctx context.Context) {
   112  		ticker := time.NewTicker(time.Second)
   113  		defer ticker.Stop()
   114  		var prevStats *cgroup.CPUAccountingSubsystem
   115  		for {
   116  			select {
   117  			case <-ticker.C:
   118  				stats, err := cgroup.GetCPUAcctStats(pid)
   119  				if err != nil {
   120  					logutil.Errorf("failed to get cpu acct cgroup stats: %v", err)
   121  					continue
   122  				}
   123  				if prevStats != nil {
   124  					work := stats.Stats.UserNanos + stats.Stats.SystemNanos -
   125  						(prevStats.Stats.UserNanos + prevStats.Stats.SystemNanos)
   126  					total := uint64(cpuNum) * uint64(time.Second)
   127  					if total != 0 {
   128  						usage := float64(work) / float64(total)
   129  						cpuUsage.Store(math.Float64bits(usage))
   130  					}
   131  				}
   132  				prevStats = &stats
   133  
   134  			case <-ctx.Done():
   135  				return
   136  			}
   137  		}
   138  	}); err != nil {
   139  		logutil.Errorf("failed to start system runner: %v", err)
   140  	}
   141  }
   142  
   143  func runWithoutContainer(stopper *stopper.Stopper) {
   144  	if err := stopper.RunNamedTask("system runner", func(ctx context.Context) {
   145  		s := gosigar.ConcreteSigar{}
   146  		cpuC, stopC := s.CollectCpuStats(time.Second)
   147  		for {
   148  			select {
   149  			case cpu := <-cpuC:
   150  				work := cpu.User + cpu.Nice + cpu.Sys
   151  				total := cpu.Total()
   152  				if total != 0 {
   153  					usage := float64(work) / float64(total)
   154  					cpuUsage.Store(math.Float64bits(usage))
   155  				}
   156  
   157  			case <-ctx.Done():
   158  				stopC <- struct{}{}
   159  				return
   160  			}
   161  		}
   162  	}); err != nil {
   163  		logutil.Errorf("failed to start system runner: %v", err)
   164  	}
   165  }
   166  
   167  // Run starts a new goroutine go calculate the CPU usage.
   168  func Run(stopper *stopper.Stopper) {
   169  	if InContainer() {
   170  		runWithContainer(stopper)
   171  	} else {
   172  		runWithoutContainer(stopper)
   173  	}
   174  }
   175  
   176  func init() {
   177  	pid = os.Getpid()
   178  	if InContainer() {
   179  		cpu, err := cgroup.GetCPUStats(pid)
   180  		if err != nil {
   181  			logutil.Errorf("failed to get cgroup cpu stats: %v", err)
   182  		} else {
   183  			if cpu.CFS.PeriodMicros != 0 && cpu.CFS.QuotaMicros != 0 {
   184  				cpuNum = int(cpu.CFS.QuotaMicros / cpu.CFS.PeriodMicros)
   185  			} else {
   186  				cpuNum = runtime.NumCPU()
   187  			}
   188  		}
   189  		limit, err := cgroup.GetMemLimit(pid)
   190  		if err != nil {
   191  			logutil.Errorf("failed to get cgroup mem limit: %v", err)
   192  		} else {
   193  			memoryTotal = uint64(limit)
   194  		}
   195  	} else {
   196  		cpuNum = runtime.NumCPU()
   197  		s := gosigar.ConcreteSigar{}
   198  		mem, err := s.GetMem()
   199  		if err != nil {
   200  			logutil.Errorf("failed to get memory stats: %v", err)
   201  		} else {
   202  			memoryTotal = mem.Total
   203  		}
   204  	}
   205  }