Comment j'ai enquêté sur les fuites de mémoire dans Go en utilisant pprof sur une grande base de code

J'ai travaillé avec Go pendant la majeure partie de l'année, en mettant en œuvre une infrastructure de blockchain évolutive chez Orbs, et ce fut une année passionnante. Au cours de l'année 2018, nous avons recherché la langue à choisir pour notre implémentation blockchain. Cela nous a amenés à choisir Go car nous comprenons qu'il dispose d'une bonne communauté et d'un ensemble d'outils incroyable.

Ces dernières semaines, nous entrons dans les dernières étapes d'intégration de notre système. Comme dans tout système volumineux, les problèmes ultérieurs qui incluent des problèmes de performances, dans des fuites de mémoire spécifiques, peuvent survenir. En intégrant le système, nous avons réalisé que nous en avions trouvé un. Dans cet article, je vais aborder les détails de la façon d'étudier une fuite de mémoire dans Go, en détaillant les étapes suivies pour la trouver, la comprendre et la résoudre.

L'ensemble d'outils proposé par Golang est exceptionnel mais a ses limites. En abordant ces derniers, le plus important est la capacité limitée à enquêter sur les vidages de mémoire complets. Un vidage de mémoire complet serait l'image de la mémoire (ou de la mémoire utilisateur) prise par le processus exécutant le programme.

Nous pouvons imaginer le mappage mémoire comme un arbre, et parcourir cet arbre nous mènerait à travers les différentes allocations d'objets et les relations. Cela signifie que tout ce qui est à la racine est la raison pour laquelle la mémoire est "conservée" et non la GC (Garbage Collecting). Comme dans Go, il n'y a pas de moyen simple d'analyser le vidage de mémoire complet, il est difficile d'accéder aux racines d'un objet qui ne reçoit pas GC-ed.

Au moment de la rédaction de cet article, nous n'avons trouvé aucun outil en ligne susceptible de nous aider. Puisqu'il existe un format de vidage de mémoire et un moyen assez simple de l'exporter à partir du package de débogage, il se peut qu'il en existe un chez Google. En recherchant en ligne, il semble qu'il se trouve dans le pipeline Golang, créant un visualiseur de vidage de base de ce type, mais il ne semble pas que quiconque travaille dessus. Cela dit, même sans accès à une telle solution, avec les outils existants, nous pouvons généralement trouver la cause première.

Fuites de mémoire

Les fuites de mémoire, ou la pression de la mémoire, peuvent prendre de nombreuses formes dans tout le système. Habituellement, nous les traitons comme des bogues, mais parfois leur cause profonde peut être dans les décisions de conception.

Alors que nous construisons notre système selon les principes de conception émergents, ces considérations ne sont pas considérées comme importantes et c'est normal. Il est plus important de créer le système d'une manière qui évite les optimisations prématurées et vous permette de les exécuter plus tard au fur et à mesure que le code mûrit, plutôt que de le surcharger dès le départ. Pourtant, quelques exemples courants de voir se matérialiser des problèmes de pression de la mémoire sont:

  • Trop d'allocations, représentation incorrecte des données
  • Utilisation intensive de la réflexion ou des cordes
  • Utilisation de globals
  • Goroutines orphelines et sans fin

Dans Go, le moyen le plus simple de créer une fuite de mémoire consiste à définir une variable globale, un tableau et à ajouter des données à ce tableau. Cet excellent article de blog décrit cette affaire d'une bonne manière.

Alors pourquoi est-ce que j'écris ce post? Lors de mes recherches sur ce cas, j'ai trouvé de nombreuses ressources sur les fuites de mémoire. Pourtant, dans la réalité, les systèmes ont plus de 50 lignes de code et une seule structure. Dans de tels cas, trouver la source d'un problème de mémoire est beaucoup plus complexe que ce que cet exemple décrit.

Golang nous donne un outil incroyable appelé pprof. Cet outil, une fois maîtrisé, peut aider à enquêter et très probablement à trouver tout problème de mémoire. Un autre objectif est d'enquêter sur les problèmes de processeur, mais je n'entrerai pas dans quoi que ce soit lié au processeur dans cet article.

aller outil pprof

Couvrir tout ce que fait cet outil nécessitera plus d'un article de blog. Une chose qui a pris du temps est de savoir comment utiliser cet outil pour obtenir quelque chose d'actionnable. Je vais concentrer cet article sur la fonctionnalité liée à la mémoire.

Le pprofpackage crée un fichier de vidage échantillonné en tas, que vous pouvez ensuite analyser / visualiser pour vous donner une carte des deux:

  • Allocations de mémoire actuelles
  • Allocations de mémoire totales (cumulatives)

L'outil a la capacité de comparer des instantanés. Cela peut vous permettre de comparer un affichage différentiel de ce qui s'est passé maintenant et il y a 30 secondes, par exemple. Pour les scénarios de stress, cela peut être utile pour vous aider à localiser les zones problématiques de votre code.

profils pprof

Le fonctionnement de pprof utilise des profils.

Un profil est une collection de traces de pile montrant les séquences d'appels qui ont conduit à des instances d'un événement particulier, comme l'allocation.

Le fichier runtime / pprof / pprof.go contient les informations détaillées et l'implémentation des profils.

Go a plusieurs profils intégrés que nous pouvons utiliser dans les cas courants:

  • goroutine - empilez les traces de toutes les goroutines actuelles
  • tas - un échantillon d'allocations de mémoire d'objets vivants
  • allocs - un échantillon de toutes les allocations de mémoire passées
  • threadcreate - traces de pile qui ont conduit à la création de nouveaux threads OS
  • block - traces de pile qui ont conduit au blocage des primitives de synchronisation
  • mutex - empiler les traces de détenteurs de mutex contestés

Lorsque nous examinons les problèmes de mémoire, nous nous concentrerons sur le profil du tas. Le profil allocs est identique en ce qui concerne la collecte de données qu'il effectue. La différence entre les deux est la façon dont l'outil pprof y lit au moment du démarrage. Le profil Allocs démarrera pprof dans un mode qui affiche le nombre total d'octets alloués depuis le début du programme (y compris les octets récupérés). Nous utiliserons généralement ce mode en essayant de rendre notre code plus efficace.

Le tas

En résumé, c'est là que l'OS (Operating System) stocke la mémoire des objets que notre code utilise. Il s'agit de la mémoire qui sera par la suite «garbage collectée» ou libérée manuellement dans des langues non récupérées.

Le tas n'est pas le seul endroit où les allocations de mémoire se produisent, une partie de la mémoire est également allouée dans la pile. Le but de la pile est à court terme. Dans Go, la pile est généralement utilisée pour les affectations qui se produisent à l'intérieur de la fermeture d'une fonction. Un autre endroit où Go utilise la pile est lorsque le compilateur «sait» combien de mémoire doit être réservée avant l'exécution (par exemple des tableaux de taille fixe). Il existe un moyen d'exécuter le compilateur Go afin qu'il produise une analyse de l'endroit où les allocations `` échappent '' à la pile vers le tas, mais je n'y toucherai pas dans cet article.

Alors que les données de tas doivent être «libérées» et gc-ed, les données de pile ne le font pas. Cela signifie qu'il est beaucoup plus efficace d'utiliser la pile dans la mesure du possible.

Ceci est un résumé des différents emplacements où l'allocation de mémoire se produit. Il y a beaucoup plus à faire, mais cela sortira du cadre de cet article.

Obtention de données de tas avec pprof

Il existe deux manières principales d'obtenir les données pour cet outil. Le premier fait généralement partie d'un test ou d'une branche et inclut l'importation runtime/pprofpuis l'appel pprof.WriteHeapProfile(some_file)pour écrire les informations du tas.

Notez que WriteHeapProfilec'est du sucre syntaxique pour courir:

// lookup takes a profile namepprof.Lookup("heap").WriteTo(some_file, 0)

Selon la documentation, WriteHeapProfileexiste pour la compatibilité ascendante. Le reste des profils n'a pas de tels raccourcis et vous devez utiliser la Lookup()fonction pour obtenir leurs données de profil.

La seconde, qui est la plus intéressante, est de l'activer via HTTP (points de terminaison Web). Cela vous permet d'extraire les données adhoc, à partir d'un conteneur en cours d'exécution dans votre environnement e2e / test ou même à partir de «production». C'est un autre endroit où le runtime et les outils Go excellent. La documentation complète du package se trouve ici, mais le TL; DR est que vous devrez l'ajouter à votre code en tant que tel:

import ( "net/http" _ "net/http/pprof")
...
func main() { ... http.ListenAndServe("localhost:8080", nil)}

L '«effet secondaire» de l'importation net/http/pprofest l'enregistrement des points de terminaison pprof sous la racine du serveur Web à /debug/pprof. Maintenant, en utilisant curl, nous pouvons obtenir les fichiers d'informations du tas à étudier:

curl -sK -v //localhost:8080/debug/pprof/heap > heap.out

L'ajout de ce qui http.ListenAndServe()précède n'est requis que si votre programme n'avait pas d'écouteur http auparavant. Si vous en avez un, il s'y accrochera et il n'est pas nécessaire de réécouter. Il existe également des moyens de le configurer en utilisant un ServeMux.HandleFunc()qui aurait plus de sens pour un programme compatible http plus complexe.

Utilisation de pprof

Nous avons donc collecté les données, et maintenant? Comme mentionné ci-dessus, il existe deux stratégies principales d'analyse de la mémoire avec pprof. La première consiste à examiner les allocations actuelles (octets ou nombre d'objets), appelées inuse. L'autre examine tous les octets alloués ou le nombre d'objets tout au long de l'exécution du programme, appelé alloc. Cela signifie que peu importe si c'était gc-ed, une somme de tout ce qui a été échantillonné.

C'est un bon endroit pour réitérer que le profil de tas est un échantillon d' allocations de mémoire . pprofdans les coulisses utilise la runtime.MemProfilefonction, qui par défaut collecte des informations d'allocation sur chaque 512 Ko d'octets alloués. Il est possible de modifier MemProfile pour collecter des informations sur tous les objets. Notez que très probablement, cela ralentira votre application.

Cela signifie que par défaut, il y a une chance qu'un problème se produise avec des objets plus petits qui passeront sous le radar de pprof. Pour une base de code volumineuse / un programme de longue durée, ce n'est pas un problème.

Une fois que nous avons collecté le fichier de profil, il est temps de le charger dans la console interactive que propose pprof. Faites-le en exécutant:

> go tool pprof heap.out

Observons les informations affichées

Type: inuse_spaceTime: Jan 22, 2019 at 1:08pm (IST)Entering interactive mode (type "help" for commands, "o" for options)(pprof)

La chose importante à noter ici est le Type: inuse_space. Cela signifie que nous examinons les données d'allocation d'un moment spécifique (lorsque nous avons capturé le profil). Le type est la valeur de configuration de sample_index, et les valeurs possibles sont:

  • inuse_space - quantité de mémoire allouée et non encore libérée
  • inuse_object s: quantité d'objets alloués et non encore libérés
  • alloc_space - quantité totale de mémoire allouée (indépendamment de la libération)
  • alloc_objects - quantité totale d'objets alloués (indépendamment de la libération)

Maintenant, tapez topl'interactif, la sortie sera les principaux consommateurs de mémoire

Nous pouvons voir une ligne nous en parler Dropped Nodes, cela signifie qu'ils sont filtrés. Un nœud est une entrée d'objet ou un «nœud» dans l'arborescence. La suppression de nœuds est une bonne idée pour réduire le bruit, mais elle peut parfois masquer la cause première d'un problème de mémoire. Nous en verrons un exemple au fur et à mesure que nous poursuivrons notre enquête.

Si vous souhaitez inclure toutes les données du profil, ajoutez l' -nodefraction=0option lors de l'exécution de pprof ou saisissez le fichier nodefraction=0interactif.

Dans la liste produite, nous pouvons voir deux valeurs, flatet cum.

  • flat signifie que la mémoire allouée par cette fonction et est détenue par cette fonction
  • cum signifie que la mémoire a été allouée par cette fonction ou fonction qu'elle a appelée dans la pile

Ces informations à elles seules peuvent parfois nous aider à comprendre s'il y a un problème. Prenons par exemple un cas où une fonction est responsable d'allouer beaucoup de mémoire mais ne la détient pas. Cela signifierait qu'un autre objet pointe vers cette mémoire et la garde allouée, ce qui signifie que nous pouvons avoir un problème de conception du système ou un bogue.

Une autre astuce intéressante topdans la fenêtre interactive est qu'elle est en cours d'exécution top10. La commande top prend en charge le topNformat où Nest le nombre d'entrées que vous souhaitez voir. Dans le cas collé ci-dessus, taper top70par exemple, produirait tous les nœuds.

Visualisations

Bien que le topNdonne une liste textuelle, il existe plusieurs options de visualisation très utiles fournies avec pprof. Il est possible de taper pngou gifet bien d'autres (voir la go tool pprof -helpliste complète).

Sur notre système, la sortie visuelle par défaut ressemble à quelque chose comme:

Cela peut être intimidant au début, mais c'est la visualisation des flux d'allocation de mémoire (en fonction des traces de pile) dans un programme. La lecture du graphique n'est pas aussi compliquée qu'il y paraît. Un carré blanc avec un nombre montre l'espace alloué (et le cumul de la quantité de mémoire qu'il prend actuellement sur le bord du graphique), et chaque rectangle plus large montre la fonction d'allocation.

Notez que dans l'image ci-dessus, j'ai retiré un png d'un inuse_spacemode d'exécution. Plusieurs fois, vous devriez également y jeter un coup d'œil inuse_objects, car cela peut vous aider à trouver des problèmes d'allocation.

Creuser plus profondément, trouver une cause profonde

Jusqu'à présent, nous avons pu comprendre ce qui alloue de la mémoire dans notre application pendant l'exécution. Cela nous aide à avoir une idée de la façon dont notre programme se comporte (ou se comporte mal).

Dans notre cas, nous pourrions voir que la mémoire est conservée par membuffers, qui est notre bibliothèque de sérialisation de données. Cela ne signifie pas que nous avons une fuite de mémoire au niveau de ce segment de code, cela signifie que la mémoire est conservée par cette fonction. Il est important de comprendre comment lire le graphique et la sortie de pprof en général. Dans ce cas, sachant que lorsque nous sérialisons des données, c'est-à-dire que nous allouons de la mémoire aux structures et aux objets primitifs (int, string), elles ne sont jamais libérées.

Sautant aux conclusions ou mal interprétant le graphe, nous aurions pu supposer que l'un des nœuds sur le chemin de la sérialisation est responsable de la conservation de la mémoire, par exemple:

Quelque part dans la chaîne, nous pouvons voir notre bibliothèque de journalisation, responsable de plus de 50 Mo de mémoire allouée. C'est la mémoire qui est allouée par les fonctions appelées par notre enregistreur. En y réfléchissant bien, c'est en fait attendu. L'enregistreur provoque des allocations de mémoire car il a besoin de sérialiser les données pour les sortir dans le journal et ainsi il provoque des allocations de mémoire dans le processus.

Nous pouvons également voir que dans le chemin d'allocation, la mémoire n'est conservée que par sérialisation et rien d'autre. De plus, la quantité de mémoire conservée par l'enregistreur est d'environ 30% du total. Ce qui précède nous indique que, très probablement, le problème ne vient pas de l'enregistreur. Si c'était à 100%, ou quelque chose de proche, alors nous aurions dû y regarder - mais ce n'est pas le cas. Cela pourrait signifier que quelque chose est enregistré qui ne devrait pas l'être, mais ce n'est pas une fuite de mémoire de la part de l'enregistreur.

C'est le bon moment pour introduire une autre pprofcommande appelée list. Il accepte une expression régulière qui sera un filtre de ce qu'il faut lister. La «liste» est en réalité le code source annoté lié à l'allocation. Dans le contexte de l'enregistreur que nous examinons, nous exécuterons list RequestNewcomme nous aimerions voir les appels effectués vers l'enregistreur. Ces appels proviennent de deux fonctions qui commencent par le même préfixe.

Nous pouvons voir que les allocations effectuées se trouvent dans la cumcolonne, ce qui signifie que la mémoire allouée est conservée dans la pile d'appels. Cela correspond à ce que le graphique montre également. À ce stade, il est facile de voir que la raison pour laquelle l'enregistreur a alloué la mémoire est parce que nous lui avons envoyé tout l'objet «bloc». Il fallait au moins sérialiser certaines parties de celui-ci (nos objets sont des objets membuffer, qui implémentent toujours une String()fonction). Est-ce un message de journal utile ou une bonne pratique? Probablement pas, mais ce n'est pas une fuite de mémoire, ni du côté de l'enregistreur ni du code qui a appelé l'enregistreur.

listpeut trouver le code source lors de sa recherche dans votre GOPATHenvironnement. Dans les cas où la racine recherchée ne correspond pas, ce qui dépend de votre machine de construction, vous pouvez utiliser l' -trim_pathoption. Cela vous aidera à le réparer et vous permettra de voir le code source annoté. N'oubliez pas de définir votre git sur le bon commit qui était en cours d'exécution lorsque le profil de tas a été capturé.

Alors pourquoi la mémoire est-elle conservée?

Le contexte de cette enquête était le soupçon que nous avons un problème - une fuite de mémoire. Nous sommes arrivés à cette notion car nous avons vu que la consommation de mémoire était supérieure à ce dont nous nous attendions à ce que le système ait besoin. En plus de cela, nous l'avons vu augmenter de plus en plus, ce qui était un autre indicateur fort de «il y a un problème ici».

À ce stade, dans le cas de Java ou .Net, nous ouvririons une analyse ou un profileur «gc roots» et accédions à l'objet réel qui fait référence à ces données et qui crée la fuite. Comme expliqué, ce n'est pas exactement possible avec Go, à la fois à cause d'un problème d'outillage mais aussi à cause de la représentation de mémoire de bas niveau de Go.

Sans entrer dans les détails, nous ne pensons pas que Go conserve quel objet est stocké à quelle adresse (sauf pour les pointeurs peut-être). Cela signifie qu'en réalité, comprendre quelle adresse mémoire représente quel membre de votre objet (struct) nécessitera une sorte de mappage vers la sortie d'un profil de tas. En parlant de théorie, cela pourrait signifier qu'avant d'effectuer un vidage de mémoire complet, il faut également prendre un profil de tas afin que les adresses puissent être mappées à la ligne et au fichier d'allocation et donc à l'objet représenté dans la mémoire.

À ce stade, parce que nous connaissons notre système, il était facile de comprendre que ce n'est plus un bogue. C'était (presque) par conception. Mais continuons à explorer comment obtenir les informations des outils (pprof) pour trouver la cause racine.

Lors du réglage, nodefraction=0nous verrons toute la carte des objets alloués, y compris les plus petits. Regardons la sortie:

Nous avons deux nouveaux sous-arbres. Pour rappel, le profil de tas pprof échantillonne les allocations de mémoire. Pour notre système qui fonctionne - nous ne manquons aucune information importante. Le nouvel arbre plus long, en vert, qui est complètement déconnecté du reste du système est le lanceur de test, ce n'est pas intéressant.

Le plus court, en bleu, qui a un bord le reliant à l'ensemble du système est inMemoryBlockPersistance. Ce nom explique également la «fuite» que nous imaginions avoir. Il s'agit du backend de données, qui stocke toutes les données en mémoire et ne persiste pas sur le disque. Ce qui est agréable à noter, c'est que nous avons pu voir immédiatement qu'il contient deux gros objets. Pourquoi deux? Parce que nous pouvons voir que l'objet a une taille de 1,28 Mo et que la fonction en conserve 2,57 Mo, soit deux d'entre eux.

Le problème est bien compris à ce stade. Nous aurions pu utiliser delve (le débogueur) pour voir qu'il s'agit du tableau contenant tous les blocs pour le pilote de persistance en mémoire que nous avons.

Alors, que pourrions-nous réparer?

Eh bien, c'était nul, c'était une erreur humaine. Alors que le processus était éduquant (et le partage est bienveillant), nous ne nous sommes pas améliorés, n'est-ce pas?

Il y avait une chose qui «sentait» encore cette information. Les données désérialisées prenaient trop de mémoire, pourquoi 142 Mo pour quelque chose qui devrait en prendre beaucoup moins? . . pprof peut répondre à cela - en fait, il existe pour répondre exactement à ces questions.

Pour examiner le code source annoté de la fonction, nous allons exécuter list lazy. Nous utilisons lazy, comme le nom de la fonction que nous recherchons lazyCalcOffsets()et nous savons qu'aucune autre fonction dans notre code ne commence par lazy. La frappe list lazyCalcOffsetsfonctionnerait aussi bien sûr.

Nous pouvons voir deux informations intéressantes. Encore une fois, rappelez-vous que le profil de tas pprof échantillonne les informations sur les allocations. Nous pouvons voir que flatles cumnombres et les nombres sont identiques. Cela indique que la mémoire allouée est également conservée par ces points d'allocation.

Ensuite, nous pouvons voir que make () prend de la mémoire. Cela a du sens, c'est le pointeur vers la structure de données. Pourtant, nous voyons également que l'affectation à la ligne 43 prend de la mémoire, ce qui signifie qu'elle crée une allocation.

Cela nous a renseignés sur les cartes, où une affectation à une carte n'est pas une affectation de variable simple. Cet article explique en détail le fonctionnement de la carte. En bref, une carte a une surcharge, et plus il y a d'éléments, plus cette surcharge va «coûter» si on la compare à une tranche.

Ce qui suit doit être pris avec un grain de sel: Il serait correct de dire que l'utilisation de a map[int]T, lorsque les données ne sont pas rares ou peuvent être converties en indices séquentiels, devrait généralement être tentée avec une implémentation de tranche si la consommation de mémoire est une considération pertinente . Pourtant, une grande tranche, lorsqu'elle est étendue, peut ralentir une opération, où dans une carte ce ralentissement sera négligeable. Il n'y a pas de formule magique pour les optimisations.

Dans le code ci-dessus, après avoir vérifié comment nous avons utilisé cette carte, nous nous sommes rendu compte que, bien que nous l'imaginions comme un tableau clairsemé, il en ressortait pas si clairsemé. Cela correspond à l'argument ci-dessus et nous pourrions immédiatement voir qu'un petit refactor consistant à changer la carte en une tranche est en fait possible, et pourrait rendre ce code plus efficace en mémoire. Nous l'avons donc changé en:

Aussi simple que cela, au lieu d'utiliser une carte, nous utilisons maintenant une tranche. En raison de la façon dont nous recevons les données qui y sont chargées paresseusement, et de la façon dont nous accédons ultérieurement à ces données, à part ces deux lignes et la structure contenant ces données, aucune autre modification de code n'était requise. Qu'est-ce que cela a fait sur la consommation de mémoire?

Regardons le benchcmppour juste quelques tests

Les tests de lecture initialisent la structure de données, ce qui crée les allocations. Nous pouvons voir que le runtime s'est amélioré d'environ 30%, les allocations sont en baisse de 50% et la consommation de mémoire de> 90% (!)

Étant donné que la carte, maintenant découpée, n'a jamais été remplie de beaucoup d'objets, les chiffres montrent à peu près ce que nous verrons en production. Cela dépend de l'entropie des données, mais il peut y avoir des cas où les allocations et les améliorations de la consommation de mémoire auraient été encore plus importantes.

En regardant à pprofnouveau et en prenant un profil de tas du même test, nous verrons que maintenant la consommation de mémoire est en fait en baisse d'environ 90%.

Ce qu'il faut retenir, c'est que pour les petits ensembles de données, vous ne devriez pas utiliser de cartes où des tranches suffiraient, car les cartes ont une surcharge importante.

Vidage complet du noyau

Comme mentionné, c'est là que nous voyons actuellement la plus grande limitation de l'outillage. Lorsque nous enquêtions sur ce problème, nous étions obsédés par le fait de pouvoir accéder à l'objet racine, sans grand succès. Go évolue dans le temps à un rythme soutenu, mais cette évolution a un prix dans le cas du vidage complet ou de la représentation mémoire. Le format de vidage de tas complet, à mesure qu'il change, n'est pas rétrocompatible. La dernière version décrite ici et pour écrire un vidage de tas complet, vous pouvez utiliser debug.WriteHeapDump().

Bien que pour le moment, nous ne nous trouvions pas «coincés» car il n'y a pas de bonne solution pour explorer les décharges complètes. pprofrépondu à toutes nos questions jusqu'à présent.

Attention, Internet se souvient de nombreuses informations qui ne sont plus pertinentes. Voici quelques éléments à ignorer si vous comptez essayer d'ouvrir vous-même un vidage complet, à partir de go1.11:

  • Il n'y a aucun moyen d'ouvrir et de déboguer un vidage de mémoire complet sur MacOS, uniquement Linux.
  • Les outils sur //github.com/randall77/hprof sont pour Go1.3, il existe un fork pour 1.7+ mais cela ne fonctionne pas correctement non plus (incomplet).
  • viewcore sur //github.com/golang/debug/tree/master/cmd/viewcore ne compile pas vraiment. C'est assez facile à corriger (les paquets internes pointent vers golang.org et non vers github.com), mais cela ne fonctionne pas non plus , pas sous MacOS, peut-être sous Linux.
  • Échec de //github.com/randall77/corelib sur MacOS

Interface utilisateur pprof

Un dernier détail à prendre en compte en ce qui concerne pprof, est sa fonctionnalité d'interface utilisateur. Cela peut faire gagner beaucoup de temps lors du lancement d'une enquête sur un problème lié à un profil pris avec pprof.

go tool pprof -http=:8080 heap.out

À ce stade, il devrait ouvrir le navigateur Web. Si ce n'est pas le cas, accédez au port sur lequel vous l'avez défini. Il vous permet de modifier les options et d'obtenir le retour visuel beaucoup plus rapidement que vous ne le pouvez à partir de la ligne de commande. Une manière très utile de consommer les informations.

L'interface utilisateur m'a en fait familiarisé avec les graphiques de flamme, qui exposent très rapidement les zones coupables du code.

Conclusion

Go est un langage passionnant avec un ensemble d'outils très riche, vous pouvez faire beaucoup plus avec pprof. Cet article ne concerne pas du tout le profilage du processeur, par exemple.

Quelques autres bonnes lectures:

  • //rakyll.org/archive/ - Je pense que c'est l'un des principaux contributeurs du suivi des performances, beaucoup de bons articles sur son blog
  • //github.com/google/gops - écrit par JBD (qui exécute rakyll.org), cet outil justifie son propre article de blog.
  • //medium.com/@cep21/using-go-1-10-new-trace-features-to-debug-an-integration-test-1dc39e4e812d - go tool tracequi concerne le profilage du processeur, ceci est un excellent article sur cette fonctionnalité de profilage .