#include "database.h" #include "src/sist.h" #include "src/ctx.h" #define TREEMAP_MINIMUM_MERGES_TO_CONTINUE (100) #define SIZE_BUCKET (long)(5 * 1000 * 1000) #define DATE_BUCKET (long)(2629800) // ~30 days database_iterator_t *database_create_treemap_iterator(database_t *db, long threshold) { sqlite3_stmt *stmt; sqlite3_prepare_v2(db->db, "SELECT path, path_parent(path), size FROM tm" " WHERE path_parent(path) IN (SELECT path FROM tm)" " AND sizestmt = stmt; iter->db = db; return iter; } treemap_row_t database_treemap_iter(database_iterator_t *iter) { if (iter->stmt == NULL) { LOG_FATAL("database.c", "FIXME: database_treemap_iter() called after iteration stopped"); } int ret = sqlite3_step(iter->stmt); if (ret == SQLITE_ROW) { treemap_row_t row = { .path = (const char *) sqlite3_column_text(iter->stmt, 0), .parent = (const char *) sqlite3_column_text(iter->stmt, 1), .size = sqlite3_column_int64(iter->stmt, 2) }; return row; } if (ret != SQLITE_DONE) { LOG_FATALF("database.c", "FIXME: doc iter returned %s", sqlite3_errmsg(iter->db->db)); } sqlite3_finalize(iter->stmt); iter->stmt = NULL; return (treemap_row_t) {NULL, NULL, 0}; } void database_generate_stats(database_t *db, double treemap_threshold) { LOG_INFO("database.c", "Generating stats"); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "DELETE FROM stats_size_agg;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "DELETE FROM stats_date_agg;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "DELETE FROM stats_mime_agg;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "DELETE FROM stats_treemap;", NULL, NULL, NULL)); CRASH_IF_NOT_SQLITE_OK( sqlite3_exec(db->db, "CREATE TEMP TABLE tm(path TEXT PRIMARY KEY, size INT);", NULL, NULL, NULL)); sqlite3_prepare_v2(db->db, "UPDATE tm SET size=size+? WHERE path=?;", -1, &db->treemap_merge_up_update_stmt, NULL); sqlite3_prepare_v2(db->db, "DELETE FROM tm WHERE path = ?;", -1, &db->treemap_merge_up_delete_stmt, NULL); // size aggregation sqlite3_stmt *stmt; sqlite3_prepare_v2(db->db, "INSERT INTO stats_size_agg" " SELECT" " cast(size / ?1 as int) * ?1 as bucket," " count(*) as count" " FROM document" " GROUP BY bucket", -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, SIZE_BUCKET); CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); sqlite3_finalize(stmt); // date aggregation sqlite3_prepare_v2(db->db, "INSERT INTO stats_date_agg" " SELECT" " cast(mtime / ?1 as int) * ?1 as bucket," " count(*) as count" " FROM document" " GROUP BY bucket", -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, DATE_BUCKET); CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); sqlite3_finalize(stmt); // mime aggregation sqlite3_prepare_v2(db->db, "INSERT INTO stats_mime_agg" " SELECT" " (json_data->>'mime') as bucket," " sum(size)," " count(*)" " FROM document" " WHERE bucket IS NOT NULL" " GROUP BY bucket", -1, &stmt, NULL); CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); sqlite3_finalize(stmt); // Treemap sqlite3_prepare_v2(db->db, "SELECT SUM(size) FROM document;", -1, &stmt, NULL); CRASH_IF_STMT_FAIL(sqlite3_step(stmt)); long total_size = sqlite3_column_int64(stmt, 0); long threshold = (long) ((double) total_size * treemap_threshold); sqlite3_finalize(stmt); // flat map CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "INSERT INTO tm (path, size) SELECT json_data->>'path' as path, sum(size)" " FROM document WHERE json_data->>'parent' IS NULL GROUP BY path;", NULL, NULL, NULL)); // Merge up int merged_rows = 0; do { if (merged_rows) { LOG_INFOF("database.c", "Treemap merge iteration (%d rows changed)", merged_rows); } merged_rows = 0; sqlite3_prepare_v2(db->db, "INSERT INTO tm (path, size) SELECT path_parent(path) as parent, 0 " " FROM tm WHERE parent not IN (SELECT path FROM tm) AND sizetreemap_merge_up_update_stmt, 1, row.size); sqlite3_bind_text(db->treemap_merge_up_update_stmt, 2, row.parent, -1, SQLITE_STATIC); CRASH_IF_STMT_FAIL(sqlite3_step(db->treemap_merge_up_update_stmt)); CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->treemap_merge_up_update_stmt)); sqlite3_bind_text(db->treemap_merge_up_delete_stmt, 1, row.path, -1, SQLITE_STATIC); CRASH_IF_STMT_FAIL(sqlite3_step(db->treemap_merge_up_delete_stmt)); CRASH_IF_NOT_SQLITE_OK(sqlite3_reset(db->treemap_merge_up_delete_stmt)); merged_rows += 1; } } while (merged_rows > TREEMAP_MINIMUM_MERGES_TO_CONTINUE); CRASH_IF_NOT_SQLITE_OK(sqlite3_exec(db->db, "INSERT INTO stats_treemap (path, size) SELECT path,size FROM tm;", NULL, NULL, NULL)); LOG_INFO("database.c", "Done!"); }