/* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code to implement the stat web page ** */ #include "VERSION.h" #include "config.h" #include #include "stat.h" /* ** For a sufficiently large integer, provide an alternative ** representation as MB or GB or TB. */ void bigSizeName(int nOut, char *zOut, sqlite3_int64 v){ if( v<100000 ){ sqlite3_snprintf(nOut, zOut, "%,lld bytes", v); }else if( v<1000000000 ){ sqlite3_snprintf(nOut, zOut, "%,lld bytes (%.1fMB)", v, (double)v/1000000.0); }else{ sqlite3_snprintf(nOut, zOut, "%,lld bytes (%.1fGB)", v, (double)v/1000000000.0); } } /* ** Return the approximate size as KB, MB, GB, or TB. */ void approxSizeName(int nOut, char *zOut, sqlite3_int64 v){ if( v<1000 ){ sqlite3_snprintf(nOut, zOut, "%,lld bytes", v); }else if( v<1000000 ){ sqlite3_snprintf(nOut, zOut, "%.1fKB", (double)v/1000.0); }else if( v<1000000000 ){ sqlite3_snprintf(nOut, zOut, "%.1fMB", (double)v/1000000.0); }else{ sqlite3_snprintf(nOut, zOut, "%.1fGB", (double)v/1000000000.0); } } /* ** Generate stats for the email notification subsystem. */ void stats_for_email(void){ const char *zDest = db_get("email-send-method",0); int nSub, nASub, nPend, nDPend; const char *zDir, *zDb, *zCmd, *zRelay; @ Outgoing Email: if( fossil_strcmp(zDest,"pipe")==0 && (zCmd = db_get("email-send-command",0))!=0 ){ @ Piped to command "%h(zCmd)" }else if( fossil_strcmp(zDest,"db")==0 && (zDb = db_get("email-send-db",0))!=0 ){ sqlite3 *db; sqlite3_stmt *pStmt; int rc; @ Queued to database "%h(zDb)" rc = sqlite3_open(zDb, &db); if( rc==SQLITE_OK ){ rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM email",-1,&pStmt,0); if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ @ (%,d(sqlite3_column_int(pStmt,0)) messages, @ %,d(file_size(zDb,ExtFILE)) bytes) } sqlite3_finalize(pStmt); } sqlite3_close(db); }else if( fossil_strcmp(zDest,"dir")==0 && (zDir = db_get("email-send-dir",0))!=0 ){ @ Written to files in "%h(zDir)" @ (%,d(file_directory_size(zDir,0,1)) messages) }else if( fossil_strcmp(zDest,"relay")==0 && (zRelay = db_get("email-send-relayhost",0))!=0 ){ @ Relay to %h(zRelay) using SMTP } else{ @ Off } @ nPend = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); nDPend = db_int(0,"SELECT count(*) FROM pending_alert" " WHERE NOT sentDigest"); @ Pending Alerts: @ %,d(nPend) normal, %,d(nDPend) digest @ @ Subscribers: nSub = db_int(0, "SELECT count(*) FROM subscriber"); nASub = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" " AND NOT sdonotcall AND length(ssub)>1"); @ %,d(nASub) active, %,d(nSub) total @ } /* ** WEBPAGE: stat ** ** Show statistics and global information about the repository. */ void stat_page(void){ i64 t, fsize; int n, m; int szMax, szAvg; int brief; const char *p; login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } brief = P("brief")!=0; style_header("Repository Statistics"); style_adunit_config(ADUNIT_RIGHT_OK); if( g.perm.Admin ){ style_submenu_element("URLs", "urllist"); style_submenu_element("Schema", "repo_schema"); style_submenu_element("Web-Cache", "cachestat"); } style_submenu_element("Activity Reports", "reports"); style_submenu_element("Hash Collisions", "hash-collisions"); style_submenu_element("Artifacts", "bloblist"); if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){ style_submenu_element("Table Sizes", "repo-tabsize"); } if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){ style_submenu_element("Environment", "test_env"); } @ fsize = file_size(g.zRepositoryName, ExtFILE); @ @ if( !brief ){ @ if( n>0 ){ int a, b; Stmt q; @ @ } if( db_table_exists("repository","unversioned") ){ Stmt q; char zStored[100]; db_prepare(&q, "SELECT count(*), sum(sz), sum(length(content))" " FROM unversioned" " WHERE length(hash)>1" ); if( db_step(&q)==SQLITE_ROW && (n = db_column_int(&q,0))>0 ){ sqlite3_int64 iStored, pct; iStored = db_column_int64(&q,2); pct = (iStored*100 + fsize/2)/fsize; approxSizeName(sizeof(zStored), zStored, iStored); @ } db_finalize(&q); } @ @ @ @ } @ p = db_get("project-code", 0); if( p ){ @ @ } p = db_get("parent-project-code", 0); if( p ){ @ @ } /* @ */ @ @ if( g.eHashPolicy!=HPOLICY_AUTO ){ @ }else{ @ } @ @ if( g.perm.Admin && g.zErrlog && g.zErrlog[0] ){ i64 szFile = file_size(g.zErrlog, ExtFILE); if( szFile>=0 ){ @ @ } if( g.perm.Admin ){ @ @ } if( g.perm.Admin && alert_enabled() ){ stats_for_email(); } @
Repository Size:%,lld(fsize) bytes
Number Of Artifacts: n = db_int(0, "SELECT count(*) FROM blob"); m = db_int(0, "SELECT count(*) FROM delta"); @ %.d(n) (%,d(n-m) fulltext and %,d(m) deltas) if( g.perm.Write ){ @ Details } @
Uncompressed Artifact Size: db_prepare(&q, "SELECT total(size), avg(size), max(size)" " FROM blob WHERE size>0 /*scan*/"); db_step(&q); t = db_column_int64(&q, 0); szAvg = db_column_int(&q, 1); szMax = db_column_int(&q, 2); db_finalize(&q); @ %,d(szAvg) bytes average, %,d(szMax) bytes max, %,lld(t) total @
Compression Ratio: if( t/fsize < 5 ){ b = 10; a = t/(fsize/10); }else{ b = 1; a = t/fsize; } @ %d(a):%d(b) @
Unversioned Files: @ %z(href("%R/uvlist"))%d(n) files, @ %s(zStored) compressed, %d(pct)%% of total repository space @
Number Of Check-ins: n = db_int(0, "SELECT count(*) FROM event WHERE type='ci' /*scan*/"); @ %,d(n) @
Number Of Files: n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); @ %,d(n) @
Number Of Wiki Pages: n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE +tagname GLOB 'wiki-*'"); @ %,d(n) @
Number Of Tickets: n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE +tagname GLOB 'tkt-*'"); @ %,d(n) @
Duration Of Project: n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" " + 0.99"); @ %,d(n) days or approximately %.2f(n/365.2425) years. @
Project ID:%h(p) %h(db_get("project-name",""))
Parent Project ID:%h(p) %h(db_get("parent-project-name",""))
Server ID:%h(db_get("server-code",""))
Fossil Version: @ %h(MANIFEST_DATE) %h(MANIFEST_VERSION) @ (%h(RELEASE_VERSION)) (details) @
SQLite Version:%.19s(sqlite3_sourceid()) @ [%.10s(&sqlite3_sourceid()[20])] (%s(sqlite3_libversion())) @ (details)
Schema Version:%h(g.zAuxSchema), @ %s(hpolicy_name())
Schema Version:%h(g.zAuxSchema)
Repository Rebuilt: @ %h(db_get_mtime("rebuilt","%Y-%m-%d %H:%M:%S","Never")) @ By Fossil %h(db_get("rebuilt","Unknown"))
Database Stats: @ %,d(db_int(0, "PRAGMA repository.page_count")) pages, @ %d(db_int(0, "PRAGMA repository.page_size")) bytes/page, @ %,d(db_int(0, "PRAGMA repository.freelist_count")) free pages, @ %s(db_text(0, "PRAGMA repository.encoding")), @ %s(db_text(0, "PRAGMA repository.journal_mode")) mode @
Error Log:%h(g.zErrlog) (%,lld(szFile) bytes) } @
Backoffice:Last run: %z(backoffice_last_run())
style_footer(); } /* ** COMMAND: dbstat* ** ** Usage: %fossil dbstat OPTIONS ** ** Shows statistics and global information about the repository. ** ** Options: ** ** --brief|-b Only show essential elements ** --db-check Run a PRAGMA quick_check on the repository database ** --omit-version-info Omit the SQLite and Fossil version information */ void dbstat_cmd(void){ i64 t, fsize; int n, m; int szMax, szAvg; int brief; int omitVers; /* Omit Fossil and SQLite version information */ int dbCheck; /* True for the --db-check option */ const int colWidth = -19 /* printf alignment/width for left column */; const char *p, *z; brief = find_option("brief", "b",0)!=0; omitVers = find_option("omit-version-info", 0, 0)!=0; dbCheck = find_option("db-check",0,0)!=0; db_find_and_open_repository(0,0); /* We should be done with options.. */ verify_all_options(); if( (z = db_get("project-name",0))!=0 || (z = db_get("short-project-name",0))!=0 ){ fossil_print("%*s%s\n", colWidth, "project-name:", z); } fsize = file_size(g.zRepositoryName, ExtFILE); fossil_print( "%*s%,lld bytes\n", colWidth, "repository-size:", fsize); if( !brief ){ n = db_int(0, "SELECT count(*) FROM blob"); m = db_int(0, "SELECT count(*) FROM delta"); fossil_print("%*s%,d (stored as %,d full text and %,d deltas)\n", colWidth, "artifact-count:", n, n-m, m); if( n>0 ){ int a, b; Stmt q; db_prepare(&q, "SELECT total(size), avg(size), max(size)" " FROM blob WHERE size>0"); db_step(&q); t = db_column_int64(&q, 0); szAvg = db_column_int(&q, 1); szMax = db_column_int(&q, 2); db_finalize(&q); fossil_print( "%*s%,d average, " "%,d max, %,lld total\n", colWidth, "artifact-sizes:", szAvg, szMax, t); if( t/fsize < 5 ){ b = 10; fsize /= 10; }else{ b = 1; } a = t/fsize; fossil_print("%*s%d:%d\n", colWidth, "compression-ratio:", a, b); } n = db_int(0, "SELECT COUNT(*) FROM event e WHERE e.type='ci'"); fossil_print("%*s%,d\n", colWidth, "check-ins:", n); n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); fossil_print("%*s%,d across all branches\n", colWidth, "files:", n); n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE tagname GLOB 'wiki-*'"); m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='w'"); fossil_print("%*s%,d (%,d changes)\n", colWidth, "wiki-pages:", n, m); n = db_int(0, "SELECT count(*) FROM tag /*scan*/" " WHERE tagname GLOB 'tkt-*'"); m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'"); fossil_print("%*s%,d (%,d changes)\n", colWidth, "tickets:", n, m); n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'"); fossil_print("%*s%,d\n", colWidth, "events:", n); n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'"); fossil_print("%*s%,d\n", colWidth, "tag-changes:", n); z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||" " CAST(julianday('now') - mtime AS INTEGER)" " || ' days ago' FROM event " " ORDER BY mtime DESC LIMIT 1"); fossil_print("%*s%s\n", colWidth, "latest-change:", z); } n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" " + 0.99"); fossil_print("%*s%,d days or approximately %.2f years.\n", colWidth, "project-age:", n, n/365.2425); p = db_get("project-code", 0); if( p ){ fossil_print("%*s%s\n", colWidth, "project-id:", p); } #if 0 /* Server-id is not useful information any more */ fossil_print("%*s%s\n", colWidth, "server-id:", db_get("server-code", 0)); #endif fossil_print("%*s%s\n", colWidth, "schema-version:", g.zAuxSchema); if( !omitVers ){ fossil_print("%*s%s %s [%s] (%s)\n", colWidth, "fossil-version:", MANIFEST_DATE, MANIFEST_VERSION, RELEASE_VERSION, COMPILER_NAME); fossil_print("%*s%.19s [%.10s] (%s)\n", colWidth, "sqlite-version:", sqlite3_sourceid(), &sqlite3_sourceid()[20], sqlite3_libversion()); } fossil_print("%*s%,d pages, %d bytes/pg, %,d free pages, " "%s, %s mode\n", colWidth, "database-stats:", db_int(0, "PRAGMA repository.page_count"), db_int(0, "PRAGMA repository.page_size"), db_int(0, "PRAGMA repository.freelist_count"), db_text(0, "PRAGMA repository.encoding"), db_text(0, "PRAGMA repository.journal_mode")); if( dbCheck ){ fossil_print("%*s%s\n", colWidth, "database-check:", db_text(0, "PRAGMA quick_check(1)")); } } /* ** WEBPAGE: urllist ** ** Show ways in which this repository has been accessed */ void urllist_page(void){ Stmt q; int cnt; int showAll = P("all")!=0; int nOmitted; sqlite3_int64 iNow; char *zRemote; login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); return; } style_header("URLs and Checkouts"); style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Stat", "stat"); style_submenu_element("Schema", "repo_schema"); iNow = db_int64(0, "SELECT strftime('%%s','now')"); @
URLs
@ db_prepare(&q, "SELECT substr(name,9), datetime(mtime,'unixepoch'), mtime" " FROM config WHERE name GLOB 'baseurl:*' ORDER BY 3 DESC"); cnt = 0; nOmitted = 0; while( db_step(&q)==SQLITE_ROW ){ if( !showAll && db_column_int64(&q,2)<(iNow - 3600*24*30) && cnt>8 ){ nOmitted++; }else{ @ @ } cnt++; } db_finalize(&q); if( cnt==0 ){ @ }else if( nOmitted ){ @
%h(db_column_text(&q,0))%h(db_column_text(&q,1))
(none)
Show %d(nOmitted) more... } @
@
Checkouts
@ db_prepare(&q, "SELECT substr(name,7), datetime(mtime,'unixepoch')" " FROM config WHERE name GLOB 'ckout:*' ORDER BY 2 DESC"); cnt = 0; while( db_step(&q)==SQLITE_ROW ){ const char *zPath = db_column_text(&q,0); if( vfile_top_of_checkout(zPath) ){ @ @ } cnt++; } db_finalize(&q); if( cnt==0 ){ @ } @
%h(zPath)%h(db_column_text(&q,1))
(none)
zRemote = db_text(0, "SELECT value FROM config WHERE name='last-sync-url'"); if( zRemote ){ @
Last Sync URL
if( sqlite3_strlike("http%", zRemote, 0)==0 ){ UrlData x; url_parse_local(zRemote, URL_OMIT_USER, &x); @

%h(zRemote) }else{ @

%h(zRemote)

} @ } style_footer(); } /* ** WEBPAGE: repo_schema ** ** Show the repository schema */ void repo_schema_page(void){ Stmt q; Blob sql; const char *zArg = P("n"); login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); return; } style_header("Repository Schema"); style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Stat", "stat"); style_submenu_element("URLs", "urllist"); if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){ style_submenu_element("Table Sizes", "repo-tabsize"); } blob_init(&sql, "SELECT sql FROM repository.sqlite_master WHERE sql IS NOT NULL", -1); if( zArg ){ style_submenu_element("All", "repo_schema"); blob_appendf(&sql, " AND (tbl_name=%Q OR name=%Q)", zArg, zArg); } blob_appendf(&sql, " ORDER BY tbl_name, type<>'table', name"); db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/); blob_reset(&sql); @
  while( db_step(&q)==SQLITE_ROW ){
    @ %h(db_column_text(&q, 0));
  }
  @ 
db_finalize(&q); if( db_table_exists("repository","sqlite_stat1") ){ if( zArg ){ db_prepare(&q, "SELECT tbl, idx, stat FROM repository.sqlite_stat1" " WHERE tbl LIKE %Q OR idx LIKE %Q" " ORDER BY tbl, idx", zArg, zArg); @
@
      while( db_step(&q)==SQLITE_ROW ){
        const char *zTab = db_column_text(&q,0);
        const char *zIdx = db_column_text(&q,1);
        const char *zStat = db_column_text(&q,2);
        @ INSERT INTO sqlite_stat1 VALUES('%h(zTab)','%h(zIdx)','%h(zStat)');
      }
      @ 
db_finalize(&q); }else{ style_submenu_element("Stat1","repo_stat1"); } } style_footer(); } /* ** WEBPAGE: repo_stat1 ** ** Show the sqlite_stat1 table for the repository schema */ void repo_stat1_page(void){ login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); return; } style_header("Repository STAT1 Table"); style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Stat", "stat"); style_submenu_element("Schema", "repo_schema"); if( db_table_exists("repository","sqlite_stat1") ){ Stmt q; db_prepare(&q, "SELECT tbl, idx, stat FROM repository.sqlite_stat1" " ORDER BY tbl, idx"); @
    while( db_step(&q)==SQLITE_ROW ){
      const char *zTab = db_column_text(&q,0);
      const char *zIdx = db_column_text(&q,1);
      const char *zStat = db_column_text(&q,2);
      char *zUrl = href("%R/repo_schema?n=%t",zTab);
      @ INSERT INTO sqlite_stat1 VALUES('%z(zUrl)%h(zTab)','%h(zIdx)','%h(zStat)');
    }
    @ 
db_finalize(&q); } style_footer(); } /* ** WEBPAGE: repo-tabsize ** ** Show relative sizes of tables in the repository database. */ void repo_tabsize_page(void){ int nPageFree; sqlite3_int64 fsize; char zBuf[100]; login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("Repository Table Sizes"); style_adunit_config(ADUNIT_RIGHT_OK); style_submenu_element("Stat", "stat"); if( g.perm.Admin ){ style_submenu_element("Schema", "repo_schema"); } db_multi_exec( "CREATE TEMP TABLE trans(name TEXT PRIMARY KEY,tabname TEXT)WITHOUT ROWID;" "INSERT INTO trans(name,tabname)" " SELECT name, tbl_name FROM repository.sqlite_master;" "CREATE TEMP TABLE piechart(amt REAL, label TEXT);" "INSERT INTO piechart(amt,label)" " SELECT count(*), " " coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)" " FROM dbstat('repository')" " GROUP BY 2 ORDER BY 2;" ); nPageFree = db_int(0, "PRAGMA repository.freelist_count"); if( nPageFree>0 ){ db_multi_exec( "INSERT INTO piechart(amt,label) VALUES(%d,'freelist')", nPageFree ); } fsize = file_size(g.zRepositoryName, ExtFILE); approxSizeName(sizeof(zBuf), zBuf, fsize); @

Repository Size: %s(zBuf)

@
piechart_render(800,500,PIE_OTHER|PIE_PERCENT); @
if( g.localOpen ){ db_multi_exec( "DELETE FROM trans;" "INSERT INTO trans(name,tabname)" " SELECT name, tbl_name FROM localdb.sqlite_master;" "DELETE FROM piechart;" "INSERT INTO piechart(amt,label)" " SELECT count(*), " " coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)" " FROM dbstat('localdb')" " GROUP BY 2 ORDER BY 2;" ); nPageFree = db_int(0, "PRAGMA localdb.freelist_count"); if( nPageFree>0 ){ db_multi_exec( "INSERT INTO piechart(amt,label) VALUES(%d,'freelist')", nPageFree ); } fsize = file_size(g.zLocalDbName, ExtFILE); approxSizeName(sizeof(zBuf), zBuf, fsize); @

%h(file_tail(g.zLocalDbName)) Size: %s(zBuf)

@
piechart_render(800,500,PIE_OTHER|PIE_PERCENT); @
} style_footer(); } /* ** Gather statistics on artifact types, counts, and sizes. ** ** Only populate the artstat.atype field if the bWithTypes parameter is true. */ static void gather_artifact_stats(int bWithTypes){ static const char zSql[] = @ CREATE TEMP TABLE artstat( @ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID @ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc. @ isDelta BOOLEAN, -- true if stored as a delta @ szExp, -- expanded, uncompressed size @ szCmpr -- size as stored on disk @ ); @ INSERT INTO artstat(id,atype,isDelta,szExp,szCmpr) @ SELECT blob.rid, NULL, @ EXISTS(SELECT 1 FROM delta WHERE delta.rid=blob.rid), @ size, length(content) @ FROM blob @ WHERE content IS NOT NULL; ; static const char zSql2[] = @ UPDATE artstat SET atype='file' @ WHERE id IN (SELECT fid FROM mlink) @ AND atype IS NULL; @ UPDATE artstat SET atype='manifest' @ WHERE id IN (SELECT objid FROM event WHERE type='ci') AND atype IS NULL; @ UPDATE artstat SET atype='cluster' @ WHERE atype IS NULL @ AND id IN (SELECT rid FROM tagxref @ WHERE tagid=(SELECT tagid FROM tag @ WHERE tagname='cluster')); @ UPDATE artstat SET atype='ticket' @ WHERE atype IS NULL @ AND id IN (SELECT rid FROM tagxref @ WHERE tagid IN (SELECT tagid FROM tag @ WHERE tagname GLOB 'tkt-*')); @ UPDATE artstat SET atype='wiki' @ WHERE atype IS NULL @ AND id IN (SELECT rid FROM tagxref @ WHERE tagid IN (SELECT tagid FROM tag @ WHERE tagname GLOB 'wiki-*')); @ UPDATE artstat SET atype='technote' @ WHERE atype IS NULL @ AND id IN (SELECT rid FROM tagxref @ WHERE tagid IN (SELECT tagid FROM tag @ WHERE tagname GLOB 'event-*')); @ UPDATE artstat SET atype='attachment' @ WHERE atype IS NULL @ AND id IN (SELECT attachid FROM attachment UNION @ SELECT blob.rid FROM attachment JOIN blob ON uuid=src); @ UPDATE artstat SET atype='tag' @ WHERE atype IS NULL @ AND id IN (SELECT srcid FROM tagxref); @ UPDATE artstat SET atype='tag' @ WHERE atype IS NULL @ AND id IN (SELECT objid FROM event WHERE type='g'); @ UPDATE artstat SET atype='unused' WHERE atype IS NULL; ; db_multi_exec("%s", zSql/*safe-for-%s*/); if( bWithTypes ){ db_multi_exec("%s", zSql2/*safe-for-%s*/); } } /* ** Output text "the largest N artifacts". Make this text a hyperlink ** to bigbloblist if N is not too big. */ static void largest_n_artifacts(int N){ if( N>250 ){ @ (the largest %,d(N) artifacts) }else{ @ (the largest %d(N) artifacts) } } /* ** WEBPAGE: artifact_stats ** ** Show information about the sizes of artifacts in this repository */ void artifact_stats_page(void){ Stmt q; int nTotal = 0; /* Total number of artifacts */ int nDelta = 0; /* Total number of deltas */ int nFull = 0; /* Total number of full-texts */ double avgCmpr = 0.0; /* Average size of an artifact after compression */ double avgExp = 0.0; /* Average size of an uncompressed artifact */ int mxCmpr = 0; /* Maximum compressed artifact size */ int mxExp = 0; /* Maximum uncompressed artifact size */ sqlite3_int64 sumCmpr = 0; /* Total size of all compressed artifacts */ sqlite3_int64 sumExp = 0; /* Total size of all expanded artifacts */ sqlite3_int64 sz1pct = 0; /* Space used by largest 1% */ sqlite3_int64 sz10pct = 0; /* Space used by largest 10% */ sqlite3_int64 sz25pct = 0; /* Space used by largest 25% */ sqlite3_int64 sz50pct = 0; /* Space used by largest 50% */ int n50pct = 0; /* Artifacts using the first 50% of space */ int n; /* Loop counter */ int medCmpr = 0; /* Median compressed artifact size */ int medExp = 0; /* Median expanded artifact size */ int med; double r; login_check_credentials(); /* These stats are expensive to compute. To disable them for ** user without check-in privileges, to prevent excessive usage by ** robots and random passers-by on the internet */ if( !g.perm.Write ){ login_needed(g.anon.Admin); return; } style_header("Artifact Statistics"); style_submenu_element("Repository Stats", "stat"); style_submenu_element("Artifact List", "bloblist"); gather_artifact_stats(1); db_prepare(&q, "SELECT count(*), sum(isDelta), max(szCmpr)," " max(szExp), sum(szCmpr), sum(szExp)" " FROM artstat" ); db_step(&q); nTotal = db_column_int(&q,0); nDelta = db_column_int(&q,1); nFull = nTotal - nDelta; mxCmpr = db_column_int(&q, 2); mxExp = db_column_int(&q, 3); sumCmpr = db_column_int64(&q, 4); sumExp = db_column_int64(&q, 5); db_finalize(&q); if( nTotal==0 ){ @ No artifacts in this repository! style_footer(); return; } avgCmpr = (double)sumCmpr/nTotal; avgExp = (double)sumExp/nTotal; db_prepare(&q, "SELECT szCmpr FROM artstat ORDER BY 1 DESC"); r = 0; n = 0; while( db_step(&q)==SQLITE_ROW ){ r += db_column_int(&q, 0); if( n50pct==0 && r>=sumCmpr/2 ) n50pct = n; if( n==(nTotal+99)/100 ) sz1pct = r; if( n==(nTotal+9)/10 ) sz10pct = r; if( n==(nTotal+4)/5 ) sz25pct = r; if( n==(nTotal+1)/2 ){ sz50pct = r; medCmpr = db_column_int(&q,0); } n++; } db_finalize(&q); @

Overall Artifact Size Statistics:

@ @ @ \ @ @ medExp = db_int(0, "SELECT szExp FROM artstat ORDER BY szExp" " LIMIT 1 OFFSET %d", nTotal/2); @ \ @ @ \ @ db_prepare(&q, "SELECT avg(szCmpr), max(szCmpr) FROM artstat WHERE isDelta" ); if( db_step(&q)==SQLITE_ROW ){ int mxDelta = db_column_int(&q,1); double avgDelta = db_column_double(&q,0); med = db_int(0, "SELECT szCmpr FROM artstat WHERE isDelta ORDER BY szCmpr" " LIMIT 1 OFFSET %d", nDelta/2); @ \ @ } db_finalize(&q); r = db_double(0.0, "SELECT avg(szCmpr) FROM artstat WHERE NOT isDelta;"); med = db_int(0, "SELECT szCmpr FROM artstat WHERE NOT isDelta ORDER BY szCmpr" " LIMIT 1 OFFSET %d", nFull/2); @ @ @
Number of artifacts:%,d(nTotal)
Number of deltas:%,d(nDelta) (%d(nDelta*100/nTotal)%%)
Number of full-text:%,d(nFull) \ @ (%d(nFull*100/nTotal)%%)
Uncompressed artifact sizes:largest: %,d(mxExp), average: %,d((int)avgExp), median: %,d(medExp)
Compressed artifact sizes:largest: %,d(mxCmpr), average: %,d((int)avgCmpr), \ @ median: %,d(medCmpr)
Delta artifact sizes:largest: %,d(mxDelta), average: %,d((int)avgDelta), \ @ median: %,d(med)
Full-text artifact sizes:largest: %,d(mxCmpr), average: %,d((int)r), median: %,d(med)
@

Artifact size distribution facts:

@
    @
  1. The largest %.2f(n50pct*100.0/nTotal)%% of artifacts largest_n_artifacts(n50pct); @ use 50%% of the total artifact space. @

  2. The largest 1%% of artifacts largest_n_artifacts((nTotal+99)/100); @ use %lld(sz1pct*100/sumCmpr)%% of the total artifact space. @

  3. The largest 10%% of artifacts largest_n_artifacts((nTotal+9)/10); @ use %lld(sz10pct*100/sumCmpr)%% of the total artifact space. @

  4. The largest 25%% of artifacts largest_n_artifacts((nTotal+4)/5); @ use %lld(sz25pct*100/sumCmpr)%% of the total artifact space. @

  5. The largest 50%% of artifacts largest_n_artifacts((nTotal+1)/2); @ use %lld(sz50pct*100/sumCmpr)%% of the total artifact space. @

@

Artifact Sizes By Type:

db_prepare(&q, "SELECT atype, count(*), sum(isDelta), sum(szCmpr), sum(szExp)" " FROM artstat GROUP BY 1" " UNION ALL " "SELECT 'ALL', count(*), sum(isDelta), sum(szCmpr), sum(szExp)" " FROM artstat" " ORDER BY 4;" ); @ @ @ @ @ @ @ @ @ while( db_step(&q)==SQLITE_ROW ){ const char *zType = db_column_text(&q, 0); int nTotal = db_column_int(&q, 1); int nDelta = db_column_int(&q, 2); int nFull = nTotal - nDelta; sqlite3_int64 szCmpr = db_column_int64(&q, 3); sqlite3_int64 szExp = db_column_int64(&q, 4); @ @ @ @ @ @ } @
Artifact TypeCountFull-TextDeltaCompressed SizeUncompressed Size
%h(zType)%,d(nTotal)%,d(nFull)%,d(nDelta)%,lld(szCmpr)%,lld(szExp)
db_finalize(&q); if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){ @

Unused Artifacts:

db_prepare(&q, "SELECT artstat.id, blob.uuid, user.login," " datetime(rcvfrom.mtime), rcvfrom.rcvid" " FROM artstat JOIN blob ON artstat.id=blob.rid" " LEFT JOIN rcvfrom USING(rcvid)" " LEFT JOIN user USING(uid)" " WHERE atype='unused'" ); @ @ @ @ @ @ @ @ while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); const char *zHash = db_column_text(&q, 1); const char *zUser = db_column_text(&q, 2); const char *zDate = db_column_text(&q, 3); int iRcvid = db_column_int(&q, 4); @ @ @ @ @ } @
RecordIDHashUserDateRcvID
%d(rid)%z(href("%R/info/%!S",zHash))%S(zHash)%h(zUser)%h(zDate)%z(href("%R/rcvfrom?rcvid=%d",iRcvid))%d(iRcvid)
db_finalize(&q); } style_table_sorter(); style_footer(); }