Fossil SCM

fossil-scm / src / stat.c
Blame History Raw 1225 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code to implement the stat web page
19
**
20
*/
21
#include "VERSION.h"
22
#include "config.h"
23
#include <string.h>
24
#include "stat.h"
25
26
/*
27
** For a sufficiently large integer, provide an alternative
28
** representation as MB or GB or TB.
29
*/
30
void bigSizeName(int nOut, char *zOut, sqlite3_int64 v){
31
if( v<100000 ){
32
sqlite3_snprintf(nOut, zOut, "%,lld bytes", v);
33
}else if( v<1000000000 ){
34
sqlite3_snprintf(nOut, zOut, "%,lld bytes (%.1fMB)",
35
v, (double)v/1000000.0);
36
}else{
37
sqlite3_snprintf(nOut, zOut, "%,lld bytes (%.1fGB)",
38
v, (double)v/1000000000.0);
39
}
40
}
41
42
/*
43
** Return the approximate size as KB, MB, GB, or TB.
44
*/
45
void approxSizeName(int nOut, char *zOut, sqlite3_int64 v){
46
if( v<1000 ){
47
sqlite3_snprintf(nOut, zOut, "%,lld bytes", v);
48
}else if( v<1000000 ){
49
sqlite3_snprintf(nOut, zOut, "%.1fKB", (double)v/1000.0);
50
}else if( v<1000000000 ){
51
sqlite3_snprintf(nOut, zOut, "%.1fMB", (double)v/1000000.0);
52
}else{
53
sqlite3_snprintf(nOut, zOut, "%.1fGB", (double)v/1000000000.0);
54
}
55
}
56
57
/*
58
** Generate stats for the email notification subsystem.
59
*/
60
void stats_for_email(void){
61
const char *zDest = db_get("email-send-method",0);
62
int nSub, nASub, nPend, nDPend;
63
const char *zDir, *zDb, *zCmd, *zRelay;
64
int iCutoff;
65
double rDigest;
66
@ <tr><th>Outgoing&nbsp;Email:</th><td>
67
if( fossil_strcmp(zDest,"pipe")==0
68
&& (zCmd = db_get("email-send-command",0))!=0
69
){
70
@ Piped to command "%h(zCmd)"
71
}else
72
if( fossil_strcmp(zDest,"db")==0
73
&& (zDb = db_get("email-send-db",0))!=0
74
){
75
sqlite3 *db;
76
sqlite3_stmt *pStmt;
77
int rc;
78
@ Queued to database "%h(zDb)"
79
g.dbIgnoreErrors++;
80
rc = sqlite3_open(zDb, &db);
81
if( rc==SQLITE_OK ){
82
rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM email",-1,&pStmt,0);
83
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
84
@ (%,d(sqlite3_column_int(pStmt,0)) messages,
85
@ %,d(file_size(zDb,ExtFILE)) bytes)
86
}
87
sqlite3_finalize(pStmt);
88
}
89
g.dbIgnoreErrors--;
90
if( rc ){
91
@ &larr; cannot access database!
92
}
93
sqlite3_close(db);
94
}else
95
if( fossil_strcmp(zDest,"dir")==0
96
&& (zDir = db_get("email-send-dir",0))!=0
97
){
98
@ Written to files in "%h(zDir)"
99
@ (%,d(file_directory_list(zDir,0,1,0,0)) messages)
100
}else
101
if( fossil_strcmp(zDest,"relay")==0
102
&& (zRelay = db_get("email-send-relayhost",0))!=0
103
){
104
@ Relay to %h(zRelay) using SMTP
105
}
106
else{
107
@ Off
108
}
109
@ </td></tr>
110
nPend = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep");
111
nDPend = db_int(0,"SELECT count(*) FROM pending_alert"
112
" WHERE NOT sentDigest");
113
@ <tr><th>Pending&nbsp;Alerts:</th><td>
114
@ %,d(nPend) normal, %,d(nDPend) digest
115
@ </td></tr>
116
if( g.perm.Admin ){
117
@ <tr><th><a href="%R/subscribers">Subscribers:</a></th><td>
118
}else{
119
@ <tr><th>Subscribers:</th><td>
120
}
121
nSub = db_int(0, "SELECT count(*) FROM subscriber");
122
iCutoff = db_get_int("email-renew-cutoff",0);
123
nASub = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified"
124
" AND NOT sdonotcall AND octet_length(ssub)>1"
125
" AND lastContact>=%d;", iCutoff);
126
@ %,d(nASub) active, %,d(nSub) total
127
@ </td></tr>
128
rDigest = db_double(-1.0, "SELECT (julianday('now') - value)*24.0"
129
" FROM config WHERE name='email-last-digest'");
130
if( rDigest>0.0 ){
131
@ <tr><th>Last Digest:</th><td>Approximately \
132
if( rDigest>48.0 ){
133
@ %.1f(rDigest/24.0) days ago</td>
134
}else{
135
@ %.1f(rDigest) hours ago</td>
136
}
137
}
138
}
139
140
/*
141
** WEBPAGE: stat
142
**
143
** Show statistics and global information about the repository.
144
*/
145
void stat_page(void){
146
i64 t, fsize;
147
int n, m;
148
int szMax, szAvg;
149
int brief;
150
const char *p;
151
char *z;
152
int Y, M, D;
153
154
login_check_credentials();
155
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
156
brief = P("brief")!=0;
157
style_header("Repository Statistics");
158
style_adunit_config(ADUNIT_RIGHT_OK);
159
if( g.perm.Admin ){
160
style_submenu_element("URLs", "urllist");
161
style_submenu_element("Schema", "repo_schema");
162
style_submenu_element("Web-Cache", "cachestat");
163
}
164
style_submenu_element("Activity Reports", "reports");
165
style_submenu_element("Hash Collisions", "hash-collisions");
166
style_submenu_element("Artifacts", "bloblist");
167
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
168
style_submenu_element("Table Sizes", "repo-tabsize");
169
}
170
if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
171
style_submenu_element("Environment", "test-env");
172
}
173
@ <table class="label-value">
174
fsize = file_size(g.zRepositoryName, ExtFILE);
175
@ <tr><th>Repository&nbsp;Size:</th><td>%,lld(fsize) bytes</td>
176
@ </td></tr>
177
if( !brief ){
178
@ <tr><th>Number&nbsp;Of&nbsp;Artifacts:</th><td>
179
n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
180
m = db_int(0, "SELECT count(*) FROM delta");
181
@ %,d(n) (%,d(n-m) fulltext and %,d(m) deltas)
182
if( g.perm.Write ){
183
@ <a href='%R/artifact_stats'>Details</a>
184
}
185
@ </td></tr>
186
if( n>0 ){
187
int a, b;
188
Stmt q;
189
@ <tr><th>Uncompressed&nbsp;Artifact&nbsp;Size:</th><td>
190
db_prepare(&q, "SELECT total(size), avg(size), max(size)"
191
" FROM blob WHERE content IS NOT NULL /*scan*/");
192
db_step(&q);
193
t = db_column_int64(&q, 0);
194
szAvg = db_column_int(&q, 1);
195
szMax = db_column_int(&q, 2);
196
db_finalize(&q);
197
@ %,d(szAvg) bytes average, %,d(szMax) bytes max, %,lld(t) total
198
@ </td></tr>
199
@ <tr><th>Compression&nbsp;Ratio:</th><td>
200
if( t/fsize < 5 ){
201
b = 10;
202
a = t/(fsize/10);
203
}else{
204
b = 1;
205
a = t/fsize;
206
}
207
@ %d(a):%d(b)
208
@ </td></tr>
209
}
210
if( db_table_exists("repository","unversioned") ){
211
Stmt q;
212
char zStored[100];
213
db_prepare(&q,
214
"SELECT count(*), sum(sz), sum(octet_length(content))"
215
" FROM unversioned"
216
" WHERE length(hash)>1"
217
);
218
if( db_step(&q)==SQLITE_ROW && (n = db_column_int(&q,0))>0 ){
219
sqlite3_int64 iStored, pct;
220
iStored = db_column_int64(&q,2);
221
pct = (iStored*100 + fsize/2)/fsize;
222
approxSizeName(sizeof(zStored), zStored, iStored);
223
@ <tr><th>Unversioned&nbsp;Files:</th><td>
224
@ %z(href("%R/uvlist"))%d(n) files</a>,
225
@ %s(zStored) compressed, %d(pct)%% of total repository space
226
@ </td></tr>
227
}
228
db_finalize(&q);
229
}
230
@ <tr><th>Number&nbsp;Of&nbsp;Check-ins:</th><td>
231
n = db_int(0, "SELECT count(*) FROM event WHERE type='ci' /*scan*/");
232
@ %,d(n)
233
@ </td></tr>
234
@ <tr><th>Number&nbsp;Of&nbsp;Files:</th><td>
235
n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
236
@ %,d(n)
237
@ </td></tr>
238
@ <tr><th>Number&nbsp;Of&nbsp;Wiki&nbsp;Pages:</th><td>
239
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
240
" WHERE +tagname GLOB 'wiki-*'");
241
@ %,d(n)
242
@ </td></tr>
243
if( g.perm.Chat && db_table_exists("repository","chat") ){
244
sqlite3_int64 sz = 0;
245
char zSz[100];
246
n = db_int(0, "SELECT max(msgid) FROM chat");
247
m = db_int(0, "SELECT count(*) FROM chat WHERE mdel IS NOT TRUE");
248
sz = db_int64(0, "SELECT sum(coalesce(octet_length(xmsg),0)+"
249
"coalesce(octet_length(file),0)) FROM chat");
250
approxSizeName(sizeof(zSz), zSz, sz);
251
@ <tr><th>Number&nbsp;Of&nbsp;Chat&nbsp;Messages:</th>
252
@ <td>%,d(n) (%,d(m) still alive, %s(zSz) in size)</td></tr>
253
}
254
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
255
" WHERE +tagname GLOB 'tkt-*'");
256
if( n>0 ){
257
@ <tr><th>Number&nbsp;Of&nbsp;Tickets:</th><td>%,d(n)</td></tr>
258
}
259
if( db_table_exists("repository","forumpost") ){
260
n = db_int(0, "SELECT count(*) FROM forumpost/*scan*/");
261
if( n>0 ){
262
int nThread = db_int(0, "SELECT count(*) FROM forumpost"
263
" WHERE froot=fpid");
264
@ <tr><th>Number&nbsp;Of&nbsp;Forum&nbsp;Posts:</th>
265
@ <td>%,d(n) on %d(nThread) threads</td></tr>
266
}
267
}
268
}
269
@ <tr><th>Project&nbsp;Age:</th><td>
270
z = db_text(0, "SELECT timediff('now',(SELECT min(mtime) FROM event));");
271
sscanf(z, "+%d-%d-%d", &Y, &M, &D);
272
if( Y>0 ){
273
@ %d(Y) year%s(Y==1?"":"s") \
274
}
275
if( M>0 ){
276
@ %d(M) month%s(M==1?"":"s") \
277
}
278
if( D>0 || (Y==0 && M==0) ){
279
@ %d(D) day%s(D==1?"":"s")
280
}
281
@ </td></tr>
282
p = db_get("project-code", 0);
283
if( p ){
284
@ <tr><th>Project&nbsp;ID:</th>
285
@ <td>%h(p) %h(db_get("project-name",""))</td></tr>
286
}
287
p = db_get("parent-project-code", 0);
288
if( p ){
289
@ <tr><th>Parent&nbsp;Project&nbsp;ID:</th>
290
@ <td>%h(p) %h(db_get("parent-project-name",""))</td></tr>
291
}
292
/* @ <tr><th>Server&nbsp;ID:</th><td>%h(db_get("server-code",""))</td></tr> */
293
@ <tr><th>Fossil&nbsp;Version:</th><td>
294
@ %h(MANIFEST_DATE) %h(MANIFEST_VERSION)
295
@ (%h(RELEASE_VERSION)) <a href='version?verbose'>(details)</a>
296
@ </td></tr>
297
@ <tr><th>SQLite&nbsp;Version:</th><td>%.19s(sqlite3_sourceid())
298
@ [%.10s(&sqlite3_sourceid()[20])] (%s(sqlite3_libversion()))
299
@ <a href='version?verbose'>(details)</a></td></tr>
300
if( g.perm.Admin ){
301
const char *zCgi = P("SERVER_SOFTWARE");
302
@ <tr><th>OpenSSL&nbsp;Version:</th>
303
@ <td>%z(fossil_openssl_version())</td></tr>
304
if( zCgi ){
305
@ <tr><th>Web&nbsp;Server:</th><td>%s(zCgi)</td></tr>
306
}
307
}
308
if( g.eHashPolicy!=HPOLICY_AUTO ){
309
@ <tr><th>Schema&nbsp;Version:</th><td>%h(g.zAuxSchema),
310
@ %s(hpolicy_name())</td></tr>
311
}else{
312
@ <tr><th>Schema&nbsp;Version:</th><td>%h(g.zAuxSchema)</td></tr>
313
}
314
@ <tr><th>Repository Rebuilt:</th><td>
315
@ %h(db_get_mtime("rebuilt","%Y-%m-%d %H:%M:%S","Never"))
316
@ By Fossil %h(db_get("rebuilt","Unknown"))</td></tr>
317
@ <tr><th>Database&nbsp;Stats:</th><td>
318
@ %,d(db_int(0, "PRAGMA repository.page_count")) pages,
319
@ %d(db_int(0, "PRAGMA repository.page_size")) bytes/page,
320
@ %,d(db_int(0, "PRAGMA repository.freelist_count")) free pages,
321
@ %s(db_text(0, "PRAGMA repository.encoding")),
322
@ %s(db_text(0, "PRAGMA repository.journal_mode")) mode
323
@ </td></tr>
324
if( g.perm.Admin && g.zErrlog && g.zErrlog[0] ){
325
i64 szFile = file_size(g.zErrlog, ExtFILE);
326
if( szFile>=0 ){
327
@ <tr><th>Error Log:</th>
328
@ <td><a href='%R/errorlog'>%h(g.zErrlog)</a> (%,lld(szFile) bytes)
329
}
330
@ </td></tr>
331
}
332
if( g.perm.Admin ){
333
@ <tr><th>Backoffice:</th>
334
@ <td>Last run: %z(backoffice_last_run())</td></tr>
335
}
336
if( g.perm.Admin && alert_enabled() ){
337
stats_for_email();
338
}
339
340
@ </table>
341
style_finish_page();
342
}
343
344
/*
345
** COMMAND: dbstat
346
**
347
** Usage: %fossil dbstat OPTIONS
348
**
349
** Shows statistics and global information about the repository and/or
350
** verify the integrity of a repository.
351
**
352
** Options:
353
** -b|--brief Only show essential elements
354
** --db-check Run "PRAGMA quick_check" on the repository database
355
** --db-verify Run a full verification of the repository integrity.
356
** This involves decoding and reparsing all artifacts
357
** and can take significant time.
358
** --omit-version-info Omit the SQLite and Fossil version information
359
*/
360
void dbstat_cmd(void){
361
i64 t, fsize;
362
int n, m;
363
int szMax, szAvg;
364
int brief;
365
int omitVers; /* Omit Fossil and SQLite version information */
366
int dbCheck; /* True for the --db-check option */
367
const int colWidth = -19 /* printf alignment/width for left column */;
368
const char *p, *z;
369
370
brief = find_option("brief", "b",0)!=0;
371
omitVers = find_option("omit-version-info", 0, 0)!=0;
372
dbCheck = find_option("db-check",0,0)!=0;
373
if( find_option("db-verify",0,0)!=0 ) dbCheck = 2;
374
db_find_and_open_repository(0,0);
375
376
/* We should be done with options.. */
377
verify_all_options();
378
379
if( (z = db_get("project-name",0))!=0
380
|| (z = db_get("short-project-name",0))!=0
381
){
382
fossil_print("%*s%s\n", colWidth, "project-name:", z);
383
}
384
fsize = file_size(g.zRepositoryName, ExtFILE);
385
fossil_print( "%*s%,lld bytes\n", colWidth, "repository-size:", fsize);
386
if( !brief ){
387
n = db_int(0, "SELECT count(*) FROM blob WHERE content IS NOT NULL");
388
m = db_int(0, "SELECT count(*) FROM delta");
389
fossil_print("%*s%,d (stored as %,d full text and %,d deltas)\n",
390
colWidth, "artifact-count:",
391
n, n-m, m);
392
if( n>0 ){
393
int a, b;
394
Stmt q;
395
db_prepare(&q, "SELECT total(size), avg(size), max(size)"
396
" FROM blob WHERE size>0");
397
db_step(&q);
398
t = db_column_int64(&q, 0);
399
szAvg = db_column_int(&q, 1);
400
szMax = db_column_int(&q, 2);
401
db_finalize(&q);
402
fossil_print( "%*s%,d average, "
403
"%,d max, %,lld total\n",
404
colWidth, "artifact-sizes:",
405
szAvg, szMax, t);
406
if( t/fsize < 5 ){
407
b = 10;
408
fsize /= 10;
409
}else{
410
b = 1;
411
}
412
a = t/fsize;
413
fossil_print("%*s%d:%d\n", colWidth, "compression-ratio:", a, b);
414
}
415
n = db_int(0, "SELECT COUNT(*) FROM event e WHERE e.type='ci'");
416
fossil_print("%*s%,d\n", colWidth, "check-ins:", n);
417
n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
418
fossil_print("%*s%,d across all branches\n", colWidth, "files:", n);
419
n = db_int(0, "SELECT count(*) FROM ("
420
"SELECT DISTINCT substr(tagname,6) "
421
"FROM tag JOIN tagxref USING('tagid')"
422
" WHERE tagname GLOB 'wiki-*'"
423
" AND TYPEOF(tagxref.value+0)='integer'"
424
")");
425
m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='w'");
426
fossil_print("%*s%,d (%,d changes)\n", colWidth, "wiki-pages:", n, m);
427
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
428
" WHERE tagname GLOB 'tkt-*'");
429
m = db_int(0, "SELECT COUNT(*) FROM event WHERE type='t'");
430
fossil_print("%*s%,d (%,d changes)\n", colWidth, "tickets:", n, m);
431
n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='e'");
432
fossil_print("%*s%,d\n", colWidth, "events:", n);
433
if( db_table_exists("repository","forumpost") ){
434
n = db_int(0, "SELECT count(*) FROM forumpost/*scan*/");
435
if( n>0 ){
436
int nThread = db_int(0, "SELECT count(*) FROM forumpost"
437
" WHERE froot=fpid");
438
fossil_print("%*s%,d (on %,d threads)\n", colWidth, "forum-posts:",
439
n, nThread);
440
}
441
}
442
n = db_int(0, "SELECT COUNT(*) FROM event WHERE type='g'");
443
fossil_print("%*s%,d\n", colWidth, "tag-changes:", n);
444
z = db_text(0, "SELECT datetime(mtime) || ' - about ' ||"
445
" CAST(julianday('now') - mtime AS INTEGER)"
446
" || ' days ago' FROM event "
447
" ORDER BY mtime DESC LIMIT 1");
448
fossil_print("%*s%s\n", colWidth, "latest-change:", z);
449
}
450
n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
451
" + 0.99");
452
fossil_print("%*s%,d days or approximately %.2f years.\n",
453
colWidth, "project-age:", n, n/365.2425);
454
if( !brief ){
455
p = db_get("project-code", 0);
456
if( p ){
457
fossil_print("%*s%s\n", colWidth, "project-id:", p);
458
}
459
}
460
#if 0
461
/* Server-id is not useful information any more */
462
fossil_print("%*s%s\n", colWidth, "server-id:", db_get("server-code", 0));
463
#endif
464
fossil_print("%*s%s\n", colWidth, "schema-version:", g.zAuxSchema);
465
if( !omitVers ){
466
fossil_print("%*s%s %s [%s] (%s)\n",
467
colWidth, "fossil-version:",
468
MANIFEST_DATE, MANIFEST_VERSION, RELEASE_VERSION,
469
COMPILER_NAME);
470
fossil_print("%*s%.19s [%.10s] (%s)\n",
471
colWidth, "sqlite-version:",
472
sqlite3_sourceid(), &sqlite3_sourceid()[20],
473
sqlite3_libversion());
474
}
475
fossil_print("%*s%,d pages, %d bytes/pg, %,d free pages, "
476
"%s, %s mode\n",
477
colWidth, "database-stats:",
478
db_int(0, "PRAGMA repository.page_count"),
479
db_int(0, "PRAGMA repository.page_size"),
480
db_int(0, "PRAGMA repository.freelist_count"),
481
db_text(0, "PRAGMA repository.encoding"),
482
db_text(0, "PRAGMA repository.journal_mode"));
483
if( dbCheck ){
484
if( dbCheck<2 ){
485
char *zRes = db_text(0, "PRAGMA repository.quick_check(1)");
486
fossil_print("%*s%s\n", colWidth, "database-check:", zRes);
487
}else{
488
char *newArgv[3];
489
newArgv[0] = g.argv[0];
490
newArgv[1] = "test-integrity";
491
newArgv[2] = 0;
492
g.argv = newArgv;
493
g.argc = 2;
494
fossil_print("Full repository verification follows:\n");
495
test_integrity();
496
}
497
}
498
}
499
500
/*
501
** Return a string which is the public URL used to access this repository.
502
** Or return a NULL pointer if this repository does not have a public
503
** access URL.
504
**
505
** Algorithm:
506
**
507
** The public URL is given by the email-url property. But it is only
508
** returned if there have been one or more accesses (as recorded by
509
** "baseurl:URL" entries in the CONFIG table).
510
*/
511
const char *public_url(void){
512
const char *zUrl = db_get("email-url", 0);
513
if( zUrl==0 ) return 0;
514
if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", zUrl) ){
515
return 0;
516
}
517
return zUrl;
518
}
519
520
521
/*
522
** WEBPAGE: urllist
523
**
524
** Show ways in which this repository has been accessed
525
*/
526
void urllist_page(void){
527
Stmt q;
528
int cnt;
529
int total = 0;
530
int showAll = P("all")!=0;
531
int nOmitted;
532
sqlite3_int64 iNow;
533
char *zPriorRepo = 0;
534
535
login_check_credentials();
536
if( !g.perm.Admin ){ login_needed(0); return; }
537
538
style_set_current_feature("stat");
539
style_header("URLs and Checkouts");
540
style_adunit_config(ADUNIT_RIGHT_OK);
541
style_submenu_element("Stat", "stat");
542
style_submenu_element("Schema", "repo_schema");
543
iNow = db_int64(0, "SELECT strftime('%%s','now')");
544
545
546
db_prepare(&q, "SELECT substr(name,9), datetime(mtime,'unixepoch'), mtime"
547
" FROM config WHERE name GLOB 'baseurl:*' ORDER BY 3 DESC");
548
cnt = 0;
549
nOmitted = 0;
550
while( db_step(&q)==SQLITE_ROW ){
551
if( cnt==0 ){
552
@ <div class="section">URLs used to access this repository</div>
553
@ <table border="0" width='100%%'>
554
}
555
if( !showAll && db_column_int64(&q,2)<(iNow - 3600*24*30) && cnt>8 ){
556
nOmitted++;
557
}else{
558
@ <tr><td width='100%%'>%h(db_column_text(&q,0))</td>
559
@ <td><nobr>%h(db_column_text(&q,1))</nobr></td></tr>
560
}
561
cnt++;
562
}
563
db_finalize(&q);
564
565
if( nOmitted ){
566
@ <tr><td><a href="urllist?all"><i>Show %d(nOmitted) more...</i></a>
567
}
568
if( cnt ){
569
@ </table>
570
total += cnt;
571
}
572
if( P("urlonly") ){
573
style_finish_page();
574
return;
575
}
576
577
578
db_prepare(&q, "SELECT substr(name,7), datetime(mtime,'unixepoch')"
579
" FROM config WHERE name GLOB 'ckout:*' ORDER BY 2 DESC");
580
cnt = 0;
581
while( db_step(&q)==SQLITE_ROW ){
582
const char *zPath = db_column_text(&q,0);
583
if( vfile_top_of_checkout(zPath) ){
584
if( cnt==0 ){
585
@ <div class="section">Checkouts</div>
586
@ <table border="0" width='100%%'>
587
}
588
@ <tr><td width='100%%'>%h(zPath)</td>
589
@ <td><nobr>%h(db_column_text(&q,1))</nobr></td></tr>
590
cnt++;
591
}
592
}
593
db_finalize(&q);
594
if( cnt ){
595
@ </table>
596
total += cnt;
597
}
598
599
cnt = 0;
600
db_prepare(&q,
601
"SELECT substr(name,10), datetime(mtime,'unixepoch')"
602
" FROM config WHERE name GLOB 'syncwith:*'"
603
"UNION ALL "
604
"SELECT substr(name,10), datetime(mtime,'unixepoch')"
605
" FROM config WHERE name GLOB 'syncfrom:*'"
606
"UNION ALL "
607
"SELECT substr(name,9), datetime(mtime,'unixepoch')"
608
" FROM config WHERE name GLOB 'gitpush:*'"
609
"GROUP BY 1 ORDER BY 2 DESC"
610
);
611
while( db_step(&q)==SQLITE_ROW ){
612
const char *zURL = db_column_text(&q,0);
613
UrlData x;
614
if( cnt==0 ){
615
@ <div class="section">Recently synced with these URLs</div>
616
@ <table border='0' width='100%%'>
617
}
618
memset(&x, 0, sizeof(x));
619
url_parse_local(zURL, URL_OMIT_USER, &x);
620
@ <tr><td width='100%%'><a href='%h(x.canonical)'>%h(x.canonical)</a>
621
@ <td><nobr>%h(db_column_text(&q,1))</nobr></td></tr>
622
cnt++;
623
url_unparse(&x);
624
}
625
db_finalize(&q);
626
if( cnt ){
627
@ </table>
628
total += cnt;
629
}
630
631
cnt = 0;
632
db_prepare(&q,
633
"SELECT"
634
" substr(name,6),"
635
" datetime(mtime,'unixepoch'),"
636
" value->>'type',"
637
" value->>'src'\n"
638
"FROM config\n"
639
"WHERE name GLOB 'link:*'\n"
640
"AND json_valid(value)\n"
641
"ORDER BY 4, 2 DESC"
642
);
643
while( db_step(&q)==SQLITE_ROW ){
644
const char *zUrl = db_column_text(&q, 0);
645
const char *zType = db_column_text(&q, 2);
646
const char *zSrc = db_column_text(&q, 3);
647
if( zUrl==0 || zSrc==0 ) continue;
648
if( cnt++==0 ){
649
@ <div class="section">Links from other repositories</div>
650
@ <table border='0' width='100%%'>
651
}
652
if( zPriorRepo==0 || strcmp(zPriorRepo,zSrc)!=0 ){
653
fossil_free(zPriorRepo);
654
zPriorRepo = fossil_strdup(zSrc);
655
@ <tr><td colspan="4">\
656
@ From <a href='%T(zSrc)'>%h(zSrc)</a>...</td></tr>
657
}
658
@ <tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
659
@ <td width='90%%'><a href='%h(zUrl)'>%h(zUrl)</a></td>
660
if( zType ){
661
@ <td>&nbsp;(%h(zType))&nbsp;</td>
662
}else{
663
@ <td>&nbsp;</td>
664
}
665
@ <td><nobr>%h(db_column_text(&q,1))</nobr></td></tr>
666
}
667
db_finalize(&q);
668
fossil_free(zPriorRepo);
669
if( cnt ){
670
@ </table>
671
total += cnt;
672
}
673
674
cnt = 0;
675
db_prepare(&q,
676
"SELECT"
677
" value,"
678
" url_nouser(value),"
679
" substr(name,10),"
680
" datetime(mtime,'unixepoch')"
681
"FROM config\n"
682
"WHERE name GLOB 'sync-url:*'\n"
683
"ORDER BY 2"
684
);
685
while( db_step(&q)==SQLITE_ROW ){
686
const char *zUrl = db_column_text(&q, 0);
687
const char *zLink = db_column_text(&q, 1);
688
const char *zName = db_column_text(&q, 2);
689
if( cnt++==0 ){
690
@ <div class="section">Defined sync targets</div>
691
@ <table border='0' width='100%%'>
692
}
693
@ <tr><td>%h(zName)</td><td>&nbsp;&nbsp;</td>
694
@ <td width='95%%'><a href='%h(zLink)'>%h(zUrl)</a></td>
695
@ <td><nobr>%h(db_column_text(&q,3))</nobr></td></tr>
696
}
697
db_finalize(&q);
698
if( cnt ){
699
@ </table>
700
total += cnt;
701
}
702
703
704
if( total==0 ){
705
@ <p>No record of any URLs or checkouts</p>
706
}
707
style_finish_page();
708
}
709
710
/*
711
** WEBPAGE: repo_schema
712
**
713
** Show the repository schema
714
*/
715
void repo_schema_page(void){
716
Stmt q;
717
Blob sql;
718
const char *zArg = P("n");
719
login_check_credentials();
720
if( !g.perm.Admin ){ login_needed(0); return; }
721
722
if( zArg!=0
723
&& db_table_exists("repository",zArg)
724
&& cgi_csrf_safe(1)
725
){
726
if( P("analyze")!=0 ){
727
db_multi_exec("ANALYZE \"%w\"", zArg);
728
}else if( P("analyze200")!=0 ){
729
db_multi_exec("PRAGMA analysis_limit=200; ANALYZE \"%w\"", zArg);
730
}else if( P("deanalyze")!=0 ){
731
db_unprotect(PROTECT_ALL);
732
db_multi_exec("DELETE FROM repository.sqlite_stat1"
733
" WHERE tbl LIKE %Q", zArg);
734
db_protect_pop();
735
}
736
}
737
738
style_set_current_feature("stat");
739
style_header("Repository Schema");
740
style_adunit_config(ADUNIT_RIGHT_OK);
741
style_submenu_element("Stat", "stat");
742
style_submenu_element("URLs", "urllist");
743
if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
744
style_submenu_element("Table Sizes", "repo-tabsize");
745
}
746
blob_init(&sql,
747
"SELECT sql FROM repository.sqlite_schema WHERE sql IS NOT NULL", -1);
748
if( zArg ){
749
style_submenu_element("All", "repo_schema");
750
blob_appendf(&sql, " AND (tbl_name=%Q OR name=%Q)", zArg, zArg);
751
}
752
blob_appendf(&sql, " ORDER BY tbl_name, type<>'table', name");
753
db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/);
754
blob_reset(&sql);
755
@ <pre>
756
while( db_step(&q)==SQLITE_ROW ){
757
@ %h(db_column_text(&q, 0));
758
}
759
@ </pre>
760
db_finalize(&q);
761
if( db_table_exists("repository","sqlite_stat1") ){
762
if( zArg ){
763
db_prepare(&q,
764
"SELECT tbl, idx, stat FROM repository.sqlite_stat1"
765
" WHERE tbl LIKE %Q OR idx LIKE %Q"
766
" ORDER BY tbl, idx", zArg, zArg);
767
768
@ <hr>
769
@ <pre>
770
while( db_step(&q)==SQLITE_ROW ){
771
const char *zTab = db_column_text(&q,0);
772
const char *zIdx = db_column_text(&q,1);
773
const char *zStat = db_column_text(&q,2);
774
@ INSERT INTO sqlite_stat1 VALUES('%h(zTab)','%h(zIdx)','%h(zStat)');
775
}
776
@ </pre>
777
db_finalize(&q);
778
}else{
779
style_submenu_element("Stat1","repo_stat1");
780
}
781
}
782
@ <hr><form method="POST">
783
@ <input type="submit" name="analyze" value="Run ANALYZE"><br />
784
@ <input type="submit" name="analyze200"\
785
@ value="Run ANALYZE with limit=200"><br />
786
@ <input type="submit" name="deanalyze" value="De-ANALYZE">
787
@ </form>
788
789
style_finish_page();
790
}
791
792
/*
793
** WEBPAGE: repo_stat1
794
**
795
** Show the sqlite_stat1 table for the repository schema
796
*/
797
void repo_stat1_page(void){
798
int bTabular;
799
login_check_credentials();
800
if( !g.perm.Admin ){ login_needed(0); return; }
801
bTabular = PB("tabular");
802
803
if( P("analyze")!=0 && cgi_csrf_safe(1) ){
804
db_multi_exec("ANALYZE");
805
}else if( P("analyze200")!=0 && cgi_csrf_safe(1) ){
806
db_multi_exec("PRAGMA analysis_limit=200; ANALYZE;");
807
}else if( P("deanalyze")!=0 && cgi_csrf_safe(1) ){
808
db_unprotect(PROTECT_ALL);
809
db_multi_exec("DELETE FROM repository.sqlite_stat1;");
810
db_protect_pop();
811
}
812
style_set_current_feature("stat");
813
style_header("Repository STAT1 Table");
814
style_adunit_config(ADUNIT_RIGHT_OK);
815
style_submenu_element("Stat", "stat");
816
style_submenu_element("Schema", "repo_schema");
817
style_submenu_checkbox("tabular", "Tabular", 0, 0);
818
if( db_table_exists("repository","sqlite_stat1") ){
819
Stmt q;
820
db_prepare(&q,
821
"SELECT tbl, idx, stat FROM repository.sqlite_stat1"
822
" ORDER BY tbl, idx");
823
if( bTabular ){
824
@ <table border="1" cellpadding="0" cellspacing="0">
825
@ <tr><th>Table<th>Index<th>Stat
826
}else{
827
@ <pre>
828
}
829
while( db_step(&q)==SQLITE_ROW ){
830
const char *zTab = db_column_text(&q,0);
831
const char *zIdx = db_column_text(&q,1);
832
const char *zStat = db_column_text(&q,2);
833
char *zUrl = href("%R/repo_schema?n=%t",zTab);
834
if( bTabular ){
835
@ <tr><td>%z(zUrl)%h(zTab)</a><td>%h(zIdx)<td>%h(zStat)
836
}else{
837
@ INSERT INTO sqlite_stat1 \
838
@ VALUES('%z(zUrl)%h(zTab)</a>','%h(zIdx)','%h(zStat)');
839
}
840
}
841
if( bTabular ){
842
@ </table>
843
}else{
844
@ </pre>
845
}
846
db_finalize(&q);
847
}
848
@ <p><form method="POST">
849
if( bTabular ){
850
@ <input type="hidden" name="tabular" value="1">
851
}
852
@ <input type="submit" name="analyze" value="Run ANALYZE"><br />
853
@ <input type="submit" name="analyze200"\
854
@ value="Run ANALYZE with limit=200"><br>
855
@ <input type="submit" name="deanalyze"\
856
@ value="De-ANALYZE">
857
@ </form>
858
style_finish_page();
859
}
860
861
/*
862
** WEBPAGE: repo-tabsize
863
**
864
** Show relative sizes of tables in the repository database.
865
*/
866
void repo_tabsize_page(void){
867
int nPageFree;
868
sqlite3_int64 fsize;
869
char zBuf[100];
870
871
login_check_credentials();
872
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
873
cgi_check_for_malice();
874
style_set_current_feature("stat");
875
style_header("Repository Table Sizes");
876
style_adunit_config(ADUNIT_RIGHT_OK);
877
style_submenu_element("Stat", "stat");
878
if( g.perm.Admin ){
879
style_submenu_element("Schema", "repo_schema");
880
}
881
db_multi_exec(
882
"CREATE TEMP TABLE trans(name TEXT PRIMARY KEY,tabname TEXT)WITHOUT ROWID;"
883
"INSERT INTO trans(name,tabname)"
884
" SELECT name, tbl_name FROM repository.sqlite_schema;"
885
"CREATE TEMP TABLE piechart(amt REAL, label TEXT);"
886
"INSERT INTO piechart(amt,label)"
887
" SELECT sum(pageno),"
888
" coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)"
889
" FROM dbstat('repository',TRUE)"
890
" GROUP BY 2 ORDER BY 2;"
891
);
892
nPageFree = db_int(0, "PRAGMA repository.freelist_count");
893
if( nPageFree>0 ){
894
db_multi_exec(
895
"INSERT INTO piechart(amt,label) VALUES(%d,'freelist')",
896
nPageFree
897
);
898
}
899
fsize = file_size(g.zRepositoryName, ExtFILE);
900
approxSizeName(sizeof(zBuf), zBuf, fsize);
901
@ <h2>Repository Size: %s(zBuf)</h2>
902
@ <center><svg width='800' height='500'>
903
piechart_render(800,500,PIE_OTHER|PIE_PERCENT);
904
@ </svg></center>
905
906
if( g.localOpen ){
907
db_multi_exec(
908
"DELETE FROM trans;"
909
"INSERT INTO trans(name,tabname)"
910
" SELECT name, tbl_name FROM localdb.sqlite_schema;"
911
"DELETE FROM piechart;"
912
"INSERT INTO piechart(amt,label)"
913
" SELECT sum(pageno), "
914
" coalesce((SELECT tabname FROM trans WHERE trans.name=dbstat.name),name)"
915
" FROM dbstat('localdb',TRUE)"
916
" GROUP BY 2 ORDER BY 2;"
917
);
918
nPageFree = db_int(0, "PRAGMA localdb.freelist_count");
919
if( nPageFree>0 ){
920
db_multi_exec(
921
"INSERT INTO piechart(amt,label) VALUES(%d,'freelist')",
922
nPageFree
923
);
924
}
925
fsize = file_size(g.zLocalDbName, ExtFILE);
926
approxSizeName(sizeof(zBuf), zBuf, fsize);
927
@ <h2>%h(file_tail(g.zLocalDbName)) Size: %s(zBuf)</h2>
928
@ <center><svg width='800' height='500'>
929
piechart_render(800,500,PIE_OTHER|PIE_PERCENT);
930
@ </svg></center>
931
}
932
style_finish_page();
933
}
934
935
/*
936
** Gather statistics on artifact types, counts, and sizes.
937
**
938
** Only populate the artstat.atype field if the bWithTypes parameter is true.
939
*/
940
void gather_artifact_stats(int bWithTypes){
941
static const char zSql[] =
942
@ CREATE TEMP TABLE artstat(
943
@ id INTEGER PRIMARY KEY, -- Corresponds to BLOB.RID
944
@ atype TEXT, -- 'data', 'manifest', 'tag', 'wiki', etc.
945
@ isDelta BOOLEAN, -- true if stored as a delta
946
@ szExp, -- expanded, uncompressed size
947
@ szCmpr -- size as stored on disk
948
@ );
949
@ INSERT INTO artstat(id,atype,isDelta,szExp,szCmpr)
950
@ SELECT blob.rid, NULL,
951
@ delta.rid IS NOT NULL,
952
@ size, octet_length(content)
953
@ FROM blob LEFT JOIN delta ON blob.rid=delta.rid
954
@ WHERE content IS NOT NULL;
955
;
956
static const char zSql2[] =
957
@ UPDATE artstat SET atype='file'
958
@ WHERE +id IN (SELECT fid FROM mlink);
959
@ UPDATE artstat SET atype='manifest'
960
@ WHERE id IN (SELECT objid FROM event WHERE type='ci') AND atype IS NULL;
961
@ UPDATE artstat SET atype='forum'
962
@ WHERE id IN (SELECT objid FROM event WHERE type='f') AND atype IS NULL;
963
@ UPDATE artstat SET atype='cluster'
964
@ WHERE atype IS NULL
965
@ AND id IN (SELECT rid FROM tagxref
966
@ WHERE tagid=(SELECT tagid FROM tag
967
@ WHERE tagname='cluster'));
968
@ UPDATE artstat SET atype='ticket'
969
@ WHERE atype IS NULL
970
@ AND id IN (SELECT rid FROM tagxref
971
@ WHERE tagid IN (SELECT tagid FROM tag
972
@ WHERE tagname GLOB 'tkt-*'));
973
@ UPDATE artstat SET atype='wiki'
974
@ WHERE atype IS NULL
975
@ AND id IN (SELECT rid FROM tagxref
976
@ WHERE tagid IN (SELECT tagid FROM tag
977
@ WHERE tagname GLOB 'wiki-*'));
978
@ UPDATE artstat SET atype='technote'
979
@ WHERE atype IS NULL
980
@ AND id IN (SELECT rid FROM tagxref
981
@ WHERE tagid IN (SELECT tagid FROM tag
982
@ WHERE tagname GLOB 'event-*'));
983
@ UPDATE artstat SET atype='attachment'
984
@ WHERE atype IS NULL
985
@ AND id IN (SELECT attachid FROM attachment UNION
986
@ SELECT blob.rid FROM attachment JOIN blob ON uuid=src);
987
@ UPDATE artstat SET atype='tag'
988
@ WHERE atype IS NULL
989
@ AND id IN (SELECT srcid FROM tagxref);
990
@ UPDATE artstat SET atype='tag'
991
@ WHERE atype IS NULL
992
@ AND id IN (SELECT objid FROM event WHERE type='g');
993
@ UPDATE artstat SET atype='unused' WHERE atype IS NULL;
994
;
995
db_multi_exec("%s", zSql/*safe-for-%s*/);
996
if( bWithTypes ){
997
db_multi_exec("%s", zSql2/*safe-for-%s*/);
998
}
999
}
1000
1001
/*
1002
** Output text "the largest N artifacts". Make this text a hyperlink
1003
** to bigbloblist if N is not too big.
1004
*/
1005
static void largest_n_artifacts(int N){
1006
if( N>250 ){
1007
@ (the largest %,d(N) artifacts)
1008
}else{
1009
@ (the <a href='%R/bigbloblist?n=%d(N)'>largest %d(N) artifacts</a>)
1010
}
1011
}
1012
1013
/*
1014
** WEBPAGE: artifact_stats
1015
**
1016
** Show information about the sizes of artifacts in this repository
1017
*/
1018
void artifact_stats_page(void){
1019
Stmt q;
1020
int nTotal = 0; /* Total number of artifacts */
1021
int nDelta = 0; /* Total number of deltas */
1022
int nFull = 0; /* Total number of full-texts */
1023
double avgCmpr = 0.0; /* Average size of an artifact after compression */
1024
double avgExp = 0.0; /* Average size of an uncompressed artifact */
1025
int mxCmpr = 0; /* Maximum compressed artifact size */
1026
int mxExp = 0; /* Maximum uncompressed artifact size */
1027
sqlite3_int64 sumCmpr = 0; /* Total size of all compressed artifacts */
1028
sqlite3_int64 sumExp = 0; /* Total size of all expanded artifacts */
1029
sqlite3_int64 sz1pct = 0; /* Space used by largest 1% */
1030
sqlite3_int64 sz10pct = 0; /* Space used by largest 10% */
1031
sqlite3_int64 sz25pct = 0; /* Space used by largest 25% */
1032
sqlite3_int64 sz50pct = 0; /* Space used by largest 50% */
1033
int n50pct = 0; /* Artifacts using the first 50% of space */
1034
int n; /* Loop counter */
1035
int medCmpr = 0; /* Median compressed artifact size */
1036
int medExp = 0; /* Median expanded artifact size */
1037
int med;
1038
double r;
1039
1040
login_check_credentials();
1041
1042
/* These stats are expensive to compute. To disable them for
1043
** user without check-in privileges, to prevent excessive usage by
1044
** robots and random passers-by on the internet
1045
*/
1046
if( !g.perm.Write && !db_get_boolean("artifact_stats_enable",0) ){
1047
login_needed(g.anon.Write);
1048
return;
1049
}
1050
cgi_check_for_malice();
1051
fossil_nice_default();
1052
1053
style_set_current_feature("stat");
1054
style_header("Artifact Statistics");
1055
style_submenu_element("Repository Stats", "stat");
1056
style_submenu_element("Artifact List", "bloblist");
1057
gather_artifact_stats(1);
1058
1059
db_prepare(&q,
1060
"SELECT count(*), sum(isDelta), max(szCmpr),"
1061
" max(szExp), sum(szCmpr), sum(szExp)"
1062
" FROM artstat"
1063
);
1064
db_step(&q);
1065
nTotal = db_column_int(&q,0);
1066
nDelta = db_column_int(&q,1);
1067
nFull = nTotal - nDelta;
1068
mxCmpr = db_column_int(&q, 2);
1069
mxExp = db_column_int(&q, 3);
1070
sumCmpr = db_column_int64(&q, 4);
1071
sumExp = db_column_int64(&q, 5);
1072
db_finalize(&q);
1073
if( nTotal==0 ){
1074
@ No artifacts in this repository!
1075
style_finish_page();
1076
return;
1077
}
1078
avgCmpr = (double)sumCmpr/nTotal;
1079
avgExp = (double)sumExp/nTotal;
1080
1081
db_prepare(&q, "SELECT szCmpr FROM artstat ORDER BY 1 DESC");
1082
r = 0;
1083
n = 0;
1084
while( db_step(&q)==SQLITE_ROW ){
1085
r += db_column_int(&q, 0);
1086
if( n50pct==0 && r>=sumCmpr/2 ) n50pct = n;
1087
if( n==(nTotal+99)/100 ) sz1pct = (sqlite3_int64)r;
1088
if( n==(nTotal+9)/10 ) sz10pct = (sqlite3_int64)r;
1089
if( n==(nTotal+4)/5 ) sz25pct = (sqlite3_int64)r;
1090
if( n==(nTotal+1)/2 ){
1091
sz50pct = (sqlite3_int64)r;
1092
medCmpr = db_column_int(&q,0);
1093
}
1094
n++;
1095
}
1096
db_finalize(&q);
1097
1098
@ <h1>Overall Artifact Size Statistics:</h1>
1099
@ <table class="label-value">
1100
@ <tr><th>Number of artifacts:</th><td>%,d(nTotal)</td></tr>
1101
@ <tr><th>Number of deltas:</th>\
1102
@ <td>%,d(nDelta) (%d(nDelta*100/nTotal)%%)</td></tr>
1103
@ <tr><th>Number of full-text:</th><td>%,d(nFull) \
1104
@ (%d(nFull*100/nTotal)%%)</td></tr>
1105
medExp = db_int(0, "SELECT szExp FROM artstat ORDER BY szExp"
1106
" LIMIT 1 OFFSET %d", nTotal/2);
1107
@ <tr><th>Uncompressed artifact sizes:</th>\
1108
@ <td>largest: %,d(mxExp), average: %,d((int)avgExp), median: %,d(medExp)</td>
1109
@ <tr><th>Compressed artifact sizes:</th>\
1110
@ <td>largest: %,d(mxCmpr), average: %,d((int)avgCmpr), \
1111
@ median: %,d(medCmpr)</td>
1112
1113
db_prepare(&q,
1114
"SELECT avg(szCmpr), max(szCmpr) FROM artstat WHERE isDelta"
1115
);
1116
if( db_step(&q)==SQLITE_ROW ){
1117
int mxDelta = db_column_int(&q,1);
1118
double avgDelta = db_column_double(&q,0);
1119
med = db_int(0, "SELECT szCmpr FROM artstat WHERE isDelta ORDER BY szCmpr"
1120
" LIMIT 1 OFFSET %d", nDelta/2);
1121
@ <tr><th>Delta artifact sizes:</th>\
1122
@ <td>largest: %,d(mxDelta), average: %,d((int)avgDelta), \
1123
@ median: %,d(med)</td>
1124
}
1125
db_finalize(&q);
1126
r = db_double(0.0, "SELECT avg(szCmpr) FROM artstat WHERE NOT isDelta;");
1127
med = db_int(0, "SELECT szCmpr FROM artstat WHERE NOT isDelta ORDER BY szCmpr"
1128
" LIMIT 1 OFFSET %d", nFull/2);
1129
@ <tr><th>Full-text artifact sizes:</th>
1130
@ <td>largest: %,d(mxCmpr), average: %,d((int)r), median: %,d(med)</td>
1131
@ </table>
1132
1133
@ <h1>Artifact Size Distribution Facts:</h1>
1134
@ <ol>
1135
@ <li><p>The largest %.2f(n50pct*100.0/nTotal)%% of artifacts
1136
largest_n_artifacts(n50pct);
1137
@ use 50%% of the total artifact space.
1138
@ <li><p>The largest 1%% of artifacts
1139
largest_n_artifacts((nTotal+99)/100);
1140
@ use %lld(sz1pct*100/sumCmpr)%% of the total artifact space.
1141
@ <li><p>The largest 10%% of artifacts
1142
largest_n_artifacts((nTotal+9)/10);
1143
@ use %lld(sz10pct*100/sumCmpr)%% of the total artifact space.
1144
@ <li><p>The largest 25%% of artifacts
1145
largest_n_artifacts((nTotal+4)/5);
1146
@ use %lld(sz25pct*100/sumCmpr)%% of the total artifact space.
1147
@ <li><p>The largest 50%% of artifacts
1148
largest_n_artifacts((nTotal+1)/2);
1149
@ use %lld(sz50pct*100/sumCmpr)%% of the total artifact space.
1150
@ </ol>
1151
1152
@ <h1>Artifact Sizes By Type:</h1>
1153
db_prepare(&q,
1154
"SELECT atype, count(*), sum(isDelta), sum(szCmpr), sum(szExp)"
1155
" FROM artstat GROUP BY 1"
1156
" UNION ALL "
1157
"SELECT 'ALL', count(*), sum(isDelta), sum(szCmpr), sum(szExp)"
1158
" FROM artstat"
1159
" ORDER BY 4;"
1160
);
1161
@ <table class='sortable' border='1' \
1162
@ data-column-types='tkkkkk' data-init-sort='5'>
1163
@ <thead><tr>
1164
@ <th>Artifact Type</th>
1165
@ <th>Count</th>
1166
@ <th>Full-Text</th>
1167
@ <th>Delta</th>
1168
@ <th>Compressed Size</th>
1169
@ <th>Uncompressed Size</th>
1170
@ </tr></thead><tbody>
1171
while( db_step(&q)==SQLITE_ROW ){
1172
const char *zType = db_column_text(&q, 0);
1173
int nTotal = db_column_int(&q, 1);
1174
int nDelta = db_column_int(&q, 2);
1175
int nFull = nTotal - nDelta;
1176
sqlite3_int64 szCmpr = db_column_int64(&q, 3);
1177
sqlite3_int64 szExp = db_column_int64(&q, 4);
1178
@ <tr><td>%h(zType)</td>
1179
@ <td data-sortkey='%08x(nTotal)' align='right'>%,d(nTotal)</td>
1180
@ <td data-sortkey='%08x(nFull)' align='right'>%,d(nFull)</td>
1181
@ <td data-sortkey='%08x(nDelta)' align='right'>%,d(nDelta)</td>
1182
@ <td data-sortkey='%016llx(szCmpr)' align='right'>%,lld(szCmpr)</td>
1183
@ <td data-sortkey='%016llx(szExp)' align='right'>%,lld(szExp)</td>
1184
}
1185
@ </tbody></table>
1186
db_finalize(&q);
1187
1188
if( db_exists("SELECT 1 FROM artstat WHERE atype='unused'") ){
1189
@ <h1>Unused Artifacts:</h1>
1190
db_prepare(&q,
1191
"SELECT artstat.id, blob.uuid, user.login,"
1192
" datetime(rcvfrom.mtime), rcvfrom.rcvid"
1193
" FROM artstat JOIN blob ON artstat.id=blob.rid"
1194
" LEFT JOIN rcvfrom USING(rcvid)"
1195
" LEFT JOIN user USING(uid)"
1196
" WHERE atype='unused'"
1197
);
1198
@ <table class='sortable' border='1' \
1199
@ data-column-types='ntttt' data-init-sort='0'>
1200
@ <thead><tr>
1201
@ <th>RecordID</th>
1202
@ <th>Hash</th>
1203
@ <th>User</th>
1204
@ <th>Date</th>
1205
@ <th>RcvID</th>
1206
@ </tr></thead><tbody>
1207
while( db_step(&q)==SQLITE_ROW ){
1208
int rid = db_column_int(&q, 0);
1209
const char *zHash = db_column_text(&q, 1);
1210
const char *zUser = db_column_text(&q, 2);
1211
const char *zDate = db_column_text(&q, 3);
1212
int iRcvid = db_column_int(&q, 4);
1213
@ <tr><td>%d(rid)</td>
1214
@ <td>%z(href("%R/info/%!S",zHash))%S(zHash)</a></td>
1215
@ <td>%h(zUser)</td>
1216
@ <td>%h(zDate)</td>
1217
@ <td>%z(href("%R/rcvfrom?rcvid=%d",iRcvid))%d(iRcvid)</a></td></tr>
1218
}
1219
@ </tbody></table></div>
1220
db_finalize(&q);
1221
}
1222
style_table_sorter();
1223
style_finish_page();
1224
}
1225

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button