1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25
26 #include <cf3.defs.h>
27
28 #include <known_dirs.h>
29 #include <eval_context.h>
30 #include <promises.h>
31 #include <probes.h>
32 #include <files_lib.h>
33 #include <files_names.h>
34 #include <files_interfaces.h>
35 #include <vars.h>
36 #include <item_lib.h>
37 #include <conversion.h>
38 #include <scope.h>
39 #include <matching.h>
40 #include <instrumentation.h>
41 #include <pipes.h>
42 #include <locks.h>
43 #include <string_lib.h>
44 #include <exec_tools.h>
45 #include <unix.h>
46 #include <file_lib.h>
47 #include <monitoring_read.h>
48
49
NovaNamedEvent(const char * eventname,double value)50 void NovaNamedEvent(const char *eventname, double value)
51 {
52 Event ev_new, ev_old;
53 time_t now = time(NULL);
54 CF_DB *dbp;
55
56 if (!OpenDB(&dbp, dbid_measure))
57 {
58 return;
59 }
60
61 ev_new.t = now;
62
63 if (ReadDB(dbp, eventname, &ev_old, sizeof(ev_old)))
64 {
65 if (isnan(ev_old.Q.expect))
66 {
67 ev_old.Q.expect = value;
68 }
69
70 if (isnan(ev_old.Q.var))
71 {
72 ev_old.Q.var = 0;
73 }
74
75 ev_new.Q = QAverage(ev_old.Q, value, 0.7);
76 }
77 else
78 {
79 ev_new.Q = QDefinite(value);
80 }
81
82 Log(LOG_LEVEL_VERBOSE, "Wrote scalar named event \"%s\" = (%.2lf,%.2lf,%.2lf)", eventname, ev_new.Q.q,
83 ev_new.Q.expect, sqrt(ev_new.Q.var));
84 WriteDB(dbp, eventname, &ev_new, sizeof(ev_new));
85
86 CloseDB(dbp);
87 }
88
89 /*****************************************************************************/
90 /* Level */
91 /*****************************************************************************/
92
93
Nova_DumpSlots(void)94 static void Nova_DumpSlots(void)
95 {
96 #define MAX_KEY_FILE_SIZE 16384 /* usually around 4000, cannot grow much */
97
98 char filename[CF_BUFSIZE];
99 int i;
100
101 snprintf(filename, CF_BUFSIZE - 1, "%s%cts_key", GetStateDir(), FILE_SEPARATOR);
102
103 char file_contents_new[MAX_KEY_FILE_SIZE] = {0};
104
105 for (i = 0; i < CF_OBSERVABLES; i++)
106 {
107 char line[CF_MAXVARSIZE];
108
109 if (NovaHasSlot(i))
110 {
111 snprintf(line, sizeof(line), "%d,%s,%s,%s,%.3lf,%.3lf,%d\n",
112 i,
113 NULLStringToEmpty((char*)NovaGetSlotName(i)),
114 NULLStringToEmpty((char*)NovaGetSlotDescription(i)),
115 NULLStringToEmpty((char*)NovaGetSlotUnits(i)),
116 NovaGetSlotExpectedMinimum(i), NovaGetSlotExpectedMaximum(i), NovaIsSlotConsolidable(i) ? 1 : 0);
117 }
118 else
119 {
120 snprintf(line, sizeof(line), "%d,spare,unused\n", i);
121 }
122
123 strlcat(file_contents_new, line, sizeof(file_contents_new));
124 }
125
126 bool contents_changed = true;
127
128 Writer *w = FileRead(filename, MAX_KEY_FILE_SIZE, NULL);
129 if (w)
130 {
131 if(strcmp(StringWriterData(w), file_contents_new) == 0)
132 {
133 contents_changed = false;
134 }
135 WriterClose(w);
136 }
137
138 if(contents_changed)
139 {
140 Log(LOG_LEVEL_VERBOSE, "Updating %s with new slot information", filename);
141
142 if(!FileWriteOver(filename, file_contents_new))
143 {
144 Log(LOG_LEVEL_ERR, "Nova_DumpSlots: Could not write file '%s'. (FileWriteOver: %s)", filename,
145 GetErrorStr());
146 }
147 }
148 }
149
GetObservable(int i,char * name,char * desc)150 void GetObservable(int i, char *name, char *desc)
151 {
152 Nova_LoadSlots();
153
154 if (i < ob_spare)
155 {
156 strncpy(name, OBSERVABLES[i][0], CF_MAXVARSIZE - 1);
157 strncpy(desc, OBSERVABLES[i][1], CF_MAXVARSIZE - 1);
158 }
159 else
160 {
161 if (SLOTS[i - ob_spare])
162 {
163 strncpy(name, SLOTS[i - ob_spare]->name, CF_MAXVARSIZE - 1);
164 strncpy(desc, SLOTS[i - ob_spare]->description, CF_MAXVARSIZE - 1);
165 }
166 else
167 {
168 strncpy(name, OBSERVABLES[i][0], CF_MAXVARSIZE - 1);
169 strncpy(desc, OBSERVABLES[i][1], CF_MAXVARSIZE - 1);
170 }
171 }
172 }
173
SetMeasurementPromises(Item ** classlist)174 void SetMeasurementPromises(Item ** classlist)
175 {
176 CF_DB *dbp;
177 CF_DBC *dbcp;
178 char eventname[CF_MAXVARSIZE], assignment[CF_BUFSIZE];
179 Event entry;
180 char *key;
181 void *stored;
182 int ksize, vsize;
183
184 if (!OpenDB(&dbp, dbid_measure))
185 {
186 return;
187 }
188
189 if (!NewDBCursor(dbp, &dbcp))
190 {
191 Log(LOG_LEVEL_INFO, "Unable to scan class db");
192 CloseDB(dbp);
193 return;
194 }
195
196 memset(&entry, 0, sizeof(entry));
197
198 while (NextDB(dbcp, &key, &ksize, &stored, &vsize))
199 {
200 if (stored != NULL)
201 {
202 if (sizeof(entry) < (size_t) vsize)
203 {
204 Log(LOG_LEVEL_ERR, "Invalid entry in measurements database. Expected size: %zu, actual size: %d", sizeof(entry), vsize);
205 continue;
206 }
207
208 strcpy(eventname, (char *) key);
209 memcpy(&entry, stored, MIN(vsize, sizeof(entry)));
210
211 Log(LOG_LEVEL_VERBOSE, "Setting measurement event %s", eventname);
212
213 // a.measure.data_type is not longer known here, so look for zero decimals
214
215 if ((int) (entry.Q.q * 10) % 10 == 0)
216 {
217 snprintf(assignment, CF_BUFSIZE - 1, "value_%s=%.0lf", eventname, entry.Q.q);
218 }
219 else
220 {
221 snprintf(assignment, CF_BUFSIZE - 1, "value_%s=%.2lf", eventname, entry.Q.q);
222 }
223
224 AppendItem(classlist, assignment, NULL);
225
226 snprintf(assignment, CF_BUFSIZE - 1, "av_%s=%.2lf", eventname, entry.Q.expect);
227 AppendItem(classlist, assignment, NULL);
228 snprintf(assignment, CF_BUFSIZE - 1, "dev_%s=%.2lf", eventname, sqrt(entry.Q.var));
229 AppendItem(classlist, assignment, NULL);
230 }
231 }
232
233 DeleteDBCursor(dbcp);
234 CloseDB(dbp);
235 }
236
237 /*****************************************************************************/
238 /* Clock handling */
239 /*****************************************************************************/
240
241 /* MB: We want to solve the geometric series problem to simulate an unbiased
242 average over a grain size for long history aggregation at zero cost, i.e.
243 we'd ideally like to have
244
245 w = (1-w)^n for all n
246
247 The true average is expensive to compute, so forget the brute force approach
248 because this gives a pretty good result. The eqn above has no actual solution
249 but we can approximate numerically to w = 0.01, see this test to show that
250 the distribution is flat:
251
252 main ()
253
254 { int i,j;
255 double w = 0.01,wp;
256
257 for (i = 1; i < 20; i++)
258 {
259 printf("(");
260 wp = w;
261
262 for (j = 1; j < i; j++)
263 {
264 printf("%f,",wp);
265 wp *= (1- w);
266 }
267 printf(")\n");
268 }
269 }
270
271 */
272
273 /*****************************************************************************/
274 /* Level */
275 /*****************************************************************************/
276
NovaGetSlot(const char * name)277 static int NovaGetSlot(const char *name)
278 {
279 int i;
280
281 Nova_LoadSlots();
282
283 /* First try to find existing slot */
284 for (i = 0; i < CF_OBSERVABLES - ob_spare; ++i)
285 {
286 if (SLOTS[i] && !strcmp(SLOTS[i]->name, name))
287 {
288 Log(LOG_LEVEL_VERBOSE, "Using slot ob_spare+%d (%d) for %s", i, i + ob_spare, name);
289 return i + ob_spare;
290 }
291 }
292
293 /* Then find the spare one */
294 for (i = 0; i < CF_OBSERVABLES - ob_spare; ++i)
295 {
296 if (!SLOTS[i])
297 {
298 Log(LOG_LEVEL_VERBOSE, "Using empty slot ob_spare+%d (%d) for %s", i, i + ob_spare, name);
299 return i + ob_spare;
300 }
301 }
302
303 Log(LOG_LEVEL_ERR,
304 "Measurement slots are all in use - it is not helpful to measure too much, you can't usefully follow this many variables");
305
306 return -1;
307 }
308
309 /*****************************************************************************/
310
NovaRegisterSlot(const char * name,const char * description,const char * units,double expected_minimum,double expected_maximum,bool consolidable)311 int NovaRegisterSlot(const char *name, const char *description,
312 const char *units, double expected_minimum,
313 double expected_maximum, bool consolidable)
314 {
315 int slot = NovaGetSlot(name);
316
317 if (slot == -1)
318 {
319 return -1;
320 }
321
322 Nova_FreeSlot(SLOTS[slot - ob_spare]);
323 SLOTS[slot - ob_spare] = Nova_MakeSlot(name, description, units, expected_minimum, expected_maximum, consolidable);
324 Nova_DumpSlots();
325
326 return slot;
327 }
328