• At chevron_right

      Explorer des logs ELK avec JsonPath

      motius · pubsub.gugod.fr / atomtest · Friday, 3 July, 2020 - 22:00 · 5 minutes

    Bonjour ! Aujourd'hui, je veux vous parler d'une bibliothèque Python3 : jsonpath2. Un peu de contexte Il s'agit d'une petite bibliothèque de code qui permet de filtrer des données au format JSON. Vous me direz, on peut déjà faire ça avec des petites fonctions utilitaires, quelques coups de liste en ...

    Bonjour !

    Aujourd'hui, je veux vous parler d'une bibliothèque Python3 : jsonpath2.

    Un peu de contexte

    Il s'agit d'une petite bibliothèque de code qui permet de filtrer des données au format JSON. Vous me direz, on peut déjà faire ça avec des petites fonctions utilitaires, quelques coups de liste en compréhension. Il y a bien sûr un grand nombre de choses que la bibliothèque ne permet pas de faire, on y reviendra, mais concentrons-nous d'abord sur ce qu'elle permet, et les avantages qu'elle procure.

    Pour cela, je vous propose tout simplement de vous présenter l'exemple que j'ai eu à traiter.

    Un exemple

    Supposez que vous ayez comme moi une application qui écrive un journal d'exécution au format JSON, dont les entrées sont relevées périodiquement par un ELK, et que vous ayez à analyser une journée complète, ce qui vous donne environ 50 Mo de logs compressés en gzip, et 700 Mo une fois décompressés. Vous rentrez ça dans un interpréteur ipython et bam, 3 Go de RAM supplémentaires utilisés. (Dans les cas où vous utilisez beaucoup de mémoire dans ipython, rappelez-vous que celui-ci stocke tout ce que vous taper dans des variables nommées _i1, _i2... et les résultats de ces opérations dans les variables correspondantes _1, _2, ce qui peut faire que votre interpréteur consomme une très grande quantité de mémoire, pensez à la gérer en créant vous-même des variables et en les supprimant avec del si nécessaire. Mais je m'égare.)

    Il peut y avoir plusieurs raisons qui font que ces logs ne seront pas complètement homogènes :

    • vous avez plusieurs applications qui fontionnent en microservices ;
    • les messages comportant des exceptions ont des champs que les autres messages plus informatifs n'ont pas ;
    • etc.

    Toujours est-il que pour analyser ce JSON, vous pouvez être dans un beau pétrin au moment où vous vous rendez compte que chacune des petites fonctions utilitaires que vous écrivez doit :

    • gérer un grand nombre de cas ;
    • gérer des cas d'erreur ;
    • être facilement composable, même pour les cas d'erreur.

    Je ne dis pas que ce soit infaisable, et il m'est arriver de le faire ainsi pour certaines actions plutôt qu'en utilisant JsonPath.

    Pour l'installation, c'est comme d'habitude dans le nouveau monde Python :

    # dans un virtualenv Python3
    pip install jsonpath2

    Cas pratiques

    Exemple de code n°1

    Par exemple, si je souhaite obtenir toutes les valeurs du champ message où qu'il se trouve dans mon JSON, je peux le faire ainsi :

    import json
    from jsonpath2.path import Path as JsonPath
    with open("/path/to/json_file/file.json", mode='r') as fr:
        json_data = json.loads(fr.read())
    pattern = "$..message"
    ls_msg = [
        match.curent_value
        for match in JsonPath.parse_str(pattern).match(json_data)
    ]

    La variable qui nous intéresse ici, c'est pattern. Elle se lit ainsi :

    1. $ : racine de l'arbre analysé
    2. .. : récursion sur tous les niveaux
    3. message : la chaîne de caractères recherchés

    Avantage

    Le premier avantage que l'on voit ici, c'est la possibilité de rechercher la valeur d'un champ quelle que soit la profondeur de ce champ dans des logs.

    Exemple de code n°2

    On peut aussi raffiner la recherche. Dans mon cas, j'avais une quantité de champs "message", mais tous ne m'intéressaient pas. J'ai donc précisé que je souhaitais obtenir les champs "message" seulement si le champ parent est, dans mon cas, "_source" de la manière suivante :

    pattern = "$.._source.message"

    Par rapport au motif précédent, le seul nouveau caractère spécial est :

    1. . : permet d'accéder au descendant direct d'un champ.

    Avantage

    L'autre avantage qu'on vient de voir, c'est la possibilité de facilement rajouter des contraintes sur la structure de l'arbre, afin de mieux choisir les champs que l'on souhaite filtrer.

    Exemple de code n°3

    Dans mon cas, j'avais besoin de ne récupérer le contenu des champ "message" que si le log sélectionné était celui associé à une exception, ce qui correspondait à environ 1% des cas sur à peu près 600 000 entrées.

    Le code suivant me permet de sélectionner les "message" des entrées pour lesquelles il y a un champ "exception" présent :

    pattern = "$..[?(@._source.exception)]._source.message"

    Il y a pas mal de nouveautés par rapport aux exemples précédents :

    1. @ : il s'agit de l'élément couramment sélectionné
    2. [] : permet de définir un prédicat ou d'itérer sur une collection
    3. ?() : permet d'appliquer un filtre

    Avantage

    On peut facilement créer un prédicat simple pour le filtrage d'éléments, même lorsque l'élément sur lequel on effectue le prédicat n'est pas le champ recherché in fine.

    Au sujet de jsonpath2

    Si vous êtes intéressé par le projet, je vous mets à disposition les liens suivants (ils sont faciles à trouver en cherchant un peu sur le sujet) :

    jsonpath2 utilise le générateur de parseur ANTLR, qui est un projet réputé du Pr. Terence Parr.

    Inconvénients

    Parmi les prédicats qu'on peut faire, on peut tester si une chaîne de caractères est égale à une chaîne recherchée, mais les caractères qu'on peut mettre dans la chaîne recherchée sont assez limités : je n'ai pas essayé de faire compliqué, seulement de rechercher des stacktraces Python ou Java, qui ont peu de caractères spéciaux.

    Il paraît qu'on peut effectuer des filtrages plus puissants avec une fonctionnalité supplémentaire que je n'ai pas présentés parce que je n'ai pas pris le temps de l'utiliser :

    1. () : s'utilise afin d'exécuter des expressions personnalisées.

    J'espère que tout ceci pourra vous être utile. Je vous recommande notamment de tester vos motifs sur un petit jeu de données, on peut facilement faire des bêtises et consommer beaucoup de mémoire et pas mal de temps sans cela.

    Joyeux code !

    Motius



    Lnav Log Files Navigator

    The log file navigator, lnav, is an enhanced log file viewer that takes advantage of any semantic information that can be gleaned from the files being viewed, such as timestamps and log levels. Using this extra semantic information, lnav can do things like interleaving messages from different files, generate histograms of messages over time, and providing hotkeys for navigating through the file. It is hoped that these features will allow the user to quickly and efficiently zero in on problems.

    http://lnav.org/
    https://github.com/tstack/lnav

    #log #logviewer #viewer
    • At chevron_right

      Du dev et du monitoring : YaPLog

      motius · pubsub.gugod.fr / atomtest · Monday, 1 August, 2016 - 22:00 · 7 minutes

    Bonjour à tous ! Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev. Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois ...

    Bonjour à tous !

    Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev.

    Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois quarts du projet, faisons un point sur son avancement, ce qu'il reste à faire, les technos, etc.

    Le cahier des charges

    Le monitoring réseau c'est tout sauf un sujet original. Hyper intéressant, sa mise en pratique évolue au fur et à mesure que les outils, les pratiques, et les protocoles évoluent.

    Vous vous demandez sûrement pourquoi je parle de développement dans ce cas. Il y a certes de nombreux programmes permettant de faire pas mal de choses. Voici pourquoi.

    • La meilleure raison : je n'ai pas accès (question de contrat avec l'opérateur réseau) aux routeurs, et il faudrait que j'aie la main sur une centaine de routeurs ;
    • la deuxième meilleure raison : on m'a demandé une tâche spécifique (du reporting quotidien, entre autres). Je ne dis pas que ce n'est pas possible avec un Nagios-Cacti ou un Munin, mais je n'aurais pas commencé ce qu'on m'a demandé (voir ce qui suit) dans le temps imparti ;
    • une raison pratique : on ne change pas la configuration d'une centaine de routeurs à travers le monde en 40 jours. C'est à peine suffisant pour traverser un désert.

    Que veut-on savoir ? La question initiale est "quand est-ce que ma ligne est saturée" ce qui est déjà visible sur les logs du FAI (Nagios-Cacti, exporté en petites images JPG) mais n'est pas suffisant pour plusieurs raisons :

    • on n'a pas de système d'alerte (facile à mettre en œuvre) ;
    • on n'a pas de données exploitables plus avant (l'image JPG, c'est pas beaucoup) ;

    On veut donc savoir quand est-ce que la ligne est saturée. C'est-à-dire, a priori, quand est-ce que la ligne atteint sont débit maximal.

    Le but suivant est de déterminer si une saturation est gênante. Cela comprend plusieurs facteurs :

    • l'heure : une saturation entre 2h et 4h du mat' parce qu'on fait des backup, ça ne gêne personne (ça pourrait, mais pas dans ce cas) ;
    • une saturation d'une seconde dans la journée n'est pas grave. De même, 250 saturations quasi-instantanées, réparties de 8h à 20h ne gênent pas le travail ;

    Une fois qu'on a déterminé si une saturation est gênante, on veut savoir qui elle gêne et pourquoi, afin de voir si on peut faire quelque chose pour améliorer l'utilisation des ressources.

    • Par exemple, lorsqu'un site est saturé, cela gêne-t-il 1 ou 50 personnes ? Dans le premier cas, la personne est peut-être trop gourmande en ressources, dans le second, le lien est peut-être mal dimensionné.
    • Certains processus peuvent être automatisés la nuit, certaines pratiques changées : backup du travail pendant la pause de midi et la nuit, passer de FTP à rsync pour les backup, ou au moins un système incrémental plutôt que full

    Implémentation

    Comme je sens que vous êtes des warriors,

    cyclops_warrior_concept_by_masterfulmind-d4vi7mx

    Un warrior, probablement.

    je vous propose de rentrer dans le vif du sujet avec les détails techniques.

    Unzip

    Je pars avec une archive zip, gentiment déposée par mon FAI, dans laquelle il y a des logs au format nfcapd, défini par Cisco.

    Nfdump

    Nfcapd est un fichier binaire, pas très comestible pour l'œil humain. J'ai donc trouvé nfdump, un programme qui permet de transformer les logs binaires en texte ASCII. ça ressemble à peu près à ça :

    2016-09-21 21:47:42.360     0.000 TCP         192.168.5.3:445   ->    192.168.5.249:56011        1       52     1

    csvify

    C'est mieux, mais c'est toujours pas optimal. Il y a notamment trois problèmes :

    • la taille du fichier ASCII par rapport au binaire nfcapd est importante, environ 9.5 fois plus ;
    • le texte ASCII est très irrégulier, le nombre d'espaces est très variable ;
    • sont inutiles :
      • la flèche ;
      • le protocole aussi dans la majorité des cas (si on fait des calculs de poids en octets) ;
      • les ports, sauf éventuellement dans le cas d'un téléchargement, le port source pour déterminer le type de trafic (HTTP : 80, POP/IMAP, IRC : 6667/6697...) ;
      • le "1" à la toute fin, qui désigne le nombre de flow, toujours à 1 dans mon cas, vu les options passées à nfdump.

    J'ai donc créé un préparser en C (il s'agit en fait d'une spécification flex, puis flex génère le code C correspondant, à l'aide d'automates) (il y en a deux, l'un d'entre eux affiche des couleurs, utile pour visualiser, interdit pour l'utilisation en production, parce qu'il génère bien trop de caractères d'échappement).

    En moyenne, mon préparser sort un fichier au format CSV qui fait 70% de la taille du fichier d'entrée, et est beaucoup plus normalisé. Il ne supprime cependant pas beaucoup d'informations (la flèche seulement).

    Parse/Somme et plus si affinités

    Maintenant qu'on s'est facilité la tâche, il ne reste plus qu'à analyser le tout.

    C'est le but du programme C++ suivant, qui va itérer sur les entrées du CSV qu'on a produit, et ranger les paquets aux bons endroits.

    Enfin, en théorie. Parce qu'on se retrouve devant quelques petits problèmes :

    • Il y a des paquets qui on une durée de passage au travers du réseau nulle, et pour faire des graphs, c'est pas top. Il faut donc gérer cela.
      • On attribue une valeur faible à la durée de transmission.
      • On effectuera une moyenne (moyenne simple ou mieux, implémentation de l'algorithme SMA (ici en fr) pour lisser la courbe obtenue.
    • Même si le C/C++ est rapide, la partie lecture du fichier sur disque est très rapide, la partie traitement est lente, la partie écriture des résultats sur disque est rapide.
      • On adopte donc l'architecture suivante pour le programme C++ :

    C++ n-multithread

    Architecture du programme C++

    Le thread lecteur en haut lit depuis le disque à gauche, range les éléments dans la std::queue fileQ protégée par un mutex (fileQmutex, je suis d'une originalité à toute épreuve), tandis que les n threads workers vont travailler à générer la donnée, puis vont passer leur résultat à la liste de résultats resQD. Finalement le thread écrivain va réunir la liste de résultats, et l'écrire sur disque (à droite).

    Comme vous le voyez, beaucoup de choses ont été faites, beaucoup de choix, et j'en passe de très nombreux (j'en mettrai en PS), et il reste beaucoup à accomplir, notamment :

    • la doc : pour la contiuation du projet ;
    • le manuel d'uilisation pour l'exploitation ;
    • un How-to pour installer/améliorer le bidule ;
    • utiliser la classe Traffic() du programme C++ pour effectivement ranger les paquets dans des types de Traffic (par port pour le protocole, par IP source pour les utilisateurs les plus gourmands, par IP destination pour les serveurs les plus consultés/les plus consommateurs de ressource, et pour déterminer combien d'utilisateurs sont effectivement gênés lors d'une saturation).

    Des graphs. For free.

    Une fois que l'on a écrit le CSV, il ne reste qu'à faire le graph. le programme gnuplot peut faire ça très bien.

    Un chef d'orchestre

    Tout cela est opéré depuis un script bash.

    Voilà, j'espère que ça vous a plu, si ça vous amuse, le projet est libre, disponible ici. Comme d'habitude n'hésitez pas à commenter et à bientôt !

     

    Motius

    PS : j'ai appris plein de choses, mais qui sont de l'ordre du détail pour ce projet, alors je vais en lister quelques unes ici :

    • mes CPU aiment travailler de manière symétrique. quand je mets n workers, je gagne plus en pourcentage de vitesse en passant d'un nombre impair à un nombre pair. Les chiffres :
      • de 1 à 2 CPU : 3.37 fois plus rapide (le programme n-multithread est plus lent que ne l'était le programme monothread équivalent si l'on ne met qu'un seul thread worker, n=1) ;
      • de 2 à 3 CPU : 1.12 fois ;
      • de 3 à 4 CPU : 1.67 fois ;
    • les regex sont assez lentes, il y a potentiellement une bonne optimisation possible en bidouillant des C-strings au lieu des std::string du C++ (le pré-parser C est beaucoup beaucoup plus rapide que le programme C++, aussi parce que son travail ne consiste qu'en des substitutions/suppressions), mais le temps de développement serait plus long, et le risque d'overflow/segfault possible.
    • Ha chevron_right

      Du dev et du monitoring : YaPLog

      motius · pubsub.gugod.fr / hashtagueule · Monday, 1 August, 2016 - 22:00 · 7 minutes

    Bonjour à tous ! Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev. Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois ...

    Bonjour à tous !

    Aujourd'hui on va parler boulot, mais n'ayez pas peur, on parle du mien. Petite balade réseau et dev.

    Ce que j'aime le plus avec mon stage courant, c'est les défis. J'ai 6 semaines pour réaliser un prototype et sa documentation, et je peux vous dire que c'est serré. À près de trois quarts du projet, faisons un point sur son avancement, ce qu'il reste à faire, les technos, etc.

    Le cahier des charges

    Le monitoring réseau c'est tout sauf un sujet original. Hyper intéressant, sa mise en pratique évolue au fur et à mesure que les outils, les pratiques, et les protocoles évoluent.

    Vous vous demandez sûrement pourquoi je parle de développement dans ce cas. Il y a certes de nombreux programmes permettant de faire pas mal de choses. Voici pourquoi.

    • La meilleure raison : je n'ai pas accès (question de contrat avec l'opérateur réseau) aux routeurs, et il faudrait que j'aie la main sur une centaine de routeurs ;
    • la deuxième meilleure raison : on m'a demandé une tâche spécifique (du reporting quotidien, entre autres). Je ne dis pas que ce n'est pas possible avec un Nagios-Cacti ou un Munin, mais je n'aurais pas commencé ce qu'on m'a demandé (voir ce qui suit) dans le temps imparti ;
    • une raison pratique : on ne change pas la configuration d'une centaine de routeurs à travers le monde en 40 jours. C'est à peine suffisant pour traverser un désert.

    Que veut-on savoir ? La question initiale est "quand est-ce que ma ligne est saturée" ce qui est déjà visible sur les logs du FAI (Nagios-Cacti, exporté en petites images JPG) mais n'est pas suffisant pour plusieurs raisons :

    • on n'a pas de système d'alerte (facile à mettre en œuvre) ;
    • on n'a pas de données exploitables plus avant (l'image JPG, c'est pas beaucoup) ;

    On veut donc savoir quand est-ce que la ligne est saturée. C'est-à-dire, a priori, quand est-ce que la ligne atteint sont débit maximal.

    Le but suivant est de déterminer si une saturation est gênante. Cela comprend plusieurs facteurs :

    • l'heure : une saturation entre 2h et 4h du mat' parce qu'on fait des backup, ça ne gêne personne (ça pourrait, mais pas dans ce cas) ;
    • une saturation d'une seconde dans la journée n'est pas grave. De même, 250 saturations quasi-instantanées, réparties de 8h à 20h ne gênent pas le travail ;

    Une fois qu'on a déterminé si une saturation est gênante, on veut savoir qui elle gêne et pourquoi, afin de voir si on peut faire quelque chose pour améliorer l'utilisation des ressources.

    • Par exemple, lorsqu'un site est saturé, cela gêne-t-il 1 ou 50 personnes ? Dans le premier cas, la personne est peut-être trop gourmande en ressources, dans le second, le lien est peut-être mal dimensionné.
    • Certains processus peuvent être automatisés la nuit, certaines pratiques changées : backup du travail pendant la pause de midi et la nuit, passer de FTP à rsync pour les backup, ou au moins un système incrémental plutôt que full

    Implémentation

    Comme je sens que vous êtes des warriors,

    cyclops_warrior_concept_by_masterfulmind-d4vi7mx

    Un warrior, probablement.

    je vous propose de rentrer dans le vif du sujet avec les détails techniques.

    Unzip

    Je pars avec une archive zip, gentiment déposée par mon FAI, dans laquelle il y a des logs au format nfcapd, défini par Cisco.

    Nfdump

    Nfcapd est un fichier binaire, pas très comestible pour l'œil humain. J'ai donc trouvé nfdump, un programme qui permet de transformer les logs binaires en texte ASCII. ça ressemble à peu près à ça :

    2016-09-21 21:47:42.360     0.000 TCP         192.168.5.3:445   ->    192.168.5.249:56011        1       52     1

    csvify

    C'est mieux, mais c'est toujours pas optimal. Il y a notamment trois problèmes :

    • la taille du fichier ASCII par rapport au binaire nfcapd est importante, environ 9.5 fois plus ;
    • le texte ASCII est très irrégulier, le nombre d'espaces est très variable ;
    • sont inutiles :
      • la flèche ;
      • le protocole aussi dans la majorité des cas (si on fait des calculs de poids en octets) ;
      • les ports, sauf éventuellement dans le cas d'un téléchargement, le port source pour déterminer le type de trafic (HTTP : 80, POP/IMAP, IRC : 6667/6697...) ;
      • le "1" à la toute fin, qui désigne le nombre de flow, toujours à 1 dans mon cas, vu les options passées à nfdump.

    J'ai donc créé un préparser en C (il s'agit en fait d'une spécification flex, puis flex génère le code C correspondant, à l'aide d'automates) (il y en a deux, l'un d'entre eux affiche des couleurs, utile pour visualiser, interdit pour l'utilisation en production, parce qu'il génère bien trop de caractères d'échappement).

    En moyenne, mon préparser sort un fichier au format CSV qui fait 70% de la taille du fichier d'entrée, et est beaucoup plus normalisé. Il ne supprime cependant pas beaucoup d'informations (la flèche seulement).

    Parse/Somme et plus si affinités

    Maintenant qu'on s'est facilité la tâche, il ne reste plus qu'à analyser le tout.

    C'est le but du programme C++ suivant, qui va itérer sur les entrées du CSV qu'on a produit, et ranger les paquets aux bons endroits.

    Enfin, en théorie. Parce qu'on se retrouve devant quelques petits problèmes :

    • Il y a des paquets qui on une durée de passage au travers du réseau nulle, et pour faire des graphs, c'est pas top. Il faut donc gérer cela.
      • On attribue une valeur faible à la durée de transmission.
      • On effectuera une moyenne (moyenne simple ou mieux, implémentation de l'algorithme SMA (ici en fr) pour lisser la courbe obtenue.
    • Même si le C/C++ est rapide, la partie lecture du fichier sur disque est très rapide, la partie traitement est lente, la partie écriture des résultats sur disque est rapide.
      • On adopte donc l'architecture suivante pour le programme C++ :

    C++ n-multithread

    Architecture du programme C++

    Le thread lecteur en haut lit depuis le disque à gauche, range les éléments dans la std::queue fileQ protégée par un mutex (fileQmutex, je suis d'une originalité à toute épreuve), tandis que les n threads workers vont travailler à générer la donnée, puis vont passer leur résultat à la liste de résultats resQD. Finalement le thread écrivain va réunir la liste de résultats, et l'écrire sur disque (à droite).

    Comme vous le voyez, beaucoup de choses ont été faites, beaucoup de choix, et j'en passe de très nombreux (j'en mettrai en PS), et il reste beaucoup à accomplir, notamment :

    • la doc : pour la contiuation du projet ;
    • le manuel d'uilisation pour l'exploitation ;
    • un How-to pour installer/améliorer le bidule ;
    • utiliser la classe Traffic() du programme C++ pour effectivement ranger les paquets dans des types de Traffic (par port pour le protocole, par IP source pour les utilisateurs les plus gourmands, par IP destination pour les serveurs les plus consultés/les plus consommateurs de ressource, et pour déterminer combien d'utilisateurs sont effectivement gênés lors d'une saturation).

    Des graphs. For free.

    Une fois que l'on a écrit le CSV, il ne reste qu'à faire le graph. le programme gnuplot peut faire ça très bien.

    Un chef d'orchestre

    Tout cela est opéré depuis un script bash.

    Voilà, j'espère que ça vous a plu, si ça vous amuse, le projet est libre, disponible ici. Comme d'habitude n'hésitez pas à commenter et à bientôt !

     

    Motius

    PS : j'ai appris plein de choses, mais qui sont de l'ordre du détail pour ce projet, alors je vais en lister quelques unes ici :

    • mes CPU aiment travailler de manière symétrique. quand je mets n workers, je gagne plus en pourcentage de vitesse en passant d'un nombre impair à un nombre pair. Les chiffres :
      • de 1 à 2 CPU : 3.37 fois plus rapide (le programme n-multithread est plus lent que ne l'était le programme monothread équivalent si l'on ne met qu'un seul thread worker, n=1) ;
      • de 2 à 3 CPU : 1.12 fois ;
      • de 3 à 4 CPU : 1.67 fois ;
    • les regex sont assez lentes, il y a potentiellement une bonne optimisation possible en bidouillant des C-strings au lieu des std::string du C++ (le pré-parser C est beaucoup beaucoup plus rapide que le programme C++, aussi parce que son travail ne consiste qu'en des substitutions/suppressions), mais le temps de développement serait plus long, et le risque d'overflow/segfault possible.