Skip to content

Commit f806c59

Browse files
authored
Add support for collecting CPU usage for SystemRule and polish related code (#57)
* Add support for collecting CPU usage for SystemRule Signed-off-by: Eric Zhao <[email protected]> * Improve system metric collector Signed-off-by: Eric Zhao <[email protected]> * Add stat.system.collectIntervalMs conf item and polish related code Signed-off-by: Eric Zhao <[email protected]>
1 parent 400e957 commit f806c59

File tree

5 files changed

+158
-59
lines changed

5 files changed

+158
-59
lines changed

api/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func initSentinel(configPath string, logDir string) (err error) {
5555
}
5656

5757
metric.InitTask()
58-
system.InitCollector()
58+
system.InitCollector(config.SystemStatCollectIntervalMs())
5959

6060
return err
6161
}

core/config/entity.go

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,25 @@ package config
22

33
import "github.com/pkg/errors"
44

5-
type Config struct {
6-
Version string
7-
Sentinel struct {
8-
App struct {
9-
// Name represents the name of current running service.
10-
Name string
11-
// Type indicates the classification of the service (e.g. web service, API gateway).
12-
Type int32
13-
}
14-
Log struct {
15-
// Dir represents the log directory path.
16-
Dir string
17-
// UsePid indicates whether the filename ends with the process ID (PID).
18-
UsePid bool
19-
// Metric represents the configuration items of the metric log.
20-
Metric struct {
21-
SingleFileMaxSize uint64
22-
MaxFileCount uint32
23-
FlushIntervalSec uint32
24-
}
25-
}
5+
type Entity struct {
6+
// Version represents the format version of the entity.
7+
Version string
8+
9+
Sentinel SentinelConfig
10+
}
11+
12+
// SentinelConfig represent the general configuration of Sentinel.
13+
type SentinelConfig struct {
14+
App struct {
15+
// Name represents the name of current running service.
16+
Name string
17+
// Type indicates the classification of the service (e.g. web service, API gateway).
18+
Type int32
2619
}
20+
// Log represents configuration items related to logging.
21+
Log LogConfig
22+
// Stat represents configuration items related to statistics.
23+
Stat StatConfig
2724
}
2825

2926
// LogConfig represent the configuration of logging in Sentinel.
@@ -43,23 +40,15 @@ type MetricLogConfig struct {
4340
FlushIntervalSec uint32 `yaml:"flushIntervalSec"`
4441
}
4542

46-
// SentinelConfig represent the general configuration of Sentinel.
47-
type SentinelConfig struct {
48-
App struct {
49-
// Name represents the name of current running service.
50-
Name string
51-
// Type indicates the classification of the service (e.g. web service, API gateway).
52-
Type int32
53-
}
54-
// Log represents configuration items related to logging.
55-
Log LogConfig
43+
// StatConfig represents the configuration items of statistics.
44+
type StatConfig struct {
45+
System SystemStatConfig `yaml:"system"`
5646
}
5747

58-
type Entity struct {
59-
// Version represents the format version of the entity.
60-
Version string
61-
62-
Sentinel SentinelConfig
48+
// SystemStatConfig represents the configuration items of system statistics.
49+
type SystemStatConfig struct {
50+
// CollectIntervalMs represents the collecting interval of the system metrics collector.
51+
CollectIntervalMs uint32 `yaml:"collectIntervalMs"`
6352
}
6453

6554
func NewDefaultConfig() *Entity {
@@ -73,7 +62,11 @@ func NewDefaultConfig() *Entity {
7362
Name: UnknownProjectName,
7463
Type: DefaultAppType,
7564
},
76-
Log: LogConfig{Metric: MetricLogConfig{SingleFileMaxSize: DefaultMetricLogSingleFileMaxSize, MaxFileCount: DefaultMetricLogMaxFileAmount, FlushIntervalSec: DefaultMetricLogFlushIntervalSec}},
65+
Log: LogConfig{Metric: MetricLogConfig{SingleFileMaxSize: DefaultMetricLogSingleFileMaxSize,
66+
MaxFileCount: DefaultMetricLogMaxFileAmount, FlushIntervalSec: DefaultMetricLogFlushIntervalSec}},
67+
Stat: StatConfig{
68+
System: SystemStatConfig{CollectIntervalMs: DefaultSystemStatCollectIntervalMs},
69+
},
7770
},
7871
}
7972
}
@@ -92,5 +85,8 @@ func checkValid(conf *SentinelConfig) error {
9285
if mc.SingleFileMaxSize <= 0 {
9386
return errors.New("Bad metric log config: singleFileMaxSize <= 0")
9487
}
88+
if conf.Stat.System.CollectIntervalMs == 0 {
89+
return errors.New("Bad system stat config: collectIntervalMs = 0")
90+
}
9591
return nil
9692
}

core/config/local_storage.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ const (
1717
AppNameEnvKey = "SENTINEL_APP_NAME"
1818
AppTypeEnvKey = "SENTINEL_APP_TYPE"
1919

20-
DefaultConfigFilename = "sentinel.yml"
21-
DefaultAppType int32 = 0
22-
DefaultMetricLogFlushIntervalSec uint32 = 1
23-
DefaultMetricLogSingleFileMaxSize uint64 = 1024 * 1024 * 50
24-
DefaultMetricLogMaxFileAmount uint32 = 8
20+
DefaultConfigFilename = "sentinel.yml"
21+
DefaultAppType int32 = 0
22+
DefaultMetricLogFlushIntervalSec uint32 = 1
23+
DefaultMetricLogSingleFileMaxSize uint64 = 1024 * 1024 * 50
24+
DefaultMetricLogMaxFileAmount uint32 = 8
25+
DefaultSystemStatCollectIntervalMs uint32 = 1000
2526
)
2627

2728
var localConf = NewDefaultConfig()
@@ -44,6 +45,10 @@ func InitConfigFromFile(filePath string) error {
4445
}
4546
loadFromSystemEnv()
4647

48+
if err = checkValid(&localConf.Sentinel); err != nil {
49+
return err
50+
}
51+
4752
logger := logging.GetDefaultLogger()
4853
logger.Infof("App name resolved: %s", AppName())
4954

@@ -74,7 +79,7 @@ func loadFromSystemEnv() {
7479
appTypeStr := os.Getenv(AppTypeEnvKey)
7580
appType, err := strconv.ParseInt(appTypeStr, 10, 32)
7681
if err != nil {
77-
82+
logging.GetDefaultLogger().Warnf("Ignoring bad appType from system env: %s", appTypeStr)
7883
} else {
7984
localConf.Sentinel.App.Type = int32(appType)
8085
}
@@ -99,3 +104,7 @@ func MetricLogSingleFileMaxSize() uint64 {
99104
func MetricLogMaxFileAmount() uint32 {
100105
return localConf.Sentinel.Log.Metric.MaxFileCount
101106
}
107+
108+
func SystemStatCollectIntervalMs() uint32 {
109+
return localConf.Sentinel.Stat.System.CollectIntervalMs
110+
}

core/system/sys_stat.go

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package system
22

33
import (
44
"github.com/alibaba/sentinel-golang/util"
5+
"math"
6+
"sync"
57
"sync/atomic"
68
"time"
79

@@ -17,25 +19,38 @@ var (
1719
currentLoad atomic.Value
1820
currentCpuUsage atomic.Value
1921

22+
prevCpuStat *cpu.TimesStat
23+
initOnce sync.Once
24+
2025
ssStopChan = make(chan struct{})
2126
)
2227

23-
func InitCollector() {
28+
func init() {
2429
currentLoad.Store(notRetrievedValue)
2530
currentCpuUsage.Store(notRetrievedValue)
31+
}
32+
33+
func InitCollector(intervalMs uint32) {
34+
if intervalMs == 0 {
35+
return
36+
}
37+
initOnce.Do(func() {
38+
// Initial retrieval.
39+
retrieveAndUpdateSystemStat()
2640

27-
ticker := time.NewTicker(1 * time.Second)
28-
go util.RunWithRecover(func() {
29-
for {
30-
select {
31-
case <-ticker.C:
32-
retrieveAndUpdateSystemStat()
33-
case <-ssStopChan:
34-
ticker.Stop()
35-
return
41+
ticker := time.NewTicker(time.Duration(intervalMs) * time.Millisecond)
42+
go util.RunWithRecover(func() {
43+
for {
44+
select {
45+
case <-ticker.C:
46+
retrieveAndUpdateSystemStat()
47+
case <-ssStopChan:
48+
ticker.Stop()
49+
return
50+
}
3651
}
37-
}
38-
}, logger)
52+
}, logger)
53+
})
3954
}
4055

4156
func retrieveAndUpdateSystemStat() {
@@ -48,15 +63,49 @@ func retrieveAndUpdateSystemStat() {
4863
logger.Warnf("Failed to retrieve current system load: %+v", err)
4964
}
5065
if len(cpuStats) > 0 {
51-
// TODO: calculate the real CPU usage
52-
// cpuStat := cpuStats[0]
53-
// currentCpuUsage.Store(cpuStat.User)
66+
curCpuStat := &cpuStats[0]
67+
recordCpuUsage(prevCpuStat, curCpuStat)
68+
// Cache the latest CPU stat info.
69+
prevCpuStat = curCpuStat
5470
}
5571
if loadStat != nil {
5672
currentLoad.Store(loadStat.Load1)
5773
}
5874
}
5975

76+
func recordCpuUsage(prev, curCpuStat *cpu.TimesStat) {
77+
if prev != nil && curCpuStat != nil {
78+
prevTotal := calculateTotalCpuTick(prev)
79+
curTotal := calculateTotalCpuTick(curCpuStat)
80+
81+
tDiff := curTotal - prevTotal
82+
var cpuUsage float64
83+
if tDiff == 0 {
84+
cpuUsage = 0
85+
} else {
86+
prevUsed := calculateUserCpuTick(prev) + calculateKernelCpuTick(prev)
87+
curUsed := calculateUserCpuTick(curCpuStat) + calculateKernelCpuTick(curCpuStat)
88+
cpuUsage = (curUsed - prevUsed) / tDiff
89+
cpuUsage = math.Max(0.0, cpuUsage)
90+
cpuUsage = math.Min(1.0, cpuUsage)
91+
}
92+
currentCpuUsage.Store(cpuUsage)
93+
}
94+
}
95+
96+
func calculateTotalCpuTick(stat *cpu.TimesStat) float64 {
97+
return stat.User + stat.Nice + stat.System + stat.Idle +
98+
stat.Iowait + stat.Irq + stat.Softirq + stat.Steal
99+
}
100+
101+
func calculateUserCpuTick(stat *cpu.TimesStat) float64 {
102+
return stat.User + stat.Nice
103+
}
104+
105+
func calculateKernelCpuTick(stat *cpu.TimesStat) float64 {
106+
return stat.System + stat.Irq + stat.Softirq
107+
}
108+
60109
func CurrentLoad() float64 {
61110
r, ok := currentLoad.Load().(float64)
62111
if !ok {

core/system/sys_stat_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package system
2+
3+
import (
4+
"github.com/shirou/gopsutil/cpu"
5+
"github.com/stretchr/testify/assert"
6+
"testing"
7+
)
8+
9+
func Test_recordCpuUsage(t *testing.T) {
10+
var emptyStat *cpu.TimesStat = nil
11+
// total: 2260, user+nice: 950, system+irqs=210
12+
prev := &cpu.TimesStat{
13+
CPU: "all",
14+
User: 900,
15+
System: 200,
16+
Idle: 300,
17+
Nice: 50,
18+
Iowait: 100,
19+
Irq: 5,
20+
Softirq: 5,
21+
Steal: 700,
22+
}
23+
// total: 4180, user+nice: 1600, system+irqs=430
24+
cur := &cpu.TimesStat{
25+
CPU: "all",
26+
User: 1500,
27+
System: 400,
28+
Idle: 400,
29+
Nice: 100,
30+
Iowait: 150,
31+
Irq: 15,
32+
Softirq: 15,
33+
Steal: 1600,
34+
}
35+
expected := float64(1600+430-950-210) / (4180 - 2260)
36+
37+
recordCpuUsage(emptyStat, cur)
38+
assert.Equal(t, notRetrievedValue, CurrentCpuUsage())
39+
40+
recordCpuUsage(prev, prev)
41+
assert.Equal(t, 0.0, CurrentCpuUsage())
42+
43+
recordCpuUsage(prev, cur)
44+
assert.InEpsilon(t, expected, CurrentCpuUsage(), 0.001)
45+
}

0 commit comments

Comments
 (0)