/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code */ /** This app displays various stats for a fossil repo db, analog to (fossil dbstat). */ #include "libfossil.h" static int dump_repo_stats(bool brief, int check){ int rc = 0; fsl_cx * const f = fcli_cx(); fsl_db * const db = fsl_cx_db_repo(f); char * z = NULL; //char const * zC = NULL; int const colWidth = -19; fsl_int_t repoSize; int32_t n, m; rc = fsl_cx_transaction_begin(f); if(rc) return rc; if( (z = fsl_config_get_text(f, FSL_CONFDB_REPO, "project-name", NULL)) || (z = fsl_config_get_text(f, FSL_CONFDB_REPO, "short-project-name", NULL))){ f_out("%*s%z\n", colWidth, "project-name:", z); z = NULL; } repoSize = fsl_file_size(fsl_cx_db_file_repo(f, NULL)); f_out("%*s%,"FSL_INT_T_PFMT" bytes\n", colWidth, "repository-size:", repoSize); if(brief) goto end_nonbrief; int32_t a, b, szAvg, szMax; int64_t t; fsl_stmt q = fsl_stmt_empty; rc = fsl_cx_prepare(f, &q, "SELECT total(size), avg(size), max(size)" " FROM blob WHERE size>0"); if(rc) goto end; else{ fsl_stmt_step(&q); t = fsl_stmt_g_int64(&q, 0); szAvg = fsl_stmt_g_int32(&q, 1); szMax = fsl_stmt_g_int32(&q, 2); fsl_stmt_finalize(&q); } f_out("%*s%,"PRIi32" average, %,"PRIi32" max, %,"PRIi64" total\n", colWidth, "artifact-sizes:", szAvg, szMax, t); if(t/repoSize < 5){ b = 10; repoSize /= 10; }else{ b = 1; } a = t / repoSize; f_out("%*s%"PRIi32":%"PRIi32"\n", colWidth, "compression-ratio:", a, b); n = fsl_db_g_int32(db, 0, "SELECT COUNT(*) FROM event e WHERE e.type='ci'"); f_out("%*s%,d\n", colWidth, "check-ins:", n); n = fsl_db_g_int32(db, 0, "SELECT count(*) FROM filename /*scan*/"); f_out("%*s%,"PRIi32" across all branches\n", colWidth, "files:", n); n = fsl_db_g_int32(db, 0, "SELECT count(*) FROM (" "SELECT DISTINCT substr(tagname,6) " "FROM tag JOIN tagxref USING('tagid')" " WHERE tagname GLOB 'wiki-*'" " AND TYPEOF(tagxref.value+0)='integer'" ")"); m = fsl_db_g_int32(db, 0, "SELECT COUNT(*) FROM event WHERE type='w'"); f_out("%*s%,"PRIi32" (%,"PRIi32" changes)\n", colWidth, "wiki-pages:", n, m); n = fsl_db_g_int32(db, 0, "SELECT count(*) FROM tag /*scan*/" " WHERE tagname GLOB 'tkt-*'"); m = fsl_db_g_int32(db, 0, "SELECT COUNT(*) FROM event WHERE type='t'"); f_out("%*s%,"PRIi32" (%,"PRIi32" changes)\n", colWidth, "tickets:", n, m); n = fsl_db_g_int32(db, 0, "SELECT COUNT(*) FROM event WHERE type='e'"); f_out("%*s%,d\n", colWidth, "tech-notes:", n); if(fsl_db_table_exists(db, FSL_DBROLE_REPO, "forumpost")){ n = fsl_db_g_int32(db, 0, "SELECT count(*) FROM forumpost/*scan*/"); if( n>0 ){ m = fsl_db_g_int32(db, 0, "SELECT count(*) FROM forumpost" " WHERE froot=fpid"); f_out("%*s%,"PRIi32" (on %,"PRIi32" threads)\n", colWidth, "forum-posts:", n, m); } } n = fsl_db_g_int32(db, 0, "SELECT COUNT(*) FROM event WHERE type='g'"); f_out("%*s%,"PRIi32"\n", colWidth, "tag-changes:", n); z = fsl_db_g_text(db, NULL, "SELECT datetime(mtime) || ' UTC - about ' ||" " CAST(julianday('now') - mtime AS INTEGER)" " || ' days ago' FROM event " " ORDER BY mtime DESC LIMIT 1"); f_out("%*s%z\n", colWidth, "latest-change:", z); z = NULL; end_nonbrief: n = fsl_db_g_int32(db, 0, "SELECT julianday('now') - " "(SELECT min(mtime) FROM event) + 0.99"); f_out("%*s%,"PRIi32" days, approximately %.2f years\n", colWidth, "project-age:", n, n/365.2425); if(!brief){ z = fsl_config_get_text(f, FSL_CONFDB_REPO, "project-code", NULL); if( z ){ f_out("%*s%z\n", colWidth, "project-id:", z); z = NULL; } } z = fsl_config_get_text(f, FSL_CONFDB_REPO, "aux-schema", NULL); f_out("%*s%s\n", colWidth, "schema-version:", z ? z : "???"); fsl_free(z); z = NULL; f_out("%*s[%.16s] %.19s UTC (%s)\n", colWidth, "libfossil-version:", FSL_LIB_VERSION_HASH, FSL_LIB_VERSION_TIMESTAMP, fsl_library_version()); f_out("%*s[%.16s] %.19s UTC (%s)\n", colWidth, "sqlite-version:", &sqlite3_sourceid()[20], sqlite3_sourceid(), sqlite3_libversion()); f_out("%*s%,"PRIi32" pages, %,"PRIi32" bytes/pg, %,"PRIi32" free page(s), " "%z, %z mode\n", colWidth, "database-stats:", fsl_db_g_int32(db, 0, "PRAGMA repo.page_count"), fsl_db_g_int32(db, 0, "PRAGMA repo.page_size"), fsl_db_g_int32(db, 0, "PRAGMA repo.freelist_count"), fsl_db_g_text(db, NULL, "PRAGMA repo.encoding"), fsl_db_g_text(db, NULL, "PRAGMA repo.journal_mode")); if(check>0){ z = fsl_db_g_text(db, NULL, "PRAGMA repo.quick_check(1)"); f_out("%*s%z\n", colWidth, "database-check:", z); z = NULL; } /* Possible TODO from fossil dbstat: test-integrity (requires feature additional porting) */ end: fsl_cx_transaction_end(f, true); return rc; } int main(int argc, const char * const * argv ){ bool brief = false; bool quickCheck = false; const fcli_cliflag FCliFlags[] = { FCLI_FLAG_BOOL("b","brief", &brief, "Show fewer stats."), FCLI_FLAG_BOOL("c","check", &quickCheck, "Perform quick db integrity check."), fcli_cliflag_empty_m // list MUST end with this (or equivalent) }; const fcli_help_info FCliHelp = { "Shows various statistics for a fossil repository database.", NULL, // very brief usage text, e.g. "file1 [...fileN]" NULL // optional callback which outputs app-specific help }; int rc = fcli_setup_v2(argc, argv, FCliFlags, &FCliHelp); if(rc) goto end; else if((rc=fcli_has_unused_args(false))) goto end; else if(!fsl_needs_repo(fcli_cx())){ /* Sets the context's error state and will produce an appropriate error message from fcli_end_of_main(). */ goto end; } /** Perform your app-specific work... */ rc = dump_repo_stats(brief, quickCheck ? 1 : 0); end: return fcli_end_of_main(rc) /* Will report any pending error state and return either EXIT_SUCCESS or EXIT_FAILURE. */; /* Sidebar: all of the memory allocated by this demo app is owned by the fcli internals and will be properly cleaned up during the at-exit phase. Running this app through valgrind should report no memory leaks. */ }