Skip to content

Commit cb9aeea

Browse files
committed
statd: compress journal snapshots with gzip
Store historical snapshots as compressed .json.gz files to reduce disk usage. The operational.json file remains uncompressed for easy access. Signed-off-by: Richard Alpe <[email protected]>
1 parent c767a6f commit cb9aeea

File tree

4 files changed

+58
-24
lines changed

4 files changed

+58
-24
lines changed

src/statd/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ statd_CFLAGS = -W -Wall -Wextra
88
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
99
statd_CFLAGS += $(libsrx_CFLAGS) $(libite_CFLAGS)
1010
statd_LDADD = $(jansson_LIBS) $(libyang_LIBS) $(sysrepo_LIBS)
11-
statd_LDADD += $(libsrx_LIBS) $(libite_LIBS) $(EV_LIBS)
11+
statd_LDADD += $(libsrx_LIBS) $(libite_LIBS) $(EV_LIBS) -lz
1212

1313
# Test stub for journal retention policy (no dependencies, standalone)
1414
noinst_PROGRAMS = journal_retention_stub

src/statd/journal.c

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <sys/stat.h>
1212
#include <pthread.h>
1313
#include <dirent.h>
14+
#include <zlib.h>
1415

1516
#include <srx/common.h>
1617

@@ -30,40 +31,73 @@ static void get_timestamp_filename(char *buf, size_t len, time_t ts)
3031
{
3132
struct tm *tm = gmtime(&ts);
3233

33-
snprintf(buf, len, "%04d%02d%02d-%02d%02d%02d.json",
34+
snprintf(buf, len, "%04d%02d%02d-%02d%02d%02d.json.gz",
3435
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
3536
tm->tm_hour, tm->tm_min, tm->tm_sec);
3637
}
3738

38-
/* Create timestamped snapshot and update operational.json symlink */
39+
/* Compress a file using gzip */
40+
static int gzip_file(const char *src, const char *dst)
41+
{
42+
FILE *in;
43+
gzFile gz;
44+
char buf[4096];
45+
size_t n;
46+
47+
in = fopen(src, "r");
48+
if (!in) {
49+
ERROR("Error, opening %s: %s", src, strerror(errno));
50+
return -1;
51+
}
52+
53+
gz = gzopen(dst, "wb");
54+
if (!gz) {
55+
ERROR("Error, opening %s: %s", dst, strerror(errno));
56+
fclose(in);
57+
return -1;
58+
}
59+
60+
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
61+
if (gzwrite(gz, buf, n) != (int)n) {
62+
ERROR("Error, writing to %s", dst);
63+
gzclose(gz);
64+
fclose(in);
65+
unlink(dst);
66+
return -1;
67+
}
68+
}
69+
70+
gzclose(gz);
71+
fclose(in);
72+
return 0;
73+
}
74+
75+
/* Create timestamped snapshot and update operational.json */
3976
static int create_snapshot(const struct lyd_node *tree)
4077
{
4178
char timestamp_file[300];
4279
char timestamp_path[512];
4380
time_t now;
4481
int ret;
4582

83+
/* Write latest snapshot as uncompressed operational.json for easy access */
84+
ret = lyd_print_path(DUMP_FILE, tree, LYD_JSON, LYD_PRINT_WITHSIBLINGS);
85+
if (ret != LY_SUCCESS) {
86+
ERROR("Error, writing operational.json: %d", ret);
87+
return -1;
88+
}
89+
90+
/* Compress operational.json to timestamped archive */
4691
now = time(NULL);
4792
get_timestamp_filename(timestamp_file, sizeof(timestamp_file), now);
4893
snprintf(timestamp_path, sizeof(timestamp_path), "%s/%s",
4994
JOURNAL_DIR, timestamp_file);
5095

51-
/* Write timestamped snapshot directly */
52-
ret = lyd_print_path(timestamp_path, tree, LYD_JSON,
53-
LYD_PRINT_WITHSIBLINGS);
54-
if (ret != LY_SUCCESS) {
55-
ERROR("Error, writing snapshot %s: %d", timestamp_file, ret);
56-
unlink(timestamp_path); /* Clean up incomplete file */
96+
if (gzip_file(DUMP_FILE, timestamp_path) != 0) {
97+
ERROR("Error, compressing snapshot to %s", timestamp_file);
5798
return -1;
5899
}
59100

60-
/* Update operational.json symlink to point to latest */
61-
unlink(DUMP_FILE); /* Remove old symlink/file */
62-
if (symlink(timestamp_file, DUMP_FILE) != 0) {
63-
ERROR("Error, creating symlink to %s: %s",
64-
timestamp_file, strerror(errno));
65-
}
66-
67101
DEBUG("Created snapshot %s", timestamp_file);
68102
return 0;
69103
}

src/statd/journal_retention.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static int parse_timestamp_filename(const char *filename, time_t *ts)
3030
struct tm tm = {0};
3131
int year, mon, day, hour, min, sec;
3232

33-
if (sscanf(filename, "%4d%2d%2d-%2d%2d%2d.json",
33+
if (sscanf(filename, "%4d%2d%2d-%2d%2d%2d.json.gz",
3434
&year, &mon, &day, &hour, &min, &sec) != 6)
3535
return -1;
3636

@@ -76,7 +76,7 @@ int journal_scan_snapshots(const char *dir, struct snapshot **out_snapshots, int
7676

7777
if (strcmp(entry->d_name, "operational.json") == 0)
7878
continue;
79-
if (!strstr(entry->d_name, ".json"))
79+
if (!strstr(entry->d_name, ".json.gz"))
8080
continue;
8181

8282
if (parse_timestamp_filename(entry->d_name, &ts) != 0)

test/case/statd/journal-retention/test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@
2323
def create_snapshot(test_dir, timestamp):
2424
"""Create an empty snapshot file with the given timestamp"""
2525
dt = datetime.utcfromtimestamp(timestamp)
26-
filename = dt.strftime("%Y%m%d-%H%M%S.json")
26+
filename = dt.strftime("%Y%m%d-%H%M%S.json.gz")
2727
path = os.path.join(test_dir, filename)
2828
open(path, 'w').close()
2929
return filename
3030

3131
def count_snapshots(test_dir):
32-
"""Count JSON snapshot files in directory"""
33-
return len([f for f in os.listdir(test_dir) if f.endswith('.json')])
32+
"""Count compressed snapshot files in directory"""
33+
return len([f for f in os.listdir(test_dir) if f.endswith('.json.gz')])
3434

3535
def count_snapshots_by_age(test_dir, now):
3636
"""Count snapshots by age bucket"""
@@ -50,12 +50,12 @@ def count_snapshots_by_age(test_dir, now):
5050
}
5151

5252
for filename in os.listdir(test_dir):
53-
if not filename.endswith('.json'):
53+
if not filename.endswith('.json.gz'):
5454
continue
5555

56-
# Parse timestamp from filename (YYYYMMDD-HHMMSS.json)
56+
# Parse timestamp from filename (YYYYMMDD-HHMMSS.json.gz)
5757
try:
58-
ts_str = filename.replace('.json', '')
58+
ts_str = filename.replace('.json.gz', '')
5959
dt = datetime.strptime(ts_str, "%Y%m%d-%H%M%S")
6060
ts = int(dt.timestamp())
6161
age = now - ts

0 commit comments

Comments
 (0)