【问题标题】:How to measure function run times如何测量函数运行时间
【发布时间】:2021-12-28 15:08:01
【问题描述】:

在 golang 网络服务器中,我想测量一些 http 控制器所花费的时间。我在调用控制器函数之前调用time.Now(),在控制器函数返回之后调用time.Since()。但是如果它有需要 1 秒的长远程 io 请求,或者进程被限制,或者控制器与 goroutine 并行 - 那么那个时间将不是我想要的。

如果我们假设类比为 bash time 命令 - 那么我将获得 real 使用此技术的时间:

time go build

real    0m5,204s
user    0m12,012s
sys 0m2,043s

如何在 golang 程序(最好使用标准包)中测量函数运行(最好是 goroutine 及其分叉子代)的 usersys 时间?

这是我的分析器实现。如何使用每个 goroutine 的 sys 和用户时间来扩展它?

const HeaderCost = "Cost"

// Timed middleware will set Cost header in http response
func Timed(h http.Handler) http.HandlerFunc {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.ServeHTTP(&responseWriterWithTimer{
            ResponseWriter: w,
            headerWritten:  false,
            startedAt:      time.Now(),
        }, r)
    })
}

type responseWriterWithTimer struct {
    http.ResponseWriter
    headerWritten bool
    startedAt     time.Time
}

func (w *responseWriterWithTimer) WriteHeader(statusCode int) {
    w.Header().Set(
        HeaderCost,
        strconv.FormatFloat(
            time.Since(w.startedAt).Seconds(),
            'g',
            64,
            64,
        ),
    )
    w.ResponseWriter.WriteHeader(statusCode)
    w.headerWritten = true
}

func (w *responseWriterWithTimer) Write(b []byte) (int, error) {
    if !w.headerWritten {
        w.WriteHeader(http.StatusOK)
    }
    return w.ResponseWriter.Write(b)
}

【问题讨论】:

  • 您可以使用github.com/shirou/gopsutil 之类的方法在进程级别测量 CPU 时间。在我看来,目前在 Go 例程级别执行此操作是不可能的。如果您精确测量您的处理程序而不是其他任何东西,那么测量实时应该没问题。
  • @Zyl 进程级别包括所有线程,其中每个线程运行未知数量的 goroutine。所以这不是一个正确的衡量标准。

标签: go profiling


【解决方案1】:

如何测量函数运行的用户和系统时间

你不能。这种区别对于 Go 函数是不可观察的。

(但老实说:测量它们没有实际用处,也没有多大意义。这听起来像是一个 XY 问题。)

【讨论】:

  • 我需要类似 stackoverflow.com/a/65150085/1976993 的东西,但即使使用 RUSAGE_THREAD,也不需要使用 RUSAGE_GOROUTINE
  • go 调度器可能会在将 goroutine 置于睡眠状态时测量周期。每个控制器函数运行都是一个 goroutine。所以它是可实施的。为什么没有意义?..不明白你
  • @xakepp35 "go scheduler may measure cycles" 它可能,但实际上它没有。我仍然不明白你想用那个指标做什么。您将从那些无法从“正常”分析(配置文件、块、互斥体、跟踪等)中获得的数据中派生出哪些操作。顺便说一句:你的“我想要这个,因为我说过我想要它”的态度对其他人来说可能有点粗鲁。
  • @xakepp35 感谢您的澄清。答案是“不,你不能”。说得够多了。
【解决方案2】:

如果你想在运行时进行基本的检测,你可以包装你的处理程序来测量它们的执行时间:

func perfMiddleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        t := time.Now()
        h.ServeHTTP(w, r)
        log.Printf("handler took %s", time.Since(t))
    })
}

您可以使用expvar 更轻松地公开这一点。除此之外,如果您寻找它们,还有许多可用于 Go 的仪器/遥测/APM 库,以及 TICK 堆栈、Datadog 等指标管理解决方案。

至于time 输出的realusersys 数据,这些是 posix 措施,并不完全适用于检测 Go HTTP 处理程序(或运行时的任何其他代码单元) ),原因有很多:

  • goroutines 没有父/子关系;所有人都是平等的同行,因此没有衡量您的处理程序的“孩子”所花费的时间。
  • 大部分 I/O 在 stdlib 中处理,它没有被检测到这个级别(并且这个级别的检测本身会产生不可忽略的性能影响)

当然,您可以单独为每件乐器演奏,这通常更有用;例如,检测您的 HTTP 处理程序以及发出自己的外部请求的任何代码,以测量每个组件的性能。从中您可以分析数据并更清楚地了解所花费的时间,以解决您发现的任何性能问题。

【讨论】:

  • 这将是一个单独的问题,带有不同的标签,以吸引 C 开发人员而不是 Go 开发人员的注意力。与 C 相比,Go 中的线程/并发工作方式非常不同。我不确定您所说的“golang 程序和函数没有 cpu 时间”是什么意思。
  • 有一个功能。功能最终在机器代码中。我想测量多少核心*秒(如工时)cpu 在用户模式下主动执行该功能程序集。如果您将程序视为一个函数 - 您可以通过在 bash 中的命令前添加 time 来测量其执行时间。我希望该仪器更细化 - 用于 golang 控制器功能。 time.Since 不起作用 - 它还计算 CPU 空闲时间 - 这不是解决方案。
  • time 不是这样做的。 timeprocess 级别产生有关 CPU 使用的现有内核指标。在线程级别不存在这样的指标,当然在 goroutine 级别也不存在(这甚至不是内核中存在的概念)。如果您将程序视为一个函数,然后尝试颠倒逻辑以根据程序的真实性对函数做出假设,那么您将最终得到一些关于这些事情如何工作的毫无根据和不准确的假设。
【解决方案3】:

如果您想单独衡量某事,benchmarks 可能正是您所追求的。

如果您尝试测量http.Handler,您可以使用httptest.NewRecorderhttptest.NewRequest 创建新的响应编写器和请求对象,然后直接在您的基准测试中调用处理程序。

func BenchmnarkHttpHandler(b*testing.B) {

  req := httptest.NewRequest("GET", "/foo", nil)

  myHandler := thingtotest.Handler{}

  for n := 0; n < b.N; n++ {
     myHandler.ServeHTTP(httptest.NewRecorder(), req);
  }
}

【讨论】:

  • 不,我想在生产系统中而不是孤立地衡量请求。主要的是我不想测量实时,而是服务 goroutine 及其分叉子代的用户/系统时间。有办法吗?
猜你喜欢
  • 2012-04-23
  • 1970-01-01
  • 1970-01-01
  • 2012-08-15
  • 1970-01-01
  • 2014-10-21
  • 1970-01-01
  • 2014-10-04
  • 2023-03-25
相关资源
最近更新 更多