You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  1. /*
  2. * Copyright 2024 Vsevolod Stakhov
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #include "config.h"
  17. #include "rrd.h"
  18. #include "util.h"
  19. #include "cfg_file.h"
  20. #include "logger.h"
  21. #include "unix-std.h"
  22. #include "cryptobox.h"
  23. #include <math.h>
  24. #define RSPAMD_RRD_DS_COUNT METRIC_ACTION_MAX
  25. #define RSPAMD_RRD_OLD_DS_COUNT 4
  26. #define RSPAMD_RRD_RRA_COUNT 4
  27. #define msg_err_rrd(...) rspamd_default_log_function(G_LOG_LEVEL_CRITICAL, \
  28. "rrd", file->id, \
  29. G_STRFUNC, \
  30. __VA_ARGS__)
  31. #define msg_warn_rrd(...) rspamd_default_log_function(G_LOG_LEVEL_WARNING, \
  32. "rrd", file->id, \
  33. G_STRFUNC, \
  34. __VA_ARGS__)
  35. #define msg_info_rrd(...) rspamd_default_log_function(G_LOG_LEVEL_INFO, \
  36. "rrd", file->id, \
  37. G_STRFUNC, \
  38. __VA_ARGS__)
  39. #define msg_debug_rrd(...) rspamd_conditional_debug_fast(NULL, NULL, \
  40. rspamd_rrd_log_id, "rrd", file->id, \
  41. G_STRFUNC, \
  42. __VA_ARGS__)
  43. INIT_LOG_MODULE(rrd)
  44. static GQuark
  45. rrd_error_quark(void)
  46. {
  47. return g_quark_from_static_string("rrd-error");
  48. }
  49. /**
  50. * Convert rrd dst type from string to numeric value
  51. */
  52. enum rrd_dst_type
  53. rrd_dst_from_string(const char *str)
  54. {
  55. if (g_ascii_strcasecmp(str, "counter") == 0) {
  56. return RRD_DST_COUNTER;
  57. }
  58. else if (g_ascii_strcasecmp(str, "absolute") == 0) {
  59. return RRD_DST_ABSOLUTE;
  60. }
  61. else if (g_ascii_strcasecmp(str, "gauge") == 0) {
  62. return RRD_DST_GAUGE;
  63. }
  64. else if (g_ascii_strcasecmp(str, "cdef") == 0) {
  65. return RRD_DST_CDEF;
  66. }
  67. else if (g_ascii_strcasecmp(str, "derive") == 0) {
  68. return RRD_DST_DERIVE;
  69. }
  70. return RRD_DST_INVALID;
  71. }
  72. /**
  73. * Convert numeric presentation of dst to string
  74. */
  75. const char *
  76. rrd_dst_to_string(enum rrd_dst_type type)
  77. {
  78. switch (type) {
  79. case RRD_DST_COUNTER:
  80. return "COUNTER";
  81. case RRD_DST_ABSOLUTE:
  82. return "ABSOLUTE";
  83. case RRD_DST_GAUGE:
  84. return "GAUGE";
  85. case RRD_DST_CDEF:
  86. return "CDEF";
  87. case RRD_DST_DERIVE:
  88. return "DERIVE";
  89. default:
  90. return "U";
  91. }
  92. return "U";
  93. }
  94. /**
  95. * Convert rrd consolidation function type from string to numeric value
  96. */
  97. enum rrd_cf_type
  98. rrd_cf_from_string(const char *str)
  99. {
  100. if (g_ascii_strcasecmp(str, "average") == 0) {
  101. return RRD_CF_AVERAGE;
  102. }
  103. else if (g_ascii_strcasecmp(str, "minimum") == 0) {
  104. return RRD_CF_MINIMUM;
  105. }
  106. else if (g_ascii_strcasecmp(str, "maximum") == 0) {
  107. return RRD_CF_MAXIMUM;
  108. }
  109. else if (g_ascii_strcasecmp(str, "last") == 0) {
  110. return RRD_CF_LAST;
  111. }
  112. /* XXX: add other CF functions supported by rrd */
  113. return RRD_CF_INVALID;
  114. }
  115. /**
  116. * Convert numeric presentation of cf to string
  117. */
  118. const char *
  119. rrd_cf_to_string(enum rrd_cf_type type)
  120. {
  121. switch (type) {
  122. case RRD_CF_AVERAGE:
  123. return "AVERAGE";
  124. case RRD_CF_MINIMUM:
  125. return "MINIMUM";
  126. case RRD_CF_MAXIMUM:
  127. return "MAXIMUM";
  128. case RRD_CF_LAST:
  129. return "LAST";
  130. default:
  131. return "U";
  132. }
  133. /* XXX: add other CF functions supported by rrd */
  134. return "U";
  135. }
  136. void rrd_make_default_rra(const char *cf_name,
  137. gulong pdp_cnt,
  138. gulong rows,
  139. struct rrd_rra_def *rra)
  140. {
  141. g_assert(cf_name != NULL);
  142. g_assert(rrd_cf_from_string(cf_name) != RRD_CF_INVALID);
  143. rra->pdp_cnt = pdp_cnt;
  144. rra->row_cnt = rows;
  145. rspamd_strlcpy(rra->cf_nam, cf_name, sizeof(rra->cf_nam));
  146. memset(rra->par, 0, sizeof(rra->par));
  147. rra->par[RRA_cdp_xff_val].dv = 0.5;
  148. }
  149. void rrd_make_default_ds(const char *name,
  150. const char *type,
  151. gulong pdp_step,
  152. struct rrd_ds_def *ds)
  153. {
  154. g_assert(name != NULL);
  155. g_assert(type != NULL);
  156. g_assert(rrd_dst_from_string(type) != RRD_DST_INVALID);
  157. rspamd_strlcpy(ds->ds_nam, name, sizeof(ds->ds_nam));
  158. rspamd_strlcpy(ds->dst, type, sizeof(ds->dst));
  159. memset(ds->par, 0, sizeof(ds->par));
  160. ds->par[RRD_DS_mrhb_cnt].lv = pdp_step * 2;
  161. ds->par[RRD_DS_min_val].dv = NAN;
  162. ds->par[RRD_DS_max_val].dv = NAN;
  163. }
  164. /**
  165. * Check rrd file for correctness (size, cookies, etc)
  166. */
  167. static gboolean
  168. rspamd_rrd_check_file(const char *filename, gboolean need_data, GError **err)
  169. {
  170. int fd, i;
  171. struct stat st;
  172. struct rrd_file_head head;
  173. struct rrd_rra_def rra;
  174. int head_size;
  175. fd = open(filename, O_RDWR);
  176. if (fd == -1) {
  177. g_set_error(err,
  178. rrd_error_quark(), errno, "rrd open error: %s", strerror(errno));
  179. return FALSE;
  180. }
  181. if (fstat(fd, &st) == -1) {
  182. g_set_error(err,
  183. rrd_error_quark(), errno, "rrd stat error: %s", strerror(errno));
  184. close(fd);
  185. return FALSE;
  186. }
  187. if (st.st_size < (goffset) sizeof(struct rrd_file_head)) {
  188. /* We have trimmed file */
  189. g_set_error(err, rrd_error_quark(), EINVAL, "rrd size is bad: %ud",
  190. (unsigned int) st.st_size);
  191. close(fd);
  192. return FALSE;
  193. }
  194. /* Try to read header */
  195. if (read(fd, &head, sizeof(head)) != sizeof(head)) {
  196. g_set_error(err,
  197. rrd_error_quark(), errno, "rrd read head error: %s",
  198. strerror(errno));
  199. close(fd);
  200. return FALSE;
  201. }
  202. /* Check magic */
  203. if (memcmp(head.version, RRD_VERSION, sizeof(head.version)) != 0) {
  204. g_set_error(err,
  205. rrd_error_quark(), EINVAL, "rrd head error: bad cookie");
  206. close(fd);
  207. return FALSE;
  208. }
  209. if (head.float_cookie != RRD_FLOAT_COOKIE) {
  210. g_set_error(err,
  211. rrd_error_quark(), EINVAL, "rrd head error: another architecture "
  212. "(file cookie %g != our cookie %g)",
  213. head.float_cookie, RRD_FLOAT_COOKIE);
  214. close(fd);
  215. return FALSE;
  216. }
  217. /* Check for other params */
  218. if (head.ds_cnt <= 0 || head.rra_cnt <= 0) {
  219. g_set_error(err,
  220. rrd_error_quark(), EINVAL, "rrd head cookies error: bad rra or ds count");
  221. close(fd);
  222. return FALSE;
  223. }
  224. /* Now we can calculate the overall size of rrd */
  225. head_size = sizeof(struct rrd_file_head) +
  226. sizeof(struct rrd_ds_def) * head.ds_cnt +
  227. sizeof(struct rrd_rra_def) * head.rra_cnt +
  228. sizeof(struct rrd_live_head) +
  229. sizeof(struct rrd_pdp_prep) * head.ds_cnt +
  230. sizeof(struct rrd_cdp_prep) * head.ds_cnt * head.rra_cnt +
  231. sizeof(struct rrd_rra_ptr) * head.rra_cnt;
  232. if (st.st_size < (goffset) head_size) {
  233. g_set_error(err,
  234. rrd_error_quark(), errno, "rrd file seems to have stripped header: %d",
  235. head_size);
  236. close(fd);
  237. return FALSE;
  238. }
  239. if (need_data) {
  240. /* Now check rra */
  241. if (lseek(fd, sizeof(struct rrd_ds_def) * head.ds_cnt,
  242. SEEK_CUR) == -1) {
  243. g_set_error(err,
  244. rrd_error_quark(), errno, "rrd head lseek error: %s",
  245. strerror(errno));
  246. close(fd);
  247. return FALSE;
  248. }
  249. for (i = 0; i < (int) head.rra_cnt; i++) {
  250. if (read(fd, &rra, sizeof(rra)) != sizeof(rra)) {
  251. g_set_error(err,
  252. rrd_error_quark(), errno, "rrd read rra error: %s",
  253. strerror(errno));
  254. close(fd);
  255. return FALSE;
  256. }
  257. head_size += rra.row_cnt * head.ds_cnt * sizeof(double);
  258. }
  259. if (st.st_size != head_size) {
  260. g_set_error(err,
  261. rrd_error_quark(), EINVAL, "rrd file seems to have incorrect size: %d, must be %d",
  262. (int) st.st_size, head_size);
  263. close(fd);
  264. return FALSE;
  265. }
  266. }
  267. close(fd);
  268. return TRUE;
  269. }
  270. /**
  271. * Adjust pointers in mmapped rrd file
  272. * @param file
  273. */
  274. static void
  275. rspamd_rrd_adjust_pointers(struct rspamd_rrd_file *file, gboolean completed)
  276. {
  277. uint8_t *ptr;
  278. ptr = file->map;
  279. file->stat_head = (struct rrd_file_head *) ptr;
  280. ptr += sizeof(struct rrd_file_head);
  281. file->ds_def = (struct rrd_ds_def *) ptr;
  282. ptr += sizeof(struct rrd_ds_def) * file->stat_head->ds_cnt;
  283. file->rra_def = (struct rrd_rra_def *) ptr;
  284. ptr += sizeof(struct rrd_rra_def) * file->stat_head->rra_cnt;
  285. file->live_head = (struct rrd_live_head *) ptr;
  286. ptr += sizeof(struct rrd_live_head);
  287. file->pdp_prep = (struct rrd_pdp_prep *) ptr;
  288. ptr += sizeof(struct rrd_pdp_prep) * file->stat_head->ds_cnt;
  289. file->cdp_prep = (struct rrd_cdp_prep *) ptr;
  290. ptr += sizeof(struct rrd_cdp_prep) * file->stat_head->rra_cnt *
  291. file->stat_head->ds_cnt;
  292. file->rra_ptr = (struct rrd_rra_ptr *) ptr;
  293. if (completed) {
  294. ptr += sizeof(struct rrd_rra_ptr) * file->stat_head->rra_cnt;
  295. file->rrd_value = (double *) ptr;
  296. }
  297. else {
  298. file->rrd_value = NULL;
  299. }
  300. }
  301. static void
  302. rspamd_rrd_calculate_checksum(struct rspamd_rrd_file *file)
  303. {
  304. unsigned char sigbuf[rspamd_cryptobox_HASHBYTES];
  305. struct rrd_ds_def *ds;
  306. unsigned int i;
  307. rspamd_cryptobox_hash_state_t st;
  308. if (file->finalized) {
  309. rspamd_cryptobox_hash_init(&st, NULL, 0);
  310. rspamd_cryptobox_hash_update(&st, file->filename, strlen(file->filename));
  311. for (i = 0; i < file->stat_head->ds_cnt; i++) {
  312. ds = &file->ds_def[i];
  313. rspamd_cryptobox_hash_update(&st, ds->ds_nam, sizeof(ds->ds_nam));
  314. }
  315. rspamd_cryptobox_hash_final(&st, sigbuf);
  316. file->id = rspamd_encode_base32(sigbuf, sizeof(sigbuf), RSPAMD_BASE32_DEFAULT);
  317. }
  318. }
  319. static int
  320. rspamd_rrd_open_exclusive(const char *filename)
  321. {
  322. struct timespec sleep_ts = {
  323. .tv_sec = 0,
  324. .tv_nsec = 1000000};
  325. int fd;
  326. fd = open(filename, O_RDWR);
  327. if (fd == -1) {
  328. return -1;
  329. }
  330. for (;;) {
  331. if (rspamd_file_lock(fd, TRUE) == -1) {
  332. if (errno == EAGAIN || errno == EWOULDBLOCK) {
  333. nanosleep(&sleep_ts, NULL);
  334. continue;
  335. }
  336. else {
  337. close(fd);
  338. return -1;
  339. }
  340. }
  341. else {
  342. break;
  343. }
  344. }
  345. return fd;
  346. };
  347. /**
  348. * Open completed or incompleted rrd file
  349. * @param filename
  350. * @param completed
  351. * @param err
  352. * @return
  353. */
  354. static struct rspamd_rrd_file *
  355. rspamd_rrd_open_common(const char *filename, gboolean completed, GError **err)
  356. {
  357. struct rspamd_rrd_file *file;
  358. int fd;
  359. struct stat st;
  360. if (!rspamd_rrd_check_file(filename, completed, err)) {
  361. return NULL;
  362. }
  363. file = g_malloc0(sizeof(struct rspamd_rrd_file));
  364. /* Open file */
  365. fd = rspamd_rrd_open_exclusive(filename);
  366. if (fd == -1) {
  367. g_set_error(err,
  368. rrd_error_quark(), errno, "rrd open error: %s", strerror(errno));
  369. g_free(file);
  370. return FALSE;
  371. }
  372. if (fstat(fd, &st) == -1) {
  373. g_set_error(err,
  374. rrd_error_quark(), errno, "rrd stat error: %s", strerror(errno));
  375. rspamd_file_unlock(fd, FALSE);
  376. g_free(file);
  377. close(fd);
  378. return FALSE;
  379. }
  380. /* Mmap file */
  381. file->size = st.st_size;
  382. if ((file->map =
  383. mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
  384. MAP_SHARED, fd, 0)) == MAP_FAILED) {
  385. rspamd_file_unlock(fd, FALSE);
  386. close(fd);
  387. g_set_error(err,
  388. rrd_error_quark(), ENOMEM, "mmap failed: %s", strerror(errno));
  389. g_free(file);
  390. return NULL;
  391. }
  392. file->fd = fd;
  393. /* Adjust pointers */
  394. rspamd_rrd_adjust_pointers(file, completed);
  395. /* Mark it as finalized */
  396. file->finalized = completed;
  397. file->filename = g_strdup(filename);
  398. rspamd_rrd_calculate_checksum(file);
  399. return file;
  400. }
  401. /**
  402. * Open (and mmap) existing RRD file
  403. * @param filename path
  404. * @param err error pointer
  405. * @return rrd file structure
  406. */
  407. struct rspamd_rrd_file *
  408. rspamd_rrd_open(const char *filename, GError **err)
  409. {
  410. struct rspamd_rrd_file *file;
  411. if ((file = rspamd_rrd_open_common(filename, TRUE, err))) {
  412. msg_info_rrd("rrd file opened: %s", filename);
  413. }
  414. return file;
  415. }
  416. /**
  417. * Create basic header for rrd file
  418. * @param filename file path
  419. * @param ds_count number of data sources
  420. * @param rra_count number of round robin archives
  421. * @param pdp_step step of primary data points
  422. * @param err error pointer
  423. * @return TRUE if file has been created
  424. */
  425. struct rspamd_rrd_file *
  426. rspamd_rrd_create(const char *filename,
  427. gulong ds_count,
  428. gulong rra_count,
  429. gulong pdp_step,
  430. double initial_ticks,
  431. GError **err)
  432. {
  433. struct rspamd_rrd_file *new;
  434. struct rrd_file_head head;
  435. struct rrd_ds_def ds;
  436. struct rrd_rra_def rra;
  437. struct rrd_live_head lh;
  438. struct rrd_pdp_prep pdp;
  439. struct rrd_cdp_prep cdp;
  440. struct rrd_rra_ptr rra_ptr;
  441. int fd;
  442. unsigned int i, j;
  443. /* Open file */
  444. fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0644);
  445. if (fd == -1) {
  446. g_set_error(err,
  447. rrd_error_quark(), errno, "rrd create error: %s",
  448. strerror(errno));
  449. return NULL;
  450. }
  451. rspamd_file_lock(fd, FALSE);
  452. /* Fill header */
  453. memset(&head, 0, sizeof(head));
  454. head.rra_cnt = rra_count;
  455. head.ds_cnt = ds_count;
  456. head.pdp_step = pdp_step;
  457. memcpy(head.cookie, RRD_COOKIE, sizeof(head.cookie));
  458. memcpy(head.version, RRD_VERSION, sizeof(head.version));
  459. head.float_cookie = RRD_FLOAT_COOKIE;
  460. if (write(fd, &head, sizeof(head)) != sizeof(head)) {
  461. rspamd_file_unlock(fd, FALSE);
  462. close(fd);
  463. g_set_error(err,
  464. rrd_error_quark(), errno, "rrd write error: %s", strerror(errno));
  465. return NULL;
  466. }
  467. /* Fill DS section */
  468. memset(&ds, 0, sizeof(ds));
  469. memset(&ds.ds_nam, 0, sizeof(ds.ds_nam));
  470. memcpy(&ds.dst, "COUNTER", sizeof("COUNTER"));
  471. memset(&ds.par, 0, sizeof(ds.par));
  472. for (i = 0; i < ds_count; i++) {
  473. if (write(fd, &ds, sizeof(ds)) != sizeof(ds)) {
  474. rspamd_file_unlock(fd, FALSE);
  475. close(fd);
  476. g_set_error(err,
  477. rrd_error_quark(), errno, "rrd write error: %s",
  478. strerror(errno));
  479. return NULL;
  480. }
  481. }
  482. /* Fill RRA section */
  483. memset(&rra, 0, sizeof(rra));
  484. memcpy(&rra.cf_nam, "AVERAGE", sizeof("AVERAGE"));
  485. rra.pdp_cnt = 1;
  486. memset(&rra.par, 0, sizeof(rra.par));
  487. for (i = 0; i < rra_count; i++) {
  488. if (write(fd, &rra, sizeof(rra)) != sizeof(rra)) {
  489. rspamd_file_unlock(fd, FALSE);
  490. close(fd);
  491. g_set_error(err,
  492. rrd_error_quark(), errno, "rrd write error: %s",
  493. strerror(errno));
  494. return NULL;
  495. }
  496. }
  497. /* Fill live header */
  498. memset(&lh, 0, sizeof(lh));
  499. lh.last_up = (glong) initial_ticks;
  500. lh.last_up_usec = (glong) ((initial_ticks - lh.last_up) * 1e6f);
  501. if (write(fd, &lh, sizeof(lh)) != sizeof(lh)) {
  502. rspamd_file_unlock(fd, FALSE);
  503. close(fd);
  504. g_set_error(err,
  505. rrd_error_quark(), errno, "rrd write error: %s", strerror(errno));
  506. return NULL;
  507. }
  508. /* Fill pdp prep */
  509. memset(&pdp, 0, sizeof(pdp));
  510. memcpy(&pdp.last_ds, "U", sizeof("U"));
  511. memset(&pdp.scratch, 0, sizeof(pdp.scratch));
  512. pdp.scratch[PDP_val].dv = NAN;
  513. pdp.scratch[PDP_unkn_sec_cnt].lv = 0;
  514. for (i = 0; i < ds_count; i++) {
  515. if (write(fd, &pdp, sizeof(pdp)) != sizeof(pdp)) {
  516. rspamd_file_unlock(fd, FALSE);
  517. close(fd);
  518. g_set_error(err,
  519. rrd_error_quark(), errno, "rrd write error: %s",
  520. strerror(errno));
  521. return NULL;
  522. }
  523. }
  524. /* Fill cdp prep */
  525. memset(&cdp, 0, sizeof(cdp));
  526. memset(&cdp.scratch, 0, sizeof(cdp.scratch));
  527. cdp.scratch[CDP_val].dv = NAN;
  528. cdp.scratch[CDP_unkn_pdp_cnt].lv = 0;
  529. for (i = 0; i < rra_count; i++) {
  530. for (j = 0; j < ds_count; j++) {
  531. if (write(fd, &cdp, sizeof(cdp)) != sizeof(cdp)) {
  532. rspamd_file_unlock(fd, FALSE);
  533. close(fd);
  534. g_set_error(err,
  535. rrd_error_quark(), errno, "rrd write error: %s",
  536. strerror(errno));
  537. return NULL;
  538. }
  539. }
  540. }
  541. /* Set row pointers */
  542. memset(&rra_ptr, 0, sizeof(rra_ptr));
  543. for (i = 0; i < rra_count; i++) {
  544. if (write(fd, &rra_ptr, sizeof(rra_ptr)) != sizeof(rra_ptr)) {
  545. rspamd_file_unlock(fd, FALSE);
  546. close(fd);
  547. g_set_error(err,
  548. rrd_error_quark(), errno, "rrd write error: %s",
  549. strerror(errno));
  550. return NULL;
  551. }
  552. }
  553. rspamd_file_unlock(fd, FALSE);
  554. close(fd);
  555. new = rspamd_rrd_open_common(filename, FALSE, err);
  556. return new;
  557. }
  558. /**
  559. * Add data sources to rrd file
  560. * @param filename path to file
  561. * @param ds array of struct rrd_ds_def
  562. * @param err error pointer
  563. * @return TRUE if data sources were added
  564. */
  565. gboolean
  566. rspamd_rrd_add_ds(struct rspamd_rrd_file *file, GArray *ds, GError **err)
  567. {
  568. if (file == NULL || file->stat_head->ds_cnt * sizeof(struct rrd_ds_def) !=
  569. ds->len) {
  570. g_set_error(err,
  571. rrd_error_quark(), EINVAL, "rrd add ds failed: wrong arguments");
  572. return FALSE;
  573. }
  574. /* Straightforward memcpy */
  575. memcpy(file->ds_def, ds->data, ds->len);
  576. return TRUE;
  577. }
  578. /**
  579. * Add round robin archives to rrd file
  580. * @param filename path to file
  581. * @param ds array of struct rrd_rra_def
  582. * @param err error pointer
  583. * @return TRUE if archives were added
  584. */
  585. gboolean
  586. rspamd_rrd_add_rra(struct rspamd_rrd_file *file, GArray *rra, GError **err)
  587. {
  588. if (file == NULL || file->stat_head->rra_cnt *
  589. sizeof(struct rrd_rra_def) !=
  590. rra->len) {
  591. g_set_error(err,
  592. rrd_error_quark(), EINVAL, "rrd add rra failed: wrong arguments");
  593. return FALSE;
  594. }
  595. /* Straightforward memcpy */
  596. memcpy(file->rra_def, rra->data, rra->len);
  597. return TRUE;
  598. }
  599. /**
  600. * Finalize rrd file header and initialize all RRA in the file
  601. * @param filename file path
  602. * @param err error pointer
  603. * @return TRUE if rrd file is ready for use
  604. */
  605. gboolean
  606. rspamd_rrd_finalize(struct rspamd_rrd_file *file, GError **err)
  607. {
  608. int fd;
  609. unsigned int i;
  610. int count = 0;
  611. double vbuf[1024];
  612. struct stat st;
  613. if (file == NULL || file->filename == NULL || file->fd == -1) {
  614. g_set_error(err,
  615. rrd_error_quark(), EINVAL, "rrd add rra failed: wrong arguments");
  616. return FALSE;
  617. }
  618. fd = file->fd;
  619. if (lseek(fd, 0, SEEK_END) == -1) {
  620. g_set_error(err,
  621. rrd_error_quark(), errno, "rrd seek error: %s", strerror(errno));
  622. close(fd);
  623. return FALSE;
  624. }
  625. /* Adjust CDP */
  626. for (i = 0; i < file->stat_head->rra_cnt; i++) {
  627. file->cdp_prep->scratch[CDP_unkn_pdp_cnt].lv = 0;
  628. /* Randomize row pointer (disabled) */
  629. /* file->rra_ptr->cur_row = g_random_int () % file->rra_def[i].row_cnt; */
  630. file->rra_ptr->cur_row = file->rra_def[i].row_cnt - 1;
  631. /* Calculate values count */
  632. count += file->rra_def[i].row_cnt * file->stat_head->ds_cnt;
  633. }
  634. munmap(file->map, file->size);
  635. /* Write values */
  636. for (i = 0; i < G_N_ELEMENTS(vbuf); i++) {
  637. vbuf[i] = NAN;
  638. }
  639. while (count > 0) {
  640. /* Write values in buffered matter */
  641. if (write(fd, vbuf,
  642. MIN((int) G_N_ELEMENTS(vbuf), count) * sizeof(double)) == -1) {
  643. g_set_error(err,
  644. rrd_error_quark(), errno, "rrd write error: %s",
  645. strerror(errno));
  646. close(fd);
  647. return FALSE;
  648. }
  649. count -= G_N_ELEMENTS(vbuf);
  650. }
  651. if (fstat(fd, &st) == -1) {
  652. g_set_error(err,
  653. rrd_error_quark(), errno, "rrd stat error: %s", strerror(errno));
  654. close(fd);
  655. return FALSE;
  656. }
  657. /* Mmap again */
  658. file->size = st.st_size;
  659. if ((file->map =
  660. mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
  661. 0)) == MAP_FAILED) {
  662. close(fd);
  663. g_set_error(err,
  664. rrd_error_quark(), ENOMEM, "mmap failed: %s", strerror(errno));
  665. return FALSE;
  666. }
  667. /* Adjust pointers */
  668. rspamd_rrd_adjust_pointers(file, TRUE);
  669. file->finalized = TRUE;
  670. rspamd_rrd_calculate_checksum(file);
  671. msg_info_rrd("rrd file created: %s", file->filename);
  672. return TRUE;
  673. }
  674. /**
  675. * Update pdp_prep data
  676. * @param file rrd file
  677. * @param vals new values
  678. * @param pdp_new new pdp array
  679. * @param interval time elapsed from the last update
  680. * @return
  681. */
  682. static gboolean
  683. rspamd_rrd_update_pdp_prep(struct rspamd_rrd_file *file,
  684. double *vals,
  685. double *pdp_new,
  686. double interval)
  687. {
  688. unsigned int i;
  689. enum rrd_dst_type type;
  690. for (i = 0; i < file->stat_head->ds_cnt; i++) {
  691. type = rrd_dst_from_string(file->ds_def[i].dst);
  692. if (file->ds_def[i].par[RRD_DS_mrhb_cnt].lv < interval) {
  693. rspamd_strlcpy(file->pdp_prep[i].last_ds, "U",
  694. sizeof(file->pdp_prep[i].last_ds));
  695. pdp_new[i] = NAN;
  696. msg_debug_rrd("adding unknown point interval %.3f is less than heartbeat %l",
  697. interval, file->ds_def[i].par[RRD_DS_mrhb_cnt].lv);
  698. }
  699. else {
  700. switch (type) {
  701. case RRD_DST_COUNTER:
  702. case RRD_DST_DERIVE:
  703. if (file->pdp_prep[i].last_ds[0] == 'U') {
  704. pdp_new[i] = NAN;
  705. msg_debug_rrd("last point is NaN for point %ud", i);
  706. }
  707. else {
  708. pdp_new[i] = vals[i] - strtod(file->pdp_prep[i].last_ds,
  709. NULL);
  710. msg_debug_rrd("new PDP %ud, %.3f", i, pdp_new[i]);
  711. }
  712. break;
  713. case RRD_DST_GAUGE:
  714. pdp_new[i] = vals[i] * interval;
  715. msg_debug_rrd("new PDP %ud, %.3f", i, pdp_new[i]);
  716. break;
  717. case RRD_DST_ABSOLUTE:
  718. pdp_new[i] = vals[i];
  719. msg_debug_rrd("new PDP %ud, %.3f", i, pdp_new[i]);
  720. break;
  721. default:
  722. return FALSE;
  723. }
  724. }
  725. /* Copy value to the last_ds */
  726. if (!isnan(vals[i])) {
  727. rspamd_snprintf(file->pdp_prep[i].last_ds,
  728. sizeof(file->pdp_prep[i].last_ds), "%.4f", vals[i]);
  729. }
  730. else {
  731. file->pdp_prep[i].last_ds[0] = 'U';
  732. file->pdp_prep[i].last_ds[1] = '\0';
  733. }
  734. }
  735. return TRUE;
  736. }
  737. /**
  738. * Update step for this pdp
  739. * @param file
  740. * @param pdp_new new pdp array
  741. * @param pdp_temp temp pdp array
  742. * @param interval time till last update
  743. * @param pre_int pre interval
  744. * @param post_int post intervall
  745. * @param pdp_diff time till last pdp update
  746. */
  747. static void
  748. rspamd_rrd_update_pdp_step(struct rspamd_rrd_file *file,
  749. double *pdp_new,
  750. double *pdp_temp,
  751. double interval,
  752. gulong pdp_diff)
  753. {
  754. unsigned int i;
  755. rrd_value_t *scratch;
  756. gulong heartbeat;
  757. for (i = 0; i < file->stat_head->ds_cnt; i++) {
  758. scratch = file->pdp_prep[i].scratch;
  759. heartbeat = file->ds_def[i].par[RRD_DS_mrhb_cnt].lv;
  760. if (!isnan(pdp_new[i])) {
  761. if (isnan(scratch[PDP_val].dv)) {
  762. scratch[PDP_val].dv = 0;
  763. }
  764. }
  765. /* Check interval value for heartbeat for this DS */
  766. if ((interval > heartbeat) ||
  767. (file->stat_head->pdp_step / 2.0 < scratch[PDP_unkn_sec_cnt].lv)) {
  768. pdp_temp[i] = NAN;
  769. }
  770. else {
  771. pdp_temp[i] = scratch[PDP_val].dv /
  772. ((double) (pdp_diff - scratch[PDP_unkn_sec_cnt].lv));
  773. }
  774. if (isnan(pdp_new[i])) {
  775. scratch[PDP_unkn_sec_cnt].lv = interval;
  776. scratch[PDP_val].dv = NAN;
  777. }
  778. else {
  779. scratch[PDP_unkn_sec_cnt].lv = 0;
  780. scratch[PDP_val].dv = pdp_new[i] / interval;
  781. }
  782. msg_debug_rrd("new temp PDP %ud, %.3f -> %.3f, scratch: %3f",
  783. i, pdp_new[i], pdp_temp[i],
  784. scratch[PDP_val].dv);
  785. }
  786. }
  787. /**
  788. * Update CDP for this rra
  789. * @param file rrd file
  790. * @param pdp_steps how much pdp steps elapsed from the last update
  791. * @param pdp_offset offset from pdp
  792. * @param rra_steps how much steps must be updated for this rra
  793. * @param rra_index index of desired rra
  794. * @param pdp_temp temporary pdp points
  795. */
  796. static void
  797. rspamd_rrd_update_cdp(struct rspamd_rrd_file *file,
  798. double pdp_steps,
  799. double pdp_offset,
  800. gulong *rra_steps,
  801. gulong rra_index,
  802. double *pdp_temp)
  803. {
  804. unsigned int i;
  805. struct rrd_rra_def *rra;
  806. rrd_value_t *scratch;
  807. enum rrd_cf_type cf;
  808. double last_cdp = INFINITY, cur_cdp = INFINITY;
  809. gulong pdp_in_cdp;
  810. rra = &file->rra_def[rra_index];
  811. cf = rrd_cf_from_string(rra->cf_nam);
  812. /* Iterate over all DS for this RRA */
  813. for (i = 0; i < file->stat_head->ds_cnt; i++) {
  814. /* Get CDP for this RRA and DS */
  815. scratch =
  816. file->cdp_prep[rra_index * file->stat_head->ds_cnt + i].scratch;
  817. if (rra->pdp_cnt > 1) {
  818. /* Do we have any CDP to update for this rra ? */
  819. if (rra_steps[rra_index] > 0) {
  820. if (isnan(pdp_temp[i])) {
  821. /* New pdp is nan */
  822. /* Increment unknown points count */
  823. scratch[CDP_unkn_pdp_cnt].lv += pdp_offset;
  824. /* Reset secondary value */
  825. scratch[CDP_secondary_val].dv = NAN;
  826. }
  827. else {
  828. scratch[CDP_secondary_val].dv = pdp_temp[i];
  829. }
  830. /* Check XFF for this rra */
  831. if (scratch[CDP_unkn_pdp_cnt].lv > rra->pdp_cnt *
  832. rra->par[RRA_cdp_xff_val].lv) {
  833. /* XFF is reached */
  834. scratch[CDP_primary_val].dv = NAN;
  835. }
  836. else {
  837. /* Need to initialize CDP using specified consolidation */
  838. switch (cf) {
  839. case RRD_CF_AVERAGE:
  840. last_cdp =
  841. isnan(scratch[CDP_val].dv) ? 0.0 : scratch[CDP_val].dv;
  842. cur_cdp = isnan(pdp_temp[i]) ? 0.0 : pdp_temp[i];
  843. scratch[CDP_primary_val].dv =
  844. (last_cdp + cur_cdp *
  845. pdp_offset) /
  846. (rra->pdp_cnt - scratch[CDP_unkn_pdp_cnt].lv);
  847. break;
  848. case RRD_CF_MAXIMUM:
  849. last_cdp =
  850. isnan(scratch[CDP_val].dv) ? -INFINITY : scratch[CDP_val].dv;
  851. cur_cdp = isnan(pdp_temp[i]) ? -INFINITY : pdp_temp[i];
  852. scratch[CDP_primary_val].dv = MAX(last_cdp, cur_cdp);
  853. break;
  854. case RRD_CF_MINIMUM:
  855. last_cdp =
  856. isnan(scratch[CDP_val].dv) ? INFINITY : scratch[CDP_val].dv;
  857. cur_cdp = isnan(pdp_temp[i]) ? INFINITY : pdp_temp[i];
  858. scratch[CDP_primary_val].dv = MIN(last_cdp, cur_cdp);
  859. break;
  860. case RRD_CF_LAST:
  861. default:
  862. scratch[CDP_primary_val].dv = pdp_temp[i];
  863. last_cdp = INFINITY;
  864. break;
  865. }
  866. }
  867. /* Init carry of this CDP */
  868. pdp_in_cdp = (pdp_steps - pdp_offset) / rra->pdp_cnt;
  869. if (pdp_in_cdp == 0 || isnan(pdp_temp[i])) {
  870. /* Set overflow */
  871. switch (cf) {
  872. case RRD_CF_AVERAGE:
  873. scratch[CDP_val].dv = 0;
  874. break;
  875. case RRD_CF_MAXIMUM:
  876. scratch[CDP_val].dv = -INFINITY;
  877. break;
  878. case RRD_CF_MINIMUM:
  879. scratch[CDP_val].dv = INFINITY;
  880. break;
  881. default:
  882. scratch[CDP_val].dv = NAN;
  883. break;
  884. }
  885. }
  886. else {
  887. /* Special carry for average */
  888. if (cf == RRD_CF_AVERAGE) {
  889. scratch[CDP_val].dv = pdp_temp[i] * pdp_in_cdp;
  890. }
  891. else {
  892. scratch[CDP_val].dv = pdp_temp[i];
  893. }
  894. }
  895. scratch[CDP_unkn_pdp_cnt].lv = 0;
  896. msg_debug_rrd("update cdp for DS %d with value %.3f, "
  897. "stored value: %.3f, carry: %.3f",
  898. i, last_cdp,
  899. scratch[CDP_primary_val].dv, scratch[CDP_val].dv);
  900. }
  901. /* In this case we just need to update cdp_prep for this RRA */
  902. else {
  903. if (isnan(pdp_temp[i])) {
  904. /* Just increase undefined zone */
  905. scratch[CDP_unkn_pdp_cnt].lv += pdp_steps;
  906. }
  907. else {
  908. /* Calculate cdp value */
  909. last_cdp = scratch[CDP_val].dv;
  910. switch (cf) {
  911. case RRD_CF_AVERAGE:
  912. if (isnan(last_cdp)) {
  913. scratch[CDP_val].dv = pdp_temp[i] * pdp_steps;
  914. }
  915. else {
  916. scratch[CDP_val].dv = last_cdp + pdp_temp[i] *
  917. pdp_steps;
  918. }
  919. break;
  920. case RRD_CF_MAXIMUM:
  921. scratch[CDP_val].dv = MAX(last_cdp, pdp_temp[i]);
  922. break;
  923. case RRD_CF_MINIMUM:
  924. scratch[CDP_val].dv = MIN(last_cdp, pdp_temp[i]);
  925. break;
  926. case RRD_CF_LAST:
  927. scratch[CDP_val].dv = pdp_temp[i];
  928. break;
  929. default:
  930. scratch[CDP_val].dv = NAN;
  931. break;
  932. }
  933. }
  934. msg_debug_rrd("aggregate cdp %d with pdp %.3f, "
  935. "stored value: %.3f",
  936. i, pdp_temp[i], scratch[CDP_val].dv);
  937. }
  938. }
  939. else {
  940. /* We have nothing to consolidate, but we may miss some pdp */
  941. if (pdp_steps > 2) {
  942. /* Just write PDP value */
  943. scratch[CDP_primary_val].dv = pdp_temp[i];
  944. scratch[CDP_secondary_val].dv = pdp_temp[i];
  945. }
  946. }
  947. }
  948. }
  949. /**
  950. * Update RRA in a file
  951. * @param file rrd file
  952. * @param rra_steps steps for each rra
  953. * @param now current time
  954. */
  955. void rspamd_rrd_write_rra(struct rspamd_rrd_file *file, gulong *rra_steps)
  956. {
  957. unsigned int i, j, ds_cnt;
  958. struct rrd_rra_def *rra;
  959. struct rrd_cdp_prep *cdp;
  960. double *rra_row = file->rrd_value, *cur_row;
  961. ds_cnt = file->stat_head->ds_cnt;
  962. /* Iterate over all RRA */
  963. for (i = 0; i < file->stat_head->rra_cnt; i++) {
  964. rra = &file->rra_def[i];
  965. if (rra_steps[i] > 0) {
  966. /* Move row ptr */
  967. if (++file->rra_ptr[i].cur_row >= rra->row_cnt) {
  968. file->rra_ptr[i].cur_row = 0;
  969. }
  970. /* Calculate seek */
  971. cdp = &file->cdp_prep[ds_cnt * i];
  972. cur_row = rra_row + ds_cnt * file->rra_ptr[i].cur_row;
  973. /* Iterate over DS */
  974. for (j = 0; j < ds_cnt; j++) {
  975. cur_row[j] = cdp[j].scratch[CDP_primary_val].dv;
  976. msg_debug_rrd("write cdp %d: %.3f", j, cur_row[j]);
  977. }
  978. }
  979. rra_row += rra->row_cnt * ds_cnt;
  980. }
  981. }
  982. /**
  983. * Add record to rrd file
  984. * @param file rrd file object
  985. * @param points points (must be row suitable for this RRA, depending on ds count)
  986. * @param err error pointer
  987. * @return TRUE if a row has been added
  988. */
  989. gboolean
  990. rspamd_rrd_add_record(struct rspamd_rrd_file *file,
  991. GArray *points,
  992. double ticks,
  993. GError **err)
  994. {
  995. double interval, *pdp_new, *pdp_temp;
  996. unsigned int i;
  997. glong seconds, microseconds;
  998. gulong pdp_steps, cur_pdp_count, prev_pdp_step, cur_pdp_step,
  999. prev_pdp_age, cur_pdp_age, *rra_steps, pdp_offset;
  1000. if (file == NULL || file->stat_head->ds_cnt * sizeof(double) !=
  1001. points->len) {
  1002. g_set_error(err,
  1003. rrd_error_quark(), EINVAL,
  1004. "rrd add points failed: wrong arguments");
  1005. return FALSE;
  1006. }
  1007. /* Get interval */
  1008. seconds = (glong) ticks;
  1009. microseconds = (glong) ((ticks - seconds) * 1000000.);
  1010. interval = ticks - ((double) file->live_head->last_up +
  1011. file->live_head->last_up_usec / 1000000.);
  1012. msg_debug_rrd("update rrd record after %.3f seconds", interval);
  1013. /* Update PDP preparation values */
  1014. pdp_new = g_malloc0(sizeof(double) * file->stat_head->ds_cnt);
  1015. pdp_temp = g_malloc0(sizeof(double) * file->stat_head->ds_cnt);
  1016. /* How much steps need to be updated in each RRA */
  1017. rra_steps = g_malloc0(sizeof(gulong) * file->stat_head->rra_cnt);
  1018. if (!rspamd_rrd_update_pdp_prep(file, (double *) points->data, pdp_new,
  1019. interval)) {
  1020. g_set_error(err,
  1021. rrd_error_quark(), EINVAL,
  1022. "rrd update pdp failed: wrong arguments");
  1023. g_free(pdp_new);
  1024. g_free(pdp_temp);
  1025. g_free(rra_steps);
  1026. return FALSE;
  1027. }
  1028. /* Calculate elapsed steps */
  1029. /* Age in seconds for previous pdp store */
  1030. prev_pdp_age = file->live_head->last_up % file->stat_head->pdp_step;
  1031. /* Time in seconds for last pdp update */
  1032. prev_pdp_step = file->live_head->last_up - prev_pdp_age;
  1033. /* Age in seconds from current time to required pdp time */
  1034. cur_pdp_age = seconds % file->stat_head->pdp_step;
  1035. /* Time of desired pdp step */
  1036. cur_pdp_step = seconds - cur_pdp_age;
  1037. cur_pdp_count = cur_pdp_step / file->stat_head->pdp_step;
  1038. pdp_steps = (cur_pdp_step - prev_pdp_step) / file->stat_head->pdp_step;
  1039. if (pdp_steps == 0) {
  1040. /* Simple update of pdp prep */
  1041. for (i = 0; i < file->stat_head->ds_cnt; i++) {
  1042. if (isnan(pdp_new[i])) {
  1043. /* Increment unknown period */
  1044. file->pdp_prep[i].scratch[PDP_unkn_sec_cnt].lv += floor(
  1045. interval);
  1046. }
  1047. else {
  1048. if (isnan(file->pdp_prep[i].scratch[PDP_val].dv)) {
  1049. /* Reset pdp to the current value */
  1050. file->pdp_prep[i].scratch[PDP_val].dv = pdp_new[i];
  1051. }
  1052. else {
  1053. /* Increment pdp value */
  1054. file->pdp_prep[i].scratch[PDP_val].dv += pdp_new[i];
  1055. }
  1056. }
  1057. }
  1058. }
  1059. else {
  1060. /* Complex update of PDP, CDP and RRA */
  1061. /* Update PDP for this step */
  1062. rspamd_rrd_update_pdp_step(file,
  1063. pdp_new,
  1064. pdp_temp,
  1065. interval,
  1066. pdp_steps * file->stat_head->pdp_step);
  1067. /* Update CDP points for each RRA*/
  1068. for (i = 0; i < file->stat_head->rra_cnt; i++) {
  1069. /* Calculate pdp offset for this RRA */
  1070. pdp_offset = file->rra_def[i].pdp_cnt - cur_pdp_count %
  1071. file->rra_def[i].pdp_cnt;
  1072. /* How much steps we got for this RRA */
  1073. if (pdp_offset <= pdp_steps) {
  1074. rra_steps[i] =
  1075. (pdp_steps - pdp_offset) / file->rra_def[i].pdp_cnt + 1;
  1076. }
  1077. else {
  1078. /* This rra have not passed enough pdp steps */
  1079. rra_steps[i] = 0;
  1080. }
  1081. msg_debug_rrd("cdp: %ud, rra steps: %ul(%ul), pdp steps: %ul",
  1082. i, rra_steps[i], pdp_offset, pdp_steps);
  1083. /* Update this specific CDP */
  1084. rspamd_rrd_update_cdp(file,
  1085. pdp_steps,
  1086. pdp_offset,
  1087. rra_steps,
  1088. i,
  1089. pdp_temp);
  1090. }
  1091. /* Write RRA */
  1092. rspamd_rrd_write_rra(file, rra_steps);
  1093. }
  1094. file->live_head->last_up = seconds;
  1095. file->live_head->last_up_usec = microseconds;
  1096. /* Sync and invalidate */
  1097. msync(file->map, file->size, MS_ASYNC | MS_INVALIDATE);
  1098. g_free(pdp_new);
  1099. g_free(pdp_temp);
  1100. g_free(rra_steps);
  1101. return TRUE;
  1102. }
  1103. /**
  1104. * Close rrd file
  1105. * @param file
  1106. * @return
  1107. */
  1108. int rspamd_rrd_close(struct rspamd_rrd_file *file)
  1109. {
  1110. if (file == NULL) {
  1111. errno = EINVAL;
  1112. return -1;
  1113. }
  1114. munmap(file->map, file->size);
  1115. close(file->fd);
  1116. g_free(file->filename);
  1117. g_free(file->id);
  1118. g_free(file);
  1119. return 0;
  1120. }
  1121. static struct rspamd_rrd_file *
  1122. rspamd_rrd_create_file(const char *path, gboolean finalize, GError **err)
  1123. {
  1124. struct rspamd_rrd_file *file;
  1125. struct rrd_ds_def ds[RSPAMD_RRD_DS_COUNT];
  1126. struct rrd_rra_def rra[RSPAMD_RRD_RRA_COUNT];
  1127. int i;
  1128. GArray ar;
  1129. /* Try to create new rrd file */
  1130. file = rspamd_rrd_create(path, RSPAMD_RRD_DS_COUNT, RSPAMD_RRD_RRA_COUNT,
  1131. 1, rspamd_get_calendar_ticks(), err);
  1132. if (file == NULL) {
  1133. return NULL;
  1134. }
  1135. /* Create DS and RRA */
  1136. for (i = METRIC_ACTION_REJECT; i < METRIC_ACTION_MAX; i++) {
  1137. rrd_make_default_ds(rspamd_action_to_str(i),
  1138. rrd_dst_to_string(RRD_DST_COUNTER), 1, &ds[i]);
  1139. }
  1140. ar.data = (char *) ds;
  1141. ar.len = sizeof(ds);
  1142. if (!rspamd_rrd_add_ds(file, &ar, err)) {
  1143. rspamd_rrd_close(file);
  1144. return NULL;
  1145. }
  1146. /* Once per minute for 1 day */
  1147. rrd_make_default_rra(rrd_cf_to_string(RRD_CF_AVERAGE),
  1148. 60, 24 * 60, &rra[0]);
  1149. /* Once per 5 minutes for 1 week */
  1150. rrd_make_default_rra(rrd_cf_to_string(RRD_CF_AVERAGE),
  1151. 5 * 60, 7 * 24 * 60 / 5, &rra[1]);
  1152. /* Once per 10 mins for 1 month */
  1153. rrd_make_default_rra(rrd_cf_to_string(RRD_CF_AVERAGE),
  1154. 60 * 10, 30 * 24 * 6, &rra[2]);
  1155. /* Once per hour for 1 year */
  1156. rrd_make_default_rra(rrd_cf_to_string(RRD_CF_AVERAGE),
  1157. 60 * 60, 365 * 24, &rra[3]);
  1158. ar.data = (char *) rra;
  1159. ar.len = sizeof(rra);
  1160. if (!rspamd_rrd_add_rra(file, &ar, err)) {
  1161. rspamd_rrd_close(file);
  1162. return NULL;
  1163. }
  1164. if (finalize && !rspamd_rrd_finalize(file, err)) {
  1165. rspamd_rrd_close(file);
  1166. return NULL;
  1167. }
  1168. return file;
  1169. }
  1170. static void
  1171. rspamd_rrd_convert_ds(struct rspamd_rrd_file *old,
  1172. struct rspamd_rrd_file *cur, int idx_old, int idx_new)
  1173. {
  1174. struct rrd_pdp_prep *pdp_prep_old, *pdp_prep_new;
  1175. struct rrd_cdp_prep *cdp_prep_old, *cdp_prep_new;
  1176. double *val_old, *val_new;
  1177. gulong rra_cnt, i, j, points_cnt, old_ds, new_ds;
  1178. rra_cnt = old->stat_head->rra_cnt;
  1179. pdp_prep_old = &old->pdp_prep[idx_old];
  1180. pdp_prep_new = &cur->pdp_prep[idx_new];
  1181. memcpy(pdp_prep_new, pdp_prep_old, sizeof(*pdp_prep_new));
  1182. val_old = old->rrd_value;
  1183. val_new = cur->rrd_value;
  1184. old_ds = old->stat_head->ds_cnt;
  1185. new_ds = cur->stat_head->ds_cnt;
  1186. for (i = 0; i < rra_cnt; i++) {
  1187. cdp_prep_old = &old->cdp_prep[i * old_ds] + idx_old;
  1188. cdp_prep_new = &cur->cdp_prep[i * new_ds] + idx_new;
  1189. memcpy(cdp_prep_new, cdp_prep_old, sizeof(*cdp_prep_new));
  1190. points_cnt = old->rra_def[i].row_cnt;
  1191. for (j = 0; j < points_cnt; j++) {
  1192. val_new[j * new_ds + idx_new] = val_old[j * old_ds + idx_old];
  1193. }
  1194. val_new += points_cnt * new_ds;
  1195. val_old += points_cnt * old_ds;
  1196. }
  1197. }
  1198. static struct rspamd_rrd_file *
  1199. rspamd_rrd_convert(const char *path, struct rspamd_rrd_file *old,
  1200. GError **err)
  1201. {
  1202. struct rspamd_rrd_file *rrd;
  1203. char tpath[PATH_MAX];
  1204. g_assert(old != NULL);
  1205. rspamd_snprintf(tpath, sizeof(tpath), "%s.new", path);
  1206. rrd = rspamd_rrd_create_file(tpath, TRUE, err);
  1207. if (rrd) {
  1208. /* Copy old data */
  1209. memcpy(rrd->live_head, old->live_head, sizeof(*rrd->live_head));
  1210. memcpy(rrd->rra_ptr, old->rra_ptr,
  1211. sizeof(*old->rra_ptr) * rrd->stat_head->rra_cnt);
  1212. /*
  1213. * Old DSes:
  1214. * 0 - spam -> reject
  1215. * 1 - probable spam -> add header
  1216. * 2 - greylist -> greylist
  1217. * 3 - ham -> ham
  1218. */
  1219. rspamd_rrd_convert_ds(old, rrd, 0, METRIC_ACTION_REJECT);
  1220. rspamd_rrd_convert_ds(old, rrd, 1, METRIC_ACTION_ADD_HEADER);
  1221. rspamd_rrd_convert_ds(old, rrd, 2, METRIC_ACTION_GREYLIST);
  1222. rspamd_rrd_convert_ds(old, rrd, 3, METRIC_ACTION_NOACTION);
  1223. if (unlink(path) == -1) {
  1224. g_set_error(err, rrd_error_quark(), errno, "cannot unlink old rrd file %s: %s",
  1225. path, strerror(errno));
  1226. unlink(tpath);
  1227. rspamd_rrd_close(rrd);
  1228. return NULL;
  1229. }
  1230. if (rename(tpath, path) == -1) {
  1231. g_set_error(err, rrd_error_quark(), errno, "cannot rename old rrd file %s: %s",
  1232. path, strerror(errno));
  1233. unlink(tpath);
  1234. rspamd_rrd_close(rrd);
  1235. return NULL;
  1236. }
  1237. }
  1238. return rrd;
  1239. }
  1240. struct rspamd_rrd_file *
  1241. rspamd_rrd_file_default(const char *path,
  1242. GError **err)
  1243. {
  1244. struct rspamd_rrd_file *file, *nf;
  1245. g_assert(path != NULL);
  1246. if (access(path, R_OK) != -1) {
  1247. /* We can open rrd file */
  1248. file = rspamd_rrd_open(path, err);
  1249. if (file == NULL) {
  1250. return NULL;
  1251. }
  1252. if (file->stat_head->rra_cnt != RSPAMD_RRD_RRA_COUNT) {
  1253. msg_err_rrd("rrd file is not suitable for rspamd: it has "
  1254. "%ul ds and %ul rra",
  1255. file->stat_head->ds_cnt,
  1256. file->stat_head->rra_cnt);
  1257. g_set_error(err, rrd_error_quark(), EINVAL, "bad rrd file");
  1258. rspamd_rrd_close(file);
  1259. return NULL;
  1260. }
  1261. else if (file->stat_head->ds_cnt == RSPAMD_RRD_OLD_DS_COUNT) {
  1262. /* Old rrd, need to convert */
  1263. msg_info_rrd("rrd file %s is not suitable for rspamd, convert it",
  1264. path);
  1265. nf = rspamd_rrd_convert(path, file, err);
  1266. rspamd_rrd_close(file);
  1267. return nf;
  1268. }
  1269. else if (file->stat_head->ds_cnt == RSPAMD_RRD_DS_COUNT) {
  1270. return file;
  1271. }
  1272. else {
  1273. msg_err_rrd("rrd file is not suitable for rspamd: it has "
  1274. "%ul ds and %ul rra",
  1275. file->stat_head->ds_cnt,
  1276. file->stat_head->rra_cnt);
  1277. g_set_error(err, rrd_error_quark(), EINVAL, "bad rrd file");
  1278. rspamd_rrd_close(file);
  1279. return NULL;
  1280. }
  1281. }
  1282. file = rspamd_rrd_create_file(path, TRUE, err);
  1283. return file;
  1284. }
  1285. struct rspamd_rrd_query_result *
  1286. rspamd_rrd_query(struct rspamd_rrd_file *file,
  1287. gulong rra_num)
  1288. {
  1289. struct rspamd_rrd_query_result *res;
  1290. struct rrd_rra_def *rra;
  1291. const double *rra_offset = NULL;
  1292. unsigned int i;
  1293. g_assert(file != NULL);
  1294. if (rra_num > file->stat_head->rra_cnt) {
  1295. msg_err_rrd("requested unexisting rra: %l", rra_num);
  1296. return NULL;
  1297. }
  1298. res = g_malloc0(sizeof(*res));
  1299. res->ds_count = file->stat_head->ds_cnt;
  1300. res->last_update = (double) file->live_head->last_up +
  1301. ((double) file->live_head->last_up_usec / 1e6f);
  1302. res->pdp_per_cdp = file->rra_def[rra_num].pdp_cnt;
  1303. res->rra_rows = file->rra_def[rra_num].row_cnt;
  1304. rra_offset = file->rrd_value;
  1305. for (i = 0; i < file->stat_head->rra_cnt; i++) {
  1306. rra = &file->rra_def[i];
  1307. if (i == rra_num) {
  1308. res->cur_row = file->rra_ptr[i].cur_row % rra->row_cnt;
  1309. break;
  1310. }
  1311. rra_offset += rra->row_cnt * res->ds_count;
  1312. }
  1313. res->data = rra_offset;
  1314. return res;
  1315. }