Analiza el rendimiento de producción con Cloud Profiler

1. Descripción general

Si bien los desarrolladores web de apps cliente y frontend suelen usar herramientas como el Generador de perfiles de CPU de Android Studio o las herramientas de generación de perfiles incluidas en Chrome para mejorar el rendimiento de su código, quienes trabajan en servicios de backend no han usado ni tan bien las técnicas equivalentes. Cloud Profiler ofrece las mismas capacidades a los desarrolladores de servicios, sin importar si su código se ejecuta en Google Cloud Platform o en otro lugar.

95c034c70c9cac22.png

La herramienta recopila información sobre la asignación de memoria y el uso de la CPU desde tus aplicaciones en producción. Este le asigna esa información al código fuente de la aplicación, lo que ayuda a identificar las partes de la aplicación que consumen más recursos y a entender mejor las características de rendimiento del código. La baja sobrecarga de las técnicas de recolección empleadas por la herramienta hace que sea adecuada para el uso continuo en entornos de producción.

En este codelab, aprenderás a configurar Cloud Profiler para un programa de Go y te familiarizarás con el tipo de estadísticas sobre el rendimiento de la aplicación que puede presentar la herramienta.

Qué aprenderás

  • Cómo configurar un programa de Go para generar perfiles con Cloud Profiler
  • Cómo recopilar, ver y analizar los datos de rendimiento con Cloud Profiler

Requisitos

  • Un proyecto de Google Cloud
  • Un navegador, como Chrome o Firefox
  • Se recomienda estar familiarizado con editores de texto estándares de Linux, como Vim, Emacs o Nano.

¿Cómo usarás este instructivo?

Ler Leer y completar los ejercicios

¿Cómo calificarías tu experiencia en Google Cloud Platform?

Principiante Intermedio Avanzado .
.

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

  1. Accede a la consola de Cloud y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

96a9c957bc475304.png

b9a10ebdf5b5a448.png

a1e3c01a38fa61c2.png

Recuerde el ID de proyecto, un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar los recursos de Google Cloud recursos.

Ejecutar este codelab no debería costar mucho, tal vez nada. Asegúrate de seguir las instrucciones de la sección “Realiza una limpieza” en la que se aconseja cómo cerrar recursos para no incurrir en facturación más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de$300.

Google Cloud Shell

Si bien Google Cloud se puede operar de manera remota desde tu laptop, para simplificar la configuración en este codelab usaremos Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

Activar Cloud Shell

  1. En la consola de Cloud, haz clic en Activar Cloud Shell4292cbf4971c9786.png.

bce75f34b2c53987.png

Si nunca iniciaste Cloud Shell, aparecerá una pantalla intermedia (mitad inferior de la página) que describe en qué consiste. Si ese es el caso, haz clic en Continuar (y no volverás a verlo). Así es como se ve la pantalla única:

70f315d7b402b476.png

El aprovisionamiento y la conexión a Cloud Shell solo tomará unos minutos.

fbe3a0674c982259.png

Esta máquina virtual está cargada con todas las herramientas de desarrollo que necesitas. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Gran parte de tu trabajo en este codelab, si no todo, se puede hacer simplemente con un navegador o tu Chromebook.

Una vez conectado a Cloud Shell, debería ver que ya se autenticó y que el proyecto ya se configuró con tu ID del proyecto.

  1. En Cloud Shell, ejecuta el siguiente comando para confirmar que está autenticado:
gcloud auth list

Resultado del comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Ejecuta el siguiente comando en Cloud Shell para confirmar que el comando de gcloud conoce tu proyecto:
gcloud config list project

Resultado del comando

[core]
project = <PROJECT_ID>

De lo contrario, puedes configurarlo con el siguiente comando:

gcloud config set project <PROJECT_ID>

Resultado del comando

Updated property [core/project].

3. Navega a Cloud Profiler

En la consola de Cloud, haz clic en “Profiler” para navegar a la IU de Profiler. en la barra de navegación izquierda.

37ad0df7ddb2ad17.png

También puedes usar la barra de búsqueda de la consola de Cloud para navegar a la IU de Profiler. Para ello, simplemente escribe “Cloud Profiler”. y selecciona el elemento encontrado. De cualquier manera, deberías ver la IU de Profiler con el mensaje “No data to display” mensaje como el que se muestra a continuación. El proyecto es nuevo, por lo que aún no se recopilaron datos de creación de perfiles.

d275a5f61ed31fb2.png

¡Es hora de conseguir un perfil!

4. Cómo generar perfiles de la comparativa

Usaremos una aplicación sintética simple de Go disponible en GitHub. En la terminal de Cloud Shell que aún tienes abierta (y mientras aún se muestra el mensaje “No hay datos para mostrar” en la IU de Profiler), ejecuta el siguiente comando:

$ go get -u github.com/GoogleCloudPlatform/golang-samples/profiler/...

Luego, cambia al directorio de la aplicación:

$ cd ~/gopath/src/github.com/GoogleCloudPlatform/golang-samples/profiler/hotapp

El directorio contiene el archivo “main.go” , que es una app sintética que tiene habilitado el agente de creación de perfiles:

main.go

...
import (
        ...
        "cloud.google.com/go/profiler"
)
...
func main() {
        err := profiler.Start(profiler.Config{
                Service:        "hotapp-service",
                DebugLogging:   true,
                MutexProfiling: true,
        })
        if err != nil {
                log.Fatalf("failed to start the profiler: %v", err)
        }
        ...
}

El agente de creación de perfiles recopila perfiles de CPU, montón y subprocesos de forma predeterminada. El código permite la recopilación de perfiles de exclusión mutua (también conocidos como "contención").

Ahora, ejecuta el programa:

$ go run main.go

A medida que se ejecute el programa, el agente de creación de perfiles recopilará periódicamente perfiles de los cinco tipos configurados. La recopilación se aleatoriza con el tiempo (con una tasa promedio de un perfil por minuto para cada uno de los tipos), por lo que la recopilación de cada uno de los tipos puede tardar hasta tres minutos. El programa te avisa cuando crea un perfil. Los mensajes se habilitan con la marca DebugLogging en la configuración anterior. De lo contrario, el agente se ejecutará de manera silenciosa:

$ go run main.go
2018/03/28 15:10:24 profiler has started
2018/03/28 15:10:57 successfully created profile THREADS
2018/03/28 15:10:57 start uploading profile
2018/03/28 15:11:19 successfully created profile CONTENTION
2018/03/28 15:11:30 start uploading profile
2018/03/28 15:11:40 successfully created profile CPU
2018/03/28 15:11:51 start uploading profile
2018/03/28 15:11:53 successfully created profile CONTENTION
2018/03/28 15:12:03 start uploading profile
2018/03/28 15:12:04 successfully created profile HEAP
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:04 successfully created profile THREADS
2018/03/28 15:12:04 start uploading profile
2018/03/28 15:12:25 successfully created profile HEAP
2018/03/28 15:12:25 start uploading profile
2018/03/28 15:12:37 successfully created profile CPU
...

La IU se actualizará poco después de que se recopile el primer perfil. No se actualizará automáticamente después de eso, por lo que, para ver los datos nuevos, deberás actualizar la IU de Profiler de forma manual. Para ello, haz clic dos veces en el botón Now del selector de intervalos de tiempo:

650051097b651b91.png

Una vez que se actualice la IU, verás algo como esto:

47a763d4dc78b6e8.png

El selector de tipo de perfil muestra los cinco tipos de perfiles disponibles:

b5d7b4b5051687c9.png

Ahora, revisaremos cada uno de los tipos de perfiles y algunas funciones importantes de la IU, y, luego, realizaremos algunos experimentos. En esta etapa, ya no necesitas la terminal de Cloud Shell, por lo que puedes salir presionando CTRL-C y escribiendo “exit”.

5. Analiza los datos de Profiler

Ahora que hemos recopilado algunos datos, veámoslos con más detalle. Usamos una app sintética (la fuente está disponible en GitHub) que simula comportamientos típicos de diferentes tipos de problemas de rendimiento en la producción.

Código de uso intensivo de CPU

Selecciona el tipo de perfil de CPU. Después de cargar la IU, verás en el gráfico tipo llama los cuatro bloques de hoja para la función load, que en conjunto representan todo el consumo de CPU:

fae661c9fe6c58df.png

Esta función está escrita específicamente para consumir muchos ciclos de CPU mediante la ejecución de un bucle cerrado:

main.go

func load() {
        for i := 0; i < (1 << 20); i++ {
        }
}

Se llama a la función de forma indirecta desde busyloop() a través de cuatro rutas de llamadas: busyloop → {foo1, foo2} → {bar, baz} → load. El ancho de un cuadro de función representa el costo relativo de la ruta de la llamada específica. En este caso, las cuatro rutas tienen aproximadamente el mismo costo. En un programa real, su objetivo es enfocarse en la optimización de las rutas de llamadas más importantes en términos de rendimiento. El gráfico tipo llama, que enfatiza visualmente las rutas más costosas con cuadros más grandes, hace que estas rutas sean fáciles de identificar.

Puedes usar el filtro de datos de perfil para definir aún mejor la visualización. Por ejemplo, prueba agregar la función "Mostrar pilas" filtro que especifica “baz” como la cadena de filtro. Deberías ver algo como la siguiente captura de pantalla, en la que solo se muestran dos de las cuatro rutas de llamada a load(). Estas dos rutas de acceso son las únicas que pasan por una función con la cadena “baz”. en su nombre. Este filtrado es útil cuando deseas enfocarte en una subparte de un programa más grande (por ejemplo, porque solo posees una parte de él).

eb1d97491782b03f.png

Código que usa mucha memoria

Ahora, cambia a "Montón". tipo de perfil. Asegúrate de quitar los filtros que creaste en experimentos anteriores. Ahora, deberías ver un gráfico tipo llama en el que allocImpl, que llama alloc, se muestra como el consumidor principal de memoria en la app:

f6311c8c841d04c4.png

En la tabla de resumen sobre el gráfico tipo llama, se indica que la cantidad total de memoria usada en la app es de aproximadamente 57.4 MiB, y la mayor parte la asigna la función allocImpl. Esto no es sorprendente, dada la implementación de esta función:

main.go

func allocImpl() {
        // Allocate 64 MiB in 64 KiB chunks
        for i := 0; i < 64*16; i++ {
                mem = append(mem, make([]byte, 64*1024))
        }
}

La función se ejecuta una vez, asigna 64 MiB en fragmentos más pequeños y, luego, almacena punteros para esos fragmentos en una variable global a fin de protegerlos de la recolección de elementos no utilizados. Ten en cuenta que la cantidad de memoria que muestra el generador de perfiles es ligeramente diferente de 64 MiB: el generador de perfiles del montón de Go es una herramienta estadística, por lo que las mediciones tienen una sobrecarga baja, pero no precisan bytes. No te sorprendas cuando veas una diferencia de alrededor del 10% como esta.

Código de E/S intensivo

Si eliges "Conversaciones" En el selector de tipo de perfil, la pantalla cambiará a un gráfico tipo llama en el que la mayor parte del ancho corresponde a las funciones wait y waitImpl:

ebd57fdff01dede9.png

En el resumen anterior del gráfico tipo llama, puedes ver que hay 100 goroutines que aumentan su pila de llamadas a partir de la función wait. Esto es correcto, dado que el código que inicia estas esperas se ve de la siguiente manera:

main.go

func main() {
        ...
        // Simulate some waiting goroutines.
        for i := 0; i < 100; i++ {
                go wait()
        }

Este tipo de perfil es útil para comprender si el programa pasa algún tiempo inesperado en esperas (como E/S). Por lo general, el generador de perfiles de CPU no muestrea estas pilas de llamadas, ya que no consumen una porción significativa del tiempo de CPU. Usarás la opción "Ocultar pilas" Filtros con perfiles de subprocesos: por ejemplo, para ocultar todas las pilas que terminan con una llamada a gopark,, ya que suelen ser goroutines inactivas y menos interesantes que las que esperan en E/S.

El tipo de perfil de subprocesos también puede ayudar a identificar puntos del programa donde los subprocesos esperan una exclusión mutua que pertenece a otra parte del programa durante un período prolongado, pero el siguiente tipo de perfil es más útil para eso.

Código de alta contención

El tipo de perfil de contención identifica a los usuarios más "deseados" bloqueos en el programa. Este tipo de perfil está disponible para los programas de Go, pero se debe habilitar explícitamente especificando "MutexProfiling: true" en el código de configuración del agente. La recopilación funciona registrando (en la métrica "Contenciones") la cantidad de veces que un bloqueo específico, al ser desbloqueado por una goroutine A, tenía otra goroutine B esperando que se desbloqueara. También registra (en la métrica "Demora") el tiempo que la goroutine bloqueada esperó el bloqueo. En este ejemplo, hay una sola pila de contención y el tiempo de espera total para el bloqueo fue de 10.5 segundos:

83f00dca4a0f768e.png

El código que genera este perfil consta de 4 goroutines que luchan por una exclusión mutua:

main.go

func contention(d time.Duration) {
        contentionImpl(d)
}

func contentionImpl(d time.Duration) {
        for {
                mu.Lock()
                time.Sleep(d)
                mu.Unlock()
        }
}
...
func main() {
        ...
        for i := 0; i < 4; i++ {
                go contention(time.Duration(i) * 50 * time.Millisecond)
        }
}

6. Resumen

En este lab, aprendiste a configurar un programa de Go para usarlo con Cloud Profiler. También aprendiste a recopilar, ver y analizar los datos de rendimiento con esta herramienta. Ahora puedes aplicar tu nueva habilidad a los servicios reales que ejecutas en Google Cloud Platform.

7. ¡Felicitaciones!

Aprendiste a configurar y usar Cloud Profiler.

Más información

Licencia

Este trabajo cuenta con una licencia Atribución 2.0 Genérica de Creative Commons.