PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Le compteur mal connu : l’HyperLogLog

mardi 7 octobre 2014 à 16:02

Non, ce n’est pas une faute de frappe. l’HyperLogLog est un algo qui permet de compter approximativement des éléments uniques en prenant très peu de mémoire.

Prenez par exemple un compteur d’IP uniques. En Python, on l’implémenterait en mettant les adresses IP dans un set() (puisqu’ils éliminent les doublons) et pour obtenir le nombre d’IP uniques, on ferait len() sur le set.

Une bonne stratégie, performante (les sets sont très rapides pour ce genre d’usage), simple, mais avec un défaut : la taille du set ne va cesser d’augmenter au fur et à mesure qu’on le remplit d’IP puisqu’il nous faut l’historique de toutes celles rencontrées pour éviter les doublons.

L’HyperLogLog répond à cette problématique : il tient un journal probabiliste qui va se remplir au fur et à mesure qu’on rencontre des nouveaus éléments. On peut ensuite demander au journal combien d’éléments uniques il a rencontré, et il répond avec une marge d’erreur.

Avantage : la taille en mémoire est fixe.
Désavantage : le compteur n’est pas parfaitement précis.

La précision obtenue est dépendante de la place en mémoire, par exemple si on on tolère 1% d’erreur, le journal prendra au maximum 12kb, permettant de compter jusqu’à 2^64 items.

Bref, si vous faites juste un compteur à afficher en pied de page de votre site, c’est un très bon compromis. On peut accepter d’avoir un peu plus ou un peu moins de visiteurs que la réalité qui s’affiche, sachant que la stat elle-même n’est pas vraiment réprésentative de la réalité (IP != de visiteurs uniques).

En Python, il existe une lib (uniquement 2.7 il me semble) pour ça :

>>> import hyperloglog, random
>>> hll = hyperloglog.HyperLogLog(0.01)  # on accepte une erreur de 1%
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("219.81.118.147")
>>> print len(hll)  
2
>>> for x in xrange(1000):
... ip = ".".join(str(random.randint(1, 255)) for x in range(4))
... print ip
... hll.add(ip)
114.208.49.91
11.72.239.16
67.56.229.66
191.62.59.163
61.104.232.43
110.58.69.141
246.123.30.234
244.246.65.219
98.93.193.114
185.143.143.69
191.177.161.213
...
>>> print len(hll) # 1000 items unique. Environ :)
1004

Vous pouvez également profiter de l’HyperLogLog via une extension PostGres ou en utilisant une version récente de Redis.

La plupart des compteurs sur les sites sont complètement bidons, alors vous, honnête que vous êtes, embrassez l’approximatif ! C’est presque la vérité. Presque.

Travailler sur une lib externe à votre projet proprement en Python

lundi 6 octobre 2014 à 14:53

Quand on a une lib externe en dépendance à son projet, on veut être capable de l’importer MAIS pouvoir en modifier le code.

L’installer avec pip ou python setup.py install va copier le code dans le dossier site-packages et ce n’est pas ce que l’on veut car ça oblige à refaire l’installation à chaque modif.

Le mettre dans un dossier “libs” qu’on ajoute au PYTHON PATH ou pire, ajouter chaque dépendance au PYTHON PATH n’est pas une solution qu’on est fier d’exhiber au hackerspace du coin.

On s’en remet donc généralement à un symlink, sans savoir qu’il existe en fait un outil fait pour ça :

python setup.py develop

Cette commande va ajouter des entrées spéciales dans des fichiers placés dans site-packages qui vont vous permettre d’utiliser votre lib comme si elle était installée. Mais en vérité il va chercher le code dans votre dossier de dev, donc si vous modifiez le code, vous avez toujours la version de votre lib la plus fraîche.

Une fois qu’on a une lib stable, on peut retirer ce lien avec :

python setup.py develop --uninstall

Et installer la lib normalement.

Notez que tout ceci ne fonctionne que si le setup.py utilise setuptools et non distutils qui provoque l’erreur error: invalid command ‘develop’.

Comme setuptools inclut maintenant le meilleur de distutils, on peut remplacer :

from distutils.core import setup

Par :

from setuptools import setup

Sans soucis.

pip vient lui aussi avec un outil pour ça en la forme du flag -e qui fait exactement la même chose. Exemple :

pip install -e /chemin/local/vers/projet

Cela fonctionne comme pour python setup.py develop --uninstall mais on bénéficie de tous les goodies de pip en prime.

Néanmoins, pip pousse plus loin l’automatisation. Si vous faites pip install -e sur un repo distant, il va également cloner pour vous le code.

Ex :

pip install -e git+https://git@github.com/sametmax/minibelt.git#egg=minibelt

Et on retrouvera un clone du projet dans /chemin/vers/virtualenv/src/ qui sera importable, modifiable et pushable. Le résultat est automatisable, puisque pip freeze le prend en compte:

$ pip freeze
argparse==1.2.1
-e git+https://git@github.com/sametmax/minibelt.git@b898155b40d7de73cc404db7d274128f2b2fc330#egg=minibelt-master
wsgiref==0.1.2

L’URL est assez batarde à trouver par contre, car elle doit toujours finir par #egg=nom-du-projet et commencer par un double protocole, celui du VCS et celui du transport.

P.S: j’ai remis la coloration syntaxique et les iframes (donc la musique). J’aime bien ce thème là, mais la header chie sa mère. Est-ce que vous l’aimez suffisamment pour que je me casse le cul à le réparer ?

Trouver ce qui prend de la place en ligne de commande

lundi 29 septembre 2014 à 11:37

Windows a l’excellent WindDirStats, et Gnome a Baobab. Mais régulièrement je suis en SSH sur un server je lance df pour voir comment vont les disques. La vérité c’est que ceux avec un monitoring installés ne sont pas la majorité dans mes missions.

À un moment ou un autre, une partition est pleine. Et une fois qu’on a vidé /var/log et que le taux de remplissage ne semble pas baisser, il faut enquêter.

En général, je recherche sur le net une incantation magique du genre :

# trouver les fichiers qui prennent le plus de place
find . -type f -print0 | xargs -0 du | sort -n | tail -10 | cut -f2 | xargs -I{} du -sh {}
# trouver les dossiers qui prennent le plus de place
find . -type d -print0 | xargs -0 du | sort -n | tail -10 | cut -f2 | xargs -I{} du -sh {}

Puis je me souviens qu’il y a du, alors je commence à faire un truc comme ça :

du -a . | sort -n -r | head -n 10

Mais un jour je suis tombé sur un post d’un forum disant que je me cassais les couilles pour rien, et qu’utiliser ncdu était plus simple. Sous Ubuntu :

sudo apt-get install ncdu
ncdu

Et effectivement, je me cassais les couilles pour rien, c’est plus simple.

Accélérer les listings de l’admin Django avec beaucoup de ForeignKeys

dimanche 28 septembre 2014 à 09:32

Je pensais que ces astuces étaient très connues, mais quand j’en ai eu besoin, j’ai galéré pour mettre la main dessus. Du coup blogage, blogation, blogitude.

L’admin Django permet de faire automatiquement un listing des objets voulus afin de les modifier. Si un modèle a une ForeignKey et qu’on définit le champ en list_editable, Django va pondre un drop down pour choisir parmi les relations possibles.

Or, le framework fait une requête pour chaque objet de la liste. Si vous avez deux ForeignKeys sur votre modèle et 20 objets (valeur par défaut) dans la liste, ça vous fait donc 40 queries, juste pour afficher ces deux drop down. Doh.

On peut néanmoins forcer Django à cacher le résultat de la première requête, réduisant ainsi le compte à 1, avec cette étrange astuce d’une mère au foyer (consultants hate her !) :

class VotreClasseAdmin(admin.ModelAdmin)
 
    # Cette méthode est appelée pour créer le champ de formulaire de pour
    # chaque objet
    def formfield_for_dbfield(self, db_field, **kwargs):
 
        request = kwargs['request']
        formfield = super(ProductPageAdmin, self).formfield_for_dbfield(db_field, **kwargs)
 
        # Si le champ est editable dans la liste
        if db_field.name in self.list_editable and hasattr(formfield, 'choices'):
 
            # On tente de récupérer la query en cache
            cache_attr_name = '_%s_cache' % db_field.name
            choices_cache = getattr(request, cache_attr_name)
            if choices_cache is not None:
                formfield.choices = choices_cache
 
            # Pas de cache, on force Django à faire la query en accédant
            # au descripteur .choices et on met le resultat
            # en cache
            else:
                setattr(request, cache_attr_name, formfield.choices)
 
        # On retourne le champ de notre formulaire avec sa valeur mise
        # en cache
        return formfield

Malheureusement, il peut arriver qu’il y ait tellement d’objets que c’est votre navigateur qui panique. En effet, si vous avez un modèle Promotion avec un lien vers un modèle Produit et que vous avez 5000 produits en base, vous allez créer 20 <select> avec 5000 <options> dedans. 100000 balises dans une page, ça peut faire mal.

Dans ce cas, il vaut mieux désactiver la possibilité de faire un drop down, et mettre un champ text à la place dans lequel on entre un ID en utilisant :

class VotreClasseAdmin(admin.ModelAdmin)
 
     raw_id_fields = ('nom_de_votre_champ',)

Django n’est pas chien, et vous mettra un petit bouton en forme de loupe pour chercher l’ID dont vous avez besoin.

Si vous avez beaucoup de ForeignKey non éditables (readonly_fields), vous pouvez aussi vous fendre d’un select_related :

class VotreClasseAdmin(admin.ModelAdmin)
 
    def queryset(self, request):
        # On filtre le queryset que Django va utiliser pour populer la liste
        return super(MyAdmin, self).queryset(request).select_related('myfield')

Ça ne marche que sur les non éditables, car pour eux Django ne créé pas de champ de formulaire.

Passer d’un clé Putty (ppk) à une clé OpenSSH

samedi 27 septembre 2014 à 14:29

Cas d’école, mon client me donne la clé pour accéder à son instance Amazon (bouh !) et il me file une clé Putty puisqu’il est sous Windows.

Heureusement, Putty existe aussi sous nunux. Par exemple sous Ubuntu :

sudo apt-get install putty-tools

Et il embarque tout ce qu’il faut pour convertir sa clé :

puttygen cle.ppk -O public-openssh -o nouvelle_cle.pub
puttygen cle.ppk -O private-openssh -o nouvelle_cle

Y a plus qu’à mettre dans son dossier .ssh. En prime, je rajoute généralement ça dans .ssh/config :

Host ip ou domaine
HostName ip ou domaine
User username pour le server
IdentityFile ~/.ssh/nouvelle_cle

Comme ça pas besoin de sélectionner la clé manuellement à chaque connexion, si SSH voit cette IP, il me prend la bonne clé.

Mon dossier .ssh est un simple lien vers le vrai dossier dans la partition Truecrypt. Malgré la disparition du projet, j’ai pas trouvé mieux.