backend_prometheus.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #define BACKENDS_INTERNALS
  3. #include "backend_prometheus.h"
  4. // ----------------------------------------------------------------------------
  5. // PROMETHEUS
  6. // /api/v1/allmetrics?format=prometheus and /api/v1/allmetrics?format=prometheus_all_hosts
  7. static struct prometheus_server {
  8. const char *server;
  9. uint32_t hash;
  10. RRDHOST *host;
  11. time_t last_access;
  12. struct prometheus_server *next;
  13. } *prometheus_server_root = NULL;
  14. static inline time_t prometheus_server_last_access(const char *server, RRDHOST *host, time_t now) {
  15. static netdata_mutex_t prometheus_server_root_mutex = NETDATA_MUTEX_INITIALIZER;
  16. uint32_t hash = simple_hash(server);
  17. netdata_mutex_lock(&prometheus_server_root_mutex);
  18. struct prometheus_server *ps;
  19. for(ps = prometheus_server_root; ps ;ps = ps->next) {
  20. if (host == ps->host && hash == ps->hash && !strcmp(server, ps->server)) {
  21. time_t last = ps->last_access;
  22. ps->last_access = now;
  23. netdata_mutex_unlock(&prometheus_server_root_mutex);
  24. return last;
  25. }
  26. }
  27. ps = callocz(1, sizeof(struct prometheus_server));
  28. ps->server = strdupz(server);
  29. ps->hash = hash;
  30. ps->host = host;
  31. ps->last_access = now;
  32. ps->next = prometheus_server_root;
  33. prometheus_server_root = ps;
  34. netdata_mutex_unlock(&prometheus_server_root_mutex);
  35. return 0;
  36. }
  37. static inline size_t backends_prometheus_name_copy(char *d, const char *s, size_t usable) {
  38. size_t n;
  39. for(n = 0; *s && n < usable ; d++, s++, n++) {
  40. register char c = *s;
  41. if(!isalnum(c)) *d = '_';
  42. else *d = c;
  43. }
  44. *d = '\0';
  45. return n;
  46. }
  47. static inline size_t backends_prometheus_label_copy(char *d, const char *s, size_t usable) {
  48. size_t n;
  49. // make sure we can escape one character without overflowing the buffer
  50. usable--;
  51. for(n = 0; *s && n < usable ; d++, s++, n++) {
  52. register char c = *s;
  53. if(unlikely(c == '"' || c == '\\' || c == '\n')) {
  54. *d++ = '\\';
  55. n++;
  56. }
  57. *d = c;
  58. }
  59. *d = '\0';
  60. return n;
  61. }
  62. static inline char *backends_prometheus_units_copy(char *d, const char *s, size_t usable, int showoldunits) {
  63. const char *sorig = s;
  64. char *ret = d;
  65. size_t n;
  66. // Fix for issue 5227
  67. if (unlikely(showoldunits)) {
  68. static struct {
  69. const char *newunit;
  70. uint32_t hash;
  71. const char *oldunit;
  72. } units[] = {
  73. {"KiB/s", 0, "kilobytes/s"}
  74. , {"MiB/s", 0, "MB/s"}
  75. , {"GiB/s", 0, "GB/s"}
  76. , {"KiB" , 0, "KB"}
  77. , {"MiB" , 0, "MB"}
  78. , {"GiB" , 0, "GB"}
  79. , {"inodes" , 0, "Inodes"}
  80. , {"percentage" , 0, "percent"}
  81. , {"faults/s" , 0, "page faults/s"}
  82. , {"KiB/operation", 0, "kilobytes per operation"}
  83. , {"milliseconds/operation", 0, "ms per operation"}
  84. , {NULL, 0, NULL}
  85. };
  86. static int initialized = 0;
  87. int i;
  88. if(unlikely(!initialized)) {
  89. for (i = 0; units[i].newunit; i++)
  90. units[i].hash = simple_hash(units[i].newunit);
  91. initialized = 1;
  92. }
  93. uint32_t hash = simple_hash(s);
  94. for(i = 0; units[i].newunit ; i++) {
  95. if(unlikely(hash == units[i].hash && !strcmp(s, units[i].newunit))) {
  96. // info("matched extension for filename '%s': '%s'", filename, last_dot);
  97. s=units[i].oldunit;
  98. sorig = s;
  99. break;
  100. }
  101. }
  102. }
  103. *d++ = '_';
  104. for(n = 1; *s && n < usable ; d++, s++, n++) {
  105. register char c = *s;
  106. if(!isalnum(c)) *d = '_';
  107. else *d = c;
  108. }
  109. if(n == 2 && sorig[0] == '%') {
  110. n = 0;
  111. d = ret;
  112. s = "_percent";
  113. for( ; *s && n < usable ; n++) *d++ = *s++;
  114. }
  115. else if(n > 3 && sorig[n-3] == '/' && sorig[n-2] == 's') {
  116. n = n - 2;
  117. d -= 2;
  118. s = "_persec";
  119. for( ; *s && n < usable ; n++) *d++ = *s++;
  120. }
  121. *d = '\0';
  122. return ret;
  123. }
  124. #define PROMETHEUS_ELEMENT_MAX 256
  125. #define PROMETHEUS_LABELS_MAX 1024
  126. #define PROMETHEUS_VARIABLE_MAX 256
  127. #define PROMETHEUS_LABELS_MAX_NUMBER 128
  128. struct host_variables_callback_options {
  129. RRDHOST *host;
  130. BUFFER *wb;
  131. BACKEND_OPTIONS backend_options;
  132. BACKENDS_PROMETHEUS_OUTPUT_OPTIONS output_options;
  133. const char *prefix;
  134. const char *labels;
  135. time_t now;
  136. int host_header_printed;
  137. char name[PROMETHEUS_VARIABLE_MAX+1];
  138. };
  139. static int print_host_variables(RRDVAR *rv, void *data) {
  140. struct host_variables_callback_options *opts = data;
  141. if(rv->options & (RRDVAR_OPTION_CUSTOM_HOST_VAR|RRDVAR_OPTION_CUSTOM_CHART_VAR)) {
  142. if(!opts->host_header_printed) {
  143. opts->host_header_printed = 1;
  144. if(opts->output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP) {
  145. buffer_sprintf(opts->wb, "\n# COMMENT global host and chart variables\n");
  146. }
  147. }
  148. calculated_number value = rrdvar2number(rv);
  149. if(isnan(value) || isinf(value)) {
  150. if(opts->output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP)
  151. buffer_sprintf(opts->wb, "# COMMENT variable \"%s\" is %s. Skipped.\n", rv->name, (isnan(value))?"NAN":"INF");
  152. return 0;
  153. }
  154. char *label_pre = "";
  155. char *label_post = "";
  156. if(opts->labels && *opts->labels) {
  157. label_pre = "{";
  158. label_post = "}";
  159. }
  160. backends_prometheus_name_copy(opts->name, rv->name, sizeof(opts->name));
  161. if(opts->output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  162. buffer_sprintf(opts->wb
  163. , "%s_%s%s%s%s " CALCULATED_NUMBER_FORMAT " %llu\n"
  164. , opts->prefix
  165. , opts->name
  166. , label_pre
  167. , opts->labels
  168. , label_post
  169. , value
  170. , opts->now * 1000ULL
  171. );
  172. else
  173. buffer_sprintf(opts->wb, "%s_%s%s%s%s " CALCULATED_NUMBER_FORMAT "\n"
  174. , opts->prefix
  175. , opts->name
  176. , label_pre
  177. , opts->labels
  178. , label_post
  179. , value
  180. );
  181. return 1;
  182. }
  183. return 0;
  184. }
  185. static void rrd_stats_api_v1_charts_allmetrics_prometheus(RRDHOST *host, BUFFER *wb, const char *prefix, BACKEND_OPTIONS backend_options, time_t after, time_t before, int allhosts, BACKENDS_PROMETHEUS_OUTPUT_OPTIONS output_options) {
  186. rrdhost_rdlock(host);
  187. char hostname[PROMETHEUS_ELEMENT_MAX + 1];
  188. backends_prometheus_label_copy(hostname, host->hostname, PROMETHEUS_ELEMENT_MAX);
  189. char labels[PROMETHEUS_LABELS_MAX + 1] = "";
  190. if(allhosts) {
  191. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  192. buffer_sprintf(wb, "netdata_info{instance=\"%s\",application=\"%s\",version=\"%s\"} 1 %llu\n", hostname, host->program_name, host->program_version, now_realtime_usec() / USEC_PER_MS);
  193. else
  194. buffer_sprintf(wb, "netdata_info{instance=\"%s\",application=\"%s\",version=\"%s\"} 1\n", hostname, host->program_name, host->program_version);
  195. if(host->tags && *(host->tags)) {
  196. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS) {
  197. buffer_sprintf(wb, "netdata_host_tags_info{instance=\"%s\",%s} 1 %llu\n", hostname, host->tags, now_realtime_usec() / USEC_PER_MS);
  198. // deprecated, exists only for compatibility with older queries
  199. buffer_sprintf(wb, "netdata_host_tags{instance=\"%s\",%s} 1 %llu\n", hostname, host->tags, now_realtime_usec() / USEC_PER_MS);
  200. }
  201. else {
  202. buffer_sprintf(wb, "netdata_host_tags_info{instance=\"%s\",%s} 1\n", hostname, host->tags);
  203. // deprecated, exists only for compatibility with older queries
  204. buffer_sprintf(wb, "netdata_host_tags{instance=\"%s\",%s} 1\n", hostname, host->tags);
  205. }
  206. }
  207. snprintfz(labels, PROMETHEUS_LABELS_MAX, ",instance=\"%s\"", hostname);
  208. }
  209. else {
  210. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  211. buffer_sprintf(wb, "netdata_info{instance=\"%s\",application=\"%s\",version=\"%s\"} 1 %llu\n", hostname, host->program_name, host->program_version, now_realtime_usec() / USEC_PER_MS);
  212. else
  213. buffer_sprintf(wb, "netdata_info{instance=\"%s\",application=\"%s\",version=\"%s\"} 1\n", hostname, host->program_name, host->program_version);
  214. if(host->tags && *(host->tags)) {
  215. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS) {
  216. buffer_sprintf(wb, "netdata_host_tags_info{%s} 1 %llu\n", host->tags, now_realtime_usec() / USEC_PER_MS);
  217. // deprecated, exists only for compatibility with older queries
  218. buffer_sprintf(wb, "netdata_host_tags{%s} 1 %llu\n", host->tags, now_realtime_usec() / USEC_PER_MS);
  219. }
  220. else {
  221. buffer_sprintf(wb, "netdata_host_tags_info{%s} 1\n", host->tags);
  222. // deprecated, exists only for compatibility with older queries
  223. buffer_sprintf(wb, "netdata_host_tags{%s} 1\n", host->tags);
  224. }
  225. }
  226. }
  227. // send custom variables set for the host
  228. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_VARIABLES){
  229. struct host_variables_callback_options opts = {
  230. .host = host,
  231. .wb = wb,
  232. .labels = (labels[0] == ',')?&labels[1]:labels,
  233. .backend_options = backend_options,
  234. .output_options = output_options,
  235. .prefix = prefix,
  236. .now = now_realtime_sec(),
  237. .host_header_printed = 0
  238. };
  239. foreach_host_variable_callback(host, print_host_variables, &opts);
  240. }
  241. // for each chart
  242. RRDSET *st;
  243. rrdset_foreach_read(st, host) {
  244. char chart[PROMETHEUS_ELEMENT_MAX + 1];
  245. char context[PROMETHEUS_ELEMENT_MAX + 1];
  246. char family[PROMETHEUS_ELEMENT_MAX + 1];
  247. char units[PROMETHEUS_ELEMENT_MAX + 1] = "";
  248. backends_prometheus_label_copy(chart, (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && st->name)?st->name:st->id, PROMETHEUS_ELEMENT_MAX);
  249. backends_prometheus_label_copy(family, st->family, PROMETHEUS_ELEMENT_MAX);
  250. backends_prometheus_name_copy(context, st->context, PROMETHEUS_ELEMENT_MAX);
  251. if(likely(backends_can_send_rrdset(backend_options, st))) {
  252. rrdset_rdlock(st);
  253. int as_collected = (BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AS_COLLECTED);
  254. int homogeneous = 1;
  255. if(as_collected) {
  256. if(rrdset_flag_check(st, RRDSET_FLAG_HOMOGENEOUS_CHECK))
  257. rrdset_update_heterogeneous_flag(st);
  258. if(rrdset_flag_check(st, RRDSET_FLAG_HETEROGENEOUS))
  259. homogeneous = 0;
  260. }
  261. else {
  262. if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AVERAGE && !(output_options & BACKENDS_PROMETHEUS_OUTPUT_HIDEUNITS))
  263. backends_prometheus_units_copy(units, st->units, PROMETHEUS_ELEMENT_MAX, output_options & BACKENDS_PROMETHEUS_OUTPUT_OLDUNITS);
  264. }
  265. if(unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP))
  266. buffer_sprintf(wb, "\n# COMMENT %s chart \"%s\", context \"%s\", family \"%s\", units \"%s\"\n"
  267. , (homogeneous)?"homogeneous":"heterogeneous"
  268. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && st->name) ? st->name : st->id
  269. , st->context
  270. , st->family
  271. , st->units
  272. );
  273. // for each dimension
  274. RRDDIM *rd;
  275. rrddim_foreach_read(rd, st) {
  276. if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
  277. char dimension[PROMETHEUS_ELEMENT_MAX + 1];
  278. char *suffix = "";
  279. if (as_collected) {
  280. // we need as-collected / raw data
  281. if(unlikely(rd->last_collected_time.tv_sec < after))
  282. continue;
  283. const char *t = "gauge", *h = "gives";
  284. if(rd->algorithm == RRD_ALGORITHM_INCREMENTAL ||
  285. rd->algorithm == RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL) {
  286. t = "counter";
  287. h = "delta gives";
  288. suffix = "_total";
  289. }
  290. if(homogeneous) {
  291. // all the dimensions of the chart, has the same algorithm, multiplier and divisor
  292. // we add all dimensions as labels
  293. backends_prometheus_label_copy(dimension, (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  294. if(unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP))
  295. buffer_sprintf(wb
  296. , "# COMMENT %s_%s%s: chart \"%s\", context \"%s\", family \"%s\", dimension \"%s\", value * " COLLECTED_NUMBER_FORMAT " / " COLLECTED_NUMBER_FORMAT " %s %s (%s)\n"
  297. , prefix
  298. , context
  299. , suffix
  300. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && st->name) ? st->name : st->id
  301. , st->context
  302. , st->family
  303. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id
  304. , rd->multiplier
  305. , rd->divisor
  306. , h
  307. , st->units
  308. , t
  309. );
  310. if(unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_TYPES))
  311. buffer_sprintf(wb, "# TYPE %s_%s%s %s\n"
  312. , prefix
  313. , context
  314. , suffix
  315. , t
  316. );
  317. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  318. buffer_sprintf(wb
  319. , "%s_%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " COLLECTED_NUMBER_FORMAT " %llu\n"
  320. , prefix
  321. , context
  322. , suffix
  323. , chart
  324. , family
  325. , dimension
  326. , labels
  327. , rd->last_collected_value
  328. , timeval_msec(&rd->last_collected_time)
  329. );
  330. else
  331. buffer_sprintf(wb
  332. , "%s_%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " COLLECTED_NUMBER_FORMAT "\n"
  333. , prefix
  334. , context
  335. , suffix
  336. , chart
  337. , family
  338. , dimension
  339. , labels
  340. , rd->last_collected_value
  341. );
  342. }
  343. else {
  344. // the dimensions of the chart, do not have the same algorithm, multiplier or divisor
  345. // we create a metric per dimension
  346. backends_prometheus_name_copy(dimension, (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  347. if(unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP))
  348. buffer_sprintf(wb
  349. , "# COMMENT %s_%s_%s%s: chart \"%s\", context \"%s\", family \"%s\", dimension \"%s\", value * " COLLECTED_NUMBER_FORMAT " / " COLLECTED_NUMBER_FORMAT " %s %s (%s)\n"
  350. , prefix
  351. , context
  352. , dimension
  353. , suffix
  354. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && st->name) ? st->name : st->id
  355. , st->context
  356. , st->family
  357. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id
  358. , rd->multiplier
  359. , rd->divisor
  360. , h
  361. , st->units
  362. , t
  363. );
  364. if(unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_TYPES))
  365. buffer_sprintf(wb, "# TYPE %s_%s_%s%s %s\n"
  366. , prefix
  367. , context
  368. , dimension
  369. , suffix
  370. , t
  371. );
  372. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  373. buffer_sprintf(wb
  374. , "%s_%s_%s%s{chart=\"%s\",family=\"%s\"%s} " COLLECTED_NUMBER_FORMAT " %llu\n"
  375. , prefix
  376. , context
  377. , dimension
  378. , suffix
  379. , chart
  380. , family
  381. , labels
  382. , rd->last_collected_value
  383. , timeval_msec(&rd->last_collected_time)
  384. );
  385. else
  386. buffer_sprintf(wb
  387. , "%s_%s_%s%s{chart=\"%s\",family=\"%s\"%s} " COLLECTED_NUMBER_FORMAT "\n"
  388. , prefix
  389. , context
  390. , dimension
  391. , suffix
  392. , chart
  393. , family
  394. , labels
  395. , rd->last_collected_value
  396. );
  397. }
  398. }
  399. else {
  400. // we need average or sum of the data
  401. time_t first_t = after, last_t = before;
  402. calculated_number value = backend_calculate_value_from_stored_data(st, rd, after, before, backend_options, &first_t, &last_t);
  403. if(!isnan(value) && !isinf(value)) {
  404. if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AVERAGE)
  405. suffix = "_average";
  406. else if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_SUM)
  407. suffix = "_sum";
  408. backends_prometheus_label_copy(dimension, (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  409. if (unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP))
  410. buffer_sprintf(wb, "# COMMENT %s_%s%s%s: dimension \"%s\", value is %s, gauge, dt %llu to %llu inclusive\n"
  411. , prefix
  412. , context
  413. , units
  414. , suffix
  415. , (output_options & BACKENDS_PROMETHEUS_OUTPUT_NAMES && rd->name) ? rd->name : rd->id
  416. , st->units
  417. , (unsigned long long)first_t
  418. , (unsigned long long)last_t
  419. );
  420. if (unlikely(output_options & BACKENDS_PROMETHEUS_OUTPUT_TYPES))
  421. buffer_sprintf(wb, "# TYPE %s_%s%s%s gauge\n"
  422. , prefix
  423. , context
  424. , units
  425. , suffix
  426. );
  427. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_TIMESTAMPS)
  428. buffer_sprintf(wb, "%s_%s%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " CALCULATED_NUMBER_FORMAT " %llu\n"
  429. , prefix
  430. , context
  431. , units
  432. , suffix
  433. , chart
  434. , family
  435. , dimension
  436. , labels
  437. , value
  438. , last_t * MSEC_PER_SEC
  439. );
  440. else
  441. buffer_sprintf(wb, "%s_%s%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " CALCULATED_NUMBER_FORMAT "\n"
  442. , prefix
  443. , context
  444. , units
  445. , suffix
  446. , chart
  447. , family
  448. , dimension
  449. , labels
  450. , value
  451. );
  452. }
  453. }
  454. }
  455. }
  456. rrdset_unlock(st);
  457. }
  458. }
  459. rrdhost_unlock(host);
  460. }
  461. #if ENABLE_PROMETHEUS_REMOTE_WRITE
  462. inline static void remote_write_split_words(char *str, char **words, int max_words) {
  463. char *s = str;
  464. int i = 0;
  465. while(*s && i < max_words - 1) {
  466. while(*s && isspace(*s)) s++; // skip spaces to the beginning of a tag name
  467. if(*s)
  468. words[i] = s;
  469. while(*s && !isspace(*s) && *s != '=') s++; // find the end of the tag name
  470. if(*s != '=') {
  471. words[i] = NULL;
  472. break;
  473. }
  474. *s = '\0';
  475. s++;
  476. i++;
  477. while(*s && isspace(*s)) s++; // skip spaces to the beginning of a tag value
  478. if(*s && *s == '"') s++; // strip an opening quote
  479. if(*s)
  480. words[i] = s;
  481. while(*s && !isspace(*s) && *s != ',') s++; // find the end of the tag value
  482. if(*s && *s != ',') {
  483. words[i] = NULL;
  484. break;
  485. }
  486. if(s != words[i] && *(s - 1) == '"') *(s - 1) = '\0'; // strip a closing quote
  487. if(*s != '\0') {
  488. *s = '\0';
  489. s++;
  490. i++;
  491. }
  492. }
  493. }
  494. void backends_rrd_stats_remote_write_allmetrics_prometheus(
  495. RRDHOST *host
  496. , const char *__hostname
  497. , const char *prefix
  498. , BACKEND_OPTIONS backend_options
  499. , time_t after
  500. , time_t before
  501. , size_t *count_charts
  502. , size_t *count_dims
  503. , size_t *count_dims_skipped
  504. ) {
  505. char hostname[PROMETHEUS_ELEMENT_MAX + 1];
  506. backends_prometheus_label_copy(hostname, __hostname, PROMETHEUS_ELEMENT_MAX);
  507. backends_add_host_info("netdata_info", hostname, host->program_name, host->program_version, now_realtime_usec() / USEC_PER_MS);
  508. if(host->tags && *(host->tags)) {
  509. char tags[PROMETHEUS_LABELS_MAX + 1];
  510. strncpy(tags, host->tags, PROMETHEUS_LABELS_MAX);
  511. char *words[PROMETHEUS_LABELS_MAX_NUMBER] = {NULL};
  512. int i;
  513. remote_write_split_words(tags, words, PROMETHEUS_LABELS_MAX_NUMBER);
  514. backends_add_host_info("netdata_host_tags_info", hostname, NULL, NULL, now_realtime_usec() / USEC_PER_MS);
  515. for(i = 0; words[i] != NULL && words[i + 1] != NULL && (i + 1) < PROMETHEUS_LABELS_MAX_NUMBER; i += 2) {
  516. backends_add_tag(words[i], words[i + 1]);
  517. }
  518. }
  519. // for each chart
  520. RRDSET *st;
  521. rrdset_foreach_read(st, host) {
  522. char chart[PROMETHEUS_ELEMENT_MAX + 1];
  523. char context[PROMETHEUS_ELEMENT_MAX + 1];
  524. char family[PROMETHEUS_ELEMENT_MAX + 1];
  525. char units[PROMETHEUS_ELEMENT_MAX + 1] = "";
  526. backends_prometheus_label_copy(chart, (backend_options & BACKEND_OPTION_SEND_NAMES && st->name)?st->name:st->id, PROMETHEUS_ELEMENT_MAX);
  527. backends_prometheus_label_copy(family, st->family, PROMETHEUS_ELEMENT_MAX);
  528. backends_prometheus_name_copy(context, st->context, PROMETHEUS_ELEMENT_MAX);
  529. if(likely(backends_can_send_rrdset(backend_options, st))) {
  530. rrdset_rdlock(st);
  531. (*count_charts)++;
  532. int as_collected = (BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AS_COLLECTED);
  533. int homogeneous = 1;
  534. if(as_collected) {
  535. if(rrdset_flag_check(st, RRDSET_FLAG_HOMOGENEOUS_CHECK))
  536. rrdset_update_heterogeneous_flag(st);
  537. if(rrdset_flag_check(st, RRDSET_FLAG_HETEROGENEOUS))
  538. homogeneous = 0;
  539. }
  540. else {
  541. if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AVERAGE)
  542. backends_prometheus_units_copy(units, st->units, PROMETHEUS_ELEMENT_MAX, 0);
  543. }
  544. // for each dimension
  545. RRDDIM *rd;
  546. rrddim_foreach_read(rd, st) {
  547. if(rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
  548. char name[PROMETHEUS_LABELS_MAX + 1];
  549. char dimension[PROMETHEUS_ELEMENT_MAX + 1];
  550. char *suffix = "";
  551. if (as_collected) {
  552. // we need as-collected / raw data
  553. if(unlikely(rd->last_collected_time.tv_sec < after)) {
  554. debug(D_BACKEND, "BACKEND: not sending dimension '%s' of chart '%s' from host '%s', its last data collection (%lu) is not within our timeframe (%lu to %lu)", rd->id, st->id, __hostname, (unsigned long)rd->last_collected_time.tv_sec, (unsigned long)after, (unsigned long)before);
  555. (*count_dims_skipped)++;
  556. continue;
  557. }
  558. if(homogeneous) {
  559. // all the dimensions of the chart, has the same algorithm, multiplier and divisor
  560. // we add all dimensions as labels
  561. backends_prometheus_label_copy(dimension, (backend_options & BACKEND_OPTION_SEND_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  562. snprintf(name, PROMETHEUS_LABELS_MAX, "%s_%s%s", prefix, context, suffix);
  563. backends_add_metric(name, chart, family, dimension, hostname, rd->last_collected_value, timeval_msec(&rd->last_collected_time));
  564. (*count_dims)++;
  565. }
  566. else {
  567. // the dimensions of the chart, do not have the same algorithm, multiplier or divisor
  568. // we create a metric per dimension
  569. backends_prometheus_name_copy(dimension, (backend_options & BACKEND_OPTION_SEND_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  570. snprintf(name, PROMETHEUS_LABELS_MAX, "%s_%s_%s%s", prefix, context, dimension, suffix);
  571. backends_add_metric(name, chart, family, NULL, hostname, rd->last_collected_value, timeval_msec(&rd->last_collected_time));
  572. (*count_dims)++;
  573. }
  574. }
  575. else {
  576. // we need average or sum of the data
  577. time_t first_t = after, last_t = before;
  578. calculated_number value = backend_calculate_value_from_stored_data(st, rd, after, before, backend_options, &first_t, &last_t);
  579. if(!isnan(value) && !isinf(value)) {
  580. if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AVERAGE)
  581. suffix = "_average";
  582. else if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_SUM)
  583. suffix = "_sum";
  584. backends_prometheus_label_copy(dimension, (backend_options & BACKEND_OPTION_SEND_NAMES && rd->name) ? rd->name : rd->id, PROMETHEUS_ELEMENT_MAX);
  585. snprintf(name, PROMETHEUS_LABELS_MAX, "%s_%s%s%s", prefix, context, units, suffix);
  586. backends_add_metric(name, chart, family, dimension, hostname, value, last_t * MSEC_PER_SEC);
  587. (*count_dims)++;
  588. }
  589. }
  590. }
  591. }
  592. rrdset_unlock(st);
  593. }
  594. }
  595. }
  596. #endif /* ENABLE_PROMETHEUS_REMOTE_WRITE */
  597. static inline time_t prometheus_preparation(RRDHOST *host, BUFFER *wb, BACKEND_OPTIONS backend_options, const char *server, time_t now, BACKENDS_PROMETHEUS_OUTPUT_OPTIONS output_options) {
  598. if(!server || !*server) server = "default";
  599. time_t after = prometheus_server_last_access(server, host, now);
  600. int first_seen = 0;
  601. if(!after) {
  602. after = now - global_backend_update_every;
  603. first_seen = 1;
  604. }
  605. if(after > now) {
  606. // oops! this should never happen
  607. after = now - global_backend_update_every;
  608. }
  609. if(output_options & BACKENDS_PROMETHEUS_OUTPUT_HELP) {
  610. char *mode;
  611. if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AS_COLLECTED)
  612. mode = "as collected";
  613. else if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_AVERAGE)
  614. mode = "average";
  615. else if(BACKEND_OPTIONS_DATA_SOURCE(backend_options) == BACKEND_SOURCE_DATA_SUM)
  616. mode = "sum";
  617. else
  618. mode = "unknown";
  619. buffer_sprintf(wb, "# COMMENT netdata \"%s\" to %sprometheus \"%s\", source \"%s\", last seen %lu %s, time range %lu to %lu\n\n"
  620. , host->hostname
  621. , (first_seen)?"FIRST SEEN ":""
  622. , server
  623. , mode
  624. , (unsigned long)((first_seen)?0:(now - after))
  625. , (first_seen)?"never":"seconds ago"
  626. , (unsigned long)after, (unsigned long)now
  627. );
  628. }
  629. return after;
  630. }
  631. void backends_rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(RRDHOST *host, BUFFER *wb, const char *server, const char *prefix, BACKEND_OPTIONS backend_options, BACKENDS_PROMETHEUS_OUTPUT_OPTIONS output_options) {
  632. time_t before = now_realtime_sec();
  633. // we start at the point we had stopped before
  634. time_t after = prometheus_preparation(host, wb, backend_options, server, before, output_options);
  635. rrd_stats_api_v1_charts_allmetrics_prometheus(host, wb, prefix, backend_options, after, before, 0, output_options);
  636. }
  637. void backends_rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(RRDHOST *host, BUFFER *wb, const char *server, const char *prefix, BACKEND_OPTIONS backend_options, BACKENDS_PROMETHEUS_OUTPUT_OPTIONS output_options) {
  638. time_t before = now_realtime_sec();
  639. // we start at the point we had stopped before
  640. time_t after = prometheus_preparation(host, wb, backend_options, server, before, output_options);
  641. rrd_rdlock();
  642. rrdhost_foreach_read(host) {
  643. rrd_stats_api_v1_charts_allmetrics_prometheus(host, wb, prefix, backend_options, after, before, 1, output_options);
  644. }
  645. rrd_unlock();
  646. }
  647. #if ENABLE_PROMETHEUS_REMOTE_WRITE
  648. int backends_process_prometheus_remote_write_response(BUFFER *b) {
  649. if(unlikely(!b)) return 1;
  650. const char *s = buffer_tostring(b);
  651. int len = buffer_strlen(b);
  652. // do nothing with HTTP responses 200 or 204
  653. while(!isspace(*s) && len) {
  654. s++;
  655. len--;
  656. }
  657. s++;
  658. len--;
  659. if(likely(len > 4 && (!strncmp(s, "200 ", 4) || !strncmp(s, "204 ", 4))))
  660. return 0;
  661. else
  662. return discard_response(b, "prometheus remote write");
  663. }
  664. #endif