risorse | StatHg

StatHg

StatHg è un insieme di script che analizzano un repository Mercurial allo scopo di descrivere la dinamica dello sviluppo di un progetto software, alla stregua di quanto StatSVN fa per Subversion.

Il processo si articola in tre fasi:

  1. analisi del repository Hg;
  2. raccolta delle statistiche;
  3. creazione dei prospetti.

Analisi del repository Hg

L'archivio viene analizzato per tutto l'arco della sua evoluzione, un changeset dopo l'altro; il numero di revisione dell'ultimo changeset viene determinato con il comando:

hg log -l 1 --template "{rev}"

Di ogni changeset si raccolgono gli attributi principali con il comando:

hg log -r %REV% --template "{rev}|{node|short}|{date|isodatesec}|{author}|{tags}|{desc}"

L'insieme dei file coinvolti nel changeset è ottenuto tramite il comando:

hg status --change %REV%

Viene infine determinato il numero di righe modificate in ogni file, basandosi sulle indicazioni fornite dal comando:

hg diff --change %REV% %PATH%

Conteggi

Il conteggio del numero di linee aggiunte o eliminate avviene solo per alcuni file, selezionati secondo l'estensione:

I file di testo (*.txt, *.md, *.htm, ...) non sono considerati perché non necessariamente organizzati a linee, e comunque sostanzialmente ininfluenti — se non fonte di rumore — per le considerazioni sull'ampiezza della base di codice.

Schema

Lo schema dati è costituito da due entità, changeset e change:

                            +----------------------+
    +--------------+        | change               |
    | changeset    |        +----------------------+
    +--------------+        | id           integer |
    | id   integer |<-------| changeset_id integer |
    | node text    |        | action       text    |
    | date text    |        | path         text    |
    | user text    |        | ins_lines    integer |
    | tag  text    |        | del_lines    integer |
    | desc text    |        +----------------------+
    +--------------+

changeset

Rappresenta un changeset di Hg, ed è caratterizzato da:

change

Rappresenta le modifiche effettuate ad un singolo file, ed è caratterizzato da:

Raccolta delle statistiche

Segue l'elenco dei report generati da StatSVN e la query statHg associata.

Lines of Code – numero di linee di codice per revisione

select
 cs.id rev,
 cs.date date,
 cs.tag tag,
 sum(c.ins_lines - c.del_lines) loc
from
 changeset cs
 inner join change c on (c.changeset_id <= cs.id)
group by rev
order by rev;

Top 10 Authors – sviluppatori con il maggior numero di linee

Non disponibile. Il repository di riferimento è privato, quindi con un unico sviluppatore che ha in carico l'intera code-base.

Repository Tags – numero di linee, numero di linee modificate per tag

select
 tags.tagged_id, cs.tag, sum(c.ins_lines), sum(c.del_lines)
from (
 select
  cs1.id id,
  max(cs2.id) tagged_id
 from
  changeset cs1,
  changeset cs2
 where cs2.tag <> '' and cs2.id <= cs1.id
 group by cs1.id) tags
left join changeset cs
on tags.tagged_id = cs.id
left join change c
on c.changeset_id = tags.id
group by tags.tagged_id, cs.tag
having sum(c.ins_lines) > 0 and sum(c.del_lines) > 0;

Nota: viene determinato solo il numero di linee modificate.

Directories – numero di file, numero di linee per cartella

Non disponibile. Ricavabile dall'analisi del file-system.

Developers – numero di commit, numero di linee per sviluppatore

Non disponibile. Il repository di riferimento è privato, quindi con un unico sviluppatore che ha in carico l'intera code-base.

Contributed Lines of Code – linee di codice per sviluppatore

Non disponibile. Il repository di riferimento è privato, quindi con un unico sviluppatore che ha in carico l'intera code-base.

Developer of the Month – sviluppatore col il maggior numero di linee nel mese/anno

Non disponibile. Il repository di riferimento è privato, quindi con un unico sviluppatore che ha in carico l'intera code-base.

Commit Activity – commit del singolo sviluppatore nel tempo

Non disponibile. Il repository di riferimento è privato, quindi con un unico sviluppatore che ha in carico l'intera code-base.

Author Activity – percentuale modifiche/aggiunte per sviluppatore

select
 cs.user,
 sum(c.ins_lines) ins_lines,
 sum(c.del_lines) del_lines
from changeset cs, change c
where c.changeset_id = cs.id
group by cs.user;

Activity by Hour of Day – numero di commit per ora del giorno

select
 strftime('%H', substr(date, 1, 16), substr(date, 18, 3) || ' hours') hour,
 count(1)
from changeset
group by hour;

Activity by Day of Week – numero di commit per giorno della settimana

select
 strftime('%w', substr(date, 1, 16), substr(date, 18, 3) || ' hours') day,
 count(1)
from changeset
group by day;

Nota: 0 = domenica, 1 = lunedì, ...

Activity Punchcard – numero di commit nel giorno/ora

select
 strftime('%w', substr(date, 1, 16), substr(date, 18, 3) || ' hours') day,
 strftime('%H', substr(date, 1, 16), substr(date, 18, 3) || ' hours') hour,
 count(1) activity
from changeset
group by day, hour;

Nota: 0 = domenica, 1 = lunedì, ...

Commit Log – elenco dei commit per mese/anno

select
 id,
 user,
 date,
 desc
from changeset
where substr(date, 1, 7) = '%(year)s-%(month)s'
order by id desc;

Nota: elenco dei commit dell'ultimo mese.

Nota: in alternativa si può fornire l'elenco degli ultimi N commit.

TODO: aggiungere il numero di linee aggiunte/rimosse, elencare i file del changeset.

File Count – numero di file per revisione

select
 cs.id rev,
 cs.date date,
 cs.tag tag,
 sum(
  case
   when c.action='A' then 1
   when c.action='R' then -1
   else 0
  end) file_count
from
 changeset cs
 inner join change c on (c.changeset_id <= cs.id)
where ins_lines is not null
group by rev
order by rev;

Average File Size – LOC/file per revisione

select rev, date, tag, loc, file_count, loc * 1. / file_count
from (
 select
   cs.id rev,
   cs.date date,
   cs.tag tag,
   sum(c.ins_lines - c.del_lines) loc,
    sum(
     case
      when c.action='A' then 1
      when c.action='R' then -1
      else 0
     end) file_count
  from
   changeset cs
   inner join change c on (c.changeset_id <= cs.id)
  where ins_lines is not null
  group by rev)
order by rev;

File Types – numero di file, numero di linee per tipo di file

Non disponibile. Ricavabile dall'analisi del file-system.

Largest Files – file col maggior numero di linee

Non disponibile. Ricavabile dall'analisi del file-system.

Files With Most Revisions – file col maggior numero di revisioni

select
 path,
 count(1) rev_count
from change
group by path
having rev_count >= 10
order by rev_count desc
limit 20;

Lines of Code Per Directory – linee di codice per directory

TODO

Directory Statistics – numero di commit, numero di linee per directory

TODO

LOC and Churn – numero di linee, numero di linee modificate per revisione

select
 cs.id rev,
 cs.date date,
 cs.tag tag,
 sum(min(c.ins_lines, c.del_lines)) modified,
 sum(max(0, c.ins_lines - c.del_lines)) added,
 sum(max(0, c.del_lines - c.ins_lines)) removed
from changeset cs, change c
where c.changeset_id = cs.id
group by rev
order by rev;

Nota: ins_lines + del_lines = added + removed + 2 × modified.

Creazione dei prospetti

Vengono generati due tipologie di prospetti, tabellari e grafici. I primi sono ottenuti a partire da una serializzazione in formato XML dei risultati della query associata per essere successivamente trasformati in HTML via XSLT, mentre quelli grafici sono ottenuti diagrammando i contenuti di un file CSV tramite gnuplot:

Esempio di prospetto tabellare

Esempio di prospetto grafico

Implementazione

Uno script batch si occupa di lanciare in successione le tre fasi della procedura:

La cancellazione dei file temporanei conclude la procedura.

Appendice

La scelta di usare un database sqlite consente con poca fatica di produrre il tag-cloud dei messaggi di commit, grazie all'estensione FTS4 ed in particolare alla tabella virtuale fts4aux:

create virtual table tag_cloud using fts4(message);
create virtual table tag_cloud_aux using fts4aux(tag_cloud);

-- a tabella changeset già popolata!
insert into tag_cloud select desc from changeset;

È facile a questo punto determinare la frequenza relativa di tutti i termini indicizzati; per limitare i risultati alle parole chiave più significative, vengono ignorati quelli costituiti da una sola lettera, quelli presenti una volta sola, oltre ai numeri e alle stop-word più comuni:

select
 term,
 sum(occurrences) as occurrences,
 length(sum(occurrences)) score
from tag_cloud_aux
where
 length(term) > 1
 and cast(term as numeric) = '0'
 and term not in (
  'a', 'about', 'added', 'after', 'all', 'an', 'and', 'as',
  'by',
  'changeset',
  'does',
  'for',
  'in', 'into', 'is',
  'of', 'on',
  'new', 'not', 'now',
  'some',
  'tag', 'that', 'the', 'to', 'too',
  'when', 'with')
group by term
having occurrences > 1
order by occurrences desc;

Per agevolare la costruzione del tag-cloud, a ciascun termine viene assegnato un indice di importanza che corrisponde al numero di cifre di cui è composto il conteggio delle occorrenze: i termini che compaiono da due fino a nove volte sono di indice 1, quelli il cui numero è compreso tra dieci e novantanove sono di indice 2 e così via. Definendo per ogni indice uno stile apposito, via via più appariscente all'aumentare dell'indice, e assegnando ad ogni termine lo stile relativo all'indice, si ottiene il tag-cloud:

Esempio di tag cloud


Nota: le versioni 2.6 e 2.7 di Python per Windows non supportano l'estensione FTS4 di sqlite (vedere ad esempio la domanda n. 3823659 su Stack Overflow). Basta tuttavia sostituire la DLL <PYTHON_INSTALL_DIR>\DLLs\sqlite.dll con quella scaricabile dalla pagina dei download di sqlite.

Esempi

Qui un esempio di prospetto completo di tag-cloud.

Pagina modificata il 27/05/2014