Un coup de boost

ajouté le 07.08.2011 dans Documentation • par SwissTenguCommentaires (4)
Tags: nginx memcached optimisation perl python

Mon blog a eu quelques petits hoquets ces dernières heures - c'est normal.
Je me suis amusé à mettre en place diverses choses, dont memcached.

J'ai aussi pris un peu de temps pour mettre en place la "minification" de mes divers javascripts et CSS, ce qui permet d'accélérer un peu le temps de chargement, et de minimiser la taille des fichiers.

Au final, le site devrait aller un poil plus vite, modulo la durée de vie du cache (quelques heures).

Voici comment j'ai pu faire tout ceci:

memcached


J'ai commencé par vouloir employer le décorateur @beaker_cache intégré à Pylons. Mal m'en a pris, les clefs générées dans le cache sont tout bonnement imbuvables, et je n'ai pas trouvé de moyen de sortir les contenus par nginx (ce qui permet donc de passer outre la lenteur de pylons).
Il m'a donc fallu mettre en place une gestion du cache personnalisée. J'ai créé un nouveau fichier lib/cache.py:
Code: python
#-*- coding:utf-8 -*-
import memcache
class Cache(object):
  def __init__(self, url, timeout=None, prefix=None): 
    self.mc = memcache.Client([url]) 
    self.timeout = timeout or 300 
    self.prefix = prefix 

  def get(self, key): 
    return self.mc.get(key) 

  def set(self, key, value, timeout=None): 
    if self.prefix: 
      key = self.prefix + key 
    self.mc.set(key, value, timeout or self.timeout) 

  def revoke(self, key):
    self.mc.delete(key)

import decorator
import app_globals
from pylons.configuration import PylonsConfig
def cache(timeout=None):
  config = PylonsConfig()
  g = app_globals.Globals(config)
  def wrapper(func, *args, **kwargs): 
    request = args[0]._py_object.request 
    content = func(*args, **kwargs) 
    key = request.path_qs 
    g.cache.set(key, str(content.encode('utf-8')), timeout) 
    return content 
  return decorator.decorator(wrapper) 


En gros, je crée une classe "Cache" et un décorateur me permettant d'injecter mes contenus dans le cache - en l’occurrence memcached. La clef est prédictible et simple: le chemin appelé pour accéder au contrôleur, avec les arguments etc.

Ensuite j'ai dû instancier la classe dans lib/app_globals.py:
Code: python
from blogger.lib.cache import Cache
class Globals(object):
    def __init__(self, config):
        self.cache = Cache(url='127.0.0.1:11211')


Après, bin y a plus qu'à employer le décorateur dans les contrôleurs:
Code: python
from blogger.lib.cache import cache
class BlogController(BaseController):
  @cache(600)
  def post(self, id):
    # display a post, with comments
    try:
      c.post = Session.query(Content).filter(Content.id == id).one()
    except:
      redirect( url(controller='blog', action='index') )
    c.title = c.post.title
    c.comments = Session.query(Comment).filter(Comment.commentID == id).order_by(Comment.time.asc()).all()
    return render('/blog/%s/post.html' % self.theme)


Easy ;).

Vient ensuite la partie fun:
dans l'action "post", il affiche les commentaires. Et le formulaire pour en ajouter, avec mon fameux captcha. Ooops, lui, il doit être dynamique !

C'est là que le SSI (Server-Side Inclusion) intervient :).

J'ai donc créé un contrôleur appelé "ssi" de manière à savoir à quoi il sert. Dedans, y a les diverses actions utiles à regénérer le contenu réellement dynamique. Dans mes templates mako, j'ai simplement mis le code permettant à nginx de faire les appels en interne:
Code:
<!--# include file="/ssi/formular/${c.post.id}" -->


Et Voilà, le formulaire est à nouveau dynamique ! Top-moumoute quoi.

Maintenant, reste la configuration nginx. Là, je me suis un peu explosé les dents, principalement à cause de memcached, vu qu'au début je ne savais pas quelles étaient les clefs employées. Et je suis tombé sur ce post. Et j'ai vu la Lumière.
De là, tout a été rapide, et j'ai obtenu cette configuration pour nginx:
Code: perl
server {
# ..... blabla etc
  location /ssi {
    proxy_pass        http://localhost:9090/ssi;
    proxy_set_header  Host $host;
    proxy_set_header  SSL yes ;
    add_header X-Memcached "missed";
  }
  location / {
    proxy_set_header  Host $host;
    proxy_set_header  SSL yes ;
    ssi on;
    if ($request_method = POST) { 
      proxy_pass        http://localhost:9090;
    }
    set $memcached_key $uri;
    memcached_pass 127.0.0.1:11211;
    default_type text/html;
    error_page 404 = @pylons;
  }

  location @pylons {
    ssi on;
    proxy_pass        http://localhost:9090;
    proxy_set_header  Host $host;
    proxy_set_header  SSL yes ;
    add_header X-Memcached "missed";
  }
}


Que de l'amour, cette configuration ;).

Bref. Memcached est mis en place, marche... mon serveur ronronne, et tout va bien.

Mini-lui


Vient ensuite la partie "mes css/js ont des espaces vides, des retours à la ligne inutiles etc... Mais si je les vire, je pourrai plus trop les éditer". Que faire ??
Bah simple:
on va planter un peu de Perl dans la config nginx :)

Pour se faire, il faut ajouter le support Perl dans nginx - je vous laisse vous référer à la doc.
Ensuite, dans la partie http, j'ai ajouté ceci:
Code: perl
http {
# blablab...
  perl_modules perl;
  perl_require JSMinify.pm;
  perl_require CSSMinify.pm;
}


et dans mon vhost:
Code: perl
server {
  # blabla..
  location ~* \.css$ {
    root              /var/www/vhosts/blog.tengu.ch/blogger/blogger/public/;
    expires           30d;
    add_header        Cache-Control '30d, must-revalidate';
    try_files $uri @miniCSS;
  }
  location @miniCSS {
    perl CSSMinify::handler;
  }
  location ~* \.js$ {
    root              /var/www/vhosts/blog.tengu.ch/blogger/blogger/public/;
    expires           30d;
    add_header        Cache-Control '30d, must-revalidate';
    try_files $uri @miniJS;
  }
  location @miniJS {
    perl JSMinify::handler;
  }
  # blabla
}


J'vais être gentil et vous filer le code des deux minifiers - il est vraiment simple:
Code: perl
package CSSMinify;
use nginx;
use CSS::Minifier qw(minify);
sub handler {
  my $r=shift;
  my $cache_dir="/tmp"; # Cache directory where minified file will be kept
  my $cache_file=$r->uri;
  $cache_file=~s!/!_!g;
  $cache_file=join("/", $cache_dir, $cache_file);
  my $uri=$r->uri;
  my $filename=$r->filename;
  local $/=undef;
  return DECLINED unless -f $filename;

  if (! -f $cache_file) {
    open(INFILE, $filename) or die "Error reading file: $!";
    open(OUTFILE, '>' . $cache_file ) or die "Error writting file:
    $! $cache_file\n";
    minify(input => *INFILE, outfile => *OUTFILE);
    close(INFILE);
    close(OUTFILE);
  }

  $r->send_http_header('text/css');
  $r->header_out('Cache-Control', '30d, must-revalidate');
  $r->sendfile($cache_file);
  return OK;
}
1;
__END__

Code: perl
package JSMinify;
use nginx;
use JavaScript::Minifier qw(minify);
sub handler {
  my $r=shift;
  my $cache_dir="/tmp"; # Cache directory where minified file will be kept
  my $cache_file=$r->uri;
  $cache_file=~s!/!_!g;
  $cache_file=join("/", $cache_dir, $cache_file);
  my $uri=$r->uri;
  my $filename=$r->filename;
  return DECLINED unless -f $filename;

  if (! -f $cache_file) {
    open(INFILE, $filename) or die "Error reading file: $!";
    open(OUTFILE, '>' . $cache_file ) or die "Error writting file:
    $!";
    minify(input => *INFILE, outfile => *OUTFILE);
    close(INFILE);
    close(OUTFILE);
  }

  $r->header_out('Cache-Control', '30d, must-revalidate');
  $r->send_http_header('text/css');
  $r->sendfile($cache_file);
  return OK;
}
1;
__END__

(Note: je les ai mis dans /etc/perl)

Il me reste encore à intégrer la compression gzip - là j'ai eu pas mal de problèmes et ce n'est pas encore en place.
Il faut savoir que la méthode sendfile() bypass complètement les filtres gzip et charset (et peut-être encore un autre) - donc il faut faire tout cela dans le module perl, en live... et tenter de balancer le bon header, au bon moment, de la bonne manière... Pas encore gagné :(.

Bref. Avec ces 2-3 améliorations, le blog va un poil plus vite, y a moins de trucs qui transitent, mais c'est pas encore optimisé au max.

Il manque encore:
- arriver à faire passer les infos de cache aux fichiers "minimisés" (actuellement, ça semble pas marcher)
- concaténer tous les CSS et tous les JS en un seul fichier, si possible à la volée
- compresser les CSS et JS
- ajouter un peu de gestion de cache, par exemple lors que j'édite un post depuis l'admin, il devrait s'assurer qu'il est invalidé dans le cache. Actuellement, seul l'ajout de commentaires fait cela...
- voir si je peux aussi optimiser la taille d'une page, avec des filtres pour supprimer les espaces inutiles etc (HTML::Minifier doit sans doute exister :D)

Voilà... J'espère arriver à faire les points ci-dessus, particulièrement la partie gzip des css/js... Si j'y arrive, je ferai évidemment un post avec les explications et le code.

++

T.
 

Smartq 5

ajouté le 26.11.2009 dans Geek World • par SwissTenguCommentaires (35)
Tags: smartq optimisation

J'ai récemment acquis un Smartq5, fabriqué par http://en.smartdevices.com.cn/. Sans entrer dans les détails de la bête, c'est un petit MID pratique, remplissant bien son rôle, à condition de faire deux trois modifications ;).

Il est évident que j'ai pris la version Linux du smartq5 (il est aussi vendu avec un wince6). Après un rapide check, le firmware 5.0 est une ubuntu karmic koala modifiée, avec un petit kernel, un lxde comme IDE, et un openbox pour le WM. Cool, y a de quoi purger :)

On va commencer par le commencement : virer wbar, le machin immonde, gros et inutile en bas de l'écran.
Code: bash
~$ sudo -i
~# vim /etc/xdg/lxsession/LXDE/autostart
vim: command not found

.... Ok. bon. on va faire le gros de la tâche maintenant.

Smartdevices a prévu qu'on puisse installer facilement les packages sur la carte SD. Pour ma part, j'ai une carte SD de 8G. Ça s'avère pratique.
Un petit setup s'impose. Pour cela, deux manières :

Je suis une flemme


Code: bash
~# leafpad /usr/lib/python2.5/site-packages/GDebi/SDInst.py
# modifier la valeur de LOOP_FILE_SIZE, mettre par exemple 4096
# attention, selon le degré de mise à jour de votre OS, cela peut aussi être
# /usr/lib/python2.6/dist-packages/GDebi/SDInst.py
~# gdebi-gtk

et attendre que le temps passe (c'est relativement long, ne surtout PAS interrompre la procédure!)

J'suis un vrai, vas-y, balance le code


Code: bash
~# dd if=/dev/zero of=/media/disk/soft.img bs=512 count=8000000 # créer une image de 4G
~# mkfs.ext3 /media/disk/soft.img
~# mkdir -p /mnt/loop # normalement le dossier existe, mais...
~# mount -o loop /media/disk/soft.img /mnt/loop
~# mount -t aufs -o rw,si=c6d1c180,xino=/mnt/loop.aufs.xino,br:/mnt/loop=rw:/usr=ro none /usr

En gros, on crée une image de 4G, on la monte en loopback, et on la plante dans un AUFS lié à /usr. Plus propre qu'un mount -o bind ou autres, et permet de laisser le /usr d'origine en place.
On verra la partie "fstab" après.

Bref. Reprenons :
Code: bash
~# apt-get update
~# apt-get install -y vim screen
~# vim /etc/xdg/lxsession/LXDE/autostart
# commenter la première ligne "@xcompmgr &"
# commenter la dernière ligne "@wbar &"
~# killall -9 wbar
~# killall -9 xcompmgr


Cool, deux trucs inutiles de moins.

Mais au fait, le "pcmanfm -d", il sert à quoi? Simple : à faire le montage de la SD "automatiquement" lors de l'insertion, plus lancer des scripts qui vont planter un dossier "apt" dessus (pour le cache et la bdd), ainsi que le montage AUFS si dispo.
Et c'est tout.

Hmmm. bon, donc en fait, on peut le shooter :)

Pour se faire, c'est simple :
Créer un dossier /mnt/disk, qui remplacera /media/disk par la suite[/li]
Mettre les entrées qui vont bien dans le fstab (je vous conseille de faire un petit "mount" pour voir les options) [/li]
Ne pas oublier le bind de /mnt/disk/apt sur /var/cache/apt[/li]
Commenter la ligne dans le autostart[/li]
Rebooter le smartq[/li]

La mise en place dans /mnt évite que le dossier ne soit détruit lors de chaque boot par notre ami HAL. Il est évident que le symlink dans le home de user ne marchera plus, en passant ;). A vous de le corriger.

Bien. Donc là, on a un LXDE sans pcmanfm (ou du moins son daemon), sans wbar, sans xcompmgr... Ok, mais on peut faire mieux, sans doute... ?

Oui. On peut. Il suffit de dire à LXDE de ne PAS gérer le desktop. A ce moment-là, openbox prendra toute la responsabilité de la chose, et lxde ne sera même pas lancé. On peut directement commenter la ligne "@lxde-settings-daemon" de l'autostart, et tout ira mieux.

Reste à configurer un peu openbox. Pour se faire, il vous faudra menumaker. Après 2-3 recherches, il n'est pas dispo dans ubuntu (bah tien...). Il vous faudra aller le downloader (sur la SD, bien entendu), et le lancer de la manière suivante :
Code: bash
~$ mmaker -s Console OpenBox3

Le menu généré se trouvera dans ~/.config/openbox/menu.xml. Libre à vous de le modifier si besoin.

Pour le fond d'écran, je conseille d'installer le package "hsetroot", qui permet de mettre des images de manière plus pratique que xsetroot (qui bouffe que du bitmap). Ensuite, trouvez une image (800x480), mettez-la sur la SD, et mettez ceci dans /etc/xdg/lxsession/LXDE/autostart :
@/usr/bin/hsetroot -center /mnt/disk/<votre image>

Vous pouvez aussi modifier l'image de boot en modifiant l'image située dans :
/etc/splashy/themes/default/background.png
Attention à bien mettre un PNG de 800x480px.

Voilà... Je crois que c'est tout pour le moment. Mon Smartq a résisté à ce traitement (moyennant quelques flashages après avoir tout cassé), donc je pense qu'avec les quelques indications présentes, cela devrait aller.

Pour info, j'ai mis en place http://smartq.akemi.internux.c..., contenant pour le moment quelques liens et le firmware d'origine v5.0, ubuntu. Au final, un pote (Aelia sur #nimo@irc.geeknode.org) va compiler une gentoo pour les smartq, et mettra les binaires ainsi qu'un stage3 sur cette adresse ('fin normalement, s'il ne s'est pas suicidé entre deux avec le nombre de problèmes qu'il a relevés..)

A suivre donc ;)

Enjoy!

A+

Tengu