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