Introdução

logo

Esse é um dos meus projetos sobre visualização de dados em comemoração a 1 ano de aniversário do podricing.

podricing é um pod de diaspora*, uma rede social descentralizada, auto regulada e de código aberto, a qual constitui-se de nós independentes, chamados pods, que participam da rede. Projeto iniciado em novembro de 2010, em março de 2014 diaspora* contava com mais de 1 milhão de usuários. Este site possui estatísticas atualizadas.

Esse vídeo mostra diversos usuários do podricing interagindo com o pod ao longo do ano. Cada galho é uma profile e posts de usuários são folhas.

podricing foi criado a partir da ideia na comunidade a qual eu faço parte, composta por desenvolvedores de software livre e apoiadores, de rodar uma rede social moderna, multimídia, de código livre, que possa ser regulada pelos próprios membros da comunidade, com foco na transparência, integridade e em preservar a privacidade de seus usuários.

Hoje, podricing serve mais de 12043 posts e é lar de 297 usuários. Embora não usado nesse video, comentários e curtidas contam como mais de 63850 interações entre usuários do podricing.

Esse projeto foi possível graças ao uso de ferramentas livres e a possibilidade de acesso à estrutura do diaspora*, que foi fundamental para estudo e possibilitou a extensão de suas funcionalidades para meu projeto. Também graças ao podmin do meu pod pela sua cooperação com o meu projeto.

Resumo do desenvolvimento

Ao longo de um período de uma semana, durante a qual foi aniversário do meu pod, foi desenvolvido um script que gera a animação da interação de usuários do podricing consultando o servidor de banco de dados do pod por dados relevantes.

Respeitando a privacidade do usuário, os dados copiados são os mesmos dados publicamente acessíveis pela interface web.

Os dados são armazenados em arquivos de texto no computador do cliente para serem usados na renderização da animação, necessitando, portanto, conexão com o servidor apenas uma vez para a geração desses dados.

Os dados da animação são armazenados e codificados no computador do cliente.

Requerimentos

O script foi escrito na linguagem Shell. Enquanto o código do script controla as operações de movimentação de dados, foi explorado o uso das seguintes ferramentas de código livre para a geração de dados.

  • Open-ssh para estabelecer conexão segura com o servidor, abrir um shell e executar remotamente comandos no servidor;
  • curl, para fazer o download de dados via protocolo HTTP do servidor para o cliente;
  • A aplicação convert, da biblioteca imagemagick, utilizada na fase de conversão de imagens para um formato de arquivo padronizado;
  • gource, ferramenta de visualização de controle de código, adaptada para ler e interpretar os dados gerados por essa aplicação e responsavel pela renderização da animação;
  • A aplicação ffmpeg, responsável pela codificação da animação gerada pelo gource em um formato de arquivo de vídeo;
  • Instalado no servidor, o cliente de mysql é usado para estabelecer conexão com o banco de dados e executar consultas em linguagem SQL.

Uso

A o processo de execução do script é dividido em 3 fases, executados na ordem a seguir. Cada fase subsequente depende do resultado da fase anterior.

  1. Consulta de dados:

    Nessa fase, dados são consultados do banco de dados do servidor e copiados para o cliente. Com base nesses dados serão copiadas também imagens de perfil dos usuários para o cliente.

    ./script.sh update
    
  2. Renderização da animação:

    O script roda a animação no cliente com base nos dados copiados do servidor e imagens de perfil dos usuários. Os quadros da animação são armazenados em disco no cliente.

    ./script.sh run
    
  3. Codificação da animação em vídeo:

    Nessa última fase, os quadros da animação são codificados em formato de vídeo que também é salvo em disco no cliente.

    ./script.sh encode
    

Adicionalmente, todas as 3 fases podem ser executadas em sequencia automaticamente rodando:

./script.sh all

Esse script não precisa ser executado como super-usuário.

Configuração

O script precisa ser configurado antes de ser executado. A abaixo estão as variáveis importantes para o script.

O script conecta-se com o servidor por uma sessão ssh. Nessa sessão deve-se informar o domínio do servidor, a porta de conexão e o usuário. O script poderá pedir a senha de login do usuário durante a execução.

SSH_HOST="EXAMPLE.ORG";
SSH_PORT="22";
SSH_USER="USERNAME";

Dentro da sessão ssh, o script acessa o banco de dados do servidor. Supõe-se que o administrador do servidor instalou pod e o servidor de banco de dados na mesma máquina.

Esse script depende da configuração de banco de dados do pod, o qual pode ser visualizada dentro do diretório onde o pod foi instalado, em config/database.yml.

Deve-se alterar o nome do usuário e a senha. Dentro da sessão ssh, o script é executado não interativamente. Por essa razão, diferentemente do que acontece na autenticação ssh, não aparecerá um diálogo de entrada de senha para o usuário do banco de dados, fazendo-se necessário que esta senha seja escrita definida no script antes de sua execução.

DB_HOST="localhost"
DB_PORT="3306";
DB_USER="USERNAME";
DB_PASS="PASSOWRD";
DATABASE="diaspora_production";

Por se tratar de um sistema descentralizado, o banco de dados de um pod pode armazenar dados tanto de seus usuários quanto de usuários de outros pods. O script restringe a consulta de dados a usuários pertencentes ao pod. Deve-se especificar o domínio do pod da maneira como ele aparece no URL da interface web, que será usado para filtrar os usuários.

POD_HOSTNAME="EXAMPLE.ORG";

Detalhes da implementação

A o processo de execução do script é dividido em 3 fases, executados na ordem a seguir. Cada fase subsequente depende do resultado da fase anterior.

workflow

Em uma primeira fase, o script conecta-se com o servidor do pod e executa a si mesmo lá. Você precisa ter uma conta de usuário no servidor e ela precisa ser especificada como usuário e host na seção VARIABLES no script. O password pode ser solicitado à você durante a execução.

Já dentro do servidor, entretanto, esse script roda em modo não interativo, por essa razão você, que também deve ter uma conta com permissão de acesso ao servidor de banco de dados, precisa especificar o usuário e a senha para acesso ao banco na sessão VARIABLES. O script consulta o banco de dados e salva o resultado em arquivo de texto no servidor para então desconectar-se, também saindo do modo não interativo.

Agora, o script conecta-se novamente ao servidor para copiar os dados em arquivo te texto. Essa cópia e gravada no seu computador. Esses dados também serão usados para baixar imagens de perfil dos usuários e salvar em um diretório criado junto ao diretório do script.

Na próxima fase, o script roda o programa gource para gerar a animação usando os dados copiados. Esse processo pode salvar os quadros da animação no computador. Seja avisado que esses dados podem ocupar uma grande quantidade de espaço de disco.

Na terceira e última fase, os quadros da animação serão codificados no formato de vídeo especificado. Esse processo pode levar uma grande quantidade de tempo e também pode ocupar uma grande quantidade de espaço de disco.

Desafios

O script em si não processa dados, em contraste, ele gerencia o fluxo de dados de um programa para outro. Com isso em mente, dados de entrada e saída tiveram que seguir o padrão dos programas anteriormente mencionados.

log

O primeiro desafio foi estudar o programa gource e a ferramenta de controle de versão git. gource lê o log de interação em arquivos de um projeto. Esse log segue um formato definido git, que escreve uma linha no log para cada interação. Por sua vez, gource lê linha por linha do log e extrai os registros referentes a data, tipo, autor e arquivo da interação, para gerar uma animação. Os tipos de interação são os de criação de um novo arquivo ou modificação de um arquivo já existente.

1395937306|krendil|A|Krendil/1
1395938641|superdicq|A|SuperDicq/2
1395942343|thebluehue|M|thebluehue/1
1395942361|thebluehue|M|thebluehue/1
1395942470|frunobulax|A|Frunobulax/5
1395942498|thebluehue|M|thebluehue/2
1395942517|thebluehue|M|thebluehue/2
1395942741|dr. worm|A|Dr. Worm/6
1395942903|frunobulax|M|Frunobulax/6
1395942991|dr. worm|M|Dr. Worm/6
1395942995|rat king|A|Rat King/7
1395943086|tommy|A|Tommy/8

As interações entre usuários podem ser registradas de forma análoga ao modo como interações em arquivos de um projetos estão escritas no log como no exemplo acima, onde a um dado momento (primeira coluna), o autor da interação (segunda coluna), interage com um arquivo (terceira e quarta coluna respectivamente). Postagens são análogas a arquivos e os usuários do pod são seus autores. A criação de um post é equivalente a criação de um arquivo (A) e a interação com o post, comentando ou curtindo modificam o post (M).

visual do gource

gource representa arquivos como folhas em uma árvore análoga a estrutura de diretórios dentro de um projeto, onde ramos representam diretórios e o centro é o diretório raiz.

resultado visual

Uma vez que postagens são análogas a arquivos, o diretório a que pertencem é o perfil de usuário quem as criou. Essa decisão criativa gera um resultado visual que lembra o ícone do diaspora*, o dente de leão.

dente de leão

Com a adaptação do conceito de interação em arquivos à interação de usuários de um pod, os dados relevantes a suas interações devem ser consultados do servidor de banco de dados do pod. Graças a licença do código do diaspora* garantir nossos direitos de estudá-lo, o código relativo a definição schema do banco de dados pode ser visto nesse arquivo, no repositório do projeto.

create_table "comments", force: :cascade do |t|
    t.text     "text",                    limit: 65535,                  null: false
    t.integer  "commentable_id",          limit: 4,                      null: false
    t.integer  "author_id",               limit: 4,                      null: false
    t.string   "guid",                    limit: 255,                    null: false
    t.text     "author_signature",        limit: 65535
    t.text     "parent_author_signature", limit: 65535
    t.datetime "created_at",                                             null: false
    t.datetime "updated_at",                                             null: false
    t.integer  "likes_count",             limit: 4,     default: 0,      null: false
    t.string   "commentable_type",        limit: 60,    default: "Post", null: false
end

Acima está a definição de uma das tabelas do banco de dados, descrevendo a tabela de comentários, uma das 4 tabelas relevantes para a consulta, estas sendo as tabelas de comentários, posts, likes e profiles. Podemos consultá-la dessa maneira:

SELECT  comments.created_at                         AS timestamp,
        profiles.full_name                          AS username,
        'M'                                         AS type,
        CONCAT(profiles.first_name,'/',posts.id)    AS file
FROM comments, profiles, posts WHERE
        comments.commentable_id = posts.id          AND
        comments.author_id = profiles.person_id;

Infelizmente, porém, o banco de dados foi alimentado com dados do mundo real, cheio de variáveis inesperadas. O banco de dados contém dados inválidos, resultados de erros de usuários e ações maléficas como a de bots com o pod. No banco de dados do pod existem vários usuários inválidos.

usuários fantasmas

É necessário, então, filtros adicionais à consulta. Embora não completamente garantido de eliminar esses usuários inválidos, destingir à usuários que se deram o trabalho de adicionar um nome à sua conta e modificar imagem de perfil espera-se eliminar os bots.

SELECT  comments.created_at                         AS timestamp,
        profiles.full_name                          AS username,
        'M'                                         AS type,
        CONCAT(profiles.first_name,'/',posts.id)    AS file
FROM comments, profiles, posts WHERE
        comments.commentable_id = posts.id          AND
        comments.author_id = profiles.person_id     AND 
        LENGTH(profiles.full_name) > 0              AND
        LENGTH(profiles.image_url) > 0

Outro desafio tem a ver com o próprio design do diaspora*. Usuários de pods diferentes podem interagir entre si, e essa característica também é refletida no banco de dados, onde há o registros de usuários de fora do pod.

usuários de outros pods

A solução, novamente, foi de restringir a consulta para usuários que têm o URL imagem de perfil qual fizeram upload pertencente ao domínio do podricing, esperando eliminar os usuários de outros pods do resultado da consulta. Abaixo está a consulta pelo nome e imagem de perfil de usuários pertencentes ao podricing, excluindo bots.

SELECT CONCAT(full_name,'|',image_url)
FROM profiles WHERE
    searchable = true       AND
    LENGTH(full_name) > 0   AND
    LENGTH(image_url) > 0   AND
    image_url like "%podricing.org/uploads%";

Essa consulta gera o seguinte resultado.

krendil|https://podricing.org/uploads/images/thumb_large_e162cb27fd2468bd9819.png
risu|https://podricing.org/uploads/images/thumb_large_3a21f1177da273eeff29.png
thebluehue|https://podricing.org/uploads/images/thumb_large_1d1ff6eb32ced1af48f2.jpg
superdicq|https://podricing.org/uploads/images/thumb_large_74abea02817a22f6ced1.png
ochatama|https://podricing.org/uploads/images/thumb_large_26513c8c55f91d4e2d7c.jpg
frunobulax|https://podricing.org/uploads/images/thumb_large_595b98ad6f4a8eadaa9b.jpg
dr. worm|https://podricing.org/uploads/images/thumb_large_d95d0b41699466bfef23.jpg
rat king|https://podricing.org/uploads/images/thumb_large_db28ab210de30bbad149.png
harrison w|https://podricing.org/uploads/images/thumb_large_1b1c8d61797005d7f8bc.jpg
chameco|https://podricing.org/uploads/images/thumb_large_062c956186971a281ead.png
funtoo fig|https://podricing.org/uploads/images/thumb_large_248d048a08475e780da2.png
manly|https://podricing.org/uploads/images/thumb_large_a49f019d5f56a8bf3f4f.jpg

A consulta de interações de usuários pertencentes ao podricing, por fim, é mostrada abaixo, gerando o resultado previamente mostrado mais acima.

SELECT CONCAT(UNIX_TIMESTAMP(timestamp),'|',username,'|',type,'|',file) FROM
(   -- Throw everything on the table interactions(timestamp,username,file)
    -- Get all posts:
        SELECT  posts.created_at                            AS timestamp,
                profiles.full_name                          AS username,
                'A'                                         AS type,
                CONCAT(profiles.first_name,'/',posts.id)    AS file
        FROM posts, profiles WHERE
                posts.author_id = profiles.person_id        AND
                LENGTH(profiles.full_name) > 0              AND
                LENGTH(profiles.image_url) > 0              AND
                profiles.image_url like "%podricing.org/uploads%"
    UNION
    -- Get all comments:
        SELECT  comments.created_at                         AS timestamp,
                profiles.full_name                          AS username,
                'M'                                         AS type,
                CONCAT(profiles.first_name,'/',posts.id)    AS file
        FROM comments, profiles, posts WHERE
                comments.commentable_id = posts.id          AND
                comments.author_id = profiles.person_id     AND 
                LENGTH(profiles.full_name) > 0              AND
                LENGTH(profiles.image_url) > 0              AND
                profiles.image_url like "%podricing.org/uploads%"
    UNION
    -- Get all likes:
        SELECT  likes.created_at                            AS timestamp,
                profiles.full_name                          AS username,
                'M'                                         AS type,
                CONCAT(profiles.first_name,'/',posts.id)    AS file
        FROM likes, profiles, posts WHERE
                likes.target_id = posts.id                  AND
                likes.author_id = profiles.person_id        AND
                length(profiles.full_name) > 0              AND
                length(profiles.image_url) > 0              AND
                profiles.image_url like "%podricing.org/uploads%"
    ORDER BY timestamp
) AS whatever;

Trabalhos futuros

Esse script foi especialmente desenvolvido e exclusivamente testado no pod qual eu faço parte, mas eu poderia entrar em contato com diversos podmins para rodar-nos o script em seus posts.

Diferentes pods rodam em sistemas com diferentes configurações, com servidores de banco de dados diferentes, dependendo da configuração feita pelo podmin. Ainda, pods são criados por razões sociais diversas, atraindo usuários de perfis diferentes, formando grupos ainda maiores que podricing.

Seria tanto desafiador no aspecto de desenvolvimento em portar esse script para os demais pods quanto de valor científico ao comparar diferentes resultados que possamos obter com dados reais de diferentes pods, com diferentes usuários.

Esse script depende de vários programas externos para funcionar, embora esses programas deixem meu trabalho mais fácil, esse aspecto aumenta a complexidade de dependências para se satisfazer antes de executá-lo. Desenvolver meu próprio motor gráfico para renderizar a animação tanto reduziria o número de dependências quanto me daria mais possibilidades de ser criativo e inventar amplos modos de visualização diferentes.

Como esse trabalho me proporcionou experiências que me familiarizaram com o aspecto estrutural do armazenamento de dados de usuários, eu posso aproveitá-lo para extender a visualização para além da interação entre usuários. Usar esses dados armazenados para gerar animações que focam em outros aspectos, como o relacionamento entre usuários e seus hábitos.


  • O código fonte do projeto está disponível aqui.
  • O poster do projeto está disponível aqui.