1. Descripción general
Si bien los desarrolladores de apps para clientes y de frontend web 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, las técnicas equivalentes no han sido tan accesibles ni adoptadas por quienes trabajan en servicios de backend. Cloud Profiler ofrece estas mismas capacidades a los desarrolladores de servicios, independientemente de si su código se ejecuta en Google Cloud Platform o en otro lugar.

La herramienta recopila información de la asignación de memoria y el uso de CPU de 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 recopilación que emplea la herramienta la hace adecuada para el uso continuo en entornos de producción.
En este codelab, aprenderás a configurar Cloud Profiler para un programa 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 en Go para crear 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?
¿Cómo calificarías tu experiencia con Google Cloud Platform?
2. Configuración y requisitos
Configuración del entorno de autoaprendizaje
- 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.



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.
- 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 que no se te facture más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de USD 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
- En la consola de Cloud, haz clic en Activar Cloud Shell
.

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

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

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.
- 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`
- En Cloud Shell, ejecuta el siguiente comando para confirmar que el comando 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" en la barra de navegación de la izquierda para navegar a la IU de Profiler:

Como alternativa, puedes usar la barra de búsqueda de la consola de Cloud para navegar a la IU de Profiler. Solo escribe "Cloud Profiler" y selecciona el elemento encontrado. De cualquier manera, deberías ver la IU de Profiler con el mensaje "No hay datos para mostrar", como se muestra a continuación. El proyecto es nuevo, por lo que aún no tiene datos de generación de perfiles recopilados.

Ahora es momento de obtener un perfil de algo.
4. Cómo crear el perfil de la comparativa
Usaremos una aplicación sintética simple en Go disponible en GitHub. En la terminal de Cloud Shell que aún tienes abierta (y mientras el mensaje "No data to display" aún se muestra en la IU del 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 generació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)
}
...
}
De forma predeterminada, el agente de creación de perfiles recopila perfiles de CPU, montón y subprocesos. El código aquí permite la recopilación de perfiles de mutex (también conocidos como "contención").
Ahora, ejecuta el programa:
$ go run main.go
A medida que se ejecuta 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 puede tardar hasta tres minutos en recopilar cada uno de los tipos. El programa te indica cuándo crea un perfil. Los mensajes se habilitan con la marca DebugLogging en la configuración anterior. De lo contrario, el agente se ejecuta de forma 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. Después de eso, no se actualizará automáticamente, por lo que deberás actualizar la IU del generador de perfiles de forma manual para ver los datos nuevos. Para ello, haz clic dos veces en el botón Ahora del selector de intervalos:

Después de que se actualice la IU, verás algo como lo siguiente:

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

Ahora, revisemos cada uno de los tipos de perfiles y algunas capacidades importantes de la IU, y luego realicemos algunos experimentos. En esta etapa, ya no necesitas la terminal de Cloud Shell, por lo que puedes salir de ella presionando Ctrl + C y escribiendo "exit".
5. Analiza los datos del generador de perfiles
Ahora que recopilamos algunos datos, analicé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 producción.
Código con uso intensivo de la CPU
Selecciona el tipo de perfil de CPU. Después de que se cargue la IU, verás en el gráfico de llamas los cuatro bloques hoja de la función load, que representan de forma colectiva todo el consumo de CPU:

Esta función se escribió específicamente para consumir muchos ciclos de CPU ejecutando un bucle ajustado:
main.go
func load() {
for i := 0; i < (1 << 20); i++ {
}
}
La función se llama de forma indirecta desde busyloop() a través de cuatro rutas de llamada: busyloop → {foo1, foo2} → {bar, baz} → load. El ancho de un cuadro de función representa el costo relativo de la ruta de llamada específica. En este caso, las cuatro rutas tienen aproximadamente el mismo costo. En un programa real, debes enfocarte en optimizar las rutas de llamadas que son más importantes en términos de rendimiento. El gráfico de llamas, que enfatiza visualmente las rutas más costosas con cuadros más grandes, facilita la identificación de estas rutas.
Puedes usar el filtro de datos del perfil para definir mejor la pantalla. Por ejemplo, intenta agregar un filtro "Mostrar pilas" que especifique "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 llamadas a load(). Estas dos rutas 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).

Código con uso intensivo de memoria
Ahora cambia al tipo de perfil "Heap". Asegúrate de quitar los filtros que creaste en experimentos anteriores. Ahora deberías ver un gráfico de llamas en el que allocImpl, llamado por alloc, se muestra como el principal consumidor de memoria en la app:

La tabla de resumen que se encuentra sobre el gráfico de llamas indica que la cantidad total de memoria usada en la app es de ~57.4 MiB en promedio, 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 a esos fragmentos en una variable global para protegerlos de la recolección de basura. Ten en cuenta que la cantidad de memoria que se muestra como utilizada por el generador de perfiles es ligeramente diferente de 64 MiB: el generador de perfiles de montón de Go es una herramienta estadística, por lo que las mediciones tienen una sobrecarga baja, pero no son precisas en términos de bytes. No te sorprendas si ves una diferencia del 10% aproximadamente como esta.
Código con uso intensivo de E/S
Si eliges "Subprocesos" en el selector de tipo de perfil, la pantalla cambiará a un gráfico tipo llama en el que la mayor parte del ancho está ocupada por las funciones wait y waitImpl:

En el resumen sobre el gráfico de llamas, puedes ver que hay 100 goroutines que aumentan su pila de llamadas desde la función wait. Esto es exactamente correcto, dado que el código que inicia estas esperas se ve así:
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 dedica tiempo inesperado a las esperas (como E/S). El analizador de CPU no suele tomar muestras de estas pilas de llamadas, ya que no consumen una parte significativa del tiempo de CPU. A menudo, querrás usar filtros de "Ocultar pilas" con los 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 E/S.
El tipo de perfil de subprocesos también puede ayudar a identificar puntos en el programa en los que los subprocesos esperan un mutex que posee otra parte del programa durante un período prolongado, pero el siguiente tipo de perfil es más útil para eso.
Código con uso intensivo de la contención
El tipo de perfil Contention identifica los bloqueos más "deseados" en el programa. Este tipo de perfil está disponible para los programas en Go, pero se debe habilitar de forma explícita especificando "MutexProfiling: true" en el código de configuración del agente. La recopilación funciona registrando (con la métrica "Contention") la cantidad de veces que un bloqueo específico, cuando se desbloquea con una goroutine A, tenía otra goroutine B esperando que se desbloqueara. También registra (en la métrica "Delay") el tiempo que la goroutine bloqueada esperó el bloqueo. En este ejemplo, hay una sola pila de contención y el tiempo total de espera para el bloqueo fue de 10.5 segundos:

El código que genera este perfil consta de 4 rutinas que compiten por un mutex:
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 cómo se puede configurar un programa 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
- Cloud Profiler: https://cloud.google.com/profiler/
- Paquete runtime/pprof de Go que usa Cloud Profiler: https://golang.org/pkg/runtime/pprof/
Licencia
Este trabajo cuenta con una licencia Atribución 2.0 Genérica de Creative Commons.