César D. Rodas http://cesar.la From the Jungles of Paraguay Fri, 18 Sep 2009 17:08:59 +0000 http://wordpress.org/?v=2.7 en hourly 1 Cache con SimpleORM http://cesar.la/cache-con-simpleorm.html http://cesar.la/cache-con-simpleorm.html#comments Fri, 31 Jul 2009 08:06:10 +0000 César Rodas http://cesar.la/?p=392 En esta entrada escribiré detalladamente sobre una característica que me encanta SimpleORM y que, en mi humilde opinión, es fundamental para la escalabilidad, estoy hablado el Cacheo de los datos. Según su definición, el Cache es la duplicación de datos a un medio más rápido para así reducir el tiempo de acceso a dichos datos, y así optimizar el rendimiento del sistema. Dicha duplicación es volátil, es decir su eliminación o alteración no afecta a los datos originales.

El gran cuello de botella de los sistemas Webs son las bases de datos, como un amigo suele decir “the fastest database applications are those that avoid accessing the database as much as possible” (las mas aplicaciones mas rápidas utilizando bases de datos son las que la evitan lo mas posible, o algo así) lo cual tiene gran sentido, ya que las bases de datos bastante lentas, pero son necesarias. Son lentas ya que detras de una simple consulta SQL existen varios pasos, como ser (solo algunos pasos, no soy un experto esta área):

  1. Conexión a la base de datos (o utilizar una existente)
  2. Autenticación
  3. Compilación del SQL (o interpretación)
  4. Se planea y optimización lo generado/interpretado, buscando índices que agilicen la ejecución de la consulta
  5. Ejecución de la consulta, leyendo datos del I/O (generalmente el disco duro).
  6. Se devuelven los resultados.

Los arriba mencionados, son solo algunos pasos genéricos que utilizan los manejadores de bases de datos, pero puede ser menos o mas dependiendo de la implementación, peor lo que no varia nunca es el exceso de trabajo por cada consulta, haciendo de esta, en la mayoría de los casos, la parte mas lenta de cualquier sistema, incluyendo la Web donde es un factor crucial el tiempo de respuesta.

¿Y como ser podría evitar?, seria insano de mi parte a estas alturas recomendar escribir códigos para acceder a los datos utilizando algún tipo de ordenamiento, seria re-inventar la rueda. Una solución, por cierto la mas simple, es utilizar algún tipo de Cache para los resultados de consultas que no cambian frecuentemente, guardando dicho cache en un medio de rápido acceso, podría ser un directorio, memoria RAM, memcached.

Un error bastante común, que vi especialmente en CMS y similares, es que aunque el resultado a la consulta está en el cache, la conexión a la base de datos es siempre realizada, ya que esta es realizada automáticamente al inicio del código, es por este motivo que SimpleORM tiene conexión de forma implícita, solo es realizada si es estrictamente necesariamente, ejemplo:

require "DB.php";
DB::setUser("root");
DB::setPass("foobar");
DB::setDB("testing");
DB::setDriver("mysql");

Esto, a pesar de la gran utilidad, tiene un pequeño defecto, no se comprueban si los parámetros de conexión son correctos (usuario, contraseña, permisos, etc) en único lugar, en realidad nada imposible para un buen programador (utilizar transacciones siempre que se modifica algo, y utilizar siempre manejo de errores para cada operación).

En realidad, lo anterior citado solo es una pequeña acotación, lo más importante es determinar si los resultados de una consulta tienen que ser guardados en cache, y por cuanto tiempo. Después de mucho pensar en una manera sencilla, mantenible y eficiente de definir si una consulta puede ser cacheada, no se me vino nada mas simple que esto, definir un método (que por defecto en la clase padre DB retorna falso) que retorna true si la consulta actual puede ser cacheada. Los parámetros que el método recibe son los siguientes, $sql que contiene la consulta a ser ejecutada, $values que contienen las variables referenciadas en la consulta SQL, y $ttl donde se puede especificar el tiempo de vida del cacheo, en segundos.

class User
{
    public $user; /* unique index */
    public $pass;
    /* otras columnas */
    public $nombre;
    public $email;

    function isCacheable($sql, $values, &$ttl) {
        if (count($values) == 1 && isset($values['username'])) {
            $ttl = 7200; /* dos horas */
            return true;
        }
        return false;
    }
}

Como lo vieron, la definición del cacheo es sencilla, en este caso seran cacheadas (por dos horas) todas las consultas que tengan como variable username, (analizar el SQL en sí no muy lento, por eso utilizo solo las variables). Esto quiere decir realizamos una selección, donde username es “foobar”, la primera vez se conectará a la base de datos y realizará la búsqueda, luego el resultado será guardado en cache y por las siguientes dos horas, para la misma búsqueda (donde username sea “foobar”) el resultado será proveida por el cache.

$user = new User;
$user->username = "foobar";
$user->load();

Pero no todo es tan sencillo, pues existe un pequeñisimo problema que sería lo siguiente, si los datos de la tabla user para username es “foobar” cambia, digamos el nombre, nuestro cache mostraría datos no actualizado por dos horas (en el peor de los casos).

$user = new User;
$user->username = "foobar";
if ($user->load()->valid()) {
    $user->nombre = "nuevo nombre";
    $user->save();
}

La solución más eficiente que pude imaginarme fue la siguiente. Creé una función que es ejecutada cuando una actualización es realizada (a futuro lo haré para inserciones y datos borrados), y ahí proveo una simple manera de reconstruir el cache. Solo miren el siguiente código, que deberíamos agregar a la clase User.

    protected function onUpdate ($changes, $sql, $params)
    {
        /* obtener los parametros de la consulta original */
        $values = $this->getQueryParams();
        /* crear un objeto con del mismo tipo del objeto actual */
        $class   = get_class($this);
        $table   = new $class( $values );
        if (count($values) == 1 && isset($values['username'])) {
            /* deshabilitar el cacheo temporalmente y hacer la consulta */
            /* así, los datos serán traídos de la base de datos, y luego */
            /* será guardado en el cache, así actualizaremos nuestro cache */
            $table->disableQueryCache();
            $table->load();
        }
    }

Como vieron (el código es auto-explicado) , a cada cambio de los datos lo que hago es, previa verificación si la consulta es cacheada, deshabilitar temporalmente el cacheo para las selecciones y realizar la misma consulta, que posterior a su ejecución sobre-escribe el cache, actualizándolo de esta forma. No soluciona todos los casos, pero ayuda bastante, ya que esta soluciona combinada con la creatividad del programador puede hacer un sistema que dependa del cache lo más posible.

Finalmente, solo falta elegir cual método de cacheo que será el utilizado, lo cual se realizado simplemente incluyendo uno de los Cache drivers, en la distribución viene para simples archivos y memoria RAM o pero no obstante cualquier persona puede crear su propio Cache driver extendiendo esta clase base.

]]>
http://cesar.la/cache-con-simpleorm.html/feed
Twitter (+OAuth) desde PHP (PECL/OAuth) http://cesar.la/twitter-oauth-desde-php-pecloauth.html http://cesar.la/twitter-oauth-desde-php-pecloauth.html#comments Thu, 30 Jul 2009 22:16:57 +0000 César Rodas http://cesar.la/?p=375 Con el aumento de la popularidad de Twitter, a cada día vemos miles de nuevas aplicaciones, por cierto algunas muy útiles, debido probablemente a su amplia API. En este post describiré como autenticarse utilizando OAuth (en mi ejemplo con PECL/OAuth), y como hacer algunas operaciones básicas. Antes que nada, en mis códigos (utilizado en algunos proyecto que aun no han visto la luz) he utilizado lo escritor por Rasmus en su blog, por lo cual esto podría ser similar (pero no igual).

Para el ejemplo, utilizaré la siguiente table (en MySQL):

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL auto_increment,
  `user` varchar(100) NOT NULL,
  `password` varchar(32) default NULL,
  `tw_stage` tinyint(1) not null default 0,
  `tw_user` varchar(100) default NULL,
  `tw_token` varchar(64) default NULL,
  `tw_secret` varchar(64) default NULL,
  `tw_followers` int(11) default NULL,
  `tw_following` int(11) default NULL,
  `tw_description` text,
  `tw_avatar` varchar(250) default NULL,
  `tw_location` varchar(250) default NULL,
  `tw_created_at` datetime default NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `user` (`user`)
) ENGINE=InnoDB;

Y el siguiente código (utilizando SimpleORM) para manejar la tabla de arriba:

class User extends DB
{
    /* Informaciones del usuario, del sistema en si */
    public $user;
    public $password;
    /* Informaciones de Twitter */
    public $tw_stage;
    public $tw_user;
    public $tw_token;
    public $tw_secret;
    public $tw_avatar;
    public $tw_following;
    public $tw_followers;
    public $tw_description;
    public $tw_location;
    public $tw_created_at;
}

La parte mas complicada del API es la autenticación en si, ya que tiene varios pasos.

define("KEY", "clave");
define("SECRET_KEY", "clave-privada");

$twitter = new OAuth(KEY,SECRET_KEY,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI);
$twitter->enableDebug();  

$user = new User;
$user->ID  = $_SESSION['user_id'];
if (!$user->load()->valid()) {
    die("invalid user");
}

La primera parte es muy sencilla, donde solicitamos a Twitter las claves (tokens) para la autenticación, luego guardamos en la base de datos (o podría ser $_SESSION). Luego con redirigimos a Twitter para que nuestro usuario se puede autenticar y autorizarnos el acceso.

    $token_info      = $twitter->getRequestToken('https://twitter.com/oauth/request_token');
    $user->tw_token  = $token_info['oauth_token'];
    $user->tw_secret = $token_info['oauth_token_secret'];
    $user->tw_stage  = 1;
    $user->save();
    header('Location: https://twitter.com/oauth/authenticate?oauth_token='.$user->tw_token);
    exit();

Luego si el usuario nos dio el acceso, Twitter redigirá a nuestro `callback URL` (que para comodidad debería ser el mismo script). Ahora tendremos que verificar si el usuario nos autorizó, y pedir los nuevos tokens para guardalos, ya que esos tokens serán los utilizados para autenticarnos, haciendo una analogía simple seria como usuario y contraseña.

    $twitter->setToken($_GET['oauth_token'],$user->tw_secret);
    $access_token_info = $twitter->getAccessToken('https://twitter.com/oauth/access_token');
    $user->tw_token    = $access_token_info['oauth_token'];
    $user->tw_secret   = $access_token_info['oauth_token_secret'];
    $user->tw_stage    = 2;
    $user->save();

Si hasta aquí todo fue bien (no hubo ninguna excepción lanzada) el usuario ya está autenticado y ya podemos realizar todas las operaciones que requieran autenticación para el actual usuario. Este proceso solo debe realizarse una vez, ya que los tokens no expiran. Ahora, esta porción de código extrae informacion referente al perfil del usuario, generalmente también lo incluyo en la autenticación.

    $twitter->setToken($user->tw_token,$user->tw_secret);
    $twitter->fetch('https://twitter.com/account/verify_credentials.json');
    $response = json_decode($this->getLastResponse());
    $user->tw_followers   = $response->followers_count;
    $user->tw_following   = $response->friends_count;
    $user->tw_description = $response->description;
    $user->tw_avatar      = $response->profile_image_url;
    $user->tw_name        = $response->name;
    $user->tw_location    = $response->location;
    $user->tw_created_at  = $response->created_at;
    $user->save();

Y poniendo todo lo arriba mencionado en un solo lugar obtendríamos algo así:

require "DB.php";
require "model.php";

define("KEY", "clave");
define("SECRET_KEY", "clave-privada");

$twitter = new OAuth(KEY,SECRET_KEY,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI);
$twitter->enableDebug();  

$user = new User;
$user->ID  = $_SESSION['user_id'];
if (!$user->load()->valid()) {
    die("invalid user");
}

/* Previniendo errores */
if ($user->tw_stage == 1 && !isset($_GET['oauth_token'])) {
    $user->tw_stage = 0;
}

try {

switch ($user->tw_stage) {
    case 0:
        $token_info = $twitter->getRequestToken('https://twitter.com/oauth/request_token');
        $user->tw_token  = $token_info['oauth_token'];
        $user->tw_secret = $token_info['oauth_token_secret'];
        $user->tw_stage  = 1;
        $user->save();
        header('Location: https://twitter.com/oauth/authenticate?oauth_token='.$user->tw_token);
        exit();
    case 1:
        $twitter->setToken($_GET['oauth_token'],$user->tw_secret);
        $access_token_info = $twitter->getAccessToken('https://twitter.com/oauth/access_token');
        $user->tw_token    = $access_token_info['oauth_token'];
        $user->tw_secret   = $access_token_info['oauth_token_secret'];
        $user->tw_stage    = 2;
        $twitter->setToken($user->tw_token,$user->tw_secret);
        $twitter->fetch('https://twitter.com/account/verify_credentials.json');
        $response = json_decode($this->getLastResponse());
        $user->tw_followers   = $response->followers_count;
        $user->tw_following   = $response->friends_count;
        $user->tw_description = $response->description;
        $user->tw_avatar      = $response->profile_image_url;
        $user->tw_name        = $response->name;
        $user->tw_location    = $response->location;
        $user->tw_created_at  = $response->created_at;
        $user->save();
        header("location: go-some-where.php");
        break;
}

} catch (Exception $e) {
    var_dump($e);
    die("error");
}

Una vez autenticado, podemos realizar todas las acciones que un usuario puede hacer, como leer y enviar tweets, ver los tweets de sus amigos, y otras cosas

function sendTweet($text)
{
    $twitter = new OAuth(KEY,SECRET_KEY,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_FORM);
    $twitter->setToken($user->tw_token, $user->tw_secret);
    $args = array('status'=> $text);
    try {
        $twitter->fetch('http://twitter.com/statuses/update.json',$args);
        $json = json_decode($twitter->getLastResponse(),true);
     } catch (Exception $e) {
         return false;
     }
    return isset($json['id']) ? $json['id'] : false;
}

Espero que les sea de utilidad, no duden en comentar si tienen dudas.

]]>
http://cesar.la/twitter-oauth-desde-php-pecloauth.html/feed
RFC: Simple-Storage http://cesar.la/rfc-simple-storage.html http://cesar.la/rfc-simple-storage.html#comments Tue, 28 Jul 2009 08:41:55 +0000 César Rodas http://cesar.la/?p=368

As you might know, days ago I released a new (still in beta) project named Simple-ORM, which is a very basic (but efficient) database abstraction. It’s major feature are:

  1. Easy iteration and data update inspired by ActiveRecords
  2. Built on top of PDO
  3. Implicit database connection, connecting only when it’s necessary
  4. Native query cache support.
  5. Support single-level transactions (limited by PDO)
  6. Data filtering with callback functions per column

Right now, it helps developer with some very basic standard SQL (insert, update, delete and a very limited select) and it is really simple to extend just with “raw” SQL. Another pretty cool feature is that it supports cache natively. Just visit the examples dir (I’ll add more samples ASAP).

It’s main disadvantage is that it is not a ORM properly, you still need to know SQL in order to make it usable for real life situations, I did it in that way because perform normal tasks (query based on objects, relationships or anything else that a proper ORM offers) at run-time is not efficient, it requires a lot of RAM, and is not that scalable. As much easier for the developer, harder for the server (me, right now)

In order to fix that (for me it’s okay, right I’m composing by hand all my SQL) I’m creating a project called Simple-Storage that will generate code, using Simple-ORM as base-code, to manipulate data. Additionally it would generate tables schemes, indexes (when it’s necessary), validation, relations, data-migration and several other features (when I would need something else I’ll add). Simple-storage would be a compiler (finally something useful where I can apply my knowledge from University) that will generate PHP (or any other if someone wants to extend it) code to manipulate the tables. By the fact that it wouldn’t perform any sort of checking/validation/generate-SQL/etc at run time and it has cache, it will be very scalable, and useful (IMHO).

Right now I’m sharing (expecting a feedback) about its possible syntax (I’d to have something clear, easy to learn/read and useful):

/**
 *  Simple table definition, we create a table called
 *  "posts".
 *
 */
(set default length 100)
table posts
    title:    string
    uri:      string
    abstract: text
    content:  text
    lang:     langs

/**
 *  Tag table.
 *
 */
table tags
    tag: string(50)
    lang: langs

/**
 *  This is the simplest table, it just contains
 *  a language list.
 *
 */
table langs:
    lang string(20)

/**
 *  Many-to-many relations ships, a post might
 *  has several tags, and a tag might has several posts.
 */
posts has many tags
tags  has many posts

/**
 *  we create a "hook" on table "posts" in order to
 *  recreate cache when a give post is updated.
 */
onUpdate: posts
    delete cache getPostByUri uri

/**
 *  Queries
 */

/**
 *  Create a method into the "posts" model that will retrieve three columns
 *  (title, lang.lang and uri) from posts that has some tag or set of tags.
 */
getPostByTag: posts
    columns:
        title
        lang.lang
        uri
    filters:
       posts in tags
    cache: 1h

/**
 *  Create a method that retrieves the entire post from based on its
 *  "uri", this query is cached for 1 hour
 *
 */
getPostByUri: posts
    uri equalto $1
    cache: 1h

/**
 *  Another query
 */
getPostByLang: posts
    lang equalto langs

This is a sample script that will create three tables (posts, tags and lang), relationships and some method to read data from the table. I’d like to get your thoughts about the language and idea in general.

]]>
http://cesar.la/rfc-simple-storage.html/feed
Modelado de datos — A mi estilo :) http://cesar.la/modelado-de-datos-a-mi-estilo.html http://cesar.la/modelado-de-datos-a-mi-estilo.html#comments Tue, 21 Jul 2009 10:28:43 +0000 César Rodas http://cesar.la/?p=340 Siguiendo a mi post sobre MVC hecho en casa, que en mi humilde opinión fue útil, ya que hubo muchos comentarios y correos electrónicos sobre el tema, esta vez vengo con algo relacionado, que es sobre una clase sencilla para manejar los datos sin escribir SQL (o escribiendo lo menos posible), siempre teniendo en cuenta eficiencia, simpleza y extensiblidad.

Seguramente se preguntaran porque implementar algo que ya hay bastante, y la verdad que probé casi todos los existentes (no puedo dar nombre ni criticas porque conozco personalmente a algunos miembros de esos proyectos) y ninguno lleno mis expectativas, principalmente porque eran muy complicados a la hora de hacer consultas complicadas (varias consultas anidas, consultas con varias tablas, etc). Otra cosa que no me gustó es que crea todas las tablas en base a la definiciones en algún formato, en realidad es una característica muy útil, pero tiene un costo muy alto, que son muchas consultas (imagínese tener 100 tablas, realizar 100 consultas solo para hacer diffs). Todas esas características, tienen un costo bastante algo, que es mucho código en memoria por cada consulta, aunque exista métodos para alivianar de alguna manera esta ultima desventaja.

Mientras navega leyendo códigos, tratando de tener un idea de que hacer algo mas sencillo, de pronto al leer (para fines educativos) los fuentes de Menéame (donde tengo algunos mínimos e insignificantes aportes), pareció muy práctica la abstracción de la base de datos, por ejemplo la manera de como se abstrae la tabla user, no solo el acceso/modificación de la tabla User estan ahí, sino también todo lo relacionado con el usuario, lo cual hace todo bastante ordenado.

Tomando como base las clases de acceso de datos, más que nada el API en general, ninguna porción de código fue copiado ya que no podría por cuestiones legales (soy fanático de las licencias mas liberales, BSD o similares) comencé un proyecto llamado Simple ORM. Básicamente es un ORM bastante simple, no crea las tablas en base de una definición, tampoco hace lo inverso (osea generar codigo en base a tablas existentes), simplemente es una abstracción de los datos con un plus muy importante que es el cacheo de los mismo para optimizar (totalmente opcional). Esta construida sobre PDO, que agrega otra interfaz hacia la base de datos en sí, la cual al ser nativa (escriba en C) es bastante eficiente, y además emula transacciones y SQL preparadas con variables (útil para evitar SQL injections).

La verdad que hablar/escribir mucho no me gusta, así que vamos a la parte divertida, el código. La conexión   es bastante sencilla, casi como lo haríamos normalmente:

<?php
require "DB.php";

DB::setUser("root");
DB::setPass("foobar");
DB::setDB("testing");
DB::setDriver("mysql"); //Por defecto

Como vieron, nada de otro mundo, además nótese que la conexión no es realizada en este punto, sino que al ser necesitado, esto es así porque existe la probabilidad que la consulta a realizar ya este en el Cache. Ahora supongamos que contamos con la siguiente tabla SQL:

Simple DB example

Simple DB example

A continuación una simple representación de la base de datos utilizando SimpleORM:

<?php
class SessionModel extends DB
{
    public $startdate;
    public $endate;
    public $user;

    function user_filter(&$user)
    {
        if (!$user InstaceOf User) {
            $user = $user->ID;
        } else {
             $uobj = new User;
             $uobj->ID = $user;
             if (!$uobj->load()->valid()) {
                 throw new Exception("Invalid user");
             }
        }
    }

    function getTableName()
    {
        return "session";
    }
}

class User extends DB
{
    public $username;
    public $passwd;

    function passwd_filter(&$pass)
    {
        $pass = md5($pass);
   }

    function username_filter(&$username)
    {
        $oUsername = $this->getOriginalValue('username');
         if ($oUsername && $username != $oUsername) {
             throw new Exception("You can't modify your username");
         }
    }
}

Como vieron las clases son sencillas, solo necesitan declararse explícitamente las propiedades con el mismo nombre de las columnas (no necesariamente, esto puede cambiarse re-implementando el método relations — no está muy probado todavía) y nada mas. El filtrado/validación de los datos se realiza mediante la declaración de nuevo métodos con un nombre especial (nombre propiedad del objeto + “_filter”). Al ser de esta manera los filtros son bastante maleables, solo miren los métodos SessionModel::user_filter(), User::username_filter() y User::password_filter(), donde notamos que los filtros pueden modificar el dato a ser guardado o pueden detener la ejecución lanzando una excepción. También por defecto las clases tienen que tener el mismo nombre que las tablas o de lo contrario hay que redefinir el método getTableName().

Para realizar consultas, la clase en sí es muy limitada, ya que no provee ordenamiento, ni limitaciones (por ahora) de ningún tipo. Simplemente provee una interfaz amigable para las iteraciones, alteraciones e inserciones de datos.

$user = new User;
$user->username = "foobar";
/* "password" es transformado a su md5 antes del select [usea load()] */
$user->password = "password";
if ($user->load()->valid()) {
     /* Inserta un nuevo campo en la tabla session */
     $session = new SessionClass;
     $session->user = $user;
     $session->startdate = date("Y-m-d H:i:s");
     $session->save();
}

/* Ejemplo sencillo de Iteracion */
$sessions = new SessionClass;
$sessions->user = $user;
/* de este modo */
$sessions->load();
foreach ($sessions as $session) {
    /* hacer algo con la sesion */
}
/* o */
foreach ($sessions->load() as $session) {
    /* hacer algo con la sesion */
}

Hasta aquí nada fuera de lo común, y la verdad poco útil, ya que en la vida real se necesitan mucho mas que simples operaciones, y he aquí la parte que me gusta, como hacer cosas complicadas, y la verdad que la mejor manera (mas sencillo, mas eficiente) de hacer las cosas complicadas es usando SQL en sí, nada de otros sub-lenguajes de manipulación de datos, ni tantos objetos y métodos para formar una consulta. Hablando en ejemplos prácticos, supóngase que necesita un listado de los usuarios con más de 5 sesiones en un rango variable de fechas, ¿como lo haríamos?

/* Agregamos este método a la clase User */
function & getMostActivedUsers($min, $max) {
     $sql = <<<EOT
SELECT u.* FROM users u WHERE u.id in (
     SELECT id FROM session WHERE startdate > :idate && enddate < :edate
     GROUP BY id HAVING count(*) > 5
)
EOT;
     $this->setDataSource($sql, array("idate" => $min, "edate" => $max), true);
     return $this; /* opcional para ahora una linea al iterar */
}

/* Iteración de ejemplo donde también se muestra un alteración de los datos */
/* (no contemplada en mi modelo) */
$users = new User;
foreach ($users->getMostActivedUsers($min, $max) as $user) {
      /* Los datos tambien pueden ser accedidos como Arrays */
      $user['level'] = 'god';
      /* Si el ultimo parametro de setDataSource es false (por defecto) */
      /* esto lanzaría una excepción (util para consultas anidadas)       */
      $user->save();
}

Como vieron, es muy sencillo de extender, y es lo mas manual posible solo se necesita conocer bastante bien de SQL, siguiente a mi frase “mientras mas fácil es para los programadores, mas difícil para el servidor”(referente a los frameworks que ya hacen todo ;) )

La limitación mas notoria que me viene a esta hora (6:24 am) a la cabeza es que la tabla necesita tener una columna “id” ($obj->ID). Este puede ser auto-incrementado (recomendado) o puede ser otro valor. Esto no limita a tener tablas sin “id” (ejemplo notorio tablas auxiliares para guardar las relaciones de tablas n:n), que pueden ser implementados como métodos de algunas clases, prometo que escribiré lo mas pronto posible ejemplos mas complicado y como representarlo con SimpleORM.

En próximas semanas estaré escribiendo mas ejemplos sobre el uso de SimpleORM, y eventualmente seguiré modificando el proyecto y agregando nuevas características.

]]>
http://cesar.la/modelado-de-datos-a-mi-estilo.html/feed
Inteligencia artificial para sugerir amigos http://cesar.la/inteligencia-artificial-para-sugerir-amigos.html http://cesar.la/inteligencia-artificial-para-sugerir-amigos.html#comments Wed, 10 Jun 2009 06:39:08 +0000 César Rodas http://cesar.la/?p=308 Siguiendo la serie de entradas que comencé hace unos días atrás con Pensando en paralelo - Introducción a Map/Reduce, hoy voy a escribir sobre una de las maneras de sugerir amigos para una red social, o cual otra aplicación que puedan encontrar.

Antes que nada, hago una pequeña introducción hacia mi proyecto PHPCluster. Básicamente proyecto implementa (o trata) algoritmos para la clusterización de datos, que hablando mal y rápido son técnicas para encontrar elementos similares en grandes conjuntos sin tener conocimiento previo de ninguno de los datos . Hasta ahora implemente dos algoritmos, el K-means (actualmente esta en funcionamiento, y lo estoy optimizando con algunas ideas raras que me vienen a la cabeza) y el Biclustering (no esta funcionando es solo un esbozo ya que su eficiente computación es complicada).

Algunas aplicaciones practicas son, por ejemplo, buscar textos (entradas de blogs, noticias, sitios webs, etc) similares, algunas de mis ultimas y mejores pruebas pueden ser vistas en mi wiki-privado de proyectos. En este caso daré un enfoque diferente para resolver el el problema de sugerir potenciales amigos a usuarios.

require "phpcluster/k-means.php";

/**
 *  Clase descendiente de KMeans que sobre-escribe el método getFeatures,
 *  ya que la clase KMeans esta preparada para utilizar palabras como "features".
 */
final class GroupOfFriends extends KMeans
{
    protected function getFeatures($friends)
    {
        $ids = explode(" ", $friends);
        return array_combine($ids, array_fill(0,count($ids), 1));
    }
}

Ahora con su propio dataset, o podrían utilizar el que yo utilice, deberían codificar la búsqueda de usuarios similares basado en amigos. Del resultado de la búsqueda saldrán algunos grupos de usuarios (en mis pruebas fueron 42 grupos). Obj: Este programa debe ser ejecutado desde la consola, nunca jamas desde Apache o algún otro webserver ya que tarda algún tiempo

$fsuggest = new GroupOfFriends;
/* cuantos grupos de usuarios quiero buscar*/
$fsuggest->setCentroids(60);
/* porcentaje de similitud entre usuarios */
$fsuggest->setThreshold(45);

foreach(file("friendship-dataset.txt") as $line) {
    list($id, $friend) = explode("\t", trim($line), 2);
    $fsuggest->addElement($id, $friend);
}

/* a buscar los usuarios similares */
$clusters = $fsuggest->doCluster(300);

En uno de los grupos, al pertenece mi usuario, tuvo a los siguiente miembros, y me sorprendió mi idea ya que a casi todos los usuarios los conozco y todos somos informáticos (o casi todos). @pabloacastillo, @diegoplus, @crodas, @javox, @alejandroValdez, @willin64, @randallflagg, @eliaya, @bocho, @Robez, @xgurix, @chi83, @flyestudios. Dicha similitud de intereses no necesariamente significa que yo (@crodas) debería seguir a dichos usuarios, porque lo mas probable es que ya lo este haciendo (en este caso solo a 3 personas no les sigo en Twitter), sino que también a las personas que ellos siguen y que yo no son los potenciales amigos míos.

El resultado de la clusterizacion esta retornada en $cluster, que simplemente es una matriz que contiene como indice el numero de cluster (o grupo) y como valor una matriz con los usuarios que pertenecen al grupo. Como podrán notar, no todos los usuarios tienen un grupo, eso es gracias al $threshold lo cual asegura la calidad del clustering y a la vez reduce notablemente el numero de iteraciones. Como lo explicado en el párrafo anterior, esto no significa que el sistema no podrá sugerir potenciales amigos a dichos usuarios o a nuevos usuarios. El valor de $cluster debe ser guardar guardo en un medio persistente, para este ejemplo lo haré con json_* pero para la vida una base de datos MySQL o CouchDB son mejores alternativas.

Antes de guardar la informacion obtenida mediante el agrupamiento, son necesarios algunos pasos para hacer optima su lectura al momento de buscar amigos para una cierta persona. La primera es crear modelos de usuarios [1], porque es mas rápido comprar un usuario (a sugerirle amigos) contra esos modelos (en mis pruebas no pasaron de los 46) que comprarlo contra 7926 (la cantidad de usuarios en el dataset de pruebas). Los modelos de usuarios se forman a partir de los grupos de usuarios descubiertos, y tienen como amigos a todos los amigos de los usuarios en el grupo, si les parece confuso, miren el siguiente código:

function Array_Merge_ex1(&$arr, $arr1)
{
    foreach ($arr1 as $value) {
        if (!isset($arr[$value])) {
            $arr[$value] = 0;
        }
       $arr[$value] += 1;
    }
}

$knowledge = array();

foreach ($cluster as $group) {
    $data = & $knowledge[];
    $data = new stdclass;
    $following = array();
    foreach ($group as $member) {
        Array_Merge_Ex1($following, explode(" ",$member[0]) );
    }
    arsort($following);
    $data->value   = $following;
    $data->members = array_keys($group);
}

file_put_contents("knowledge.json", json_encode($knowledge));

Ahora se viene la parte interesante, el código que toma lo aprendido y sugiere posibles amigos. El algoritmo es relativamente sencillo y rápido de ejecutar (que es lo mejor), se carga en memoria lo aprendido y buscar el grupo mas similar al usuario y luego se sugieren los amigos del grupo que sean todavía sus amigos.

class FriendFinder
{
    /**
     *  distance, esta es la funcion que compara a un usuario
     *  con el modelo de usuarios.
     *
     */
    protected function distance($gfriend, $friends)
    {
        $shr = 0;
        $c1 = count($gfriend);
        $c2 = count($friends);

        foreach($friends as $item) {
            if (isset($gfriend[$item])) {
                $shr++;
            }
        }
        return 1 - ($shr/($c1+$c2 - $shr));
    }

    /**
     *  Retorna los detalles de los amigos de un usuario.
     *  (esto debe re-escrito en sitios de produccion)
     */
    final private function _getUserFriends($uid)
    {
        foreach(file("friendship-dataset.txt") as $line) {
            list($id, $friends) = explode("\t", trim($line), 2);
            if ($id == $uid) {
                return explode(" ", $friends);
            }
        }
        throw new Exception("User ($uid) not found");
    }

    /**
     *  Retorna los detalles de los un usuario
     *  (esto debe re-escrito en sitios de produccion)
     */
    final private function _getUserDetails($uname)
    {
        foreach(file("userids.txt") as $line) {
            list($id, $extra)  = explode(" ", trim($line),2);
            list($name, $user) = explode("|", $extra);
            if ($user == $uname) {
                return array($id, $user, $name, $this->_getUserFriends($id));
            }
        }
        throw new Exception("User ($uname) not found");
    }

    /**
     *  Retorna los detalles de los un usuario por ID.
     *  (esto debe re-escrito en sitios de produccion)
     */
    final private function _getUserDetailsById($uid)
    {
        foreach(file("userids.txt") as $line) {
            list($id, $extra)  = explode(" ", trim($line),2);
            list($name, $user) = explode("|", $extra);
            if ($id == $uid) {
                return $user;
            }
        }
        /* todavia no tenemos el ID del usuario, en teoria nunca pasaria */
        /* pero como no tenemos toda la DB de twitter,  por suerte existe el */
        /* YSQL para consultar */
        $q    = "?q=select%20*%20from%20twitter.user.profile%20where%20id%3D";
        $q   .= "'${uid}'&format=json&env=http%3A%2F%2Fdatatables.org%2Falltable";
        $q   .= "s.env&callback=";

       $content = file_get_contents("https://query.yahooapis.com/v1/public/yql?$q");
        $content = json_decode($content);
        return $content->query->results->item->meta[1]->content;
    }

    /**
     *  Esta funcion carga en memoria lo previamente aprendido
     *  (esto debe re-escrito en sitios de produccion)
     */
    final public function getPossibleFriends($name)
    {
        $info   = $this->_getUserDetails($name);
        $groups = json_decode(file_get_contents("knowledge.json")); 

        $distance = 20;
        foreach ($groups as $id => $group) {
            /* json_decode trata los arrays como objectos de stdclass (PHP5)*/
            $gfriend = get_object_vars($group->value);
            $d = $this->distance($gfriend, $info[3]);
            if ($d < $distance) {
                $distance = $d;
                $gmatch   = $id;
            }
        }

        $group = get_object_vars($groups[$gmatch]->value);
        $cands = array();

        foreach (array_keys($group) as $buddy) {
            if (array_search($buddy, $info[3])===false && $buddy != $info[0]) {
                $cands[] = $this->_getUserDetailsById($buddy);
            }
            if (count($cands)==15) break;
        }

        return $cands;
    }
}

$friend = new FriendFinder;
var_dump($friend->getPossibleFriends('crodas'));

A continuación los resultados para algunos amigos míos y sus recomendaciones.

pabloacastillo

array(15) {
  [0]=>
  string(8) "cleonati"
  [1]=>
  string(6) "chelun"
  [2]=>
  string(11) "grungelover"
  [3]=>
  string(8) "nambucom"
  [4]=>
  string(11) "marcoarturo"
  [5]=>
  string(10) "tipocracia"
  [6]=>
  string(10) "biblosfera"
  [7]=>
  string(9) "Mailplane"
  [8]=>
  string(13) "oscarsanchezo"
  [9]=>
  string(13) "punkgodangell"
  [10]=>
  string(6) "dsosap"
  [11]=>
  string(8) "IRONLOBO"
  [12]=>
  string(8) "sigridee"
  [13]=>
  string(10) "horaciusdr"
  [14]=>
  string(9) "wordpress"
}

piojita

array(15) {
  [0]=>
  string(8) "Kokochon"
  [1]=>
  string(10) "maxwellweb"
  [2]=>
  string(6) "eliaya"
  [3]=>
  string(7) "taguiar"
  [4]=>
  string(6) "czayas"
  [5]=>
  string(6) "pp1713"
  [6]=>
  string(10) "LadyIracix"
  [7]=>
  string(11) "manuelacuna"
  [8]=>
  string(11) "javierpreda"
  [9]=>
  string(11) "fabiojose80"
  [10]=>
  string(8) "cleonati"
  [11]=>
  string(11) "grungelover"
  [12]=>
  string(8) "nambucom"
  [13]=>
  string(7) "mharcos"
  [14]=>
  string(14) "SantiagoValdez"
}

diegoplus

array(15) {
  [0]=>
  string(4) "GLun"
  [1]=>
  string(12) "LauEsplendix"
  [2]=>
  string(11) "flyestudios"
  [3]=>
  string(8) "willin64"
  [4]=>
  string(7) "carlink"
  [5]=>
  string(4) "QLRP"
  [6]=>
  string(7) "rubuntu"
  [7]=>
  string(14) "littletiramisu"
  [8]=>
  string(5) "kirai"
  [9]=>
  string(9) "galletita"
  [10]=>
  string(8) "PoKeTiTa"
  [11]=>
  string(6) "lastfm"
  [12]=>
  string(11) "daveexplosm"
  [13]=>
  string(6) "Pk_JoA"
  [14]=>
  string(14) "pisitoenmadrid"
}

tetsumo

array(15) {
  [0]=>
  string(6) "eliaya"
  [1]=>
  string(4) "GLun"
  [2]=>
  string(7) "lettuix"
  [3]=>
  string(7) "carlink"
  [4]=>
  string(4) "QLRP"
  [5]=>
  string(14) "littletiramisu"
  [6]=>
  string(5) "kirai"
  [7]=>
  string(9) "galletita"
  [8]=>
  string(8) "PoKeTiTa"
  [9]=>
  string(6) "lastfm"
  [10]=>
  string(11) "daveexplosm"
  [11]=>
  string(6) "Pk_JoA"
  [12]=>
  string(14) "pisitoenmadrid"
  [13]=>
  string(6) "Aterea"
  [14]=>
  string(9) "Baratijas"
}

cleonati

array(15) {
  [0]=>
  string(7) "piojita"
  [1]=>
  string(14) "pabloacastillo"
  [2]=>
  string(5) "abcpy"
  [3]=>
  string(13) "sheldoncooper"
  [4]=>
  string(4) "GLun"
  [5]=>
  string(12) "LauEsplendix"
  [6]=>
  string(8) "willin64"
  [7]=>
  string(15) "ElRinconDelGeek"
  [8]=>
  string(7) "carlink"
  [9]=>
  string(4) "QLRP"
  [10]=>
  string(7) "rubuntu"
  [11]=>
  string(14) "littletiramisu"
  [12]=>
  string(5) "kirai"
  [13]=>
  string(9) "galletita"
  [14]=>
  string(8) "PoKeTiTa"
}
La calidad de los resultados solo pueden medidas mediante los “feedbacks” de los usuarios, así que veremos que dicen mis amigos arriba mencionados.

Happy coding!

]]>
http://cesar.la/inteligencia-artificial-para-sugerir-amigos.html/feed
Pensando en paralelo - Introduccion a Map/Reduce http://cesar.la/pensando-en-paralelo-introduccion-a-mapreduce.html http://cesar.la/pensando-en-paralelo-introduccion-a-mapreduce.html#comments Wed, 03 Jun 2009 06:38:12 +0000 César Rodas http://cesar.la/?p=275 Como se habrán dado cuenta (al menos si me siguen en Twitter o sino lo pueden ver ahora) me he pasado los últimos días jugando con Hadoop que es la implementación mas popular del paradigma MapReduce para procesamientos de datos en paralelo. Para ello comencé a escribir una serie de APIs para su fácil manipulación desde PHP, asi podriamos escribir aplicaciones que se ejecuten en paralelo desde PHP, lo que voy a usar por primera vez en mi proyecto del Google Summer of Code como un plus del proyecto. Este post es una introducción cuasi-teórica sobre la programación concurrente (o en paralelo).

Imagínense que el siguiente pseudo-código C se ejecuta en 8 minutos en una computadora normal, y esta optimizado al máximo.

int i;
long * result;
for (i = 0; i < 3984; i++) {
     *(result + i) = operation(i);
}

Asumiendo que la función operation no modifica ninguna variable global, el orden de la ejecución del ciclo no afectaría en nada el resultado. Partiendo de esa afirmación, si la computadora que ejecuta el programa cuenta con mas de 1 procesador/núcleo (para el ejemplo asumiremos que cuenta con 8), entonces podríamos crear 8 hilos/procesos y que cada uno vaya resolviendo una iteración del ciclo, así resolveríamos aproximadamente 8 veces mas rápido (hay una pequeña perdida por la partición de los datos y asignación de iteración a cada hilo/proceso). Este es el concepto de la computación concurrente o en paralelo.

Por supuesto, no todos los problemas pueden ser resueltos de forma concurrente, ejemplo el siguiente pseudo-código escrito en C. Esto no puede ser concurrente ya que esta vez la función operation recibe como parámetro el resultado de la iteración anterior, lo que fuerza la ejecución secuencial y en orden del ciclo.

int i;
long * result;
long xout = 0;
for (i = 0; i < 3984; i++) {
   xout  = operation(i, xout);
   *(result + i)  = xout;
}

Los lenguajes funcionales, son por naturaleza  concurrente, ya que no existen el concepto de variable y una cada función nunca modifica un dato, siempre crea una nueva copia y lo modifica, ademas las funciones son como funciones puramente matemáticas, en las que se verifican ciertas propiedades como la transparencia referencial y por tanto, la carencia total de efectos laterales.

Personalmente, entre las pocas cosas que aprendí estudiando se destaca ampliamente  Haskell, que me ayudó bastante a la hora de leer sobre MapReduce, altamente recomendado para ejercitar la mente para resolver problemas concurrentes.

Ahora que tenemos los conceptos básicos, podemos entrar un poco en lo que sea MapReduce. Básicamente existen dos funciones map y reduce que son aplicados a todos los datos. Al ser la programación concurrente, esto es divido en procesos que son proporcional al numero de datos a procesar y al numero de recurso de hardware disponible (núcleos, memoria ram, etc) para su ejecución. Los datos de entrada están formadas por una (clave, valor) y a cada dato es aplicada la función map, la cual produce una serie de resultados intermedios (de 0 a N), dichos resultados también están formados por (clave, valor). Luego, una vez finalizado todas las funciones map el proceso de MapReduce agrupa por clave los resultados intermedios, y a cada dato (clave, [resultado1, resultado2, ... resultadoN]) aplica la función reduce.

Hadoop (la mas popular Codigo Libre) o cualquier otra implementación del paradigma MapReduce, es una gran abstracción para el programador, que a diferencia de antes se centra en como resolver un problema utilizando solo funciones map y reduce. Parte de la abstracción incluye la partición de los datos, asignación y monitoreo de tareas, distribución de los datos a través de las maquinas utilizando un sistema de archivo distribuido, entre otras cosas. Este es un simple pseudo-código en PHP de como seria un indice invertido para Hadoop. (Funciona con la API que mencione anteriormente, esta en fase de desarrollo entonces me lo guardo para mi hasta que este terminado)

final class InvertedIndex extends Job
{
    function map($key, &$value)
    {
        $value = strtolower($value);
        foreach (preg_split("/[^a-záéíóúüñ]/i", $value) as $word) {
            $this->EmitIntermediate($word, $key);
        }
    }

    function reduce($key, &$values)
    {
        $this->Emit($key, array_unique($values));
    }

}

El código de arriba, toma datos (id, textos) y construye un Indice Invertido, que básicamente es una lista de palabras y las referencias de los documentos en donde aparece, ejemplo (palabra, [doc1, doc2, doc3]). Básicamente por cada documento, se emite la palabra y la clave del documento en el cual aparece (en la función map), y la función reduce retorna una lista única de palabras.

Obviamente, para la resolución de problemas, se necesitan varios trabajos, entiéndase como trabajo aplicar las funciones map y reduce a una serie de datos, un claro ejemplo seria el algoritmo de agrupación por similitud de datos K-means, que luego de terminar y optimizar la versión convencional del algoritmo lo estoy escribiendo en versión concurrente.

Espero que esta pequeña introducción les haya sido entendible y útil, mas adelante escribiré mas sobre Hadoop y ejemplos prácticos

]]>
http://cesar.la/pensando-en-paralelo-introduccion-a-mapreduce.html/feed
Soy más que sólo PHP http://cesar.la/soy-mas-que-solo-php.html http://cesar.la/soy-mas-que-solo-php.html#comments Tue, 12 May 2009 05:06:32 +0000 César Rodas http://cesar.la/?p=255

Update: Gracias al primer feedback de Roberto Alsina (un verdadero Guru del Python), ahora siempre retorna una lista de tuplas con (idioma, porcentaje).

Este un solo un post rápido para demostrar que soy mas que solo PHP ;)

El proyecto se relativamente simple, pero por algo se empieza. Básicamente implemento la teoría de este paper para detectar los idiomas con los N-Gramas que lo he usado bastante de forma privada para mis experimentos de Clustering (clasificación automática de textos) y para mi sitio Languess.

PY-Languess (así se llama el proyecto) está hosteado en Github, a continuación como se utiliza:

#!/usr/bin/python
#encoding: iso-8859-1
from languess import Languess
from glob import glob

def main():
    # Creamos el objeto
    lg = Languess()
    # Cargamos los modelos de los lenguajes
    # que fue entrenado por train.py
    klm = {}
    for lm in glob('knowledge/*.lm'):
        klm[ lm[10:-3] ] = [n.strip() for n in open(lm).readlines()]
    lg.load_knowledge( klm )

    # Ahora a obtener los idiomas
    print lg.getLang("This is just a bloody text")
    print lg.getLang("Pues esto es un texto en español")
    print lg.getLang("Mba'eteko chera'a mba'eichapa reiko?")
    # Se me acabaron los idiomas para probar :D 

if __name__ == "__main__":
    main()

Se preguntaran porque lo he re-implementado, pues la respuesta es simple, quería hacer algo con el AppEngine, y aproveche el sábado para pelearme un poco con el Python. Ahora esta el Webservice (o intento de WS) en linea, y funciona de una manera muy simple (me encantan las cosas simples). La URL tiene la siguiente forma http://textlang.appspot.com/service?text=TEXTO_A_OBTENER_EL_IDIOMA y retorna un objeto serializado con JSON.

La URL http://textlang.appspot.com/service?text=This is just a bloody text retornaría:

{"lang":[["english",100]],"text":"This is just a bloody text"}

En caso de existir dos candidatas (existe cuando los textos son muy cortos) retornaría un vector en vez de un string en la propiedad “lang”.

Saludos y Happy hacking.

]]>
http://cesar.la/soy-mas-que-solo-php.html/feed
“Windows se hace con las notebooks”, el gran hoygan del momento http://cesar.la/windows-se-hace-con-las-notebooks-el-gran-hoygan-del-momento.html http://cesar.la/windows-se-hace-con-las-notebooks-el-gran-hoygan-del-momento.html#comments Sat, 02 May 2009 19:51:17 +0000 César Rodas http://cesar.la/?p=243 hoygan

Para los que no esten aconstumbrado con el termino hoygan, encontre una imagen que lo describe perfectamente.

Generalmente, en mi blog trato al máximo de evitar escribir entradas de contenido político/filosófico (en otras palabras de contenido no-técnico), pero luego de leer una blasfemia de tal envergadura no resistí las ganas y tengo que escribir algo al Ing. (quien sabe de que facultad sera Ing. pero ya me imagino, sin animo ofender).

“Aunque lo aprenda en la escuela, el software libre no me serviría, por el escaso soporte técnico que tiene esta herramienta y un mercado laboral que todavía no demanda este tipo de software”

Es la cosa mas ridícula y errada que escucho/leo en meses, hasta parece una broma de Google en el April’s fool day. Si el Software libre no tiene tanto soporte como puede explicar el crecimiento exponencial de los sistemas operativos libres, y de la total perdida de territorio de Windows en el ambito de los servidores, si no me creen pueden mirar los mejores servidores de la Internet, o porque no explican porque Google utiliza GNU/Linux para sus maquinas (=~450,000 pcs).

“Como usuario genérico que usa Word, Excel, etc., no me inclinaría a un software libre, que para usarlo bien es preciso tener más conocimientos técnicos”

Totalmente errado, un día mi novia necesitaba un trabajo practico para su facultad, entonces le preste mi Acer Aspire One que tiene Debian (and no dual booting), y lo utilizo sin problemas, lo unico que le di fue mi nombre de usuario y contraseña y lo utilizo… así que, si una persona no informática (estudiante de Odonto en este caso) puede trabajar con Debian (considerada como muchos “gurus” no amigable) sin problemas, lo que demuestra que dicha afirmación es totalmente falsa.

“El software libre no tiene soportes que garanticen tener estabilidad en la plataforma del servicio web”

Opa, opa, se metieron en mi campo, entonces porque su ASP, Windows servers, SQL Server tiene una minoría totalmente despreciable en el mercado, en realidad solo conozco alguna pseudo-empresas que utilizan algunas de las “tecnologías” previamente citadas, o porque Microsoft esta apoyando a PHP, sino miren esta presentación de un amigo en Microsoft ¿como explicarían eso?

La innegable realidad es que estos ladrones tratan de justificar por los medios millonarias sumas de dinero en licencias (+ coimas claro)… y es lamentable dar tantos justificativos sin sentidos. Ni siquiera son personas conocedoras del tema, ni siquiera son de Microsoft, son solo políticos tratando de justificar sus robos…

]]>
http://cesar.la/windows-se-hace-con-las-notebooks-el-gran-hoygan-del-momento.html/feed
Programando por la remera (v2.0) http://cesar.la/programando-por-la-remera-v20.html http://cesar.la/programando-por-la-remera-v20.html#comments Thu, 23 Apr 2009 17:12:02 +0000 César Rodas http://cesar.la/?p=234

Update: La lista oficial de parte de Wordpress esta online.

Como algunos ya saben este año fuí seleccionado otra para el Google Summer of Code para trabajar en plug-in para BuddyPress y Wordpress MU. Así que me pasaré todo el verano invierno programando para ganar la remera (y otros beneficios que casi los olvido).

Google T-shirt

El proyecto consiste en implementar algoritmos “sociales” y categorización de textos. Básicamente el proyecto recogerá información del usuario, y mediante algunos algoritmos, aprenderá sus gustos, y le sugerirá cosas (posibles amigos con los mismo gustos, artículos a leer según temas generalmente leídos, por lo que las personas similares leen, agrupar posts similares basados en visitantes en común, etc, etc).Para la parte de la categorización de textos, me enfocaré en agrupar textos similares por su contenido, también agregaré la detección de idiomas y más.

Escribiré regularmente aquí (y en Español) acerca de los avances del proyecto, y también crearé un repositorio público para los que quieran seguirlo.

Tremenda semana, ya que tuve una entrevista por ganar por segundo año el PHP Innovation award, esto sumado a lo del Google Summer of Code, me dan una semana grandiosa. Aún así nada podrá ponerme mejor, ya que es difícil superar el asesinato de un familiar muy cercano.

Saludos y happy hacking.

]]>
http://cesar.la/programando-por-la-remera-v20.html/feed
Mi primera extensión para PHP http://cesar.la/mi-primera-extension-para-php.html http://cesar.la/mi-primera-extension-para-php.html#comments Fri, 03 Apr 2009 19:13:33 +0000 César Rodas http://cesar.la/?p=208 Luego de algún tiempo de inactividad, buscando tema para GSoC, me he metido en territorio desconocido para mí, jugar un poco con el Zend Engine, de ahí que bajé algunas presentaciones sobre como incluír PHP en una aplicación. Luego leyendo un poco más vi unos ejemplos de lo que hace tiempo he querido hacer, escribir una extesión nativa para PHP. Nativa significa código escrito en C,  ya que la máquina virutal, el compilado y el resto de PHP está escrito en C.

Me puse manos a la obra, y lo que nació como un juego, hoy en día (una semana más tarde exáctamente) es la versión alfa de los wrappers del LibTextCat. El libTextcat es un interesante proyecto que aplica la teoría presentada en N-Gram-Based Text Categorization, paper que también lo he implemendado el PHP para Languess.

Seguramente se preguntaran ¿porqué escribir algo nativo si ya lo tenias en PHP? , la respuesta es simple, rapidez. El código PHP tenia algo así como 2000 ejecuciones de bytecode, mientas con la nativa solo unas cuantas, más o menos 10, para pasar parámetros a la función C. La diferencia es de alrededor 0.05 segundos contra 0.001 segundos, es bastante pero para pequeños requerimientos la diferencia no es mucha, pero para procesamientos a larga escala, es considerable.

Más adelante escribiré como crear una extensión nativa para PHP, es relativamente sencillo, pero requiere conocimientos del lenguaje C, y cosas como autoconf, configure, en realidad más de C que otra cosa, por obvias razones ;)

Básicamente el PHPTC  (para no escribir PHPLibTextCat), se utiliza de la siguiente manera,

<?php
/* crear el "recurso" de textcat */
$tc = textcat_init();
/*  agregar un source.lm que tiene la definición del lenguage "source lang" */
textcat_add_knowledge($tc,"source.lm","source lang");
?>

Los archivos *.lm (en este caso source.lm) contiene la definición del lenguaje o conjunto de textos similares los cuales se utilizaron como ejemplos, aquí un ejemplo de como entrenarlo, aunque la librería ya venga con algunos ejemplos ya procesados.

<?php
/* crear el "recurso" de textcat */
textcat_train("source.lm", # Archivo de salida
    "este es un texto, escrito en Español, en teoría podrían ir textos más largos",
    "y otros textos, todo referentes a la misma cosa, claro",
    "podrían ser del mismo tema, o el mismo idioma, depende de tí"
);
?>

Para clasificar ciertos textos, es relativamente sencillo:

<?php
/* crear el "recurso" de textcat */
$tc = textcat_init();
/*  cargar el "conocimiento" */
textcat_add_knowledge($tc,"english.lm","Inglés");
textcat_add_knowledge($tc,"spanish.lm","Español");

$cat = textcat_get_cat($tc,"este es un pequeño texto a clasificar");
if (is_string($cat)) {
    echo "El texto esá escrito en $cat";
} else if (is_array($cat)) {
    echo "El texto parece estar escrito en ".implode(",",$cat);
} else if ($ret == SHORT_TEXT) {
    echo "Imposible de obtener categoria, texto muy corto";
} else {
    echo "Ocurrio un error"; # a leer los warnings
}
?>

Pensé por un momento agregar hacer open source www.languess.com, pero sería tonto, ya que básicamente los códigos de arriba, son toda la página.

Para el futuro implementaré una API usando Class,y me enfocaré en re-escribir el proyecto LibTextcat para agregar mejor soporte para el manejo de memoria, agergar soporte para unsupervised learnings alias text clustering, utf8 por defecto y más características.

Como instalar

Bajar el código fuente

$ git clone "git://github.com/crodas/phplibtextcat.git"

Compilar

$ cd phplibtextcat
$ phpize
$ ./configure --with-textcat
$ make
$ make test
$ make install

Agregar la extension a PHP

$ echo "extension=textcat.so" > /etc/php.d/textcat.ini
$ /etc/init.d/httpd restart

Sus feedbacks, bug reports, features requests, serán bien recibidas aquí

]]>
http://cesar.la/mi-primera-extension-para-php.html/feed
PHP Application server - GSoC proposal http://cesar.la/php-application-server-gsoc-proposal.html http://cesar.la/php-application-server-gsoc-proposal.html#comments Fri, 20 Mar 2009 02:35:35 +0000 César Rodas http://cesar.la/?p=185 After, not so good feedback after present my idea on the PHP Dev maillist (probably for my “English”) so I’ve decide to write a blog post about it, with the main goal of clarify all those dark sides.

Objectives

  1. Simple principle of work.
  2. Migration painless process, people should be able to copy existence code and put on the worker and it should work.
  3. Easy scalability, just add more workers on need.
  4. Provide a simple and effortless way to deploy application across many servers.

Some concepts

  1. Client: The client has a list of workers, he chooses one randomly (similar to memcached), and query a function or queue a job.
  2. Worker: C or C++ application with PHP embed, which listens a TCP port (probably 25887). The worker can accept request  from anywhere, but only a single IP (from the master process) can submit and deploy
  3. Master process: PHP web page, where the sysadmin can upload PHP code and “deploy” (submit to every worker).

System overview

phpserver - system overview
The graphic above show how the system will look like, the clients (PHP scripts that runs on the webserver) can connect to any worker and request a function or queue a procedure. In the case of a function the client expects a response, otherwise it will not. The master process is the only one allowed to submit PHP code to the workers, with this the system has a really easy way to deploy applications.

Advantages

  1. Single point of source code distributions.
  2. There is not a central point (aka manager process), the client speaks directly to a worker, this keeps it really simple and fault tolerant. In traditional models if the manager crash, nothing works.
  3. I am planning to add a Cache layer on the client, to avoid call same functions with same parameters, if the function is cacheable, in order to reduce the network latency.
  4. It can be used to process large amount of data in parallel, as hadoop does.

Why

The big question, Why should I use this to scale if there are plenty of others well tested method to scale?

Answer this question without fanaticism it’s quite hard, but I will try. Basically my goal it’s provide an easy, painless and efficient way to scale, basically those three words are the differences, in theory you will be able to migrate a site by simple cut-and-paste from your application to the application server (through the master web-admin-page), also the master web-admin-page will be able to generate Proxy functions with the same name as the old name (the function that was cut and pasted on the appserver), so for your application (on the webserver) nothing had changed. Also that proxy function will have cache support, if the function’s return can be cached.

Part of it’s efficient would be the fact that there is not human readable protocol (no json, xml, http), just a method for fast serialization and deserialization, representing in a binary format the data.

Will it be secure, ssl, authentication or something?

The answer is no, part of the simplicity and efficient it’s avoid what is unnecessary since it is supposed that the appserver will run in a secure private network, so why loose CPU cycles checking out security?, of course any function could receive, in example, user and password, and check before execute it, but it’s out of appserver’s scope.

What would be the advantage over similar projects

  1. Speed, since it will be coded in C with threads and PHP embed
  2. Fault tolerant, since no central point, no-worker it’s indispensable.
  3. Add worker on hot, (I still need to figure out how to let know to clients about new workers),.
  4. Cache on client by the default (If a function it’s configured has return a cacheable object).
]]>
http://cesar.la/php-application-server-gsoc-proposal.html/feed
MVC Hecho en casa - Simple, lindo y efectivo http://cesar.la/mvc-hecho-en-casa-simple-lindo-y-efectivo.html http://cesar.la/mvc-hecho-en-casa-simple-lindo-y-efectivo.html#comments Mon, 02 Mar 2009 20:14:08 +0000 César Rodas http://cesar.la/?p=132

Este post va dedicado a mi amiga Piojita, que gracias a sus ganas de aprender tengo ánimos e ideas para escribir acá, ojalá puedan ser de utilidad a los que por miedo no preguntan

En estos días está muy de moda todo lo que sea MVC, que no es otra que escribir aplicaciones en tres niveles, Modelo (acceso a los datos), Vista (la presentación al usuario) y el Controlador (Encargada de los eventos, y en muchos casos es el nexo entre el modelo y la vista). Como Rasmus dice en su post The non-framework PHP MVC Framework, que en su época trajo polémica, utilizar frameworks tiene ventajas y desventajas, para mi gusto más desventajas que ventajas.

Primero que nada los frameworks webs convencionales, realizan muchas tareas automáticamente, y abstrae muchos detalles al programador que si bien ahora un montón de tiempo, no es efectiva, ya que asúme varías cosas por defecto. Cuando digo que no es efectiva hablo de que lo que se gana en tiempo, se pierde en el rendimiento de la aplicación, ¿de que nos sirve hacer una aplicación web en 5 minutos si sólo puede tener hasta 5 visitas en simultáneo? ¿No sería acaso mejor tardar 40 minutos y que soporte 30 visitas en simultáneo?, quizá  parezca una  exageración pero se acerca mucho a la realidad.

No estoy diciendo que utilizar frameworks es algo malo, depende de las necesidades del sitio web a ser realizado. <ironic>Si fuésemos radicales cada uno escribiría programas en asembler que corra con en nuestro propio sistema operativo,</ironic>

Mi idea con esta entrada es mostrar que no sólo utilizando frameworks que fuerzan a uno a utilizar uno u otro modelo de programación es posible escribir aplicaciones web en poco tiempo manteniendo la simpleza y mantenibilidad del código como primer meta. En otras palabras escribiré, o al menos esbozaré, un mini-framework MVC en PHP.

Modelo

El modelo es, probablemente, la parte más delicada del sistema ya que es la encargada de interactuar con las bases de datos. Siempre es bueno diseñar una buena API de datos persistentes, para no tener que escribir SQL en todas partes. Aparte de mantener el código ordenado, ayuda a la legibilidad del mismo, ya que sabemos si hay un error en base de datos, sabremos con seguridad de que parte del código hay que parchar.

A mi me gusta usar clases para definir una tabla y relaciones, sería algo así como lo siguiente (no es código funcional, solo un ejemplo).

<?php
class Users {
    function GetAll() {
        return $this->Select();
    }

    function Select($params=array()) {
        $sql = "SELECT * FROM users";
        if (is_array($params) && count($params)) {
            $where=array();
            foreach($values as $col=>$val) {
                $where[] = "$col='".addslashes($val)."'";
            }
            $sql .= " WHERE ".implode(" and ",$where);
        }
        return db_exec($sql);
    }

    function Update($id,$values) {
        if (!is_numeric($id)||!is_array($values)||!count($values))
            return false;
        $id  = addslashes($id);
        $cols = "";
        foreach($values as $col=>$val) {
            $cols .= "$col='".addslashes($val)."',";
        }
        $cols[strlen($cols)-1] = ' '; /* borrar el ultimo , */
        $sql = "UPDATE users SET $cols WHERE id = $id";
        db_exec($exec); /* call some db */
    }
}

?>

Algo un poco más automático es esta parte sería grandioso, es un poco molesto escribir mucho SQL, especialmente para las relaciones y esas cosas. Actualmente estoy trabajando, con un amigo brasilero, en una implementación de ActiveRecord (con API similar al de AppEngine) para PHP 5 (es que quiero hacer algo útil también con PHP5). No voy a dar más detalles hasta que esté terminado, pero doy la primicia, se podrán definir relaciones en las clases. Ejemplo:

<?php
class Author extends XXORM {
    function data() {
        $this->name = DB::String(array("size"=>50,"required"=>true));
        $this->book = DB::Relation("Books",DB::MANY);
    }
}

class Books extends XXORM {
    function data() {
        $this->author = DB::Relation("Author",DB::ONE);
        $this->title = DB::String(array("required"=>true,"size"=>50));
    }
}
?>

El proyecto, creara las estructuras de las tablas (si se ejecuta en modo development), también vendrá con una API para extender utilizando Hooks, especial para el cacheado de consultas y demás. Se podrán crear filtros de validaciones por campos, entre otras caracteristicas, si están interesados pueden seguirme twitter

La vista

Una de las características de PHP es que puede ser mezclada con HTML, pues entonces ¿para que el overhead de utilizar un template engine?

<html>
<head>
    <title><?php echo $title?></title>
</head>
<body>
    <?php foreach($users as $user): ?>
        <?php echo $user?>
    <?php endforeach;?>
</body>
</html>

Algunos consejos, evitar los short tags(<?=, <?), evitar utilizar las llaves ({}) para ello utilizar los : y los end*; asi como el ejemplo.

El controlador

El controlador no es nada mas que una función que la mayoría del tiempo llama a la vista y al modelo, aunque no se limíta solamente a eso (ej. enviar un e-mail, subir un archivo de un a ftp, etc).

<?php

function user_controller($params) {
    if (
            !is_array($params) ||
            !isSet($params[1]) ||
            !is_numeric($params[1])
        )
            return false;
    /* llamamos el modelo */
    $dbu   = new User;
    $users = $dbu->Select(array("id"=>$params[1]));
    /* incluimos la vista */
    /* la variable $users es visible para vista */
    include("vista.php");
}

?>

Ruteo de URL a Controladores

<?php
class Router {
    function AddRoute($reg_expr,$action) {
        if (!is_callable($action) && !is_file($action))
            return false;
        $this->route[$reg_expr] = $action;
    }

    function FindAction($url) {
        $routes = & $this->route;
        foreach((array)$routes as $route) {
            extract($route);
            if (preg_match($reg_expr,$url,$params)) {
                $ret = true;
                if (is_callable($action))
                    $ret = call_user_func($action,$params);
                else
                    include($action);
                return true;
            }
        }
        if (!isset($ret) && !$ret)
            $this->err();
        return false;

    }

    function err() {
        header("HTTP/1.1 404 Not Found");
        include("not-found.php");
    }
}

?>

Poniendo todo en orden

Hasta ahora vimos el MVC, separados, ahora ¿como juntamos todo eso?, pues sería algo así

<?php
/* la clase de ruteo URL -> Controlador*/
include("routes.php");
/* los modelos de datos */
include("model.php");
/* controladores */
include("controllers.php");

/* */
$router = new Router;
/* ruta hacia un controlador */
$router->addRoute('/user\/([0-9]+)$/i',"user_controller");
/* ruta hacia una vista, por no se necesite un */
/* controlador */
$router->addRoute("/","principal.php");
#... Demas reglas

/*
 * buscamos el controlador, basado en el URI,
 * suponiendo que tenemos el URL Rewrite activado
 * un uri com "/user/11" haria match, y se ejecutaria
 * un controlador.
 */
$route->FindAction($_SERVER['REQUEST_URI']);
?>

Como verán, un poco de orden en la forma de programar, utilizando un mini-framework, que en realidad son buenos hábitos a la hora de programar, fácilita nuestro trabajo (y de los que mantendrán nuestro código), a la vez de hacerlo efecto (¿para que derrochar valiosos ciclos de procesador?).

]]>
http://cesar.la/mvc-hecho-en-casa-simple-lindo-y-efectivo.html/feed
How to unlock the audio device on fedora http://cesar.la/how-to-unlock-the-audio-device-on-fedora.html http://cesar.la/how-to-unlock-the-audio-device-on-fedora.html#comments Tue, 24 Feb 2009 02:25:00 +0000 César Rodas http://cesar.la/?p=119 If use Fedora (I didn’t get yet this problems on Debian nor FreeBSD), probably you got stuck with the famous “Audio Device  Locked” (or some similar text) when your viewing a flash-video on firefox. It is really annoying restart the computer or Xorg to solve that little problem, so I decide to Googling about its answer, despite I didn’t found anything, then I’ve tried a simple command that works, so I am sharing here a solution.

Execute the follow command as root,

$ kill -9 `lsof -t /dev/snd/*  2> /dev/null`

Warning, the previous command will close all the audio programs (i.e xmms, amarok, etc).

I love UNIX, I can’t figure out live without it.

]]>
http://cesar.la/how-to-unlock-the-audio-device-on-fedora.html/feed
Concatenando videos http://cesar.la/concatenando-videos.html http://cesar.la/concatenando-videos.html#comments Mon, 16 Feb 2009 00:05:01 +0000 César Rodas http://cesar.la/?p=110 Si son como yo, de los que se pasa bajando videos de sitios (ya que no es un delito), se habran encontrado con los videos que vienen en dos partes (especialmente los conciertos musicales). Bueno, aquí comparto en este post relámpago como hacerlo:

#!/bin/bash
#el archivo de salida
SALIDA="concat.avi"
#  si tienen poco espacio en disco pal-vcd es mejor
ARGS="-target pal-dvd"
# archivo temporal, si o si tiene que ser mpg
TMP="concat.mpg"
> $TMP

for i in "$@"
do
    ffmpeg -i "$i" $ARGS - >> $TMP
done
ffmpeg -i "$TMP" $SALIDA
rm -rf $TMP

Su utilización es sencilla

$ ./concat.sh video-part-1.avi video-part-2.avi video-part3.avi
]]>
http://cesar.la/concatenando-videos.html/feed
Los “ayudadores” del calamar http://cesar.la/los-ayudadores-del-calamar.html http://cesar.la/los-ayudadores-del-calamar.html#comments Thu, 29 Jan 2009 19:33:50 +0000 César Rodas http://cesar.la/?p=79 El título les parecerá que este entrada sería una mera perdida de tiempo, que sería un off-topic total o una entrada de la GreenPeace, pero no es así es que quería ser nacionalista y escribir en Español (Squid helpers).

Hoy en día necesité  autenticar un Squid-cache contra el Active Directory de Windows, y como a la primera no me funcionó el helper LDAP que viene por defecto escribí un pequeño script en python que funciona, capaz a alguien le sea útil.

#!/usr/bin/env python  

import ldap, sys
from syslog import *

LDAP_SERVER='ldap://' + sys.argv[1]
try:
    ldap_client = ldap.initialize(LDAP_SERVER)
except:
    syslog(LOG_ERR,"Couldn't connect to the LDAP")
    sys.exit()

while (1):
    try:
        (user,passwd) = sys.stdin.readline().strip().split(" ")
        ldap_client.simple_bind_s(user,passwd)
        syslog(LOG_INFO,"%s login" % user)
        # Login success
        print "OK"
    except:
        syslog(LOG_INFO,"%s login failed" % user)
        # Sorry, an error
        print "ERR"
        sys.stdout.flush() # Requerido en algunas distros
ldap_client.unbind()

Es un simple script como lo ven, puede estar escrito en cualquier lenguaje (que tenga stdin y stdout) como PHP, C, Ruby, Haskell, Bash, pero elegí python para mostrar que tambíen hablo otro lenguajes (aparte de PHP y C). Tambíen pueden autenticar contra cualqueir cosa (ejemplo contra una DB como hacen acá) y por ejemplo asignar usuario por tiempos. Por último, para configurar el helper (asumiendo que el 192.168.1.1 es el servidor de ActiveDirectory):

auth_param basic program /path/to/login.py 192.168.1.1

# Aquí vi que algunos muggles que ponian 20, es muy alto, y no vale la pena
# a no ser que se tenga miles de usuarios.
auth_param basic children 5 

auth_param basic realm Type your password
# tiempo que será valido el login, para no hacer
# overhead tendría que ser un tiempo razonable
auth_param basic credentialsttl 5 minutes

Espero que mis días como sysadm lleguen a su fin, extraño los días de programar para vivir, no es lo mismo que pase a ser solo un hobbie.

]]>
http://cesar.la/los-ayudadores-del-calamar.html/feed
SQLite - Una breve intro http://cesar.la/sqlite-una-breve-intro.html http://cesar.la/sqlite-una-breve-intro.html#comments Tue, 27 Jan 2009 21:34:34 +0000 César Rodas http://cesar.la/?p=63 Hace varios años conocí SQLite, una excelente base de datos escrita por D. Ricard Hipp, quien también escribió otros proyectos como CVSTrac (sirvió de inspiración para el famoso The Trac), Fossil-SCM el scm que adoro, entre otros proyectos. La idea de escribir sobre SQLite mientras leía el artículo sobre SQLite en el IPhone

Lo que me gusta de SQLite es que su API es muy sencilla y que cuanta con cosas para los perezosos (”sqlite3_execute”) y con cosas para personas que piensan que lo primordial es el performance. Un código vale más que mil palabras, miren un pequeño ejemplo de SQLite…

/**
 * Coded by crodas,
 *
 * The author disclaims the copyright of this code.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

int table_exists(sqlite3 * dbh, const char * table) {
    sqlite3_stmt* pStmtPrep;
    int rc,exists;
    /* preparamos el SQL a ejecutar, muy util para evitar SQL injections */
    rc=sqlite3_prepare(dbh,"select * from sqlite_master where type='table' and name=?",-1 /* null terminated str*/, &pStmtPrep,NULL);
    if (rc!=SQLITE_OK) {
        printf("This is an SQL bug %s",sqlite3_errmsg(dbh));
        exit(-1);
    }

    /* asignamos a la primera variabla (?) el valor de table*/
    sqlite3_bind_text(pStmtPrep,1,table,-1,0);

    /* ejecutamos */
    rc = sqlite3_step(pStmtPrep);
    switch(rc) {
        case SQLITE_ERROR:
        case SQLITE_BUSY:
            printf("Database locked or unknown error");
            fflush(stdout);
            exit(-1);
            break;
        case SQLITE_DONE:
            exists = -1; /* no existe */
            break;
        case SQLITE_ROW:
            exists = 1; /* existe */

    }

    /* a liberar memoria! */
    sqlite3_finalize(pStmtPrep);
    return exists;
}

int main() {
    sqlite3 *  dbh;
    int rc,i;
    sqlite3_stmt* pStmtPrep;

    if (sqlite3_open_v2("test.db",&dbh,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,0)!=SQLITE_OK) {
        printf("Can't  open db, please check permissions on this folder. %s\n",sqlite3_errmsg(dbh));
        return -1;
    }

    /**
     *  Hice una pequenha funcion que ve si existe.
     *  lo bueno de la funcion es que muestra el primer ejemplo
     *  el SQLPrepare, y muestra como saber si la consulta
     *  tiene resultado o no
     */
    if (table_exists(dbh,"testing")==-1) {
        printf("Table doesn't exists,execute\n");
        /* tabla sencilla, utilizo exec porque no espero resultado */
        sqlite3_exec(dbh,"create table testing (id int, text varchar(250))",0,0,0);
        /* sql preparada para el insert */

        int data_id[] = {5,5,5,8,0}; /* el ID no es PK, en la vida real seria PK*/
        const char*  data_data[] = {"Some text","Another text","foobar","This is the 8 text",0};
        rc=sqlite3_prepare(dbh,"insert into testing values(?,?)",-1, &pStmtPrep,NULL);
        /* no controlo nada porque ya mostre en la funcion anterior como controlar errores */
        for(i=0;;i++) {
            if (data_id[i] == 0) break;
            /* cambiamos ? ? por sus valores */
            sqlite3_bind_int(pStmtPrep,1,data_id[i]);
            sqlite3_bind_text(pStmtPrep,2,data_data[i],-1,0);

            /* ejecutamos */
            rc=sqlite3_step(pStmtPrep);
            /* se tendria que controlar la salida*/

            /* ya que no hay ciclos, para cambiar los parametros */
            /* debemos reinicar al Statement */
            sqlite3_reset(pStmtPrep);
        }

        sqlite3_finalize(pStmtPrep);
    }

    /* ahora queremos los datos con id 5,6,7,8 */
    int data[] = {5,6,7,8,0};
    char * text;
    rc=sqlite3_prepare(dbh,"select * from testing where id=?",-1 /* null terminated str*/, &pStmtPrep,NULL);
    /* no controlo nada porque ya mostre en la funcion anterior como controlar errores */
    for(i=0;;i++) {
        if (data[i] == 0) break;
        /* asignar valor a ? */
        sqlite3_bind_int(pStmtPrep,1,data[i]);
        /* mientas haya resultado */
        while (sqlite3_step(pStmtPrep)==SQLITE_ROW) {
            text = sqlite3_column_text(pStmtPrep,1); /* id = 0, text=1 */
            printf("%d: %s\n",data[i],text);
        }

        /* reinicar el stmt */
        sqlite3_reset(pStmtPrep);
    }

    /* */
    sqlite3_finalize(pStmtPrep);

    sqlite3_close(dbh);
}

Lo que me gusta es que tenemos a toda una base de datos relacional en nuestro programa, y si de rendimiento se trata se puede hacer “prepare” de todas las consultas SQL al principio del programa y luego ir ejecutando, no es cosa complicada,  se ahoraría un montón de tiempo y procesador.

Otra cosa que me gusta mucho es que fácilmente extendible, una vez necesitaba una función para comprimir y descomprimir (la función compress y decompress del MySQL), envíe mi consulta en la lista de SQLite, y Dr. Hipp me respondío lo siguiente:

/*
** SQL function to compress content into a blob using libz
*/
static void compressFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int nIn, nOut;
  long int nOut2;
  const unsigned char *inBuf;
  unsigned char *outBuf;
  assert( argc==1 );
  nIn = sqlite3_value_bytes(argv[0]);
  inBuf = sqlite3_value_blob(argv[0]);
  nOut = 13 + nIn + (nIn+999)/1000;
  outBuf = malloc( nOut+4 );
  outBuf[0] = nIn>>24 & 0xff;
  outBuf[1] = nIn>>16 & 0xff;
  outBuf[2] = nIn>>8 & 0xff;
  outBuf[3] = nIn & 0xff;
  nOut2 = (long int)nOut;
  compress(&outBuf[4], &nOut2, inBuf, nIn);
  sqlite3_result_blob(context, outBuf, nOut2+4, free);
}

/*
** An SQL function to decompress.
*/
static void uncompressFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  unsigned int nIn, nOut, rc;
  const unsigned char *inBuf;
  unsigned char *outBuf;
  long int nOut2;

  assert( argc==1 );
  nIn = sqlite3_value_bytes(argv[0]);
  if( nIn<=4 ){
    return;
  }
  inBuf = sqlite3_value_blob(argv[0]);
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  outBuf = malloc( nOut );
  nOut2 = (long int)nOut;
  rc = uncompress(outBuf, &nOut2, &inBuf[4], nIn);
  if( rc!=Z_OK ){
    free(outBuf);
  }else{
    sqlite3_result_blob(context, outBuf, nOut2, free);
  }
}

/* Make the functions above accessible to SQLite as follows:
*/
  sqlite3_create_function(db, "compress", 1, SQLITE_UTF8, 0,
     compressFunc, 0, 0);
  sqlite3_create_function(db, "uncompress", 1, SQLITE_UTF8, 0,
     uncompressFunc, 0, 0);

Como vieron no es cosa de otro mundo, muy sencillo, y ya extendimos SQLite!. Ahora que estaré de vacaciones empiezaré a jugar con el SQlite VFS, veremos que sale…

]]>
http://cesar.la/sqlite-una-breve-intro.html/feed
Seven Things you might not know about me http://cesar.la/seven-things-you-might-not-know-about-me.html http://cesar.la/seven-things-you-might-not-know-about-me.html#comments Sat, 10 Jan 2009 22:10:55 +0000 César Rodas http://cesar.la/?p=61

Last week I was “pinged” by two Brazilians friends (Augusto and Bruno “Porkaria”) to follow a funny chain of blog posts, and I read a lot about this on several blogs and it is funny, I never thought that I’d could be included in the circle, so now I’m in, so here I come (I apologise, probably I am not very interesting).

  1. I start coding with Visual Basic 5 (my dark side), then I switch to PHP3.
  2. I love Metal Music (heavy, death, black, heavy), and to me there is not other kind of music.
  3. I disliked PHP5 ‘coz it’s look as a Java’s ripe off, but now I am trying out, and it looks nice, probably I will drop off PHP4 soon.
  4. I’d like to be a designer, but it’s really really hard, I know CSS and the basic of GIMP, but still I don’t have inspiration,
  5. I want to work outside this country ASAP, and no one wants to hire me, I think I need to finish my University first, :(
  6. I am coke addict, I can’t live without it (1l per day), I need to leave it ASAP,
  7. I don’t like beer nor Whisky, I prefer Sprite + sugar + Vodka.

Now let’s choose seven people (I don’t warranty they will follow this chain):

  • Richardo Galli: He is a PHP/Ninja, a great GTalk friend, and the creator of Menéame.net, He is the second guy that I met with a profile in Wikipedia (English && Spanish).
  • Manuel Lemos: The greatest PHPDev I meet in person, I don’t think he will write about this (usually he is busy), but let’s try.
  • Matias Insaurralde: He is a “Railer” but I am PHPzing him… soon he will be a great PHPdev.
  • Felipe Ribeiro: A great friend, he taught me ’bout the RESTful in one great talk in ConaPHP 2008.
  • Pablo Castillo: Great developer, great friend… together we’re planning to move this poor country.
  • Matt Mullenweg: Everybody knows him, I meet him at the Latinoware 2008, very nice person.
  • Matias Montes: I meet him at the Latinoware 2008, great speecher, great PHPdev.

I have several other friend, but probably they already done this, or they don’t have a blog (as my friend Cristian Medeiros, come on man open a blog!)

Rules are quite simple:

- Link your Original tagger(s), and list these rules on you blog

- Share seven facts about yourself in the post - some random, some weird.

- Tag seven people at the end of your post by leaving their names and the links to their blogs.

- Let them know they’ve been tagged by leaving a comment on their blogs and/or Twitter.

]]>
http://cesar.la/seven-things-you-might-not-know-about-me.html/feed
“You type, we guess” (mi nuevo juguete) http://cesar.la/you-type-we-guess-mi-nuevo-juguete.html http://cesar.la/you-type-we-guess-mi-nuevo-juguete.html#comments Sun, 04 Jan 2009 20:38:38 +0000 César Rodas http://cesar.la/?p=60 Les presento mi nuevo jueguete, Language Guess, una pagina que hice para poner en practica mis conocimientos sobre clasificación de textos, al mismo tiempo para poner en producción mi futura contribución para PHPClasses y crear mi primer webservice RESTful (esta ultima parte todavía no termine).

Algunos de mis beta testers (osea gtalk-friends) creyeron al principio que solo utilizaba el API de Google, cosa que ni sabia que existía. Afortunadamente hace algún tiempo atrás pude ver la luz al leer el Paper que cambio mi forma de pensar, desde ahí me tuve la idea de armar un como languess.com, hasta que finalmente le gané a la pereza y ahí lo tienen.

Obviamente es casi imposible que sea perfecto, pero según mis pruebas detectaba bastante bien. Claro que si se equivoca se le puede enseñar y la próxima vez que genere los n-gramas aprenderá mejor.

Ahora mismo languess aprendió Español, Ingles, Francés y Portugués teniendo como ejemplo la Santa Biblia (obviamente en cada idioma citado), y por su parte Alemán y Esperanto gracias a los ejemplos proveídos por Matias que hizo un script en 5 min. (probablemente en ruby) que extrae textos de Wikipedia. Si alguien tiene textos puede enviarme a mi mail asi Languess puede aprender mas y mas idiomas (no se necesitan muchos textos de ejemplos, pero mientras mas mejor), seria genial si alguien puede enviarme algo de Guaraní.

Para el futuro queda afinar el detector para que sea casi perfecto, para ello creo que tengo que afinar el método de aprendizaje, pero hay una sola forma de saber, con las pruebas de los visitantes.

Saludos a todos, y feliz año 2009.

]]>
http://cesar.la/you-type-we-guess-mi-nuevo-juguete.html/feed
La “blogosfera” Paraguaya en un solo lugar… http://cesar.la/la-blogosfera-paraguaya-en-un-solo-lugar.html http://cesar.la/la-blogosfera-paraguaya-en-un-solo-lugar.html#comments Wed, 17 Dec 2008 19:24:58 +0000 César Rodas http://cesar.la/?p=58 Hace dos días, si mal no recuerdo, entre en contacto con César Sanchez, paraguayo que vive en Argentina, y chateando surgió la idea de hacer una página para mostrar los blogs nacionales, aprovechando que desde hace algún tiempo he querido hacer algo similar pero no tan elitista (como los actuales) sobre PHP (http://phpne.ws).

A simple vista parece una simple instalación del los Planetarios (ej: Planet PHP), pero no, aunque el diseño es totalmente copiado (como podrán ver, no soy diseñador.. diseñadores.. donde estan??) el código no tiene nada que ver con el Planetario. Se preguntaran por que… y simplemente porque yo quería usar mis clases y armar algo 100% paraguayo y que funcione con PHP4 y PHP5. Además ya que programé toda la página es sencilla extender.

Ahora lo que estoy viendo que haría falta es organizar los posts por temas, ciudades, departamentos e idiomas, para que sea más útil, comentarios y sugerencias son bienvenidos.

Los fuentes del sitio son está muy beta, pero si la gente está interesada puedo estructurar mejor y liberar como Open Source (claro como BSD).

PyGosfera es el nombre, como se darán cuenta no soy muy creativo, también se aceptan sugerencias para nombre nuevo… :)

Saludos y que lo disfruten.

]]>
http://cesar.la/la-blogosfera-paraguaya-en-un-solo-lugar.html/feed
El nuevo jueguete.. “is out” http://cesar.la/el-nuevo-jueguete-is-out.html http://cesar.la/el-nuevo-jueguete-is-out.html#comments Thu, 11 Dec 2008 17:12:09 +0000 César Rodas http://cesar.la/?p=57 Después de el lanzamiento de File S3, gracias al feedback de los diggers y de amigos, me sentí realizado ya que pude dejar la pereza a un lado y comenzar a hacer sitios webs que tengo planeado desde hace varios meses o inclusos años.

Ahora les presento mi nuevo juguete read2.us (Read to us), un página web que hace text to speech, es decir genera un mp3 apartir de un texto ingresado. Ahora mismo son soportados el Español y el Inglés.

Aquí les dejo una pequeña prueba de como suena. Obviamente faltan algunos detalles para el parseado mismo del texto, es decir para que lea correo electrónico, páginas webs y demás detalles que estare terminando lo más rápido posible.

Luego si el proyecto tiene la atención y el feedback que estoy esperando crearé algún webservice para la generación de audios de textos, para por ejemplo poner a algún diario, blogs o cualquier otro sitio. Así los que tienen visión disminuida podrán disfrutar de la web.

Está por demás decir que el servicio es beta, y que tendrá algunos inconvenientes, pero daré lo mejor de mí para que el proyecto siga adelante.

]]>
http://cesar.la/el-nuevo-jueguete-is-out.html/feed
Por esto amo el Open Source! http://cesar.la/por-esto-amo-el-open-source.html http://cesar.la/por-esto-amo-el-open-source.html#comments Tue, 09 Dec 2008 14:17:37 +0000 César Rodas http://cesar.la/?p=56

He aquí el porque escribo Open Source…, para que miles de personas utilicen mis códigos. Aquí esta una prueba de ello, un popular (casi 20mil descargas) plug-in de Wordpress.

Cool

Grande Photo Xhibit

Gracias a Sarita por avisarme.

]]>
http://cesar.la/por-esto-amo-el-open-source.html/feed
Backup seguro y gratuito. http://cesar.la/backup-seguro-y-gratuito.html http://cesar.la/backup-seguro-y-gratuito.html#comments Sun, 07 Dec 2008 16:58:49 +0000 César Rodas http://cesar.la/?p=54 Siguiendo con mi post sobre servidores, una parte fundamental es la parte de backup, ya que siempre es importante estar preparados los desastres. Lo importante de los backups es la redundancia, y la seguridad. Nadie mas que nosotros tiene que poder ver nuestros backups.

Anteriormente mencione que Amazon S3 es una solución barata, muchas veces barata no es suficiente (especialmente si pagas a Amazon S3 una fortuna por otros sitios). Luego de pensar, y pensar en un medio de almacenamiento mas o menos seguro y gratis de ser posible, se ocurrió que tengo bastante espacio ocioso en mi gmail (65% para ser exacto). La única limitación es que el backup generado tiene que ser menor de 20MB. El único problema que ahora surge es que Google podría leer nuestros backups y ahí obtener valiosa información sobre nuestro sitio web, base de dato u otra cosa que este contenida en el backup,

Para solucionar ese problema podríamos usar encriptación, de manera que nadie pueda ver el contenido del archivo sin una clave.

Aqui les presento el script que yo utilizo para hacer los backups de mis servidores.

#!/bin/bash -x
MAIL=foobar@gmail.com #Obviamente, no solo funciona con @gmail.
SQL=/tmp/tables.sql
DATE=`date '+%F'`

# Password que es un MD5
# de un string que tiene la fecha,
# cada dia, tiene un password unico
echo "password con $DATE " > /tmp/foo
PASS=`md5sum /tmp/foo |  sed "s/ .*//g"`
rm /tmp/foo

# backup de mysql.
mysqldump -u root --all-databases | bzip2  | openssl des3 -salt -k $PASS | dd of=$SQL
# Ahora comprimimos
tar cfj  -  /www/foobar.com | openssl des3 -salt -k $PASS | dd of=foobar.com-$DATE.bin
# /etc
tar cfj - /etc/  | openssl des3 -salt -k $PASS | dd of=etc-$DATE.bin

# Ahora enviar los mails
echo  | mutt -s "[backup] $DATE foobar.com " $MAIL -a foobar.com-$DATE.bin
echo  | mutt -s "[backup] $DATE /etc" $MAIL -a etc-$DATE.bin
echo  | mutt -s "[backup] $DATE MySQL" $MAIL -a $SQL
rm $SQL

Para restaurar el backup simplemente hay que ejecutar el siguiente script. Lo importante es proveer siempre la clave correcta, que cambia para cada día (utiliza la fecha). Sin la clave es literalmente imposible que recuperemos el contenido, de ahi la importancia de no olvidar la clave.

#!/bin/bash -x

dd if=$1 |openssl des3 -d -k $3 | dd of=$2

Para restaurar el backup simplemente debe realizar lo siguiente:

$ restore archivo-encriptado archivo-salida-desencriptado clave

Si se preguntan cuan seguro es el encriptado?, pues la respuesta es suficiente para que yo ponga informacion real sobre una de mis tarjetas de creditos con algunos dolares (sobrantes del Google Summer of code) en un sitio de descarga para que la gente intente romper la seguridad. Si deseas intentar puedes bajarte de aqui.

]]>
http://cesar.la/backup-seguro-y-gratuito.html/feed
Un poco de humor “friki” http://cesar.la/un-poco-de-humor-friki.html http://cesar.la/un-poco-de-humor-friki.html#comments Wed, 03 Dec 2008 14:35:09 +0000 César Rodas http://cesar.la/?p=52 Luego de leer esta increíble página con algunos chistes frikis, me acorde que despues de mucho googlear encontre un mirror del desaparecido raulito el friki, sin pensarlo dos veces escribí un rápido script en bash para que me bajara todas las tiras.

Para aquellos desconocedores de la tira; Raulito es un friki con unos 30 años amante de Debian GNU/Linux y con la desgracia de que las iniciales de su nombre son R P M (Raúl Peñáez Martinez), que no puede encajar en la sociedad (¿O la sociedad no encaja con él?). Sus padres frustrados de su anómalo hijo (el cual reiteradas veces piensa y actúa como un kernel de Linux) tratan de cambiarlo e integrarlo a la sociedad, por ejemplo llamando a un cura (del opus dei) para que lo guíe por el buen camino. Al final éste termina creyendo que esta poseído y que es un ferviente y apasionado homosexual.

Si se preguntan porque no dudé ni un segundo en bajarte “la temporada”, vean porque: Primera tira, raulido

Si quieren bajarse las demás tiras, aquí están de la tira 1 hasta el 95.

Si alguien quiere también tengo todas las bilo y nano.

]]>
http://cesar.la/un-poco-de-humor-friki.html/feed
Servidores baratos, seguros y eficientes. http://cesar.la/servidores-baratos-seguros-y-eficientes.html http://cesar.la/servidores-baratos-seguros-y-eficientes.html#comments Mon, 01 Dec 2008 18:35:11 +0000 César Rodas http://cesar.la/?p=50 En estos días me he dedicado a ahondar en temas de performance para sitios relativamente grandes para shared hostings, y pequeños para servidores dedicados (posiblemente tenga que hostear un periodico local con 50.000 visitas diarias, todavía no está confirmado). Aunque el título de este post suena un poco marketinero, en realidad este artículo será la continuación de como mejorar el rendimiento de una web.

Baratos

Para la mayoría de los sitios webs no se justifica un servidor dedicado y un shared hosting que pequeño, entonces ¿cuál sería el punto medio?, ahí cuando entran en juego las VPS. Básicamente las VPS son máquinas virtuales en donde tenemos acceso total, casi todos utilizan Xen aunque personalmente me encanta el Linux-VServer (esto es totalmente irrelevante en este artículo), hablando mal y rápido son como máquinas dedicadas con menores recursos y lo más importante mucho menos precio.

Yo utilizo Linode, y realmente no me puedo quejar de su servicio, pero existen varios otra opciones en el mercado.

Seguros

La seguridad es un tópico bastante extenso, y básicamente es una buena práctica. Ya que nadie no velará por la seguridad de nuestro server, más que nosotros mismos tenemos que tomar algunas medidas, básicamente ocultar los detalles de nuestro servidores. En todos los ataques “hackers” que fui parte (siempre como victima, en algún trabajo) fue por que el “atacante” conocía como funcionaba el servidor (ex-funcionario) o por culpa del administrador del servidor que dejó las configuraciones por defecto.

Algunas de las buenas prácticas son las siguientes:

  • Secure Shell (SSh): Esta herramienta es fundamental a la hora de conectarnos al servidor y administrarlo remotamente, al mismo tiempo de ser necesario puede volverse peligrosa, aunque es cierto es difícil de vulnerar (casi siempre por error humano, como una contraseña sencilla), pero en caso de vulnerar los resultados serán fatales.
    • Desabilitar el root login.
    • Utilizar contraseñas dificiles de adivinar (ej: 12 caracteres, mezclas entre mayusculas, minusculas y números) y anotar la contraseña. Para utilizar la anotación a cada vez que se necesite conectarse al servidor, se podría en una máquina personal, generar claves (ssh-keygen) y luego copiar las claves (ssh-copy-id) al servidor así a cada vez que nos hagamos login en el servidor, este no nos pedirá contraseña.
    • Escuchar en un puerto distinto del puerto 22, es preferible escuchar en un puerto superior al 20000 así sería un poquito más complicado de detectar con con algún scanner de puertos.
    • Guardar Log de los login fallidos.
    • Vía firewall permitir el acceso al SSH de algunos rangos de IPs si es posible.
  • Siempre tener un firewall (IPtables o ipfw).
  • Evitar el DoS attack: Ricardo Galli, creador de Menéame (y mi amigo vía talk), escribió en su blog, un interesante artículo de como él evitó el ataque de DoS en su sitio, bastante interesante.
  • PHP:
  • MySQL:
    • El mysqld debe escuchar 127.0.0.1 si la base de datos se encuentra en la misma máquina que el servidor web, en caso contrario siempre asegurarse que este en una red privada, nunca colocar en una red pública escuchando 0.0.0.0
  • Backup: realizar backup de los datos (códigos fuentes, dump de la base de datos) y configuraciones (/etc/, /var/logs/ y otros) frecuentemente como máximo a cada día, y copiar en otro servidor, si no se cuenta con otro servidor, una alternativa efectiva y barata es colocar en Amazon S3.
  • Generalidades: limitar los recursos de los servicios, siempre tener logs de los deamons.

Eficientes

Como decía en mi articulo anterior (como mejorar el rendimiento de una web), si deseamos tener un servidor para atender un gran número de visitas lo más importante despues de la buena práctica de programación era tener un servidor web efectivo, parte de la efectividad era separa las consultas dinámicas y las estáticas hacia diversos servidores. Esto no es problema si hacemos las aplicaciones from scratch, pero que pasaría si por ejemplo queremos instalar una aplicación Open Source (ej: wordpress), cambiar las direcciones de los archivos estáticos hacia otro dominio sería un poco trabajoso (todos sabemos que no sería un problema que con wp, solo es un ejemplo). La solución más simple sería un reverse proxy que redirija la petición dependiendo de que si el recurso requerido es estático o dinámico.

Ahí es cuando entra en juego el NGinx ya que aparte de ser un increíble webserver, también es reverse proxy entre otras cosas. La configuración será la siguiente.

Arquitectura básica

Aquí va la configuración.

# El usuario va a ser nginx, siempre es importante
# ejecutar como usuario sin privilegio.
user  nginx;
# número de procesos "workers" que se encargaran de
# atender a la peticiones entrantes.
worker_processes 2;
# log
error_log  logs/error.log;
# pid, útil para reiniciar el "servicio"
pid        logs/nginx.pid;

events {
    # procesos que se encargaran de responder la petición
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] $request '
                      '"$status" $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    # Comprimir todo, útil para ahorrar ancho de banda.
    gzip  on;

    server {
        listen       80;
        server_name  www.servidor,com;
        access_log  logs/host.access.log  main;
        location / {
            # Por defecto pasar todas las peticiones al Apache.
            proxy_pass http://127.0.0.1:8080/;
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            client_max_body_size 10m;
            client_body_buffer_size 128k;
            proxy_connect_timeout 90;
            proxy_send_timeout 90;
            proxy_read_timeout 90;
            proxy_buffer_size       4k;
            proxy_buffers       4   32k;
            proxy_busy_buffers_size 64k;
            proxy_temp_file_write_size   64k;
        }
        # Si lo requerido es una archivo "estático", nginx tiene que responder,
        # y el cliente no tiene que volver a preguntar (gracias al cache).
        location ~* ^.+.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ {
             root   /var/www/html/;
             expires max;
        }
        # Página estática de error
        error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}

Para este ejemplo el Apache tiene que escuchar el puerto 8080 del IP 127.0.0.1. También sería razonable desabilitar los logs de acceso (pero los de error no, ya que PHP escribiría ahí los errores) ya que esos logs son generados por nginx, y para qué repetir datos?

]]>
http://cesar.la/servidores-baratos-seguros-y-eficientes.html/feed
Mitos y verdades sobre el “hackeo” al NIC http://cesar.la/mitos-y-verdades-sobre-el-hackeo-al-nic.html http://cesar.la/mitos-y-verdades-sobre-el-hackeo-al-nic.html#comments Thu, 27 Nov 2008 12:39:25 +0000 César Rodas http://cesar.la/?p=49

Lo aquí expresado es estrictamente personal y nada tiene que ver con el CNC o el NIC, aunque yo trabaje en el CNC.

En estos días se hizo eco el “hackeo” a la página del NIC, un buen hackeo realizado por un conocido bastante inteligente (nada contra él, todo bien) que aprovechaba algunos bugs del CGI escrito en Perl (hace más de 10 años) haciendo un inyección (no voy a entrar en detalles). Los “daños” realizado fueron mínimos ya que solo entraron al servidor web que no tiene nada de información, bases de datos ni nada relacionado al NIC o al CNC, si desearían hackear necesitarían hacer algo mejor que eso ya que todo está en una red privada.

Si piensan que los culpables somos los funcionarios actuales del CNC, la respuesta es NO, ya que esa página está desde hace mas de 10 años, y tenemos una larga lista de actividades de cosas a hacer, no podemos pasar la vida controlando cosas que fueron hechas hace años y que funcionan (o así parece).

Hasta ahí todo bien, ahora lo que me molesta y bastante es que algunas personas anden opinando por foros o algo así, y mucho más un tal Lucho Benítez (ex funcionario del CNC, de la misma época cuando el CGI fue escrito) esté escribiendo artículos amarillistas y aprovechándose de la ignorancia de los medios de prensa, para hacer polémicas (flameware) y así tener unas cuantas visitas en su blog (y hacer unos cuantos dolares), típico troll, alguien debería comentarle que si quiere visitantes existe digg, o meneame si solo habla español.

Investigué algo referente a Lucho Benítez, lastimosamente google no me dijo nada, pregunté a gente del CNC, y tampoco me dijeron nada, por lo visto su paso por el CNC no marcó historia como el de algunos legendarios que sus nombres aún se escuchan por los pasillos. A continuación responderé algunas cosas de su artículo amarillista.

Que sucedió en el CNC? ya no tienen presupuesto para el mantenimiento de los servicios? Habría que recordar que este también es un MONOPOLIO al que ni la prensa ni los proveedores hacen referencia.

Monopolio?!, que otra empresa está preparada para tener el servicio de NIC?, algún grupito de “hackers” o usuarios Unix?, otro ente público? alguna empresa privada?. Si tienen problemas deberían hablar con el Rectorado UNA, no con el CNC.

Por qué este evento desnuda un problema grave? Simplemente por que el DNS forma parte de los protocolos que hacen a la arquitectura de Internet. Sin el DNS del NIC.py nadie en ninguna parte del mundo podrá llegar a servidores que tienen su dominio bajo .py. Podrían hacerlo por el número IP, pero no por el nombre de dominio. Al tomar cierto control de este equipo es solo cuestión de tiempo, (y si se tiene el conocimiento adecuado) tomar el control del resto de la infraestructura.

Linda clase para muggles pero totalmente irrelevante, y nada tiene que ver con el servidor web que fue “hackeado”.

Es por ello que me parece gravisima la desidia y/o negligencia del personal del CNC, y ante todo una vez más la población (sobre todo los usuarios) de vuelta esta desprotegida, y seguirá así, si no cambian ciertas prácticas.

Que tiene que ver eso con un servidor web?

Otra cosa que me molesta es la gente ignorante que se quejan por los precios del NIC asociando al CNC como responsable, si tienen problemas con el precio (o burocracia) deberían hablar con la gente del Rectorado (somos dependiente del Rectorado UNA) o comprarse .com

Modestia aparte, en el CNC sólo trabaja gente muy buena (entre las mejores del Paraguay, si no me creen pueden googlear el nombre de algún funcionario del área técnica), pero el problema principal es la constante rotación del personal debido al bajo salario (somos funcionarios del Rectorado).

]]>
http://cesar.la/mitos-y-verdades-sobre-el-hackeo-al-nic.html/feed
And finally the beta is out! http://cesar.la/and-finally-the-beta-is-out.html http://cesar.la/and-finally-the-beta-is-out.html#comments Thu, 20 Nov 2008 03:02:38 +0000 César Rodas http://cesar.la/?p=48 After some time of the alpha version, FileS3 beta is finally out. As you can see the site now have a new, simplest design, much faster than the others sites.

Probably you may be wondering why another file share system?, the answer is quite simple, I got tired of wait more than a  minute because I was not a premium user, or download slower over the time when download a bunch of things.

Another things that really really bother me is that if my connection goes down during a large download, I need to start again, and also, I cannot have parallel download.

So, I created FileS3, a simple share system, where every user is a premium user.

What is next:

  • Right now the Link live time is two hours, What I will do next is calculate the LLT (link live time) based on the file size.
  • MP3-embedding, if you upload an mp3 file, FileS3 will generate the HTML code for embedded an mp3 player in your blog.
  • Internationalization, because not all the people speak only English…
  • Desktop uploader: This will be a cool Python+Gtk2 (Of course will be Free Software) app that will help user to upload largest file and pause.
]]>
http://cesar.la/and-finally-the-beta-is-out.html/feed
¿como mejorar el rendimiento de un sitio web? http://cesar.la/como-mejorar-el-rendimiento-de-un-sitio-web.html http://cesar.la/como-mejorar-el-rendimiento-de-un-sitio-web.html#comments Sun, 16 Nov 2008 22:13:31 +0000 César Rodas http://cesar.la/?p=47 En estos días, después de terminar la beta de mi nuevo sitio para compartir archivos FileS3, me puesto en teoría mis conocimientos sobre optimización de paginas webs (para mejor rendimiento, nada que ver con el SEO, esa es otra rama). Mi motivación para escribir este post es que hay varios sitios webs en el Paraguay con mucho trafico (o al menos eso dicen) , y todos ellos desoptimizados. Esta desoptimización es costosa para la empresa que gasta millones en hardware (supongo… no trabajo para ninguna) y en ancho de banda, y para el usuario ya que el tiempo de respuesta no es optimo.

Archivos estáticos (*.png, *.jpg, *.js, *.css, etc)

Aunque Apache es un gran servidor web, perfecto para todas las consultas dinámicas (mod_php, mod_*), consultas CGI, pero es demasiado costosa para procesar archivos estáticos, con esto no estoy diciendo que tener archivo estáticos con Apache este mal, solamente digo que si quieren ahorrar recursos para recibir mas visitantes se debe cambiar esto.

Para eso existen varios servidores “lightweight”, yo recomendaría NGinx (se lee engine x) pero hay varias alterativas, solo basta googlear un poco (también leí mucho sobre el http://www.lighttpd.net/).

Si se pregunta porque estos servidores “lightweight” son mejores que Apache para archivos estáticos, la respuesta es que estos servidores usan hilos (o métodos similares) para atender a varias consultas en simultaneo, ademas cuenta con una arquitectura mucho mas simple y pequeña, lo que se adecua a las necesitas de los archivos estáticos, ya que no necesitan nada mas que ser leídas del disco duro y ser enviada al cliente.

Otra cosa muy importante para ahorrar ancho de banda, es el cacheo en el cliente. Este cacheo es simplemente que el servidor le dice al browser (claro, a firefox casi siempre) que estos archivos estáticos no cambiaran en un tiempo dado (10 días, 20 días, 10 meses, etc), entonces el browser una vez que obtiene los archivos no vuelve a bajar el mismo archivo ya que sabe que ese archivo no va a cambiar, y tiene una copia del archivo en el disco. Para realizar esta configuracion con el NGinx pueden leer esta pagina, si usan otro servidor deben buscar como manipular el header “Expires”.

Seguramente se estarán preguntando, que pasaría si actualizamos algún archivo estático, bueno, el browser no se enteraría mientras que no se alcance el tiempo de validez del cache. Pero no se alarmen, existe una solución bastante simple, un poco de orden todo lo arregla.

Ya que nuestro contenido dinámico no se debe cachear (o se debe cachear por un tiempo mínimo como 10 min, 1h ,etc), y ya que el contenido dinámico incluye, es la que incluye a el contenido estático, nuestro problema esta resuelto!, simplemente tendremos que crear un sistema rudimentario de versionamiento. Ejemplo:

Esta es la manera como una pagina incluye a un elemento estático, un archivo css:
<link rel='stylesheet' href='/style.css' type='text/css' />

Con el versionamiento seria algo asi:
<link rel='stylesheet' href='/css/1/style.css' type='text/css' />

Entonces cuando cambiamos algo, simplemente tendremos que crear una
nueva carpeta y actualizar las referencias de la pagina hacia el elemento
estatico (si es una web en serio organizada no seria un problema).
<link rel='stylesheet' href='/css/2/style.css' type='text/css' />

Como ven, no es una solución complicada y así no tenemos excusas para utilizar el cacheo de archivos y así empezar a ahorrar un montón de ancho de banda.

Otro tip importante, para ahorrar ancho de banda es utilizar algún módulo del servidor que pueda comprimir los archivos antes de enviar (si el browser soporta). Aquí está un ejemplo de configuración para el NGinx que tiene activado la compression (gzip).

Consultas dinámicas. (*.php, cualquier otro)

Bases de datos…

En esta sección hay mucho para hablar, yo me voy a enfocar en lo mas conozco, y en lo que pienso es en donde mayor desoptimización hay, las bases de datos.

La base de datos es un mal necesario, digo mal, porque es bastante costosa, pero no podemos vivir sin ella, es costosa porque hay autenticación (casi siempre, excepto SQLite), comunicación por la red (casi todos), compilación del código SQL, ejecución del código y acceder al disco duro. Pero como dije antes, no podemos vivir sin el, entonces la solución es evitarlo cuando sea posible, para eso podemos cachear el resultado de una consulta con un TTL (time to live) apropiado. El medio de almacenamiento puede ser un archivo en el disco duro, aunque es costoso el acceso al disco duro hoy en día los sistemas operativos tienen en memoria RAM los archivos que son leídos frecuentemente. Cuando hay varios servidores webs, la mejor alternativa seria guardar el cache en un servidor de Memcached, así no se duplica el cache.

Para ayudar a todo que es cacheo, en mi opinión, se debería construir un Capa de abstracción entre el acceso a base de datos y la aplicación (similiar al PDO del PHP5) y ahí implementar todo lo que sea cache. Ahora mismo estoy escribiendo un lenguaje de modelado de datos y su generador de código. Su misión sera que genera las tablas y el código PHP (mas adelante python, java,etc) para acceder a la tablas sin escribir SQL (similar a www.metastorage.net) y ahí estará implementado cacheo de consultas a disco duro, memcached o cualquier otro medio. Más adelante estaré hablando mas sobre este proyecto, que espero que muy pronto este todo terminado.

Minimizando el tráfico.

Al igual que los archivos estáticos, los archivos dinámicos pueden ser cacheado y comprimidos. Claro que el TTL tiene que ser un tiempo razonable. Imagínense la página principal de un diario digital, esa página si tiene último momento, no cambiaría tan frecuentemente, cambiaría a cada 5 minutos o 10 minutos, entonces para que generar toda la página para cada visitante?. La solución sería (para los usuarios que no este logueados, si soporta logueo) cachear toda la página (en el disco duro) y comprimir así tenemos dos versiones del mismo cache, la comprimida y la descomprimida, porque comprimir “on the fly” es costosa, se pierde tiempo y valiosos ciclos de CPU. Para cada visitante enviamos el cache comprimido si soporta o el cache normal y también le enviamos el tiempo que falta para que el cache sea recreado (para un diario yo pondria 5 ~ 10 min). Y para los artículos que generalmente no cambian creo que un TTL adecuado es de 2 horas o algo así.

Hace algún tiempo escribí una clase que hace eso mismo, gCache.

APC.

APC (Alternative PHP Cache) es un excelente cache en RAM del bytecode del PHP (PHP no es interpretado desde la versión 4). Para demostrar la diferencia, hice una simple prueba con la pagina principal del  FileS3, cuya pagina esta bien optimizada ya que cuenta con un cache y en el momento de la prueba el cache existia, lo que significa que solo una par de lineas de códigos PHP fueron ejecutadas, ademas fue probada en el servidor para que no haya retardo de red.

Para la prueba utilice ApacheBench (ab -c 30 -t 30 http://local.files3.com/) simulando a 30 usuarios concurrentes que visitaban a http://local.files3.com/ por 30 seguntos.

This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking local.files3.com (be patient)

Server Software:        Apache/2.2.8
Server Hostname:        local.files3.com
Server Port:            80

Document Path:          /
Document Length:        7232 bytes

Concurrency Level:      30
Time taken for tests:   30.8135 seconds
Complete requests:      3166
Failed requests:        0
Write errors:           0
Total transferred:      23656585 bytes
HTML transferred:       22918208 bytes
Requests per second:    105.50 [#/sec] (mean)
Time per request:       284.347 [ms] (mean)
Time per request:       9.478 [ms] (mean, across all concurrent requests)
Transfer rate:          769.86 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0  10.1      0     117
Processing:    19  281  75.0    282     675
Waiting:        1  248  75.2    252     653
Total:         20  282  74.0    283     675

Percentage of the requests served within a certain time (ms)
  50%    283
  66%    302
  75%    316
  80%    328
  90%    364
  95%    408
  98%    472
  99%    499
 100%    675 (longest request)

Como verán, con toda la optimización pudimos responder 3166 paginas. Ahora instale APC ( yum install php-pecl-apc.i386  -y), y la configuración  de facto, realice la misma prueba y aquí están los resultados.

This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking local.files3.com (be patient)

Server Software:        Apache/2.2.8
Server Hostname:        local.files3.com
Server Port:            80

Document Path:          /
Document Length:        7244 bytes

Concurrency Level:      30
Time taken for tests:   30.1087 seconds
Complete requests:      7028
Failed requests:        0
Write errors:           0
Total transferred:      52570787 bytes
HTML transferred:       50932564 bytes
Requests per second:    234.26 [#/sec] (mean)
Time per request:       128.064 [ms] (mean)
Time per request:       4.269 [ms] (mean, across all concurrent requests)
Transfer rate:          1711.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   4.2      0      69
Processing:    39  126  23.1    123     300
Waiting:        1  123  22.7    120     300
Total:         39  127  22.9    123     300

Percentage of the requests served within a certain time (ms)
  50%    123
  66%    130
  75%    136
  80%    141
  90%    154
  95%    169
  98%    191
  99%    206
 100%    300 (longest request)

El resultado es asombroso, ahora pudimos servir 7028 paginas, con solamente haber instalado APC, si pasan un poco mas de tiempo tuneando, podríamos mejor mucho mas.

Tu experiencia!

Seria espectacular si ustedes comparten aqui sus metodos de optimizacion para ayudarnos entre todos, varias cabezas son mejores que una.

Saludos.

]]>
http://cesar.la/como-mejorar-el-rendimiento-de-un-sitio-web.html/feed
GeekSlides - Presentaciones con LaTeX http://cesar.la/geekslides-presentaciones-con-latex.html http://cesar.la/geekslides-presentaciones-con-latex.html#comments Mon, 03 Nov 2008 14:06:38 +0000 César Rodas http://cesar.la/?p=45 Despues de hacer algunas presentaciones, y cansado del Impress (el mouse no fue creado para mi), he decidido utilizar Latex para crear mis presentaciones, como una prueba, para ver que tal me va.

Basicamente en este post voy a describir, paso por paso lo que hice para tener hermosas presentaciones en pdf (código fuente), escribiendo un poco de codigo con LaTeX. Antes que nada utilize la guia escrita por Matt Welsh (Creating Presentations in PDFLaTeX), por cierto muy buena guia, excepto que no pude instarlas las fuentes con el script perl que esta en esa pagina.

  • Deben bajarse el textslides.tbz2.
  • Descomprimir ( tar xfvj textslides.tbz2 -C $HOME)
  • Editar el archivo de configuración de cuenta
    • echo “export TEXINPUTS=\$TEXINPUTS:~/.texslides/” >> ~/.bashrc
    • echo “export PATH=\$PATH:~/.texslides/” >> ~/.bashrc
  • Para instalar fuentes nuevas, hacer lo siguiente:
    • Copiar las fuentes en una carpeta dada (archivos *.ttf)
    • Luego ejecutar “addfont”
    • Si siguieron todos los pasos las fuentes estaran bien instaladas.
  • Luego para compilar el archivos Latex deben realizar lo siguiente:
    • pdflatex archivo.tex
    • ppower4 archivo.pdf carchivo.pdf
    • mv carchivo.pdf archivo.pdf

Básicamente crear presentaciones con LaTeX, es algo sencillo, y vale la pena si la presentación es simple, como imágenes de fondos, incluir imágenes, citar cosas. La cosa se va complicando (cosa que me pasó) cuando se requiere hacer cosas complejas, como resaltar código (escribí un pequeño script en python que con otro script bash hace eso, es algo grocero, pero funciona más o menos). Aún asi para presentaciones normales, lo mejor es utilizar LaTeX (claro con ViM), porque todo está en su lugar, no hay utilizar el mouse para alinear textos, ni nada.

Como me encanto el LaTeX (la idea de no depender del Impress me encanta), decidí comenzar un proyecto llamado GeekSlide, que básicamente será un generador de presentaciones (por ahora a HTML) basado en modelado de texto bastante sencillo (similar pero no igual al RST), que será modular, el “core” no generará ninguna presentación, solo parseará el texto y pasara esa información a un “render” (un plug-in) que será el encargado de generar la presentación.

Así será un típico archivo de gslides (ojo: no es gnu/slides, es GeekSlides).

% Esto es un comentario que será ignorado por el parseador
% Definir variables globales
% Estas variables serán pasadas al render, generalmente será

% útil para generar la primera página

title:
Introducción a GeekSlides.
author:
Cesar Rodas
email:
talks@cesar.la
% “—” Es el separador de páginas.


=Que es GeekSlides= % Típico Título (header 1)
% Llamamos a la función “gradient” definida en el “render”.
% Básicamente mostrará los items en un color claro, luego
% cambiarán de color en los siguientes slides.

{gradient}
* Generador de Presentaciones.
* Totalmente modular.
* Open source.

=Caracteristicas=
==Textos==
{font,¨arial”,”14px”}
* *Texto en negríta*
* _Texto en cursiva_
* Resalto de código.
{source,”foobar.php”,”grey”}

{center}
=¿Preguntas?=

Como se darán cuenta la sintaxis es mucho más amigable que el LaTeX, y es bastante útil (a no ser que necesiten realizar complejas formulas matemáticas). El proyecto ahora mismo es solo un prototipo que estoy armando, y creo que pronto estará disponible para que lo puedan utilizar, extender, o lo que quiera. El proyecto será publicado en PHPClasses, y la documentación estará aqui mismo.

]]>
http://cesar.la/geekslides-presentaciones-con-latex.html/feed
PHPAJAX 3 - Write an AJAX app was never this easy http://cesar.la/phpajax-3-write-an-ajax-app-was-never-this-easy.html http://cesar.la/phpajax-3-write-an-ajax-app-was-never-this-easy.html#comments Fri, 03 Oct 2008 18:31:39 +0000 César Rodas http://cesar.la/?p=39 After some time of inactivity due personal activities (University, work, investigations, etc) I had large to-do list, basically rewrite and improve some of my classes, talking fast the Amazon S3 class and the PHPAJAX. Right now I am working on Rewrite the PHPAJAX and release its version 3 with major changes, and in the background I’m working on the PHP Pagerank (php-coding pagerank &), but ut will wait some time until the PHPAJAX is fully working.

As in previous versions, PHPAJAX aims to help developer to write AJAX app. without write javascript (or writing just a little of). In previous versions execute PHP functions directly from the browser (as most of AJAX libraries), with some little advantages, such as read data directly from the HTML objects, File upload, and others.

The new version of PHPAJAX will be completely rewritten from scratch, and it will not use anymore Prototype, it will have its own set of javascript code, optimized to what the PHPAJAX program does. The idea is produce the Javascript code on the fly (then in a near future js-cache the code). The code by default will be obfuscated and minimized.

Basically the PHPAJAX compatibility will be broken right now, later I’ll code a wrapper that will execute PHPAJAX 2 code.

Some features of the PHPAJAX 3 will be:

  • File Upload.
  • Better PHP-DOM functions, to manipulate HTML objects as if they were PHP objects.
  • Special Objects.
    • Drag and Drop (containers and objects)
    • Autocomplete fields.
  • PHP to JS API for write Javascript code using just PHP.
  • Easy to extends.

I have not an official release date, but I’m doing my best, I think it will be available in two weeks, you can keep an eye on my blog, I promise that I will disclose the repository URL soon.

BTW, you can take a look on Fossil SCM, because I’m using it for this project.

Happy hacking, have a nice weekend!

]]>
http://cesar.la/phpajax-3-write-an-ajax-app-was-never-this-easy.html/feed
Europe, DNSSEC and more! (Powered by ISOC) http://cesar.la/eurore-dnssec-and-more-powered-by-isoc.html http://cesar.la/eurore-dnssec-and-more-powered-by-isoc.html#comments Sat, 20 Sep 2008 22:49:15 +0000 César Rodas http://cesar.la/?p=37 After a great week in Amsterdam (pictures?) in a wonderful ISOC’s workshop at the RIPE.net, I learnt many useful things, specially about the weakness of the DNS and the benefits about the DNSSEC. Also I had a brief overview about the network monitoring.

The greatest part was that I met the administrator of the l.root-servers.net (If you don’t know what is the root-servers.net read this) and he gave us useful tricks of how to manage a huge network (just figure out the hard work of a root server).

Also I met people from other NICs, it was a great experiences.

From now on, I’ll start to write in English and Spanish (depending of my mood) about these topics, and also I’ll write some scripts to manage the DNSSEC, also I’ll write about the DNS poisoning and other attacks (just to show how weak is the DNS), so if you’re interested just keep watching my site.

]]>
http://cesar.la/eurore-dnssec-and-more-powered-by-isoc.html/feed
Welcome to my playground! http://cesar.la/welcome-to-my-playground.html http://cesar.la/welcome-to-my-playground.html#comments Mon, 25 Aug 2008 23:54:12 +0000 César Rodas http://cesar.la/?p=28 As you may know, I have an old blog in English, but I stop to write there because I have this blog in Spanish. Then I notice that most of my friends (most of them not Spanish speakers) stops to read my blog for obvious reasons ;), so I decided to also write in English here, and you be wondering why don’t you write at cesarodas.com in English?, and the answer is quite simple, I like this domain, and I’d like to have everything in a single place, Spanish and English, all together.

I’d try to talk about *similar* things (not just translations) in both languages, and of course, I’ll try to improve my English (now I’m studying).

So welcome back to my playground!

]]>
http://cesar.la/welcome-to-my-playground.html/feed
Al fin la facultad fue útil http://cesar.la/al-fin-la-facultad-fue-util.html http://cesar.la/al-fin-la-facultad-fue-util.html#comments Sat, 23 Aug 2008 22:12:50 +0000 César Rodas http://cesar.la/?p=27 Luego de tres años de estar en la facultad, siempre pensé que todo lo que aprendí ahí no me serviría para la vida, aunque suena un poco arrogante, pero la verdad es que pensarían lo mismo si sólo algunos profesores de los 25 profesores que me enseñaron, valen la pena. Esto es un simple comentario personal, y le moleste a quien le moleste es la verdad.

En la semana tuve una nueva meta, tener un nuevo mail @apache.org, @php.net, ambos antes de tener 23 años, aprovechando que me quedan 2 años y dos dias ;).

Bueno, me puse manos a la obra, comenzé por el que creía que sería más difícil, por apache (sin desprestigiar a @php.net), y la verdad que no me fué tan bien, pero claro que eso no me va a desanimar. Luego le llego al turno a php, y para eso la manera más fácil para mí y donde me sentiría mas cómodo, fue hacer una contribución en PEAR, y la verdad que mi primer “contacto” con PEAR el año pasado no me fue tan bien, pero por suerte este año me fue mejor de lo que estaba esperando.

Como habrán leído, estoy trabajando en mis tiempos libres en mi proyecto PEAR, una implementación del PageRank en PHP, que podrá ser ejecutada como un solo proceso, o en paralelo en varias máquinas.

El diseño de la clase, es bastante modular, y la persona que desea usarla debería tener una lista de números (fuente->destino), que se puede tener con un crawler, o bien puede ser útil para alguna red social para saber quienes son los “mejores” (más famosos, más activos, etc). usuarios.

Para mis pruebas, hice un pequeño y genérico crawler en PHP (que pronto estará publicado en PHPClasses), e indexé 116.396 páginas, 7.132.973 links, y 23.598.922 “nodos de links” (los intercambios de links origen->destino).

Como sabrán, el PageRank, es un proceso recursivo, que se necesitan iteraciones, osea repetir el proceso del calculado N veces (en el paper dice que ~52 veces fueron suficientes para sus pruebas), también menciona que es un proceso lento.

En mis pruebas, use a MySQL, como repositorio de mis datos, y para mi sorpresa la iteración tardó 2 horas exactamente, lo que significa que tardaría 104 horas (4 días) para calcular el PR de todo mi ejemplo, lo que es un tiempo inaceptable para tan insignificante grupo de páginas. Entonces buscando una solución, me acorde de mis magistrales clases de Sistemas Operativos con el Prof. Carlos Zayas, el capítulo de sistemas de archivos, específicamente la parte de cacheo en memoria, y empecé a desarrollar una capa de cacheo entre el PHP y el origen de los datos (en mi caso MySQL), el primer algoritmo fue el MRU. Alguna vez implementé este algoritmo en C, lo cual fue sencillo gracias a los punteros, pero en PHP me fue complicado, ya que no hay punteros sólo referencias, lo cual es un concepto nuevo para mí (humanware-error), y la solución era peor que la cura. Luego me di cuenta que los datos eran secuenciales, y que pensé que un cacheo usando FIFO sería suficiente, y empecé a codificar.

Para mi sorpresa, el FIFO con un buen tamaño (100MB), es perfecto, de 2 horas por iteración pasó a ser 16 minutos por iteración. Y viendo en los logs, me dí cuenta que el cache respondió a 14.598.272 consultas. Gracias a Prof. Carlos Zayas, mis problemas fueron resueltos.

Espero terminar pronto con este proyecto, por si alguien quiera ver el código, lo puede hacer en mi lugar de juego.

]]>
http://cesar.la/al-fin-la-facultad-fue-util.html/feed
Combatiendo el SPAM - ataques de diccionario http://cesar.la/combatiendo-el-spam-ataques-de-diccionario.html http://cesar.la/combatiendo-el-spam-ataques-de-diccionario.html#comments Fri, 15 Aug 2008 06:48:28 +0000 César Rodas http://cesar.la/?p=19 El spam es uno de los males que está plagando la Internet. Si tienes tu propio servidor de e-mail para uso personal o administras uno en el trabajo, lidiar con ellos es un verdadero dolor de cabezas. Existen varios tipos de ataques, en este artículo muestro como combatir y bloquear a los spammers que realizan el ataque de diccionario.

El ataque de diccionario, es el ataque más tonto que un pseudo-spammer puede hacer, y ya que es un ataque tonto, tiene una solución bastante sencilla. El ataque consiste en lo siguiente.

  • Toman un e-mail real (Ejemplo: crodas@foo-bar.com)
  • Luego se le agregan letras randomicas al inicio al final

La solución se simple, hay que seleccionar todos lo mails que fueron enviados a destinatarios no existentes, luego agrupar por IP, y comparar a ver si los destinatarios son similares, si son es un ataque de diccionario. Este script funciona con el output del Log del sendmail.

#!/usr/bin/php -q
<?php
# Dedicado para los que piensan que PHP es solo para páginas webs
# y para las personas tontas que comparan un framework cualquiera
# con un lenguaje de programación.
#
if ( isset($argv[1]) && $argv[1] == "--debug")
    define("DEBUG",1);

function x_readline()
{
    return trim(fgets(STDIN));
}

function asum($x,$y)
{
    $x += $y;
    return $x;
}

$ip=array();
$dst=array();
while ($line=x_readline())
{
    $line = strtolower($line);

    preg_match("/\]\: ([a-zA-Z0-9]+):.*from\=.*relay=.*\[([0-9\.]+)\]/i",$line,$result);
    if ( count($result) > 0) $ip[$result[1]] = $result[2];
    if ( strpos($line,"user unknown") !== false )
    {
        preg_match("/]\: ([a-zA-Z0-9]+):.*to=<?([^\@]+)>?/i",$line,$result);
        if ( count($result) > 0 && isset($ip[$result[1]]) )
            $dst[ $ip[$result[1]] ][] = $result[2];
    }
}
$result=array();
#ahora a buscar los posibles candidatos
foreach($dst as $ip => $mail)
{
    $tmp=array();
    $t = count($mail);
    if ( $t < 5) continue; /* una regla mia, un mínimo de 5 mails fallidos para el control */
    for($i=0; $i < $t-1; $i++)
    {
        $threshold = (strlen($mail[$i]) + strlen($mail[$i]))/2 * 0.3;
        $weight = levenshtein($mail[$i],$mail[$i+1]);
        $tmp[] = $weight < $threshold && $weight!=0  ? 1 : -1;
    }
    $result[$ip] = array_reduce($tmp,"asum");
}

foreach($result as $ip => $weight)
{
    if ( $weight < 0) continue;
    print "$ip\n";
    if (defined("DEBUG")) print_r($dst[$ip]);
}
?>

El script devuelve una lista de IPs que realizan ataques de SPAM. Esta lista es suficiente para poder combatirlo, ejemplo ejecutar periodicamente (crontab) a cada 3 (es un ejemplo) horas y agregar en la lista de bloqueos del sendmail. Aquí un script bash que hace el trabajo “duro” de darle forma a el “output” del php.

#!/bin/bash
#configuracion
LOG=/var/log/maillog
SENDMAIL=/etc/mail/
DB=/etc/mail/access
DATE=`date`
TEXT="550 We don't accept mail from spammers # Auto added $DATE"
#el programa en sí
if [ `whoami` != "root" ]
then
    echo "Only root can run this script"
    exit
fi

find_in_access() {
    VALUE=$1
    local ACCESSIP
    for ACCESSIP in $( cat $DB | awk '{print $1}' );
    do
        if [  "$ACCESSIP" = "$VALUE" ];
        then
            return 0
        fi
    done
    return 1
}

for SPAMMER in `cat $LOG | php php_dict_attack.php`
do
    find_in_access $IP
    if [ $? -eq 1 ];
    then
        echo "$SPAMMER			$TEXT" >> $DB
    fi
done
cd $SENDMAIL
make
]]>
http://cesar.la/combatiendo-el-spam-ataques-de-diccionario.html/feed
YouTube desde FreeBSD http://cesar.la/youtube-desde-freebsd.html http://cesar.la/youtube-desde-freebsd.html#comments Tue, 05 Aug 2008 03:34:21 +0000 César Rodas http://cesar.la/?p=25 Cuando me instalé mi FreeBSD, de la emoción de tener un verdadero Unix me olvidé de ver algunos detalles *también* importantes, la historia comienza cuando quería ver un video educativo acerca de la cultura de los Norteamericaos, para mi mala suerte me dí cuenta de que cometí dos estupideces, no instale el plug-in de Flash para Firefox, y mi tarjeta de sonido no estaba configurada… Luego me puse a laburar a full en ver como solucionar mi problemas, y el vídeo cultural se retrasaría por una hora más aproximadamente.

Primero es lo primero, tuve que “instalar” el driver de sonido, y digo “instalar” porque solo faltaba levantar el módulo en el kernel, como lo describe el glorioso handbook del diablito, pero no había nada emocionante ahi… aparte que ganas me sobraban de recompilar el kernel, y eso fue lo que hice (para este paso necesitan haber instalado los fuentes)

cd /usr/src/sys/$ARCH/conf; #en mi caso $ARCH=i386
cat GENERIC  |  sed "s/GENERIC/CRODAS/g" > CRODAS # "CRODAS" va a ser el kernel (soy un narcisista :P)
echo device sound >> CRODAS
echo device snd_emu10k1 >> CRODAS
echo device snd_sbc >> CRODAS
echo device snd_sb16 >> CRODAS
cd /usr/src
make buildkernel KERNCONF=CRODAS # compilar el kernel
make installkernel KERNCONF=CRODAS # instalando el kernel
reboot # algunas (solo raras) veces hay que reiniciar un verdadero sistema operativo

Luego de esperar no mas de media hora (depende del hardware) ya podía escuchar música con XMMS (/usr/ports/multimedia/xmms), pero todavía no era suficiente para ver el vídeo “cultural”, entonces me puse a googlear y encontre los alternativas:

  • Instalar el Firefox de Linux, luego instalar los drivers como si fuera en cualquier distro (no es un reto)

  • Instalar el plugin flash con un “wrapper” para Freebsd (como Stallman diría “the lesser evil”)

Para ello hice los siquiente:

cd /usr/ports/www/nspluginwrapper && make install clean
cd /usr/ports/www/linux-flashplugin7 && make install clean
nspluginwrapper -v -a -i # como el usuario que va a ejecutar firefox

Y al fin ya pude ver el video educativo…

Como verán la versión del flash es 7, y existe algunos sitios (gracias a Dios Youtube no) que su “player” necesita flash 9, para esos casos una buena mirada hacia sus fuentes y “wget” lo pueden solucionar.

Larga vida a UNIX

]]>
http://cesar.la/youtube-desde-freebsd.html/feed
Conviviendo con un verdadero Unix http://cesar.la/conviviendo-con-un-verdadero-unix.html http://cesar.la/conviviendo-con-un-verdadero-unix.html#comments Wed, 30 Jul 2008 02:22:51 +0000 César Rodas http://cesar.la/?p=23 Luego de bastante tiempo de usar GNU/Linux en mi notebook (y solamente GNU/Linux, no soy de esas personas con “dual booting” :P) , y después de bastante tiempo de usar FreeBSD como router en mi trabajo, me decidí instalar FreeBSD en mi notebook a ver que tal me iva con este increíble sistema operativo (ahora si ya tengo dual booting).

Antes que nada tuve que cambiar el tamaño de una partición (dejar libre unos 12GB) para poder instalar FreeBSD 7, en mi notebook. La instalación no fue nada fuera de lo normal, excepto que el FreeBSD no modifico mi MBR, ya que es más fácil (para mí) modificar el grub.

La mejor característica que tiene FreeBSD es el mágico directorio “/usr/src” donde se encuentra los fuentes de todos los fuentes, lo que se hace bastante fácil recompilar la “base” de FreeBSD. Los demás programas se encuentran en “/usr/ports”.

Otra cosa que me gusta mucho, es que viene con menos abstracción, (casi) no existen utilitarios para configurar cosas, solo archivos *.conf.

La instalación fue con X11 y por supuesto con Gnome, ya que no soy partidario de escritorios que mal gastan recursos (ya que tan escritos con “extensiones” de grandes lenguajes). Para que el FreeBSD inicie en modo gráfico, hay que modificar el archivo /etc/ttys que es similar al /etc/inittab de GNU/Linux, pero más simple y más poderoso, ya que se puede configurar fácilmente para ejecute un programa en tty, en mi caso el gdm de gnome. El archivo quedo similar al siguiente:

ttyv0 “/usr/libexec/getty Pc” cons25 on secure
# Virtual terminals
ttyv1 “/usr/libexec/getty Pc” cons25 on secure
ttyv2 “/usr/libexec/getty Pc” cons25 on secure
ttyv3 “/usr/libexec/getty Pc” cons25 on secure
ttyv4 “/usr/libexec/getty Pc” cons25 on secure
ttyv5 “/usr/libexec/getty Pc” cons25 on secure
ttyv6 “/usr/libexec/getty Pc” cons25 on secure
ttyv7 “/usr/local/sbin/gdm” gdm on secure

Los ports que instalé en mi máquina son:

  • /usr/ports/devel/kdevelop = El mejor IDE que conocí del mundo para mí.
  • /usr/ports/security/openssh = El servidor OpenSSH
  • /usr/ports/net-im/pidgin = Todos necesitamos comunicarnos
  • /usr/ports/multimedia/vlc = Lo mejor para ver vídeos
  • /usr/ports/multimedia/xmms = Músicas, músicas

Para esas carpetas hice lo siguiente:

cd $DIR; make install clean

Larga vida a UNIX!

]]>
http://cesar.la/conviviendo-con-un-verdadero-unix.html/feed
CodeJAM - Saving the Universe - Resuelto en PHP (otra más) http://cesar.la/codejam-saving-the-universe-resuelto-en-php-otra-mas.html http://cesar.la/codejam-saving-the-universe-resuelto-en-php-otra-mas.html#comments Thu, 17 Jul 2008 21:17:27 +0000 César Rodas http://cesar.la/?p=21 Otro problema más CodeJAM resuelto en PHP… larga vida a PHP.

#!/usr/bin/php
<?php
# Dedicado para los que piensan que PHP es solo para páginas webs
# y para las personas tontas que comparan un framework cualquiera
# con un lenguaje de programación.
#

/**/
function x_readline()
{
    return trim(fgets(STDIN));
}

function get_better_search_engine($Query,$Server,&$steps_done)
{
    $result=array();
    $max = 0;
    $max_pos = 0;
    for($e=0; $e < count($Server); $e++)
    {
        $perfect = true;

        for ($i=$steps_done; $i < count($Query); $i++)
        {
            if ( $Query[$i] == $Server[$e])
            {
                $perfect=false;
                if ( $max < $i)
                {
                    $max = $i;
                    $max_pos = $e;
                }
                break;
            }
        }
        if ( $perfect === true)
            return true; #perfect search engine
    }
    $steps_done = $max;
    return false; # we got a good search, but it cannot handle all queries
}

function get_lowest_switch($Query,$Server)
{
    $pos = 0;
    $switch=0;
    $i=0;
    while( get_better_search_engine($Query,$Server,$i) === false )
    {
        $switch++;

    }
    return $switch;
}

define('NO_WHERE',0);   # todavia no comenzamos
define('IN_INPUT_PHASE_1',1);  # inicializacion de servidores
define('IN_INPUT_PHASE_2',2);  # leyendo servidores
define('IN_INPUT_PHASE_3',3);  # inicializacion de consultas
define('IN_INPUT_PHASE_4',4);  # leyendo consultas
$status = NO_WHERE;
$total=0; # numero total a "inputs" a procesar
$cnt=0;
while ( ($line = x_readline()) != "")
{
    switch($status)
    {
        case NO_WHERE: # primera linea
            $total = (int) $line;
            $status++;
            break;
        case IN_INPUT_PHASE_1:
            $cnt++;
            $nServer= (int) $line;
            $Server = array();
            $status++;
            break;
        case IN_INPUT_PHASE_2:
            $Server[] = $line;
            if ( count($Server)==$nServer)
                $status++;
            break;
        case IN_INPUT_PHASE_3:
            $nQuery= (int) $line;
            if($nQuery==0) # no hay consultas, no hay switch
            {
                echo "Case #$cnt: 0\n";
                $status=IN_INPUT_PHASE_1;
                break;
            }
            $Query = array();
            $status++;
            break;
        case IN_INPUT_PHASE_4:
            $Query[] = $line;
            if ( count($Query)==$nQuery)
            {
                echo "Case #$cnt: ".get_lowest_switch($Query,$Server)."\n";
                $status=IN_INPUT_PHASE_1;
            }
            break;
    }
}
?>

El enunciado del problema:

Problem

The urban legend goes that if you go to the Google homepage and search for “Google”, the universe will implode. We have a secret to share… It is true! Please don’t try it, or tell anyone. All right, maybe not. We are just kidding.

The same is not true for a universe far far away. In that universe, if you search on any search engine for that search engine’s name, the universe does implode!

To combat this, people came up with an interesting solution. All queries are pooled together. They are passed to a central system that decides which query goes to which search engine. The central system sends a series of queries to one search engine, and can switch to another at any time. Queries must be processed in the order they’re received. The central system must never send a query to a search engine whose name matches the query. In order to reduce costs, the number of switches should be minimized.

Your task is to tell us how many times the central system will have to switch between search engines, assuming that we program it optimally.

Input

The first line of the input file contains the number of cases, N. N test cases follow.

Each case starts with the number S — the number of search engines. The next S lines each contain the name of a search engine. Each search engine name is no more than one hundred characters long and contains only uppercase letters, lowercase letters, spaces, and numbers. There will not be two search engines with the same name.

The following line contains a number Q — the number of incoming queries. The next Q lines will each contain a query. Each query will be the name of a search engine in the case.

Output

For each input case, you should output:

Case #X: Y

where X is the number of the test case and Y is the number of search engine switches. Do not count the initial choice of a search engine as a switch.

Limits

0 < N ≤ 20

Small dataset

2 ≤ S ≤ 10

0 ≤ Q ≤ 100

Large dataset

2 ≤ S ≤ 100

0 ≤ Q ≤ 1000

In the first case, one possible solution is to start by using Dont Ask, and switch to NSM after query number 8.
For the second case, you can use B9, and not need to make any switches.

]]>
http://cesar.la/codejam-saving-the-universe-resuelto-en-php-otra-mas.html/feed
CodeJAM - Train Timetable - Resuelto en PHP http://cesar.la/codejam-train-timetable-resuelto-en-php.html http://cesar.la/codejam-train-timetable-resuelto-en-php.html#comments Thu, 17 Jul 2008 19:40:54 +0000 César Rodas http://cesar.la/?p=20 Para aquellos que duden de PHP, o que piensen que PHP eso solo para páginas webs, aqui les va esta:

#!/usr/bin/php
<?php
# Dedicado para los que piensan que PHP es solo para pàginas webs
# y para las personas tontas que comparan un framework cualquiera
# con un lenguaje de programación
#

/**/
function x_readline()
{
    return trim(fgets(STDIN));
}

function hour2minutes($hour)
{
    $tmp = explode(":",$hour);
    return $tmp[0] * 60  + $tmp[1];
}

function get_number_train($depart, $arrival, $ttime)
{
    $ret=$tmp=0;
    sort($depart);
    sort($arrival);
    if ( count($arrival)==0)
        $ret = count($depart);
    else
    {
        foreach($depart as $pzTrain)
        {
            if ( $tmp < count($arrival) && $pzTrain >= ($arrival[$tmp] + $ttime))
                $tmp++;
            else
                $ret++;
        }
    }
    return $ret;
}

$total=0; # numero total a "inputs" a procesar

define('NO_WHERE',0);   # todavia no comenzamos
define('IN_INPUT_PHASE_1',1);  # ttime
define('IN_INPUT_PHASE_2',2);  # cantidad de "viajes"
define('IN_READ_TIME',3);

$status = NO_WHERE;
$inp=0;
while ( ($line = x_readline()) != "")
{
    #print "+".$line." ($status)\n";
    switch($status)
    {
        case NO_WHERE: # primera linea
            $total = (int) $line;
            $status++;
            break;
        case IN_INPUT_PHASE_1:
            #limpieza del estado anterior
            $inp++;
            #print "Input $inp\n";
            $input_line=$ttime=$cnt=$pzTrainA=$pzTrainB=0;
            $trips=$inA=$outA=$inB=$outB=array();
            #
            $ttime = (int) $line;
            $status++;
            break;
        case IN_INPUT_PHASE_2:
            $trips=explode(" ",$line);
            $input_line = $trips[0] + $trips[1];
            $status++;
            break;
        case IN_READ_TIME:
            $time = array_map("hour2minutes",explode(" ",$line));
            if ( $cnt < $trips[0]) {
                $outA[]  = $time[0];
                $inB[]   = $time[1];
            } else {
                $outB[]  = $time[0];
                $inA[]   = $time[1];
            }
            if ( ++$cnt == $input_line)  {
                $pzTrainA = get_number_train($outA,$inA,$ttime);
                $pzTrainB = get_number_train($outB,$inB,$ttime);
                print "Case #".($inp).": $pzTrainA $pzTrainB\n";
                $status = IN_INPUT_PHASE_1;
            }
            break;
    }
}
?>

Nada mas y nada menos un problema de CodeJam resuelto en PHP… no miré si PHP se puede usar, de cualquier manera estoy realizado… demostrando que no solo la viborita (o la mala extension de una gran Lenguaje) pueden hacer cosas así…

Para los que no tiene acceso al CodeJam, aqui escribo el enunciado del problema:

Problem

A train line has two stations on it, A and B. Trains can take trips from A to B or from B to A multiple times during a day. When a train arrives at B from A (or arrives at A from B), it needs a certain amount of time before it is ready to take the return journey - this is the turnaround time. For example, if a train arrives at 12:00 and the turnaround time is 0 minutes, it can leave immediately, at 12:00.

A train timetable specifies departure and arrival time of all trips between A and B. The train company needs to know how many trains have to start the day at A and B in order to make the timetable work: whenever a train is supposed to leave A or B, there must actually be one there ready to go. There are passing sections on the track, so trains don’t necessarily arrive in the same order that they leave. Trains may not travel on trips that do not appear on the schedule.

Input

The first line of input gives the number of cases, N. N test cases follow.Each case contains a number of lines. The first line is the turnaround time, T, in minutes. The next line has two numbers on it, NA and NB. NA is the number of trips from A to B, and NB is the number of trips from B to A. Then there are NA lines giving the details of the trips from A to B.

Each line contains two fields, giving the HH:MM departure and arrival time for that trip. The departure time for each trip will be earlier than the arrival time. All arrivals and departures occur on the same day. The trips may appear in any order - they are not necessarily sorted by time. The hour and minute values are both two digits, zero-padded, and are on a 24-hour clock (00:00 through 23:59).After these NA lines, there are NB lines giving the departure and arrival times for the trips from B to A.

Output

For each test case, output one line containing “Case #x: ” followed by the number of trains that must start at A and the number of trains that must start at B.

Limits

1 ≤ N ≤ 100

Small dataset

0 ≤ NA, NB ≤ 20

0 ≤ T ≤ 5

Large dataset

0 ≤ NA, NB ≤ 100

0 ≤ T ≤ 60

]]>
http://cesar.la/codejam-train-timetable-resuelto-en-php.html/feed
Virtualizando GNU/Linux con VServer http://cesar.la/virtualizando-gnulinux-con-vserver.html http://cesar.la/virtualizando-gnulinux-con-vserver.html#comments Thu, 10 Jul 2008 05:47:53 +0000 César Rodas http://cesar.la/?p=15 En estos días es más y más común tener computadoras con muchos recursos (memoria ram, discos duros, procesadores). La virtualización permite simular a varias computadoras y/o sistemas opertivos en un mismo hardware.

Un poco de teoría - Porque VServer?

Existen muchos métodos de virtualizar un sistema operativo, con los mismos resultados, el poder simular varias computadoras en un solo hardware. El método más común (y mas costoso) es el de simular todo el hardware real o ficticio (Qemu, VirtualBox, etc). Su popularidad está basada a que ni el sistema operativo anfitrión ni el huésped necesita ninguna modificación.

Otro método utilizado para virtualizar consiste en pequeños drivers aplicados al anfitrión o húesped para miniminizar el trabajo adicional de simular el hardware para el húesped. Apesar del gran mejora aún se desperdicián recursos en la mediación entre el anfitrión y el huesped Las más populares implementaciones son UML y XEN.

La gran mayoría de las veces no se dese tener maquinas virtuales con sistemas operativos distintos, sino solo tener varias instancias de del mismo sistema operativo huésped, ya que la gran mayoría de las veces las aplicaciones no necesitan acceso directo al hardware o al kernel del sistema operativo. Para esa opción se puede usar el “chroot” de unix, pero también se puede obtener una solución más avanzada como ser VServer.

Para más detalles de la implementación de VServer pueden leer este Paper de su implementación (muy interesante)

Instalando VServer

Algunas personas critican a VServer porque el huésped necesita tener un kernel Linux “patcheado”, cosa que me parece críticas sin fundamentos, porque compilar el kernel es un juego de niños, con unos cuantos comandos y ~20 minutos (dependiendo del hardware) tendremos nuestro kernel listo para la virtualización. Para ello hay que bajar el patch necesario para la versión del kernel que se desea utilizar de la página de VServer.

wget "http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.19.tar.bz2"
wget "http://ftp.linux-vserver.org/pub/kernel/vs2.2/patch-2.6.22.19-vs2.2.0.7.diff"
tar xfvj "linux-2.6.22.19.tar.bz2"
cd "linux-2.6.22.19"
patch -p1 < "../patch-2.6.22.19-vs2.2.0.7.diff"

Ahora ya tenemos preparado nuestro kernel, solo tenemos que configurarlo y compilarlo. El kernel viene con varias aplicaciones que facilitan su configuración, yo uso el menuconfig, pero existe varias otras opciones (make help ayudaría)

make menuconfig

Configuring the kernel

Si puede ven en la lista principal VServer significa que “parchearon” correctamente su kernel, ahora tienen que elegir las opciones de VServer, aqui estan las opciones con las que compilé mi kernel (el Dynamic context id no me funcionó…)

Configuring the kernel

Luego también puede personalizar su kernel sacando lo que no hace falta, y agregando atras necesarias que no estan incluidas por defecto. Una vez configurado el kernel hay que compilarlo (make help para mas info)

#compilamos todo
make all
#instalamos
make install
#reiniciamos
reboot

Luego si el sistema operativo inicia bien, ya terminamos con la parte mas complicada (que no es complicado) del proceso de instalación de VServer, ahora tenemos que instalar algunos utilitarios que nos ayudaran para crear a los huéspedes. Si son “redhateros” pueden hacer lo siguiente:

yum install util-vserver* -y

O si usan debian (no probé personalmente)

apt-get install util-vserver\*

Una vez instalado las aplicaciones *-vserver y con el kernel nuevo tenemos que testear si todo nos fue bien, para eso podemos hacer lo siguiente:

wget http://vserver.13thfloor.at/Stuff/SCRIPT/testme.sh
chmod +x testme.sh
./testme.sh

Si todo esta funcionando correctamente tendras que ver algo similar a esto:

Creando a los huespedes

Antes que nada existen varias formas de crear máquinas huésped, yo utilizo Yum para instalar los paquetes necesarios para el la distro huésped, para utilizar yum, necesita ser recompilado con un patch.

#!/bin/bash
NAME=virt01
HOSTNAME=virt01.foo-bar.com
#
ROOTDIR="/virtual/"
PKGBASE=$ROOTDIR/.pkg
VSERVER=$HOSTNAME
IP="eth0:192.168.1.106/24"
INTERFACE=$NAME
CONTEXT="43"
# por defecto VServer solo soporta hasta FC6,
# mas adelante escribiré como instalar versiones posteriores
# de Fedora
DIST="fc6"
PM="yum" 

/usr/sbin/vserver $VSERVER build -m $PM --context $CONTEXT --hostname=$HOSTNAME \
--interface $INTERFACE=$IP --rootdir $ROOTDIR --pkgbase $PKGBASE -- -d $DIST

Ahora tenemos una maquina huésped, y para comenzar a jugar con

#para iniciar la máquina virtual
vserver $NAME start
#para "entrar" a la máquina virtual
vserver $NAME enter
#para detener la máquina virtual
vserver $NAME stop
# ejecuta "something" en la máquina virtual
vserver $NAME exec something
# instalar algo en la máquina virtual
vyum $NAME -- install algo
# te queda la curiosidad?
man vserver

Algunas peculiaridades del VServer

VServer no cuenta con aislamiento de red, en vez de la virtualización, lo que en otras palabras significa que si la máquina huésped y el anfitrión utilizan una misma tarjeta pero diferentes IP, en realidad el IP de la máquina huésped es un Alias del IP del host. Claro que desde el huésped (por seguridad) solo se puede ver su IP. Esto es una gran punto a favor en rendimiento, ya que no hay desperdicios de tiempo en el CPU para la virtualización de los paquetes de red.

Sin embargo no todo es tan bueno, ya que generalmente los servicios que escuchan puertos, escuchan utilizando 0.0.0.0 (escucha en todo los IPs y alias de la máquina). Si que queremos tener httpd (o cualquier otro progama) instalado en el anfitrión y el huésped tenemos que fijarnos que desde el anfitrión no escuche desde el IP 0.0.0.0, para ver esto solo pueden ejecutar:

$ netstat -nlp | grep httpd
tcp        0      0 0.0.0.0:80                0.0.0.0:*                   LISTEN      2400/httpd

Si la httpd-anfitrión escucha 0.0.0.0:80, todas las consultas que le hagamos a nuestro httpd-huésped serán recibidas y procesadas por el http-anfitrión (por lo explicado anteriormente). Para solucionar el conflicto tendremos que forzar al httpd-anfitrión que escuche el IP-anfitrión. Si configuramos todo correctamente tendremos que ver algo similar a esto.

$ netstat -nlp | grep httpd
tcp        0      192.168.1.14:80                0.0.0.0:*                   LISTEN      2400/httpd

Debemos repetir el mismo proceso para cada aplicación que escucha el IP 0.0.0.0 y que queríamos ejecutar en el huésped y el anfitrión (eg: mysql, sshd, etc).

Lo que se viene

Conocí VServer gracias a que tuve la necesidad de virtualizar servidores en mi trabajo, y me encanto, ya que es muy similar a los Jails de FreeBSD, ademas que todo el filesystem del huésped es visto desde el anfitrión, ademas que las máquinas pueden compartir directorios entre si. Más adelante escribiré sobre como virtualizar servidores existentes, como instalar versiones más nuevas de Fedora, como forzar a vyum que lea desde el DVD de Fedora en vez de internet, esto es verdaderamente útil para instalaciones nuevas.

Espero que les haya gustado este introducción a VServer… espero sus comentarios con dudas o sugerencias.

]]>
http://cesar.la/virtualizando-gnulinux-con-vserver.html/feed
La pseudo-problemática de Stallman en Paraguay http://cesar.la/la-pseudo-problematica-de-stallman-en-paraguay.html http://cesar.la/la-pseudo-problematica-de-stallman-en-paraguay.html#comments Mon, 07 Jul 2008 14:51:44 +0000 César Rodas http://cesar.la/?p=14 Richard Stallman, fundador del Free Software Foundation, y el sistema operativo GNU, estará en Paraguay en noviembre. Dicha noticia alegra a todos lo que estamos de acuerdo (aunque sea parcialmente) con su filosofía, y los que usamos un increíble sistema operativo, como es GNU/Linux.

Al parecer el hecho que el Dr. Stallman venga a nuestro país auspiciado por una Universidad Privada, no gusta a mucho a algunos “jakers”, que tiene un concepto errado de lo que es el software libre, por eso me tomé la molestia de tratar de explicar. En la página del movimiento GNU encontramos la definición del Sofware Libre cuatro libertades esenciales:

  1. Libertad de usar el programa como queramos
  2. Libertad de estudiar y cambiar el programa como queramos, para ello es necesario el acceso a los fuentes del programa.
  3. Libertad de redistribuir el programa.
  4. Libertad para distribuir nuestras versiones del programa.

En ninguna de esas libertades ni siquiera se insinúa que debemos odiar el dinero, o no aceptar ayudas que vienen del sector privado, ni mucho menos criticar a algunas personas que hacen todo lo posible para hacer las cosas bien, sabiendo que los que critican nunca, jamás hicieron nada por la comunidad del SL del Paraguay, ni mucho menos del mundo…

Me pregunto, que es esa moda de tener tal confusión a tal punto de odiar todo lo que genere dinero, que es ese pensamiento comunista, esas personas deberían mudarse a Cuba o a la China, para que sean plenamente felices. Acaso esas personas no trabajan en el sector privado?, acaso esas personas no tienen dinero? o por lo menos hicieron algún aporte a algún proyecto libre (en dinero o con códigos)?. Como Linus Torvalds dice “Talk is cheap, show me the code” hablar es fácil, lo que les falta es más acción.

Ojala entiendan que SL no es solamente instalar un sistema operativo y hacer algunos trucos en la consola.

Más textos sobre la “pelea”

]]>
http://cesar.la/la-pseudo-problematica-de-stallman-en-paraguay.html/feed
Optimizando sitios webs utilizando gcache http://cesar.la/optimizando-sitios-webs-utilizando-gcache.html http://cesar.la/optimizando-sitios-webs-utilizando-gcache.html#comments Fri, 27 Jun 2008 04:42:38 +0000 César Rodas http://cesar.la/?p=12 Hoy en día con sitios como digg, meneame o similares, es muy fácil llegar a tener miles de visitas diarias. Esas visitas pueden producirnos algún dinero, por esa razón o simplemente por respeto a nuestros visitantes, tenemos que tener un sitio que pueda funcionar para todos los visitantes. Y ya que miles de visitas significan mucho trabajo para el servidor web, bases de datos, gran consumo de ancho de banda, entre otros cosas, tenemos que buscar la forma de optimizar todo al máximo.

Para tener una solución escribí una clase en php, llamado gCache, que ofrece una interfaz amigable para cachear fácilmente contenidos webs. En esta nueva versión que recién terminé soporta responder páginas comprimidas, que es bastante útil para ahorrar ancho de banda.

Aquí esta un ejemplo de uso:

<?php
include("gCache.php");
$cache = new gCache;
$cache->folder = "./cache/";
$cache->contentId="var45";
$cache->timeout = 1; /* 1 minuto */
/* gCache cachea toda la página */
/* entonces gCache puede decidir si enviar comprimido o no */
$cache->isPage=true;
if ($cache->Valid()) {
    echo $cache->content;
} else {
$cache->capture();
?>
<html>
<head>
<title>Cached page</title>
<head>
<body bgcolor="#CCCCCC">
    <h1>Testing Cesar D. Rodas' gCache Class</h1>
    <hr>
    <h2>Example of how to cache a hole page</h2>
    <hr>
<p>Basicaly what the gCache do, is to store a web-page or a portion of it into
  a<em> <em>cache file</em>. </em>The <em><em>cache file</em></em> has a $timeout
  in second of cache vitality, after that the cache will be re-created.</p>
<p>Also this class provides and locking system which is not depending of POSIX
  or other OS, this feature becomes to this class very portable.</p>
<hr>
<font size="1">This cache page was generated at
<?php echo date("Y/m/d H:i:s")?>
</font><font size="1"> by <a href="http://cesars.users.phpclasses.org/gcache">gCache</a>
</font>
</body>
</html>
<?
$cache->endcapture();
}
?>

Si se tiene mucha memoria RAM, optimizaría bastante que los caches que cambian frecuentemente sea hagan en un “RAM DISK” o tmpfs:

Manuel Lemos escribió en su blog otros consejos de como optimizar sitios webs.

]]>
http://cesar.la/optimizando-sitios-webs-utilizando-gcache.html/feed
Ruteamiento un poco avanzado http://cesar.la/ruteamiento-un-poco-avanzado.html http://cesar.la/ruteamiento-un-poco-avanzado.html#comments Wed, 25 Jun 2008 04:10:48 +0000 César Rodas http://cesar.la/?p=10 En estos días estuve buscando por todos lados como balancear mi tráfico de red hacia dos salidas hacia Internet. Desafortunada mente no encontré nada que me convenciera o que funcionara. Luego se me ocurrió un balanceo basado en el puerto de destino, asi podria utilizar una conexión mía para “internet” (puerto 80,443 y otros) y la otra para la descarga de archivos como gtalk y/o aMule.

Intente sin tener un resultado algunos how-to, ya que todos se enfocan en GNU/Linux como router o gateway, y nadie como maquina en sí. Mis primeras pruebas fueron sin éxito, y me percate, luego de ver el log del iptable, que el paquete salia por el dispositivo correcto (ppp0), pero salía con la IP de mi otra conexión. Para resolver a este problema acudía a un ex-profesor universitario y algunos foros, ambos coincidieron en que me faltaba poco, solo hacer un “SNAT” (source network address translation). Luego al agregar el SNAT, todo funcionó!

#!/bin/bash
# (c) 2008. copyleft crodas. all wrongs reserved
#
IP="`/sbin/ifconfig ppp0 | grep 'inet addr' | awk '{print $2}' | sed -e 's/.*://'`"
iptables -t mangle -F
iptables -t mangle -X
iptables -t mangle -Z
iptables -t nat -F

for PORT in `echo 4662 4663 4664 4665 6881 6882 6883 6884 6885 6886 6887 6888 6889`
do
        iptables -t mangle -A OUTPUT -p tcp --dport $PORT -j MARK --set-mark 0x2
done

for PORT in `echo 4672 4672 4673 4674 4675`
do
        iptables -t mangle -A OUTPUT -p udp --dport $PORT -j MARK --set-mark 0x2
done
# NATeo
# aporte basado en http://www.linux.org.py/foro/viewtopic.php?pid=5865
# y un "profesor" universitario
iptables -t nat -A POSTROUTING -o ppp0 -j SNAT --to-source $IP

#crear una tabla para ruteamiento
PPP0=3
ip route flush table $PPP0
ip route add table $PPP0 default dev ppp0 src $IP

ip rule del from all fwmark 0x2
ip rule add from all fwmark 0x2  table $PPP0
ip route flush cache
]]>
http://cesar.la/ruteamiento-un-poco-avanzado.html/feed
Instalando FreeBSD desde la red (desde GNU/Linux) http://cesar.la/instalando-freebsd-desde-la-red-desde-gnulinux.html http://cesar.la/instalando-freebsd-desde-la-red-desde-gnulinux.html#comments Fri, 20 Jun 2008 02:43:34 +0000 César Rodas http://cesar.la/?p=8 Siguiendo mi post anterior de instalación de GNU/Linux desde la red, estube googleando por ahí para hacer lo mismo, pero instalar FreeBSD, usando GNU/Linux como servidor de “instalaciones”. La documentación existente sobre este es bastante confusa y solo funciona desde un FreeBSD como servidor de instalación.

Mi idea era tener en mi servidor de instalación (que es un GNU/Linux) la posibilidad de instalar FreeBSD. Para esta misión tenia que ver alguna compatibilidad con pxelinux, afortunadamente encontre googleando mucho un increible artículo que muestra como crear un imagen de disco duro (UFS) con todo lo necesario para bootear usando pxelinux + memdisk. La imagen generada en el artículo solo ejecuta el sysinstall, y desde ahí hay que hacer todo el proceso manualmente, como si tuviéramos un cdrom de FreeBSD ( y utilizando un método de instalación, se tratará mas tarde este punto).

A continuación esta el script que modifique, pueden usarlo bajo los términos de la licencia BSD. Este script tiene que ser ejecutado desde un FreeBSD, ya que el soporte de UFS para escritura en Linux es “dangerous”, y por defecto viene con soporte solo para lectura. Para habilitarlo hay que recompilar el kernel o buscar otra alternativa. Para hacer funcionar este en GNU/Linux hay que usar losetup en vez de mdconfig

#   FreeBSD PXE Image Creator
#   Copyleft crodas (http://cesar.la). all wrongs reserved
#
#   Basado en http://web.irtnog.org/doc/how-to/freebsd-install-pxe-wds.
#   Algunas mejoras
#       - Agrega el install.cfg al mfsroot.gz para instalaciones automaticas
#       - Copia los archivos necesarios desde el CD de FreeBSD
ISO=$1
LOG=pxebsd.log
if [ ! -d $ISO ] ;
then
echo "${ISO} no es un directorio valido"
exit
fi

FLP=$ISO/floppies
if [ ! -d $FLP ] ;
then
echo "${FLP} no es un directorio valido"
exit
fi

echo "Creando directorios temporales"
mkdir data
mkdir mnt
mkdir tmp

cp -r "$FLP/*" tmp

echo "Extrayendo boot.flp"
## extract contents of boot floppy
MD=`mdconfig -a -t vnode -f tmp/boot.flp`
mount /dev/${MD} mnt
cp -R "mnt/*" data
umount /dev/${MD}
mdconfig -d -u ${MD}

echo "Extrayendo el kernel"

i=1
while ([ -e "tmp/kern${i}.flp" ])
do
MD=`mdconfig -a -t vnode -f tmp/kern${i}.flp`
mount /dev/${MD} mnt
cp -R "mnt/*" data
umount /dev/${MD}
mdconfig -d -u ${MD}
i=`expr ${i} + 1`
done

echo "Juntando el  kernel"
cat data/kernel.gz.boot data/kernel.gz.?? > data/kernel.gz
rm data/kernel.gz.*

#
#
echo "Limpiando cosas innecesarias"
grep -v acpi_before data/boot/loader.conf > loader.conf.new
mv loader.conf.new data/boot/loader.conf

#
#
if [ -f $2  ] ;
then
echo "Agregando el  install.cfg"
gunzip data/mfsroot.gz
MD=`mdconfig -a -t vnode -f data/mfsroot`
mount /dev/${MD} mnt
cp $2 mnt/install.cfg
umount /dev/${MD}
mdconfig -d -u ${MD}
gzip -9 data/mfsroot
fi

#
#
echo "Calculando el tamaño"
FLPSIZ=`du -sk data | awk '{print $1}'`
FLPSIZ=`expr ${FLPSIZ} \* 1024`
IMGSIZ=0
i=0
while ([ ${FLPSIZ} -gt ${IMGSIZ} ])
do
i=`expr ${i} + 2`
IMGSIZ=`expr ${i} \* 16 \* 63 \* 512`
done
CYL=${i}
TRK=16                # heads per cylinder
SEC=63                # sectors per head
SECSIZ=512            # bytes per sector
NUMFILES=`find data | wc -l`
NUMINODS=`expr ${FLPSIZ} \/ ${NUMFILES}`

#
#
#
echo "Creando la imagen PXE-boot"
dd if=/dev/zero of=pxeboot.img bs=${IMGSIZ} count=1 >> $LOG
MD=`mdconfig -a -t vnode -f pxeboot.img -y ${TRK} -x ${SEC} -S ${SECSIZ}`
fdisk -BIv /dev/${MD} >> $LOG 2>&1
disklabel -w -B /dev/${MD} auto >> $LOG
newfs -O1 -i ${NUMINODS} -b 4096 -f 512 -o space -m 0  /dev/${MD}a >> $LOG

mount /dev/${MD}a mnt

echo "Comprimiendo pxeboot.img"
cp -R "data/*" mnt
umount mnt
mdconfig -d -u ${MD}
gzip -9 -f pxeboot.img

echo "Limpiando directorios temporales"
rm -rf tmp data mnt

echo "Hecho"

Como usarlo:

$ DEV=`mdconfig -a -t vnode -f /path/to/freebsd.iso`
$ mount_cd9660 /dev/$DEV /mnt
$ cd ~; ./pxefbsd.sh /mnt install.cfg
$ umount /dev/$DEV
$ mdconfig -d -a $DEV

El archivo pxefbsd.sh recibe como primer parametro el directorio donde está montado el cdrom1 de FreeBSD, y el segundo parámetro es un archivo install.cfg que tiene toda la configuración de la instalación. Tambien se especifica de a donde instalar, en mi ejemplo se instala desde un directorio NFS, y se instala solo el kernel generic, base y manpages.

################################
# inicio
################################
debug=YES
nonInteractive=YES
noWarn=NO
tryDHCP=NO
#
bootManager=boot
diskInteractive=YES
diskPartitionEditor
diskInteractive=YES
diskLabelEditor
#
hostname=new-gw
domainname=somehost
#
nfs=192.168.1.1:/distros/FreeBSD/6.2-i386
tryDHCP=YES
mediaSetNFS
#
dists=base manpages GENERIC
distSetCustom
#
installCommit

Ahora solo debemos copiar el contenido del cdrom1 de freebsd a un directorio ( en el ejemplo /distros/) y compartirlo usando NFS.

mkdir -p /distro/FreeBSD/6.2-i386
mount /path/to/fbsd.iso /mnt -o loop
cp -r "/mnt/*" /distro/FreeBSD/6.2-i386
umount /mnt
echo "/distros 192.168.1.0/255.255.255.0 (ro)" >> /etc/exports
/etc/init.d/rpcbind restart
/etc/init.d/nfs restart
chkconfig rpcbind on
chkconfig nfs on

Luego de la ejecución del pxefbsd.sh, se genera un archivo “pxeboot.img.gz”, esta es la imagen booteable que tenemos que copiar a nuestro servidor de instalaciones. Luego editamos el archivo /tftpboot/pxelinux.cfg/default y agregamos algo similar, para eso ponemos memdisk (para ubicar ejecutar locate memdisk, necesita syslinux instalado ) con la imagen pxeboot.img.gz. Despues de esto ya podremos instalar FreeBSD desde la red!

label FreeBSD-6.2-i386
kernel /images/FreeBSD-6.2-i386/memdisk
append initrd=/images/FreeBSD-6.2-i386/pxeboot.img.gz harddisk noedd

Espero que les este artículo les haya sido de utilidad, cualquier duda no duden en dejar sus comentarios

]]>
http://cesar.la/instalando-freebsd-desde-la-red-desde-gnulinux.html/feed
Acer Aspire 4310 y Linux 2.6.25.7 http://cesar.la/acer-aspire-4310-y-linux-26257.html http://cesar.la/acer-aspire-4310-y-linux-26257.html#comments Wed, 18 Jun 2008 02:45:24 +0000 César Rodas http://cesar.la/?p=5 Hace mas o menos seis meses me compre una Acer Aspire 4310, que venia con w$ “bad vista”, que por supuesto lo exorcicé y le instale Fedora Release 7 (moonshine), ya se que esta un poquito viejita la distro, pero me encargé de “tunearlo” bastante.

Tuve que sufrir un poco buscando algunos drivers (wifi y Audio, ya que no me funcionaban el audífono ni el micrófono), pero aun use no dude ni por un instante instalar algun sistema operativo privativo, basado en Unix o no (mucho menos)… mi libertad no tiene precio.

Luego por cuestiones de la vida tuve que recompilar el kernel, para tener uno mas actualizado y para agregar soporte de UFS (Unix File system) que necesitaba para algunas pruebas. Grande fue mi sorpresa que al instalar el kernel 2.6.26.7 me funcionó el Wifi y el audio me reconocía los parlantes de la notebook, audifonos, parlantes y el midi, que grande Linux…

Si a alguien le interesa aqui está mi archivo de configuración, pero puede configurar lo mismo con make menuconfig. Luego seleccionar Networking –> Wireless –> y lo que muestra la imagen :P

Menuconfig Wireless

Si necesitan alguna ayuda no duden en escribir sus comentarios.

]]>
http://cesar.la/acer-aspire-4310-y-linux-26257.html/feed
Instalando GNU/Linux sin discos http://cesar.la/instalando-gnulinux-sin-discos.html http://cesar.la/instalando-gnulinux-sin-discos.html#comments Sat, 07 Jun 2008 02:58:30 +0000 César Rodas http://cesar.la/?p=3 Hoy en día es mas y mas común en medianas y grandes empresas instalar grandes cantidades de GNU/Linux para servidores y/o estaciones de trabajo.

En esos casos es muy complicado e inefectivo hacer la instalación con CDs o DVDs. también la gran variedad y versiones de distribuciones existentes hoy en día hace que sea incomodo tener CDs y DVDs.

Para resolver este inconveniente y hacer más fácil la instalación, migración, o para simplemente experimentar aquí escribo un mini-tutorial de como crear un servidor de instalaciones de GNU/Linux.

Lo necesario

Los siguientes paquetes son necesarios para soportar la instalación desde la Red:

  • TFTPD
  • Servidor DHCP
  • SysLinux
  • Servidor Web
  • Imágenes de los CD/DVD de tu dristribución

El siguiente bash script puede ayudar a instalar todo lo necesario en un Fedora Linux. Creo que para Debian y similares hay que utilizar apt-get en vez de yum, si alguien puede confirmar esto sería grandioso.

#!/bin/bash

yum install -y tftp* syslinux* http-2*

if [ ! -f  dhcp-3.0.7.tar.gz ]
then
wget http://ftp.isc.org/isc/dhcp/dhcp-3.0.7.tar.gz
fi
tar xfvz dhcp-3.0.7.tar.gz
cd dhcp-3.0.7
./configure && make && make install
# crear las directorio y archivos de configuraciones
mkdir -p /tftpboot/images/
mkdir -p /tftpboot/pxelinux.cfg
> /tftpboot/pxelinux.cfg/default

Una vez que tenemos todo lo necesario pasamos a la fase de configuraciones, para eso tenemos que entender como funciona el “servidor de instalaciones”. El DHCPD asigna una IP y también provee información de donde esta localizado el booteo desde red, si el cliente así lo requiere. El cliente se conecta a servidor de booteo y mediante el tftp (Trivial File Transferer Protocol) baja y ejecuta el kernel Linux y desde ahi ejecuta el instalador de la distribución

Configurando

Generalmente el tftp es lanzado desde el xinetd (al menos en Red Hat y derivados) y por defecto viene desactivado. Para activarlo hay que editar el archivo /etc/xinetd.d/tftp y cambiar disabled = yes por disabled = no.

Luego hay que copiar la imagen del pxelinux.0 y menu.c32 del syslinux (generalmente ubicado en /usr/lib/syslinux/) a la carpeta del tftp (por lo general /tftpboot).

Tenemos que crear directorios para cada distribución en /tftpboot/images/distroxxx-arch/ y copiar el vmlinuz el initrd.img que vienen generalmente en el directorio /boot del DVD o primer CD de la distribución dada. Este paso se puede hacer N veces para N distribuciones diferentes. Una cosa importante, las distribuciones que pueden estar alojada en un servidor de instalaciones es totalmente al servidor, ejemplo un servidor de instalación con RH7 i386 puede hostear un debian r3 x86_64.

Aquí esta una configuración ejemplo que tiene que poner en /tftpboot/pxelinux.cfg/default:

default menu
serial 0,38400
prompt 0
timeout 20

#para cada distribucion
#en ejemplo es la distro xxx-arch y el servidor de instalaciones el 192.168.2.1
label xxx-arch
kernel /images/xxx-arch/vmlinuz
append local_ramdisk=1 network initrd=/images/xxx-arch/initrd.img ks=http://192.168.2.1/images/xxx-arch-ks.cfg

Ahora como se darán cuenta el tftp despliega un menú, para que el cliente puede elegir que distribución desea elegir. Una vez elegida el tftp envia el vmlinuz (kernel linux) y el initrd.img (imagen donde se encuentra el instalador de la distro). El initrd.img a su vez recibe dos parámetros importantes, network y ks=…

El ks (kickstart) es un archivo (http://192.168.2.1/images/xxx-arch-ks.cfg) con los pasos que el instalador debe seguir al instalar. El Red Hat y derivados viene con un utilitario para generar este archivo (system-config-kickstart), aunque aquí pongo uno generico que simplemente levanta el modo gráfico y nada más, el usuario necesita configurar todo como si fuera a instalar desde un DVD. Si utilizan apache este archivo estaria localizado en /var/www/html/images/xxx-arch-ks.cfg

auth –useshadow –enablemd5
graphical
firewall –disabled
firstboot –disable
interactive
keyboard es
lang en_ES
rootpw –iscrypted $1$a6UXROwn$Ls67BE5neu5GhY2QbWsmR1
selinux –disabled
install
url –url=http://192.168.2.1/images/xxx-arch/
reboot
xconfig –defaultdesktop=GNOME –depth=8 –resolution=640×480
bootloader –location=mbr
zerombr
clearpart –all

Ahora tenemos que copiar el contenido del ISO a la carpeta web de la distribución (http://192.168.2.1/images/xxx-arch/) osea /var/www/html/images/xxx-arch/.

Por último tenemos que configurar el DHCPD, si ya existe un servidor en nuestra red solo tenemos que agregar algunas líneas a el (El 192.168.2.1 es el servidor de booteo en este ejemplo):

#en el principio
allow booting;
allow bootp;

#en la declaración de la subnet tiene que poner lo siguiente
next-server 192.168.2.1;
filename “pxelinux.0″;

Ahora hay que iniciar los servicios correspondientes en el servidor de instalación:

/etc/init.d/httpd start
/etc/init.d/xinetd start
/etc/init.d/dhcpd start

También se puede ejecutar los siguientes comandos si se quiere habilitar los mismos cuando se reinicie el sistema operativo:

chkconfig httpd on
chkconfig xinetd on
chkconfig dhcpd on

Si la tarjeta de red no soporta booteo en red les recomiento que hechen una mirada a proyecto Ethernet Boot.

Espero que les haya sido de utilidad este mini-artículo… si surgen dudas no duden en pedir ayuda dejando su comentario…

]]>
http://cesar.la/instalando-gnulinux-sin-discos.html/feed
Hello world! http://cesar.la/hello-world.html http://cesar.la/hello-world.html#comments Fri, 06 Jun 2008 20:51:30 +0000 César Rodas http://cesar.la/?p=1 Buenas nuevas para los que hablamos esta hermosa lengua Español! Este es mi nuevo blog que será similar a mi viejo blog en ingles, por eso el “.la” en el dominio (latino América).

Básicamente escribiré temas variadas, pero con dos cosas en común el idioma español y todo lo que sea Open Source. Para ser más especifico en el tema “Open source” que es bastante amplio escribiré sobre algunos lenguajes C, PHP, Perl, Python, Bash, instalación y trucos para GNU/Linux y FreeBSD.

Si compartimos las mismas pasiones espero verte frecuentemente por aquí.

]]>
http://cesar.la/hello-world.html/feed