diff --git a/doc/REST-API-Rhizome.md b/doc/REST-API-Rhizome.md index 9439fd5c..d3a77f9a 100644 --- a/doc/REST-API-Rhizome.md +++ b/doc/REST-API-Rhizome.md @@ -1139,6 +1139,37 @@ The import logic proceeds in the following steps: returns status [201 Created][201] and the [bundle status code](#bundle-status-code) for “new”. +### GET /restful/rhizome/storestatus.json + +Fetch on the current disk usage of the rhizome store. + +The results will be a single json object with the following fields; + +* `external_bytes` - the total size of all payloads larger than + rhizome.max_blob_size, that have been stored outside of sqlite, in the + rhizome blob folder. + +* `db_page_size` - the size of disk pages returned by sqlite. + +* `db_total_pages` - the number of disk pages in the sqlite database file. + +* `db_available_pages` - the number of disk pages in the sqlite database file + that have been allocated but are not currently in use. + +* `content_bytes` - the total bytes of space used in the sqlite database, and + in payloads stored outside of sqlite. This should be equal to; + db_page_size * (db_total_pages - db_available_pages) + external_bytes + +* `content_limit_bytes` - the calculated storage limit that is being applied. + This will be the smallest of the configured rhizome.database_size or the + maximum we can store while keeping rhizome.min_free_space available for + other uses. + +* `filesystem_bytes` - the measured total size of the filesystem where the + rhizome store is located. + +* `filesystem_free_bytes` - the measured free space of the filesystem. + ----- **Copyright 2015-2017 Serval Project Inc.** ![CC-BY-4.0](./cc-by-4.0.png) diff --git a/httpd.h b/httpd.h index c07f2ed0..2ec445ba 100644 --- a/httpd.h +++ b/httpd.h @@ -263,6 +263,10 @@ typedef struct httpd_request size_t offset; } file; + + struct { + struct rhizome_space_report rhizome_space; + } status; } u; } httpd_request; diff --git a/rhizome.h b/rhizome.h index 912380a6..4b3f769c 100644 --- a/rhizome.h +++ b/rhizome.h @@ -367,16 +367,34 @@ sqlite_retry_state sqlite_retry_state_init(int serverLimit, int serverSleep, int #define SQLITE_RETRY_STATE_DEFAULT sqlite_retry_state_init(-1,-1,-1,-1) +struct rhizome_space_report { + uint64_t file_count; + uint64_t internal_bytes; + uint64_t external_bytes; + + uint64_t db_page_size; + uint64_t db_total_pages; + uint64_t db_available_pages; + + uint64_t content_bytes; + uint64_t content_limit_bytes; + + uint64_t filesystem_bytes; + uint64_t filesystem_free_bytes; +}; + struct rhizome_cleanup_report { - unsigned deleted_stale_incoming_files; - unsigned deleted_expired_files; - unsigned deleted_orphan_files; - unsigned deleted_orphan_fileblobs; - unsigned deleted_orphan_manifests; + struct rhizome_space_report space_used; + unsigned deleted_stale_incoming_files; + unsigned deleted_expired_files; + unsigned deleted_orphan_files; + unsigned deleted_orphan_fileblobs; + unsigned deleted_orphan_manifests; }; int rhizome_cleanup(struct rhizome_cleanup_report *report); int rhizome_store_cleanup(struct rhizome_cleanup_report *report); +enum rhizome_payload_status rhizome_store_space_usage(struct rhizome_space_report *space); void rhizome_vacuum_db(sqlite_retry_state *retry); int rhizome_manifest_createid(rhizome_manifest *m); struct rhizome_bundle_result rhizome_private_bundle(rhizome_manifest *m, const sign_keypair_t *keypair); @@ -911,8 +929,6 @@ int rhizome_cache_close(); int rhizome_database_filehash_from_id(const rhizome_bid_t *bidp, uint64_t version, rhizome_filehash_t *hashp); void rhizome_process_added_bundles(uint64_t up_to_rowid); -void rhizome_sync_status(); - DECLARE_ALARM(rhizome_fetch_status); /* Rhizome triggers */ diff --git a/rhizome_cli.c b/rhizome_cli.c index d869721f..dbb19f00 100644 --- a/rhizome_cli.c +++ b/rhizome_cli.c @@ -472,11 +472,15 @@ static int app_rhizome_delete(const struct cli_parsed *parsed, struct cli_contex DEFINE_CMD(app_rhizome_clean, 0, "Remove stale and orphaned content from the Rhizome store", "rhizome","clean","[verify]" KEYRING_PIN_OPTIONS); +DEFINE_CMD(app_rhizome_clean, 0, + "Report on the space usage of the Rhizome store", + "rhizome","status" KEYRING_PIN_OPTIONS); static int app_rhizome_clean(const struct cli_parsed *parsed, struct cli_context *context) { DEBUG_cli_parsed(verbose, parsed); int verify = cli_arg(parsed, "verify", NULL, NULL, NULL) == 0; - + int clean = strcasecmp(parsed->args[1], "clean")==0; + /* Ensure the Rhizome database exists and is open */ if (create_serval_instance_dir() == -1) return -1; @@ -488,16 +492,36 @@ static int app_rhizome_clean(const struct cli_parsed *parsed, struct cli_context verify_bundles(); } struct rhizome_cleanup_report report; - if (rhizome_cleanup(&report) == -1) + if (clean){ + if (rhizome_cleanup(&report) == -1) + return -1; + cli_field_name(context, "deleted_stale_incoming_files", ":"); + cli_put_long(context, report.deleted_stale_incoming_files, "\n"); + cli_field_name(context, "deleted_orphan_files", ":"); + cli_put_long(context, report.deleted_orphan_files, "\n"); + cli_field_name(context, "deleted_orphan_fileblobs", ":"); + cli_put_long(context, report.deleted_orphan_fileblobs, "\n"); + cli_field_name(context, "deleted_orphan_manifests", ":"); + cli_put_long(context, report.deleted_orphan_manifests, "\n"); + } + if (rhizome_store_space_usage(&report.space_used)!=RHIZOME_PAYLOAD_STATUS_EMPTY) return -1; - cli_field_name(context, "deleted_stale_incoming_files", ":"); - cli_put_long(context, report.deleted_stale_incoming_files, "\n"); - cli_field_name(context, "deleted_orphan_files", ":"); - cli_put_long(context, report.deleted_orphan_files, "\n"); - cli_field_name(context, "deleted_orphan_fileblobs", ":"); - cli_put_long(context, report.deleted_orphan_fileblobs, "\n"); - cli_field_name(context, "deleted_orphan_manifests", ":"); - cli_put_long(context, report.deleted_orphan_manifests, "\n"); + cli_field_name(context, "file_count", ":"); + cli_put_long(context, report.space_used.file_count, "\n"); + cli_field_name(context, "file_size_bytes", ":"); + int64_t used = report.space_used.internal_bytes + report.space_used.external_bytes; + cli_put_long(context, used, "\n"); + cli_field_name(context, "overhead_bytes", ":"); + cli_put_long(context, report.space_used.content_bytes - used, "\n"); + cli_field_name(context, "used_bytes", ":"); + cli_put_long(context, report.space_used.content_bytes, "\n"); + cli_field_name(context, "available_space_bytes", ":"); + cli_put_long(context, report.space_used.content_limit_bytes - + ((report.space_used.content_limit_bytes == UINT64_MAX) ? 0 : report.space_used.content_bytes), "\n"); + cli_field_name(context, "reclaimable_bytes", ":"); + cli_put_long(context, report.space_used.db_available_pages * report.space_used.db_page_size, "\n"); + cli_field_name(context, "free_space_bytes", ":"); + cli_put_long(context, report.space_used.filesystem_free_bytes, "\n"); return 0; } diff --git a/rhizome_database.c b/rhizome_database.c index 24886987..74efefa6 100644 --- a/rhizome_database.c +++ b/rhizome_database.c @@ -1388,8 +1388,6 @@ int rhizome_cleanup(struct rhizome_cleanup_report *report) if (report && ret > 0) report->deleted_orphan_manifests += ret; - rhizome_vacuum_db(&retry); - if (report) DEBUGF(rhizome, "report deleted_stale_incoming_files=%u deleted_orphan_files=%u deleted_orphan_fileblobs=%u deleted_orphan_manifests=%u", report->deleted_stale_incoming_files, diff --git a/rhizome_restful.c b/rhizome_restful.c index a596516b..dedcebba 100644 --- a/rhizome_restful.c +++ b/rhizome_restful.c @@ -34,6 +34,7 @@ DECLARE_HANDLER("/restful/rhizome/newsince/", restful_rhizome_newsince); DECLARE_HANDLER("/restful/rhizome/insert", restful_rhizome_insert); DECLARE_HANDLER("/restful/rhizome/import", restful_rhizome_import); DECLARE_HANDLER("/restful/rhizome/append", restful_rhizome_append); +DECLARE_HANDLER("/restful/rhizome/storestatus.json", restful_rhizome_disk_status); DECLARE_HANDLER("/restful/rhizome/", restful_rhizome_); static HTTP_RENDERER render_status_and_manifest_headers; @@ -1243,3 +1244,60 @@ static void render_status_and_import_headers(const struct http_request *hr, strb render_import_headers(r, sb); } } + +static int rhizome_disk_status_content_chunk(struct http_request *hr, strbuf b) +{ + httpd_request *r = (httpd_request *) hr; + strbuf_puts(b, "{\n"); + + strbuf_puts(b, "\"rhizome_dir\":"); + strbuf_json_string(b, rhizome_database.dir_path); + strbuf_puts(b, ",\n"); + + strbuf_puts(b, "\"rhizome_uuid\":"); + strbuf_json_string(b, alloca_uuid_str(rhizome_database.uuid)); + strbuf_puts(b, ",\n"); + + strbuf_sprintf(b, "\"external_bytes\":%"PRIu64",\n", r->u.status.rhizome_space.external_bytes); + strbuf_sprintf(b, "\"db_page_size\":%"PRIu64",\n", r->u.status.rhizome_space.db_page_size); + strbuf_sprintf(b, "\"db_total_pages\":%"PRIu64",\n", r->u.status.rhizome_space.db_total_pages); + strbuf_sprintf(b, "\"db_available_pages\":%"PRIu64",\n", r->u.status.rhizome_space.db_available_pages); + strbuf_sprintf(b, "\"content_bytes\":%"PRIu64",\n", r->u.status.rhizome_space.content_bytes); + strbuf_sprintf(b, "\"content_limit_bytes\":%"PRIu64",\n", r->u.status.rhizome_space.content_limit_bytes); + strbuf_sprintf(b, "\"filesystem_bytes\":%"PRIu64",\n", r->u.status.rhizome_space.filesystem_bytes); + strbuf_sprintf(b, "\"filesystem_free_bytes\":%"PRIu64"\n}", r->u.status.rhizome_space.filesystem_free_bytes); + return 0; +} + +static int rhizome_disk_status_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result) +{ + return generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, rhizome_disk_status_content_chunk); +} + +static int restful_rhizome_disk_status(httpd_request *r, const char *remainder) +{ + if (*remainder || !is_rhizome_http_enabled()) + return 404; + int ret = authorize_restful(&r->http); + if (ret) + return ret; + + enum rhizome_payload_status p = rhizome_store_space_usage(&r->u.status.rhizome_space); + switch (p) { + case RHIZOME_PAYLOAD_STATUS_EMPTY: + break; + case RHIZOME_PAYLOAD_STATUS_STORED: + case RHIZOME_PAYLOAD_STATUS_NEW: + case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: + case RHIZOME_PAYLOAD_STATUS_BUSY: + case RHIZOME_PAYLOAD_STATUS_ERROR: + case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: + case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: + case RHIZOME_PAYLOAD_STATUS_TOO_BIG: + case RHIZOME_PAYLOAD_STATUS_EVICTED: + return http_request_rhizome_response(r, 500, "Failed to measure storage space"); + } + http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, rhizome_disk_status_content); + return 1; +} + diff --git a/rhizome_store.c b/rhizome_store.c index 4d050a0f..6264b47c 100644 --- a/rhizome_store.c +++ b/rhizome_store.c @@ -186,98 +186,142 @@ int rhizome_delete_file(const rhizome_filehash_t *filehash) return rhizome_delete_file_retry(&retry, filehash); } -static uint64_t store_get_free_space() -{ - const char *fake_space = getenv("SERVALD_FREE_SPACE"); - uint64_t space = UINT64_MAX; - if (fake_space) - space = atol(fake_space); -#if defined(HAVE_SYS_STATVFS_H) || (defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_VFS_H)) - else { - struct statvfs stats; - if (statvfs(rhizome_database.dir_path, &stats)==-1) - WARNF_perror("statvfs(%s)", alloca_str_toprint(rhizome_database.dir_path)); - else - space = stats.f_frsize * (uint64_t)stats.f_bavail; - } -#endif - if (IF_DEBUG(rhizome)) { - // Automated tests depend on this message; do not alter. - DEBUGF(rhizome, "RHIZOME SPACE FREE bytes=%"PRIu64" (%sB)", space, alloca_double_scaled_binary(space)); - } - return space; -} +static enum rhizome_payload_status store_space_report(sqlite_retry_state *retry, struct rhizome_space_report *space){ + int stepcode = sqlite_exec_uint64_retry(retry, &space->db_page_size, "PRAGMA page_size;", END); + if (sqlite_code_ok(stepcode)) + stepcode = sqlite_exec_uint64_retry(retry, &space->db_total_pages, "PRAGMA page_count;", END); + if (sqlite_code_ok(stepcode)) + stepcode = sqlite_exec_uint64_retry(retry, &space->db_available_pages, "PRAGMA freelist_count;", END); + if (sqlite_code_ok(stepcode)){ + sqlite3_stmt *statement = sqlite_prepare_bind(retry, + "SELECT CASE WHEN B.ID IS NULL THEN 0 ELSE 1 END, SUM(length), count(*) " + "FROM FILES F " + "LEFT JOIN FILEBLOBS B " + "ON F.ID = B.ID " + "GROUP BY CASE WHEN B.ID IS NULL THEN 0 ELSE 1 END;", + END); + if (statement == NULL) + return RHIZOME_PAYLOAD_STATUS_ERROR; -static uint64_t store_space_limit(uint64_t current_size) -{ - uint64_t limit = config.rhizome.database_size; - - if (config.rhizome.min_free_space!=0){ - uint64_t free_space = store_get_free_space(); - if (free_space < config.rhizome.min_free_space){ - if (current_size + free_space < config.rhizome.min_free_space) - limit = 0; - else - limit = current_size + free_space - config.rhizome.min_free_space; + space->file_count=0; + space->internal_bytes=0; + space->external_bytes=0; + while((stepcode = sqlite_step_retry(retry, statement)) == SQLITE_ROW) { + int64_t type = sqlite3_column_int64(statement, 0); + int64_t len = sqlite3_column_int64(statement, 1); + int64_t count = sqlite3_column_int64(statement, 2); + + space->file_count += count; + if (type==1){ + space->internal_bytes = len; + }else{ + space->external_bytes = len; + } } + sqlite3_finalize(statement); } - return limit; -} - -// TODO readonly version? -static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizome_cleanup_report *report) -{ - uint64_t external_bytes=0; - uint64_t db_page_size=0; - uint64_t db_page_count=0; - uint64_t db_free_page_count=0; - - // No limit? - if (config.rhizome.database_size==UINT64_MAX && config.rhizome.min_free_space==0) - return RHIZOME_PAYLOAD_STATUS_NEW; - - sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; - int stepcode = sqlite_exec_uint64_retry(&retry, &db_page_size, "PRAGMA page_size;", END); - if (sqlite_code_ok(stepcode)) - stepcode = sqlite_exec_uint64_retry(&retry, &db_page_count, "PRAGMA page_count;", END); - if (sqlite_code_ok(stepcode)) - stepcode = sqlite_exec_uint64_retry(&retry, &db_free_page_count, "PRAGMA freelist_count;", END); - if (sqlite_code_ok(stepcode)) - // TODO index and/or cache result? - stepcode = sqlite_exec_uint64_retry(&retry, &external_bytes, - "SELECT SUM(length) " - "FROM FILES " - "WHERE NOT EXISTS( " - "SELECT 1 " - "FROM FILEBLOBS " - "WHERE FILES.ID = FILEBLOBS.ID " - ");", END); - if (sqlite_code_busy(stepcode)) return RHIZOME_PAYLOAD_STATUS_BUSY; if (!sqlite_code_ok(stepcode)) return RHIZOME_PAYLOAD_STATUS_ERROR; - uint64_t db_used = external_bytes + db_page_size * (db_page_count - db_free_page_count); - const uint64_t limit = store_space_limit(db_used); + space->content_bytes = space->external_bytes + space->db_page_size * (space->db_total_pages - space->db_available_pages); - // Automated tests depend on this message; do not alter. - DEBUGF(rhizome, "RHIZOME SPACE USED bytes=%"PRIu64" (%sB), LIMIT bytes=%"PRIu64" (%sB)", - db_used, alloca_double_scaled_binary(db_used), - limit, alloca_double_scaled_binary(limit)); - - if (bytes && bytes >= limit){ + // Measure filesystem free space + space->filesystem_bytes = UINT64_MAX; + space->filesystem_free_bytes = UINT64_MAX; + +#if defined(HAVE_SYS_STATVFS_H) || (defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_VFS_H)) + { + struct statvfs stats; + if (statvfs(rhizome_database.dir_path, &stats)==-1) + WARNF_perror("statvfs(%s)", rhizome_database.dir_path); + else{ + space->filesystem_bytes = stats.f_frsize * (uint64_t)stats.f_blocks; + space->filesystem_free_bytes = stats.f_frsize * (uint64_t)stats.f_bavail; + } + } +#endif + // Fake limit for reproducible testing + const char *fake_space = getenv("SERVALD_FAKE_SPACE_LIMIT"); + if (fake_space){ + uint64_t space_limit; + // subtrace measured space used to give the same result as we add and remove content + if (str_to_uint64_scaled(fake_space, 10, &space_limit, NULL)==1 + && space_limit < space->filesystem_free_bytes + space->content_bytes) + space->filesystem_free_bytes = space_limit - space->content_bytes; + } + + // Calculate storage limit + space->content_limit_bytes = config.rhizome.database_size; + + if (config.rhizome.min_free_space !=0){ + uint64_t space_limit; + if (space->content_bytes + space->filesystem_free_bytes < config.rhizome.min_free_space) + space_limit = 0; + else + space_limit = space->content_bytes + space->filesystem_free_bytes - config.rhizome.min_free_space; + if (space_limit < space->content_limit_bytes) + space->content_limit_bytes = space_limit; + } + + DEBUGF(rhizome, "RHIZOME SPACE USED bytes=%"PRIu64" (%sB), FREE bytes=%"PRIu64" (%sB), LIMIT bytes=%"PRIu64" (%sB)", + space->content_bytes, alloca_double_scaled_binary(space->content_bytes), + space->filesystem_free_bytes, alloca_double_scaled_binary(space->filesystem_free_bytes), + space->content_limit_bytes, alloca_double_scaled_binary(space->content_limit_bytes)); + + return RHIZOME_PAYLOAD_STATUS_EMPTY; +} + +enum rhizome_payload_status rhizome_store_space_usage(struct rhizome_space_report *space) +{ + sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; + return store_space_report(&retry, space); +} + + +static enum rhizome_payload_status sqlite_vacuum(sqlite_retry_state *retry, struct rhizome_space_report *space){ + if (space->db_available_pages == 0) + return RHIZOME_PAYLOAD_STATUS_EMPTY; + + // vacuum database pages if more than 1/4 of the db is free or we're already over the limit + if (space->db_available_pages > (space->db_total_pages>>2)+1 || space->external_bytes + space->db_page_size * space->db_total_pages > space->content_limit_bytes){ + rhizome_vacuum_db(retry); + + int stepcode = sqlite_exec_uint64_retry(retry, &space->db_total_pages, "PRAGMA page_count;", END); + if (sqlite_code_ok(stepcode)) + stepcode = sqlite_exec_uint64_retry(retry, &space->db_available_pages, "PRAGMA freelist_count;", END); + + if (sqlite_code_busy(stepcode)) + return RHIZOME_PAYLOAD_STATUS_BUSY; + if (!sqlite_code_ok(stepcode)) + return RHIZOME_PAYLOAD_STATUS_ERROR; + } + return RHIZOME_PAYLOAD_STATUS_EMPTY; +} + +// TODO readonly version? +static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizome_cleanup_report *report) +{ + sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; + struct rhizome_space_report *space = (report ? &report->space_used : alloca(sizeof *space)); + int stepcode; + + enum rhizome_payload_status r; + if ((r = store_space_report(&retry, space)) != RHIZOME_PAYLOAD_STATUS_EMPTY) + return r; + + if (bytes && bytes >= space->content_limit_bytes){ DEBUGF(rhizome, "Not enough space for %"PRIu64". Used; %"PRIu64" = %"PRIu64" + %"PRIu64" * (%"PRIu64" - %"PRIu64"), Limit; %"PRIu64, - bytes, db_used, external_bytes, db_page_size, db_page_count, db_free_page_count, limit); + bytes, space->content_bytes, space->external_bytes, space->db_page_size, space->db_total_pages, space->db_available_pages, space->content_limit_bytes); return RHIZOME_PAYLOAD_STATUS_TOO_BIG; } - - // vacuum database pages if more than 1/4 of the db is free or we're already over the limit - if (db_free_page_count > (db_page_count>>2)+1 || external_bytes + db_page_size * db_page_count > limit) - rhizome_vacuum_db(&retry); - + + if ((r = sqlite_vacuum(&retry, space)) != RHIZOME_PAYLOAD_STATUS_EMPTY) + return r; + // If there is enough space, do nothing - if (db_used + bytes <= limit) + if (space->content_bytes + bytes <= space->content_limit_bytes) return RHIZOME_PAYLOAD_STATUS_NEW; // penalise new things by 10 minutes to reduce churn @@ -290,7 +334,7 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo if (!statement) return RHIZOME_PAYLOAD_STATUS_ERROR; - while (db_used + bytes > limit && (stepcode=sqlite_step_retry(&retry, statement)) == SQLITE_ROW) { + while (space->content_bytes + bytes > space->content_limit_bytes && (stepcode=sqlite_step_retry(&retry, statement)) == SQLITE_ROW) { const char *id=(const char *) sqlite3_column_text(statement, 0); uint64_t length = sqlite3_column_int(statement, 1); time_ms_t inserttime = sqlite3_column_int64(statement, 2); @@ -307,25 +351,29 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo rhizome_filehash_t hash; if (str_to_rhizome_filehash_t(&hash, id)!=-1 && rhizome_delete_external(&hash)==0) - external_bytes -= length; + space->external_bytes -= length; int rowcount=0; sqlite3_stmt *s = sqlite_prepare_bind(&retry, "DELETE FROM fileblobs WHERE id = ?", STATIC_TEXT, id, END); if (s && !sqlite_code_ok(stepcode = sqlite_exec_code_retry(&retry, s, &rowcount))) break; + if (rowcount>0) + space->internal_bytes -= length; s = sqlite_prepare_bind(&retry, "DELETE FROM files WHERE id = ?", STATIC_TEXT, id, END); if (s && !sqlite_code_ok(stepcode = sqlite_exec_code_retry(&retry, s, &rowcount))) break; + if (rowcount>0) + space->file_count --; - if (!sqlite_code_ok(stepcode = sqlite_exec_uint64_retry(&retry, &db_page_count, "PRAGMA page_count;", END))) + if (!sqlite_code_ok(stepcode = sqlite_exec_uint64_retry(&retry, &space->db_total_pages, "PRAGMA page_count;", END))) break; - if (!sqlite_code_ok(stepcode = sqlite_exec_uint64_retry(&retry, &db_free_page_count, "PRAGMA freelist_count;", END))) + if (!sqlite_code_ok(stepcode = sqlite_exec_uint64_retry(&retry, &space->db_available_pages, "PRAGMA freelist_count;", END))) break; if (report) report->deleted_expired_files++; - db_used = external_bytes + db_page_size * (db_page_count - db_free_page_count); + space->content_bytes = space->external_bytes + space->db_page_size * (space->db_total_pages - space->db_available_pages); } sqlite3_finalize(statement); @@ -334,13 +382,14 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo if (!sqlite_code_ok(stepcode)) return RHIZOME_PAYLOAD_STATUS_ERROR; - rhizome_vacuum_db(&retry); + if ((r = sqlite_vacuum(&retry, space)) != RHIZOME_PAYLOAD_STATUS_EMPTY) + return r; - if (db_used + bytes <= limit) + if (space->content_bytes + bytes <= space->content_limit_bytes) return RHIZOME_PAYLOAD_STATUS_NEW; DEBUGF(rhizome, "Not enough space for %"PRIu64". Used; %"PRIu64" = %"PRIu64" + %"PRIu64" * (%"PRIu64" - %"PRIu64"), Limit; %"PRIu64, - bytes, db_used, external_bytes, db_page_size, db_page_count, db_free_page_count, limit); + bytes, space->content_bytes, space->external_bytes, space->db_page_size, space->db_total_pages, space->db_available_pages, space->content_limit_bytes); return RHIZOME_PAYLOAD_STATUS_EVICTED; } diff --git a/tests/rhizomeops b/tests/rhizomeops index 9db3ff13..ae4cd4c0 100755 --- a/tests/rhizomeops +++ b/tests/rhizomeops @@ -1747,12 +1747,11 @@ doc_payloadUninteresting="Fail to insert a payload that is uninteresting" setup_payloadUninteresting() { setup_servald setup_rhizome - executeOk_servald config set rhizome.clean_on_open yes - # Create Rhizome database, discover fixed space overhead. - executeOk_servald rhizome list - bytes_used=$($SED -n -e '/.*RHIZOME SPACE.*USED bytes=\([0-9]\+\).*/{s//\1/p;q}' "$TFWSTDERR") - assert [ -n "$bytes_used" ] - executeOk_servald config set rhizome.database_size $((bytes_used + 65535)) + # get size of empty rhizome db + executeOk_servald rhizome status + extract_stdout_keyvalue used_bytes 'used_bytes' '[0-9]\+' + assert [ -n "$used_bytes" ] + executeOk_servald config set rhizome.database_size $((used_bytes + 65535)) } test_payloadUninteresting(){ create_file file1 32K @@ -1802,9 +1801,12 @@ setup_evictFreeSpace() { test_evictFreeSpace() { executeOk_servald config \ set rhizome.min_free_space 1M + executeOk_servald rhizome status + tfw_cat --stdout # only 640K free... - export SERVALD_FREE_SPACE=655360 + export SERVALD_FAKE_SPACE_LIMIT=2M rhizome_clean + tfw_cat --stdout assert [ $deleted_files = 0 ] assert [ $deleted_fileblobs = 0 ] assert [ $deleted_manifests = 1 ] diff --git a/tests/rhizomerestful b/tests/rhizomerestful index dc7e8e2f..a20ba721 100755 --- a/tests/rhizomerestful +++ b/tests/rhizomerestful @@ -1155,4 +1155,16 @@ test_RhizomeAppendNonJournalForbidden() { assert_rhizome_list file1 } +doc_RhizomeStatus="REST API Rhizome storage status" +setup_RhizomeStatus() { + export SERVALD_FAKE_SPACE_LIMIT=2M + setup +} +test_RhizomeStatus() { + rest_request GET "/restful/rhizome/storestatus.json" + assertJq response.json 'keys==(["rhizome_dir","rhizome_uuid","external_bytes","db_page_size","db_total_pages","db_available_pages","content_bytes","content_limit_bytes","filesystem_bytes","filesystem_free_bytes"]|sort)' + assertJq response.json 'contains({"external_bytes":2048})' + assertJq response.json 'contains({"filesystem_free_bytes":2041856})' +} + runTests "$@"