serie de monitoreo golang pprof (1) - principio y uso de las estadísticas go trace

Con respecto al uso de go tool trace, hay bastante información en Internet, pero tomando mi experiencia previa de aprendizaje de golang como ejemplo, mucha información no explica qué métodos se cuentan los indicadores relevantes en go tool trace, y qué intervalos se cuentan. Por lo tanto, este artículo no solo presentará el uso de go tool trace, sino que también analizará los principios de sus estadísticas.

golang versión go1.17.12

Permítanme hablar brevemente sobre los escenarios de uso del seguimiento de la herramienta Go. Al analizar los problemas de retraso, el seguimiento de la herramienta Go puede desempeñar un papel muy importante, ya que registrará varios eventos de retraso y analizará su duración, incluso las ubicaciones de los códigos clave pueden averiguarlo.

Con respecto al caso de combate real de trace, hubo un video ( un caso de análisis de retraso del sistema ) antes, bienvenido a navegar

A continuación, echemos un breve vistazo a cómo usar la función de rastreo en golang.

ir a rastrear usos

package main

import (
	_ "net/http/pprof"
	"os"
	"runtime/trace"
)

func main() {
	f, _ := os.Create("trace.out")
	defer f.Close()
	trace.Start(f)
	defer trace.Stop()
  ......
}

En realidad, es relativamente fácil usar la función de rastreo. Use trace.Start para habilitar la función de muestreo de eventos de trace y trace.Stop para dejar de usarla. Los datos muestreados se registrarán en el parámetro de flujo de salida enviado por trace.Start Aquí usaré un archivo A llamado trace.out que se pasa como el parámetro de flujo de salida.

Después del muestreo, puede usar el comando go tool trace trace.out para analizar el archivo muestreado.

El comando go tool trace utilizará un puerto aleatorio local para iniciar un servicio http de forma predeterminada, y la página se muestra de la siguiente manera:

Luego analizaré la información estadística correspondiente a cada enlace y los principios estadísticos detrás de ella Bien, a continuación, comienza el verdadero drama.

Introducción a los Principios Estadísticos

Por lo general, cuando usamos Prometheus para monitorear los servicios de la aplicación, usamos principalmente el método de enterrar puntos. De manera similar, go runtime también usa este método para enterrar varios eventos durante el proceso de ejecución del código y, finalmente, lee los puntos ocultos ordenados. Haga clic en los datos para formar el gráfico de seguimiento de trazas que vemos en la página web.

// src/runtime/trace.go:517
func traceEvent(ev byte, skip int, args ...uint64) {
	mp, pid, bufp := traceAcquireBuffer()
    .....
}

Cada vez que se registre el evento correspondiente, se llamará al método traceEvent, ev representa la enumeración del evento, skip es la cantidad de capas que deben saltarse para ver el marco de la pila y args se pasa cuando ciertos eventos necesitan pasar parámetros específicos.

Dentro de traceEvent, también se obtendrá la información del subproceso, la información de la rutina y la información de la cola de ejecución cuando ocurra el evento actual, y esta información se registrará junto con el evento en un búfer.

// src/runtime/trace/trace.go:120 
func Start(w io.Writer) error {
	tracing.Lock()
	defer tracing.Unlock()
	if err := runtime.StartTrace(); err != nil {
		return err
	}
	go func() {
		for {
			data := runtime.ReadTrace()
			if data == nil {
				break
			}
			w.Write(data)
		}
	}()
	atomic.StoreInt32(&tracing.enabled, 1)
	return nil
}

Cuando iniciamos el método trace.Start, se iniciará una rutina para leer continuamente el contenido del búfer en el parámetro de strace.Start al mismo tiempo.

En el código de muestra, el método trace.Start pasa el parámetro de flujo de salida denominado archivo trace.out, por lo que durante el proceso de muestreo, el tiempo de ejecución generará el flujo de bytes de eventos recopilados en el archivo trace.out y el archivo trace.out está en Cuando se lee, se usa una estructura llamada Evento para representar estos eventos de monitoreo.

// Event describes one event in the trace.
type Event struct {
	Off   int       // offset in input file (for debugging and error reporting)
	Type  byte      // one of Ev*
	seq   int64     // sequence number
	Ts    int64     // timestamp in nanoseconds
	P     int       // P on which the event happened (can be one of TimerP, NetpollP, SyscallP)
	G     uint64    // G on which the event happened
	StkID uint64    // unique stack ID
	Stk   []*Frame  // stack trace (can be empty)
	Args  [3]uint64 // event-type-specific arguments
	SArgs []string  // event-type-specific string args
	// linked event (can be nil), depends on event type:
	// for GCStart: the GCStop
	// for GCSTWStart: the GCSTWDone
	// for GCSweepStart: the GCSweepDone
	// for GoCreate: first GoStart of the created goroutine
	// for GoStart/GoStartLabel: the associated GoEnd, GoBlock or other blocking event
	// for GoSched/GoPreempt: the next GoStart
	// for GoBlock and other blocking events: the unblock event
	// for GoUnblock: the associated GoStart
	// for blocking GoSysCall: the associated GoSysExit
	// for GoSysExit: the next GoStart
	// for GCMarkAssistStart: the associated GCMarkAssistDone
	// for UserTaskCreate: the UserTaskEnd
	// for UserRegion: if the start region, the corresponding UserRegion end event
	Link *Event
}

Echemos un vistazo a la información contenida en el evento Evento:
P es la cola en ejecución, cuando go ejecuta la corrutina, programa la corrutina para que se ejecute en P, G es el id de la corrutina y el id de la pila StkID, el marco de la pila Stk, y Algunos parámetros Args y SArgs que se pueden transportar cuando ocurre el evento.
Tipo es el campo de enumeración del evento, Ts es la información de marca de tiempo de la ocurrencia del evento, Enlace son otros eventos asociados con el evento y se utiliza para calcular el tiempo que consume el evento asociado.

Tomando como ejemplo el cálculo del consumo de tiempo de llamada del sistema, los eventos relacionados con la llamada del sistema incluyen GoSysExit y GoSysCall, que son el evento de salida de la llamada del sistema y el evento de inicio de la llamada del sistema, respectivamente, por lo que GoSysExit.Ts - GoSysCall.Ts es el consumo de tiempo de la llamada del sistema.

Nota especial: la enumeración de los eventos de monitoreo utilizados dentro del tiempo de ejecución se encuentra en src/runtime/trace.go:39, mientras que la enumeración utilizada para los eventos de monitoreo en el archivo de lectura se encuentra en src/internal/trace/parser.go: 1028, aunque hay dos conjuntos, pero el valor es el mismo.

Obviamente, el campo Enlace no se configura cuando el tiempo de ejecución registra el evento de monitoreo, sino que se configura después de que la información del evento se agrupe de acuerdo con la identificación de rutina cuando se lee el evento de monitoreo en trace.out. GoSysExit.Ts: GoSysCall.Ts de la misma corrutina es el tiempo de llamada al sistema de la corrutina.

A continuación, analicemos la información estadística de la página de rastreo y los principios estadísticos detrás de ella uno por uno.

View trace es un gráfico de seguimiento compuesto por la línea de tiempo de cada evento . En el entorno de producción, se generará una gran cantidad de eventos en 1 segundo. Creo que seguirá deslumbrando a la gente al mirar este gráfico directamente. Así que saltémoslo y comencemos a analizar desde el análisis de Goroutine.

Análisis de rutina

La ubicación del código a la que finalmente hace referencia go tool trace se encuentra en el paquete go/src/cmd/trace. La función principal iniciará un servicio http y registrará algunas funciones de procesamiento. Cuando hacemos clic en el análisis de Goroutine, en realidad solicitamos una de las funciones de procesamiento.
El siguiente fragmento de código es la función de procesamiento de la goroutine registrada. Haga clic en Goroutine analysis y se asignará a la ruta /goroutines.

// src/cmd/trace/goroutines.go:22
func init() {
	http.HandleFunc("/goroutines", httpGoroutines)
	http.HandleFunc("/goroutine", httpGoroutine)
}

Hagamos clic en el análisis Goroutine


Ingresa a una lista que muestra la posición de llamada del código.Cada posición de código en la lista es la posición cuando comienza a ejecutarse la corrutina del evento EvGoStart, donde N significa que hay N corrutinas ejecutándose en esta posición durante el período de adopción.

Quizás se esté preguntando, ¿cómo determina que estas 10 corrutinas se ejecutan con la misma pieza de código? Cuando el tiempo de ejecución registra el evento EvGoStart que la corrutina comienza a ejecutar, también registrará el marco de la pila, y el marco de la pila contiene el nombre de la función y el valor del contador del programa (PC). el valor de la PC agrupada. A continuación se muestra el fragmento de código para la asignación de PC.

// src/internal/trace/goroutines.go:176
case EvGoStart, EvGoStartLabel:
			g := gs[ev.G]
			if g.PC == 0 {
				g.PC = ev.Stk[0].PC
				g.Name = ev.Stk[0].Fn
			}

Hagamos clic en la primera línea del enlace nfw/nfw_base/fw/routine.(*Worker).TryRun.func1 N=10, haga clic en el enlace de la primera línea aquí se asignará a la ruta de /goroutine (tenga en cuenta que el route no termina con s ), procesado por su función de controlador. Haga clic como se muestra en la figura:

Lo que ve ahora son las estadísticas de llamadas al sistema, bloqueo, retraso de programación y gc para estas 10 rutinas.

Luego analizamos uno por uno de arriba a abajo:
El tiempo de ejecución se refiere a la relación entre el tiempo de ejecución de 10 corrutinas y la ejecución de todas las corrutinas .
Luego están las imágenes para analizar el tiempo de espera de la red, el tiempo de bloqueo de bloqueo, el tiempo de bloqueo de llamadas del sistema y el tiempo de espera de programación.Estas son herramientas precisas para analizar el retraso del sistema y los problemas de bloqueo. No analizaré más el diagrama aquí, creo que habrá mucha información de este tipo en Internet.

Luego observe los indicadores en la siguiente tabla:

Ejecución

es el tiempo de ejecución de la rutina durante el período de muestreo.

El método de grabación también es muy simple. Al leer el evento, se lee de adelante hacia atrás de acuerdo con la línea de tiempo. Cada vez que se lee la hora en que la rutina comienza a ejecutarse, se registrará la marca de tiempo del inicio de la ejecución de la rutina ( la marca de tiempo se incluye en la estructura del evento), cuando se lee el evento de pausa de la corrutina, restará la marca de tiempo del inicio de la ejecución de la marca de tiempo del tiempo de pausa para obtener un período corto de tiempo de ejecución, y agregará este corto período de tiempo a la corrutina el tiempo total de ejecución del programa.

La pausa es causada por bloqueos de bloqueo, bloqueo de llamadas del sistema, espera de la red y programación preventiva.

Espera de red

Como su nombre lo indica, el tiempo de espera de la red es en realidad un método de grabación similar a la Ejecución. Primero, registre la marca de tiempo de la corrutina que espera en la red. Dado que el evento se lee de acuerdo con la línea de tiempo, cuando se lea el evento de desbloqueo, vaya Si la última vez que el proceso fue un evento de espera de la red, de ser así, reste la marca de tiempo del tiempo de espera de la red de la marca de tiempo actual y sume este breve período de tiempo al tiempo total de espera de la red de la rutina. .

Bloque de sincronización, bloqueo de llamadas al sistema, espera del programador

Los métodos de cálculo de estas tres duraciones son similares a los dos anteriores, pero tenga en cuenta que las condiciones de activación de los eventos asociados a ellas son diferentes.

La duración del bloque de sincronización se debe a la sincronización de bloqueo. Mutex, el canal del canal, el grupo de espera y el bloqueo causado por la declaración de caso seleccionado se registrarán aquí. A continuación se muestra el fragmento de código correspondiente.

// src/internal/trace/goroutines.go:192
case EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
			EvGoBlockSync, EvGoBlockCond:
			g := gs[ev.G]
			g.ExecTime += ev.Ts - g.lastStartTime
			g.lastStartTime = 0
			g.blockSyncTime = ev.Ts

El bloqueo de syscall es el bloqueo causado por la llamada al sistema.

La espera del programador es el período de tiempo desde el estado ejecutable hasta el estado de ejecución de la rutina. Tenga en cuenta que cuando la rutina está en el estado ejecutable, se colocará en la cola de ejecución p para esperar a ser programada. Solo después de programarse, el el código realmente comenzará a ejecutarse. Esto implica la comprensión del modelo golang gpm, por lo que no se ampliará aquí.

Las dos últimas columnas son un porcentaje del tiempo total ocupado por GC, y el conocimiento relacionado con gc en golang no seguirá ampliándose.

Varios diagramas de perfil

¿Recuerda cuando analizó por primera vez las páginas web generadas por trace.out, qué estaba bajo el análisis de Goroutine? Es un diagrama de perfil relacionado con varios retrasos de análisis.La fuente de los datos es la misma que el indicador para analizar el tiempo de espera de un solo Goroutine cuando hablamos de análisis de Goroutine, pero esto es para todos los goroutines.

Network blocking profile (⬇)
Synchronization blocking profile (⬇)
Syscall blocking profile (⬇)
Scheduler latency profile (⬇)

La herramienta de rastreo de golang también permite a los usuarios personalizar los eventos de monitoreo. En la página web de rastreo generada, las tareas definidas por el usuario y las regiones definidas por el usuario son para registrar algunos eventos de monitoreo definidos por el usuario. Esta parte de la aplicación se discutirá más adelante.

La utilización mínima del mutador es un gráfico que muestra el impacto de gc en el programa Hablaré de esto en detalle cuando tenga la oportunidad de hablar sobre gc en el futuro.

Con respecto al caso de combate real de trace, hubo un video ( un caso de análisis de retraso del sistema ) antes, bienvenido a navegar.

Supongo que te gusta

Origin blog.csdn.net/sinat_40572875/article/details/129758256
Recomendado
Clasificación