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