1 /*
2 * aprsc
3 *
4 * (c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
5 *
6 * This program is licensed under the BSD license, which can be found
7 * in the file LICENSE.
8 *
9 *
10 * The counterdata module stores periodic samples of counter values
11 * to facilitate calculating average traffic levels and do some
12 * fancy bells and whistles on the status page.
13 */
14
15 #include "ac-hdrs.h"
16
17 #include <pthread.h>
18 #include <string.h>
19 #include <errno.h>
20
21 #include "counterdata.h"
22 #include "config.h"
23 #include "hmalloc.h"
24 #include "worker.h"
25 #include "hlog.h"
26
27 struct cdata_t {
28 struct cdata_t *next;
29 struct cdata_t **prevp;
30 pthread_mutex_t mt;
31 char *name;
32 long long last_raw_value;
33 time_t times[CDATA_SAMPLES];
34 long long values[CDATA_SAMPLES];
35 int last_index;
36 int is_gauge;
37 };
38
39 struct cdata_t *counterdata = NULL;
40 pthread_mutex_t counterdata_mt = PTHREAD_MUTEX_INITIALIZER;
41
42 /*
43 * allocation and freeing of cdata structures
44 */
45
cdata_alloc(const char * name)46 struct cdata_t *cdata_alloc(const char *name)
47 {
48 int e;
49 struct cdata_t *cd;
50
51 cd = hmalloc(sizeof(*cd));
52 memset(cd, 0, sizeof(*cd));
53
54 pthread_mutex_init(&cd->mt, NULL);
55 cd->name = hstrdup(name);
56 cd->last_index = -1; // no data inserted yet
57 cd->is_gauge = 0;
58
59 if ((e = pthread_mutex_lock(&counterdata_mt))) {
60 hlog(LOG_CRIT, "cdata_allocate: failed to lock counterdata_mt: %s", strerror(e));
61 exit(1);
62 }
63
64 cd->next = counterdata;
65 if (counterdata)
66 counterdata->prevp = &cd->next;
67 counterdata = cd;
68 cd->prevp = &counterdata;
69
70 if ((e = pthread_mutex_unlock(&counterdata_mt))) {
71 hlog(LOG_CRIT, "cdata_allocate: could not unlock counterdata_mt: %s", strerror(e));
72 exit(1);
73 }
74
75 //hlog(LOG_DEBUG, "cdata: allocated: %s", cd->name);
76
77 return cd;
78 }
79
cdata_free(struct cdata_t * cd)80 void cdata_free(struct cdata_t *cd)
81 {
82 if (cd->next)
83 cd->next->prevp = cd->prevp;
84 *cd->prevp = cd->next;
85
86 hfree(cd->name);
87 hfree(cd);
88 }
89
90 /*
91 * Find a cdata element by name and lock it for use
92 */
93
cdata_find_and_lock(const char * name)94 static struct cdata_t *cdata_find_and_lock(const char *name)
95 {
96 struct cdata_t *cd = NULL;
97 int e;
98
99 if ((e = pthread_mutex_lock(&counterdata_mt))) {
100 hlog(LOG_CRIT, "cdata_find_and_lock: failed to lock counterdata_mt: %s", strerror(e));
101 exit(1);
102 }
103
104 for (cd = counterdata; (cd); cd = cd->next)
105 if (strcmp(name, cd->name) == 0) {
106 if ((e = pthread_mutex_lock(&cd->mt))) {
107 hlog(LOG_CRIT, "cdata_find_and_lock: could not lock cd: %s", strerror(e));
108 exit(1);
109 }
110 break;
111 }
112
113 if ((e = pthread_mutex_unlock(&counterdata_mt))) {
114 hlog(LOG_CRIT, "cdata_find_and_lock: could not unlock counterdata_mt: %s", strerror(e));
115 exit(1);
116 }
117
118 return cd;
119 }
120
121 /*
122 * Add a new data sample to a counter-watching cdata
123 */
124
cdata_counter_sample(struct cdata_t * cd,long long value)125 void cdata_counter_sample(struct cdata_t *cd, long long value)
126 {
127 int e;
128 long long l;
129
130 if ((e = pthread_mutex_lock(&cd->mt))) {
131 hlog(LOG_CRIT, "cdata_counter_sample %s: failed to lock mt: %s", cd->name, strerror(e));
132 exit(1);
133 }
134
135 cd->last_index++;
136 if (cd->last_index >= CDATA_SAMPLES)
137 cd->last_index = 0;
138
139 /* calculate counter's increment and insert */
140 if (value == -1) {
141 /* no data for sample */
142 l = -1;
143 } else {
144 /* check for wrap-around */
145 if (value < cd->last_raw_value)
146 l = -1;
147 else
148 l = value - cd->last_raw_value;
149 }
150
151 cd->last_raw_value = value;
152 cd->values[cd->last_index] = l;
153 cd->times[cd->last_index] = tick;
154
155 if ((e = pthread_mutex_unlock(&cd->mt))) {
156 hlog(LOG_CRIT, "cdata_counter_sample %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
157 exit(1);
158 }
159 }
160
161 /*
162 * Add a new sample to a gauge-watching cdata
163 */
164
cdata_gauge_sample(struct cdata_t * cd,long long value)165 void cdata_gauge_sample(struct cdata_t *cd, long long value)
166 {
167 int e;
168
169 if ((e = pthread_mutex_lock(&cd->mt))) {
170 hlog(LOG_CRIT, "cdata_gauge_sample %s: failed to lock mt: %s", cd->name, strerror(e));
171 exit(1);
172 }
173
174 cd->last_index++;
175 if (cd->last_index >= CDATA_SAMPLES)
176 cd->last_index = 0;
177
178 /* just insert the gauge */
179 cd->last_raw_value = value;
180 cd->values[cd->last_index] = value;
181 cd->times[cd->last_index] = tick;
182 cd->is_gauge = 1;
183
184 if ((e = pthread_mutex_unlock(&cd->mt))) {
185 hlog(LOG_CRIT, "cdata_gauge_sample %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
186 exit(1);
187 }
188 }
189
190 /*
191 * Get the last calculated value of a cdata
192 */
193
cdata_get_last_value(const char * name)194 long cdata_get_last_value(const char *name)
195 {
196 int e;
197 long v;
198 struct cdata_t *cd;
199
200 cd = cdata_find_and_lock(name);
201
202 if (!cd)
203 return -1;
204
205 if (cd->last_index < 0) {
206 v = -1;
207 } else {
208 v = cd->values[cd->last_index];
209 }
210
211 if ((e = pthread_mutex_unlock(&cd->mt))) {
212 hlog(LOG_CRIT, "cdata_get_last_value %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
213 exit(1);
214 }
215
216 return v;
217 }
218
219 /*
220 * Return a JSON string containing the contents of a cdata array.
221 * Allocated on the heap, needs to be freed by the caller.
222 * This is called by the http service to provide graph data for the
223 * web UI.
224 */
225
cdata_json_string(const char * name)226 char *cdata_json_string(const char *name)
227 {
228 struct cdata_t *cd;
229 char *out = NULL;
230 int e;
231
232 cd = cdata_find_and_lock(name);
233
234 if (!cd)
235 return NULL;
236
237 cJSON *root = cJSON_CreateObject();
238 cJSON *values = cJSON_CreateArray();
239 if (cd->is_gauge)
240 cJSON_AddNumberToObject(root, "gauge", 1);
241 cJSON_AddNumberToObject(root, "interval", CDATA_INTERVAL);
242 cJSON_AddItemToObject(root, "values", values);
243
244 if (cd->last_index >= 0) {
245 int i = cd->last_index + 1;
246 time_t tick_dif = now - tick; /* convert monotonic time to wallclock time */
247
248 do {
249 if (i == CDATA_SAMPLES)
250 i = 0;
251 //hlog(LOG_DEBUG, "cdata_json_string, sample %d", i);
252
253 if (cd->times[i] > 0) {
254 cJSON *val = cJSON_CreateArray();
255 cJSON_AddItemToArray(val, cJSON_CreateNumber(cd->times[i] + tick_dif));
256 cJSON_AddItemToArray(val, cJSON_CreateNumber(cd->values[i]));
257 cJSON_AddItemToArray(values, val);
258 }
259
260 if (i == cd->last_index)
261 break;
262 i++;
263 } while (1);
264 }
265
266 if ((e = pthread_mutex_unlock(&cd->mt))) {
267 hlog(LOG_CRIT, "cdata_get_last_value %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
268 exit(1);
269 }
270
271 out = cJSON_Print(root);
272 cJSON_Delete(root);
273
274 return out;
275 }
276
277