Keep information about HLS downloads around for some time afterwards.
[cubemap] / stats.cpp
1 #include <fcntl.h>
2 #include <stddef.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <time.h>
7 #include <unistd.h>
8 #include <vector>
9
10 #include "client.h"
11 #include "log.h"
12 #include "serverpool.h"
13 #include "stats.h"
14 #include "util.h"
15
16 using namespace std;
17
18 extern ServerPool *servers;
19
20 StatsThread::StatsThread(const string &stats_file, int stats_interval)
21         : stats_file(stats_file),
22           stats_interval(stats_interval)
23 {
24 }
25
26 void StatsThread::do_work()
27 {
28         while (!should_stop()) {
29                 int fd;
30                 char *filename;
31                 FILE *fp;
32                 timespec now;
33                 vector<ClientStats> client_stats;
34                 vector<HLSZombie> hls_zombies;
35                 unordered_map<string, HLSZombie> remaining_hls_zombies;
36
37                 if (clock_gettime(CLOCK_MONOTONIC_COARSE, &now) == -1) {
38                         log_perror("clock_gettime(CLOCK_MONOTONIC_COARSE)");
39                         goto sleep;
40                 }
41
42                 // Open a new, temporary file.
43                 filename = strdup((stats_file + ".new.XXXXXX").c_str());
44                 fd = mkostemp(filename, O_WRONLY);
45                 if (fd == -1) {
46                         log_perror(filename);
47                         free(filename);
48                         goto sleep;
49                 }
50
51                 fp = fdopen(fd, "w");
52                 if (fp == nullptr) {
53                         log_perror("fdopen");
54                         safe_close(fd);
55                         if (unlink(filename) == -1) {
56                                 log_perror(filename);
57                         }
58                         free(filename);
59                         goto sleep;
60                 }
61
62                 // Get all the HLS zombies and combine them into one map (we resolve conflicts
63                 // by having an arbitrary element win; in practice, that means the lowest
64                 // server ID).
65                 for (HLSZombie &zombie : servers->get_hls_zombies()) {
66                         const string remote_addr = zombie.remote_addr;
67                         remaining_hls_zombies[move(remote_addr)] = move(zombie);
68                 }
69
70                 // Remove all zombies whose ID match an already ongoing request.
71                 // (Normally, this is cleared out already when it starts,
72                 // but the request could happen on a different server from the zombie,
73                 // or the zombie could be deserialized.)
74                 for (const ClientStats &stats : servers->get_client_stats()) {
75                         if (stats.url != "-") {
76                                 remaining_hls_zombies.erase(stats.hls_zombie_key);
77                         }
78                 }
79
80                 for (const ClientStats &stats : servers->get_client_stats()) {
81                         string url = stats.url;
82                         if (url == "-") {
83                                 // No download going on currently; could it be waiting for more HLS fragments?
84                                 auto it = remaining_hls_zombies.find(stats.remote_addr);
85                                 if (it != remaining_hls_zombies.end()) {
86                                         url = it->second.url;
87                                         remaining_hls_zombies.erase(it);
88                                 }
89                         }
90
91                         fprintf(fp, "%s %d %d %s %d %llu %llu %llu \"%s\" \"%s\"\n",
92                                 stats.remote_addr.c_str(),
93                                 stats.sock,
94                                 0,  // Used to be fwmark.
95                                 url.c_str(),
96                                 int(now.tv_sec - stats.connect_time.tv_sec),  // Rather coarse.
97                                 (long long unsigned)(stats.bytes_sent),
98                                 (long long unsigned)(stats.bytes_lost),
99                                 (long long unsigned)(stats.num_loss_events),
100                                 stats.referer.c_str(),
101                                 stats.user_agent.c_str());
102                 }
103                 for (const auto &url_and_zombie : remaining_hls_zombies) {
104                         const HLSZombie &zombie = url_and_zombie.second;
105                         fprintf(fp, "%s %d %d %s %d %llu %llu %llu \"%s\" \"%s\"\n",
106                                 zombie.remote_addr.c_str(),
107                                 0,  // Fake socket. (The Munin script doesn't like negative numbers.)
108                                 0,  // Used to be fwmark.
109                                 zombie.url.c_str(),
110                                 0,
111                                 0ULL,
112                                 0ULL,
113                                 0ULL,
114                                 zombie.referer.c_str(),
115                                 zombie.user_agent.c_str());
116                 }
117                 if (fclose(fp) == EOF) {
118                         log_perror("fclose");
119                         if (unlink(filename) == -1) {
120                                 log_perror(filename);
121                         }
122                         free(filename);
123                         goto sleep;
124                 }
125                 
126                 if (rename(filename, stats_file.c_str()) == -1) {
127                         log_perror("rename");
128                         if (unlink(filename) == -1) {
129                                 log_perror(filename);
130                         }
131                 }
132                 free(filename);
133
134 sleep:
135                 // Wait until we are asked to quit, stats_interval timeout,
136                 // or a spurious signal. (The latter will cause us to write stats
137                 // too often, but that's okay.)
138                 timespec timeout_ts;
139                 timeout_ts.tv_sec = stats_interval;
140                 timeout_ts.tv_nsec = 0;
141                 wait_for_wakeup(&timeout_ts);
142         }
143 }