Support --stats-every with intervals <60 seconds. Add --json-stats-every option

Previously, json stats updates were fixed to 60 seconds, and using --stats--every
with an interval less than 60 seconds produced confusing results.

This commit fixes --stats-every with short intervals, and adds --json-stats-every
to control the json stats update interval. Note that if --json-stats-every is not
a multiple of 60 seconds, then the 1-/5-/15-minute data may not include all data
up to the current time (in that case, the most recent data is reflected in
the "latest" stats)

This implements the alternative approach discussed in PR #89
This commit is contained in:
Oliver Jowett 2021-02-01 15:18:42 +08:00
parent 490b5ce36f
commit f1c576b657
3 changed files with 54 additions and 30 deletions

View File

@ -116,6 +116,7 @@ static void modesInitConfig(void) {
Modes.net_heartbeat_interval = MODES_NET_HEARTBEAT_INTERVAL;
Modes.interactive_display_ttl = MODES_INTERACTIVE_DISPLAY_TTL;
Modes.json_interval = 1000;
Modes.json_stats_interval = 60000;
Modes.json_location_accuracy = 1;
Modes.maxRange = 1852 * 300; // 300NM default max range
Modes.mode_ac_auto = 1;
@ -345,7 +346,8 @@ static void showHelp(void)
"--quiet Disable output to stdout. Use for daemon applications\n"
"--show-only <addr> Show only messages from the given ICAO on stdout\n"
"--write-json <dir> Periodically write json output to <dir> (for serving by a separate webserver)\n"
"--write-json-every <t> Write json output every t seconds (default 1)\n"
"--write-json-every <t> Write json aircraft output every t seconds (default 1)\n"
"--json-stats-every <t> Write json stats output every t seconds (default 60)\n"
"--json-location-accuracy <n> Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n"
#if 0
"--dcfilter Apply a 1Hz DC filter to input data (requires more CPU)\n"
@ -356,11 +358,16 @@ static void showHelp(void)
);
}
static void display_total_stats(void)
// Accumulate stats data from stats_current to stats_periodic, stats_alltime and stats_latest;
// reset stats_current
static void flush_stats(uint64_t now)
{
struct stats added;
add_stats(&Modes.stats_alltime, &Modes.stats_current, &added);
display_stats(&added);
add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic);
add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime);
add_stats(&Modes.stats_current, &Modes.stats_latest, &Modes.stats_latest);
reset_stats(&Modes.stats_current);
Modes.stats_current.start = Modes.stats_current.end = now;
}
//
@ -373,6 +380,7 @@ static void display_total_stats(void)
static void backgroundTasks(void) {
static uint64_t next_stats_display;
static uint64_t next_stats_update;
static uint64_t next_json_stats_update;
static uint64_t next_json, next_history;
uint64_t now = mstime();
@ -396,41 +404,41 @@ static void backgroundTasks(void) {
// always update end time so it is current when requests arrive
Modes.stats_current.end = mstime();
// 1-minute stats update
if (now >= next_stats_update) {
int i;
if (next_stats_update == 0) {
next_stats_update = now + 60000;
} else {
Modes.stats_latest_1min = (Modes.stats_latest_1min + 1) % 15;
Modes.stats_1min[Modes.stats_latest_1min] = Modes.stats_current;
flush_stats(now); // Ensure stats_latest is up to date
add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime);
add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic);
// move stats_latest into 1-min ring buffer
Modes.stats_newest_1min = (Modes.stats_newest_1min + 1) % 15;
Modes.stats_1min[Modes.stats_newest_1min] = Modes.stats_latest;
reset_stats(&Modes.stats_latest);
// recalculate 5-min window
reset_stats(&Modes.stats_5min);
for (i = 0; i < 5; ++i)
add_stats(&Modes.stats_1min[(Modes.stats_latest_1min - i + 15) % 15], &Modes.stats_5min, &Modes.stats_5min);
add_stats(&Modes.stats_1min[(Modes.stats_newest_1min - i + 15) % 15], &Modes.stats_5min, &Modes.stats_5min);
// recalculate 15-min window
reset_stats(&Modes.stats_15min);
for (i = 0; i < 15; ++i)
add_stats(&Modes.stats_1min[i], &Modes.stats_15min, &Modes.stats_15min);
reset_stats(&Modes.stats_current);
Modes.stats_current.start = Modes.stats_current.end = now;
if (Modes.json_dir)
writeJsonToFile("stats.json", generateStatsJson);
next_stats_update += 60000;
}
}
// --stats-every display
if (Modes.stats && now >= next_stats_display) {
if (next_stats_display == 0) {
next_stats_display = now + Modes.stats;
} else {
add_stats(&Modes.stats_periodic, &Modes.stats_current, &Modes.stats_periodic);
flush_stats(now); // Ensure stats_periodic is up to date
display_stats(&Modes.stats_periodic);
reset_stats(&Modes.stats_periodic);
@ -442,6 +450,17 @@ static void backgroundTasks(void) {
}
}
// json stats update
if (Modes.json_dir && now >= next_json_stats_update) {
if (next_json_stats_update == 0) {
next_json_stats_update = now + Modes.json_stats_interval;
} else {
flush_stats(now); // Ensure everything we'll write is up to date
writeJsonToFile("stats.json", generateStatsJson);
next_json_stats_update += Modes.json_stats_interval;
}
}
if (Modes.json_dir && now >= next_json) {
writeJsonToFile("aircraft.json", generateAircraftJson);
next_json = now + Modes.json_interval;
@ -631,6 +650,8 @@ int main(int argc, char **argv) {
Modes.stats_range_histo = 1;
} else if (!strcmp(argv[j],"--stats-every") && more) {
Modes.stats = (uint64_t) (1000 * atof(argv[++j]));
} else if (!strcmp(argv[j],"--json-stats-every") && more) {
Modes.json_stats_interval = (uint64_t) (1000 * atof(argv[++j]));
} else if (!strcmp(argv[j],"--snip") && more) {
snipMode(atoi(argv[++j]));
exit(0);
@ -702,6 +723,7 @@ int main(int argc, char **argv) {
Modes.stats_current.start = Modes.stats_current.end =
Modes.stats_alltime.start = Modes.stats_alltime.end =
Modes.stats_periodic.start = Modes.stats_periodic.end =
Modes.stats_latest.start = Modes.stats_latest.end =
Modes.stats_5min.start = Modes.stats_5min.end =
Modes.stats_15min.start = Modes.stats_15min.end = mstime();
@ -778,9 +800,10 @@ int main(int argc, char **argv) {
interactiveCleanup();
// If --stats were given, print statistics
// If --stats was given, print final statistics
if (Modes.stats) {
display_total_stats();
flush_stats(0);
display_stats(&Modes.stats_alltime);
}
sdrClose();

View File

@ -355,6 +355,7 @@ struct _Modes { // Internal state
int mlat; // Use Beast ascii format for raw data output, i.e. @...; iso *...;
char *json_dir; // Path to json base directory, or NULL not to write json.
uint64_t json_interval; // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval
uint64_t json_stats_interval; // Interval between rewriting the json stats file, in milliseconds
int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact
int json_aircraft_history_next;
@ -373,13 +374,14 @@ struct _Modes { // Internal state
struct aircraft *aircrafts;
// Statistics
struct stats stats_current;
struct stats stats_alltime;
struct stats stats_periodic;
struct stats stats_1min[15];
int stats_latest_1min;
struct stats stats_5min;
struct stats stats_15min;
struct stats stats_current; // Currently accumulating stats, this is where all stats are initially collected
struct stats stats_alltime; // Accumulated stats since the start of the process
struct stats stats_periodic; // Accumulated stats since the last periodic stats display (--stats-every)
struct stats stats_latest; // Accumulated stats since the end of the last 1-minute period
struct stats stats_1min[15]; // Accumulated stats for a full 1-minute window; this is a ring buffer maintaining a history of 15 minutes
int stats_newest_1min; // Index into stats_1min of the most recent 1-minute window
struct stats stats_5min; // Accumulated stats from the last 5 complete 1-minute windows
struct stats stats_15min; // Accumulated stats from the last 15 complete 1-minute windows
};
extern struct _Modes Modes;

View File

@ -1831,10 +1831,10 @@ char *generateStatsJson(const char *url_path, int *len) {
MODES_NOTUSED(url_path);
p = safe_snprintf(p, end, "{\n");
p = appendStatsJson(p, end, &Modes.stats_current, "latest");
p = appendStatsJson(p, end, &Modes.stats_latest, "latest");
p = safe_snprintf(p, end, ",\n");
p = appendStatsJson(p, end, &Modes.stats_1min[Modes.stats_latest_1min], "last1min");
p = appendStatsJson(p, end, &Modes.stats_1min[Modes.stats_newest_1min], "last1min");
p = safe_snprintf(p, end, ",\n");
p = appendStatsJson(p, end, &Modes.stats_5min, "last5min");
@ -1843,8 +1843,7 @@ char *generateStatsJson(const char *url_path, int *len) {
p = appendStatsJson(p, end, &Modes.stats_15min, "last15min");
p = safe_snprintf(p, end, ",\n");
add_stats(&Modes.stats_alltime, &Modes.stats_current, &add);
p = appendStatsJson(p, end, &add, "total");
p = appendStatsJson(p, end, &Modes.stats_alltime, "total");
p = safe_snprintf(p, end, "\n}\n");
if (p <= end) {