github.com/lingyao2333/mo-zero@v1.4.1/core/stat/metrics.go (about)

     1  package stat
     2  
     3  import (
     4  	"os"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/lingyao2333/mo-zero/core/executors"
     9  	"github.com/lingyao2333/mo-zero/core/logx"
    10  	"github.com/lingyao2333/mo-zero/core/syncx"
    11  )
    12  
    13  var (
    14  	logInterval  = time.Minute
    15  	writerLock   sync.Mutex
    16  	reportWriter Writer = nil
    17  	logEnabled          = syncx.ForAtomicBool(true)
    18  )
    19  
    20  type (
    21  	// Writer interface wraps the Write method.
    22  	Writer interface {
    23  		Write(report *StatReport) error
    24  	}
    25  
    26  	// A StatReport is a stat report entry.
    27  	StatReport struct {
    28  		Name          string  `json:"name"`
    29  		Timestamp     int64   `json:"tm"`
    30  		Pid           int     `json:"pid"`
    31  		ReqsPerSecond float32 `json:"qps"`
    32  		Drops         int     `json:"drops"`
    33  		Average       float32 `json:"avg"`
    34  		Median        float32 `json:"med"`
    35  		Top90th       float32 `json:"t90"`
    36  		Top99th       float32 `json:"t99"`
    37  		Top99p9th     float32 `json:"t99p9"`
    38  	}
    39  
    40  	// A Metrics is used to log and report stat reports.
    41  	Metrics struct {
    42  		executor  *executors.PeriodicalExecutor
    43  		container *metricsContainer
    44  	}
    45  )
    46  
    47  // DisableLog disables logs of stats.
    48  func DisableLog() {
    49  	logEnabled.Set(false)
    50  }
    51  
    52  // SetReportWriter sets the report writer.
    53  func SetReportWriter(writer Writer) {
    54  	writerLock.Lock()
    55  	reportWriter = writer
    56  	writerLock.Unlock()
    57  }
    58  
    59  // NewMetrics returns a Metrics.
    60  func NewMetrics(name string) *Metrics {
    61  	container := &metricsContainer{
    62  		name: name,
    63  		pid:  os.Getpid(),
    64  	}
    65  
    66  	return &Metrics{
    67  		executor:  executors.NewPeriodicalExecutor(logInterval, container),
    68  		container: container,
    69  	}
    70  }
    71  
    72  // Add adds task to m.
    73  func (m *Metrics) Add(task Task) {
    74  	m.executor.Add(task)
    75  }
    76  
    77  // AddDrop adds a drop to m.
    78  func (m *Metrics) AddDrop() {
    79  	m.executor.Add(Task{
    80  		Drop: true,
    81  	})
    82  }
    83  
    84  // SetName sets the name of m.
    85  func (m *Metrics) SetName(name string) {
    86  	m.executor.Sync(func() {
    87  		m.container.name = name
    88  	})
    89  }
    90  
    91  type (
    92  	tasksDurationPair struct {
    93  		tasks    []Task
    94  		duration time.Duration
    95  		drops    int
    96  	}
    97  
    98  	metricsContainer struct {
    99  		name     string
   100  		pid      int
   101  		tasks    []Task
   102  		duration time.Duration
   103  		drops    int
   104  	}
   105  )
   106  
   107  func (c *metricsContainer) AddTask(v interface{}) bool {
   108  	if task, ok := v.(Task); ok {
   109  		if task.Drop {
   110  			c.drops++
   111  		} else {
   112  			c.tasks = append(c.tasks, task)
   113  			c.duration += task.Duration
   114  		}
   115  	}
   116  
   117  	return false
   118  }
   119  
   120  func (c *metricsContainer) Execute(v interface{}) {
   121  	pair := v.(tasksDurationPair)
   122  	tasks := pair.tasks
   123  	duration := pair.duration
   124  	drops := pair.drops
   125  	size := len(tasks)
   126  	report := &StatReport{
   127  		Name:          c.name,
   128  		Timestamp:     time.Now().Unix(),
   129  		Pid:           c.pid,
   130  		ReqsPerSecond: float32(size) / float32(logInterval/time.Second),
   131  		Drops:         drops,
   132  	}
   133  
   134  	if size > 0 {
   135  		report.Average = float32(duration/time.Millisecond) / float32(size)
   136  
   137  		fiftyPercent := size >> 1
   138  		if fiftyPercent > 0 {
   139  			top50pTasks := topK(tasks, fiftyPercent)
   140  			medianTask := top50pTasks[0]
   141  			report.Median = float32(medianTask.Duration) / float32(time.Millisecond)
   142  			tenPercent := fiftyPercent / 5
   143  			if tenPercent > 0 {
   144  				top10pTasks := topK(tasks, tenPercent)
   145  				task90th := top10pTasks[0]
   146  				report.Top90th = float32(task90th.Duration) / float32(time.Millisecond)
   147  				onePercent := tenPercent / 10
   148  				if onePercent > 0 {
   149  					top1pTasks := topK(top10pTasks, onePercent)
   150  					task99th := top1pTasks[0]
   151  					report.Top99th = float32(task99th.Duration) / float32(time.Millisecond)
   152  					pointOnePercent := onePercent / 10
   153  					if pointOnePercent > 0 {
   154  						topPointOneTasks := topK(top1pTasks, pointOnePercent)
   155  						task99Point9th := topPointOneTasks[0]
   156  						report.Top99p9th = float32(task99Point9th.Duration) / float32(time.Millisecond)
   157  					} else {
   158  						report.Top99p9th = getTopDuration(top1pTasks)
   159  					}
   160  				} else {
   161  					mostDuration := getTopDuration(top10pTasks)
   162  					report.Top99th = mostDuration
   163  					report.Top99p9th = mostDuration
   164  				}
   165  			} else {
   166  				mostDuration := getTopDuration(tasks)
   167  				report.Top90th = mostDuration
   168  				report.Top99th = mostDuration
   169  				report.Top99p9th = mostDuration
   170  			}
   171  		} else {
   172  			mostDuration := getTopDuration(tasks)
   173  			report.Median = mostDuration
   174  			report.Top90th = mostDuration
   175  			report.Top99th = mostDuration
   176  			report.Top99p9th = mostDuration
   177  		}
   178  	}
   179  
   180  	log(report)
   181  }
   182  
   183  func (c *metricsContainer) RemoveAll() interface{} {
   184  	tasks := c.tasks
   185  	duration := c.duration
   186  	drops := c.drops
   187  	c.tasks = nil
   188  	c.duration = 0
   189  	c.drops = 0
   190  
   191  	return tasksDurationPair{
   192  		tasks:    tasks,
   193  		duration: duration,
   194  		drops:    drops,
   195  	}
   196  }
   197  
   198  func getTopDuration(tasks []Task) float32 {
   199  	top := topK(tasks, 1)
   200  	if len(top) < 1 {
   201  		return 0
   202  	}
   203  
   204  	return float32(top[0].Duration) / float32(time.Millisecond)
   205  }
   206  
   207  func log(report *StatReport) {
   208  	writeReport(report)
   209  	if logEnabled.True() {
   210  		logx.Statf("(%s) - qps: %.1f/s, drops: %d, avg time: %.1fms, med: %.1fms, "+
   211  			"90th: %.1fms, 99th: %.1fms, 99.9th: %.1fms",
   212  			report.Name, report.ReqsPerSecond, report.Drops, report.Average, report.Median,
   213  			report.Top90th, report.Top99th, report.Top99p9th)
   214  	}
   215  }
   216  
   217  func writeReport(report *StatReport) {
   218  	writerLock.Lock()
   219  	defer writerLock.Unlock()
   220  
   221  	if reportWriter != nil {
   222  		if err := reportWriter.Write(report); err != nil {
   223  			logx.Error(err)
   224  		}
   225  	}
   226  }