query.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. /** @file query.c
  3. *
  4. * @brief This is the file containing the implementation of the
  5. * logs management querying API.
  6. */
  7. #define _GNU_SOURCE
  8. #include "query.h"
  9. #include <uv.h>
  10. #include <sys/resource.h>
  11. #include "circular_buffer.h"
  12. #include "db_api.h"
  13. #include "file_info.h"
  14. #include "helper.h"
  15. static const char esc_ch[] = "[]\\^$.|?*+(){}";
  16. /**
  17. * @brief Sanitise string to work with regular expressions
  18. * @param[in] s Input string to be sanitised - will not be modified
  19. * @return Sanitised string (escaped characters according to esc_ch[] array)
  20. */
  21. UNIT_STATIC char *sanitise_string(char *const s){
  22. size_t s_len = strlen(s);
  23. /* Truncate keyword if longer than maximum allowed length */
  24. if(unlikely(s_len > MAX_KEYWORD_LEN)){
  25. s_len = MAX_KEYWORD_LEN;
  26. s[s_len] = '\0';
  27. }
  28. char *s_san = mallocz(s_len * 2);
  29. char *s_off = s;
  30. char *s_san_off = s_san;
  31. while(*s_off) {
  32. for(char *esc_ch_off = (char *) esc_ch; *esc_ch_off; esc_ch_off++){
  33. if(*s_off == *esc_ch_off){
  34. *s_san_off++ = '\\';
  35. break;
  36. }
  37. }
  38. *s_san_off++ = *s_off++;
  39. }
  40. *s_san_off = '\0';
  41. return s_san;
  42. }
  43. const logs_qry_res_err_t *fetch_log_sources(BUFFER *wb){
  44. if(unlikely(!p_file_infos_arr || !p_file_infos_arr->count))
  45. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_SERVER_ERR];
  46. buffer_json_add_array_item_object(wb);
  47. buffer_json_member_add_string(wb, "id", "all");
  48. buffer_json_member_add_string(wb, "name", "all");
  49. buffer_json_member_add_string(wb, "pill", "100"); // TODO
  50. buffer_json_member_add_string(wb, "info", "All log sources");
  51. buffer_json_member_add_string(wb, "basename", "");
  52. buffer_json_member_add_string(wb, "filename", "");
  53. buffer_json_member_add_string(wb, "log_type", "");
  54. buffer_json_member_add_string(wb, "db_dir", "");
  55. buffer_json_member_add_uint64(wb, "db_version", 0);
  56. buffer_json_member_add_uint64(wb, "db_flush_freq", 0);
  57. buffer_json_member_add_int64( wb, "db_disk_space_limit", 0);
  58. buffer_json_object_close(wb); // options object
  59. bool queryable_sources = false;
  60. for (int i = 0; i < p_file_infos_arr->count; i++) {
  61. if(p_file_infos_arr->data[i]->db_mode == LOGS_MANAG_DB_MODE_FULL)
  62. queryable_sources = true;
  63. }
  64. if(!queryable_sources)
  65. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_NOT_FOUND_ERR];
  66. for (int i = 0; i < p_file_infos_arr->count; i++) {
  67. buffer_json_add_array_item_object(wb);
  68. buffer_json_member_add_string(wb, "id", p_file_infos_arr->data[i]->chartname);
  69. buffer_json_member_add_string(wb, "name", p_file_infos_arr->data[i]->chartname);
  70. buffer_json_member_add_string(wb, "pill", "100"); // TODO
  71. char info[1024];
  72. snprintfz(info, sizeof(info), "Chart '%s' from log source '%s'",
  73. p_file_infos_arr->data[i]->chartname,
  74. p_file_infos_arr->data[i]->file_basename);
  75. buffer_json_member_add_string(wb, "info", info);
  76. buffer_json_member_add_string(wb, "basename", p_file_infos_arr->data[i]->file_basename);
  77. buffer_json_member_add_string(wb, "filename", p_file_infos_arr->data[i]->filename);
  78. buffer_json_member_add_string(wb, "log_type", log_src_type_t_str[p_file_infos_arr->data[i]->log_type]);
  79. buffer_json_member_add_string(wb, "db_dir", p_file_infos_arr->data[i]->db_dir);
  80. buffer_json_member_add_uint64(wb, "db_version", db_user_version(p_file_infos_arr->data[i]->db, -1));
  81. buffer_json_member_add_uint64(wb, "db_flush_freq", db_user_version(p_file_infos_arr->data[i]->db, -1));
  82. buffer_json_member_add_int64( wb, "db_disk_space_limit", p_file_infos_arr->data[i]->blob_max_size * BLOB_MAX_FILES);
  83. buffer_json_object_close(wb); // options object
  84. }
  85. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_OK];
  86. }
  87. bool terminate_logs_manag_query(logs_query_params_t *const p_query_params){
  88. if(p_query_params->cancelled && __atomic_load_n(p_query_params->cancelled, __ATOMIC_RELAXED)) {
  89. return true;
  90. }
  91. if(now_monotonic_usec() > p_query_params->stop_monotonic_ut)
  92. return true;
  93. return false;
  94. }
  95. const logs_qry_res_err_t *execute_logs_manag_query(logs_query_params_t *p_query_params) {
  96. struct File_info *p_file_infos[LOGS_MANAG_MAX_COMPOUND_QUERY_SOURCES] = {NULL};
  97. /* Check all required query parameters are present */
  98. if(unlikely(!p_query_params->req_from_ts || !p_query_params->req_to_ts))
  99. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_INV_TS_ERR];
  100. /* Start with maximum possible actual timestamp range and reduce it
  101. * accordingly when searching DB and circular buffer. */
  102. p_query_params->act_from_ts = p_query_params->req_from_ts;
  103. p_query_params->act_to_ts = p_query_params->req_to_ts;
  104. if(p_file_infos_arr == NULL)
  105. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_NOT_INIT_ERR];
  106. /* Find p_file_infos for this query according to chartnames or filenames
  107. * if the former is not valid. Only one of the two will be used,
  108. * charts_names and filenames cannot be mixed.
  109. * If neither list is provided, search all available log sources. */
  110. if(p_query_params->chartname[0]){
  111. int pfi_off = 0;
  112. for(int cn_off = 0; p_query_params->chartname[cn_off]; cn_off++) {
  113. for(int pfi_arr_off = 0; pfi_arr_off < p_file_infos_arr->count; pfi_arr_off++) {
  114. if( !strcmp(p_file_infos_arr->data[pfi_arr_off]->chartname, p_query_params->chartname[cn_off]) &&
  115. p_file_infos_arr->data[pfi_arr_off]->db_mode != LOGS_MANAG_DB_MODE_NONE) {
  116. p_file_infos[pfi_off++] = p_file_infos_arr->data[pfi_arr_off];
  117. break;
  118. }
  119. }
  120. }
  121. }
  122. else if(p_query_params->filename[0]){
  123. int pfi_off = 0;
  124. for(int fn_off = 0; p_query_params->filename[fn_off]; fn_off++) {
  125. for(int pfi_arr_off = 0; pfi_arr_off < p_file_infos_arr->count; pfi_arr_off++) {
  126. if( !strcmp(p_file_infos_arr->data[pfi_arr_off]->filename, p_query_params->filename[fn_off]) &&
  127. p_file_infos_arr->data[pfi_arr_off]->db_mode != LOGS_MANAG_DB_MODE_NONE) {
  128. p_file_infos[pfi_off++] = p_file_infos_arr->data[pfi_arr_off];
  129. break;
  130. }
  131. }
  132. }
  133. }
  134. else{
  135. int pfi_off = 0;
  136. for(int pfi_arr_off = 0; pfi_arr_off < p_file_infos_arr->count; pfi_arr_off++) {
  137. if(p_file_infos_arr->data[pfi_arr_off]->db_mode != LOGS_MANAG_DB_MODE_NONE)
  138. p_file_infos[pfi_off++] = p_file_infos_arr->data[pfi_arr_off];
  139. }
  140. }
  141. if(unlikely(!p_file_infos[0]))
  142. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_NOT_FOUND_ERR];
  143. if( p_query_params->sanitize_keyword && p_query_params->keyword &&
  144. *p_query_params->keyword && strcmp(p_query_params->keyword, " ")){
  145. p_query_params->keyword = sanitise_string(p_query_params->keyword); // freez(p_query_params->keyword) in this case
  146. }
  147. if(p_query_params->stop_monotonic_ut == 0)
  148. p_query_params->stop_monotonic_ut = now_monotonic_usec() + (LOGS_MANAG_QUERY_TIMEOUT_DEFAULT - 1) * USEC_PER_SEC;
  149. struct rusage ru_start, ru_end;
  150. getrusage(RUSAGE_THREAD, &ru_start);
  151. /* Secure DB lock to ensure no data will be transferred from the buffers to
  152. * the DB during the query execution and also no other execute_logs_manag_query
  153. * will try to access the DB at the same time. The operations happen
  154. * atomically and the DB searches in series. */
  155. for(int pfi_off = 0; p_file_infos[pfi_off]; pfi_off++)
  156. uv_mutex_lock(p_file_infos[pfi_off]->db_mut);
  157. /* If results are requested in ascending timestamp order, search DB(s) first
  158. * and then the circular buffers. Otherwise, search the circular buffers
  159. * first and the DB(s) second. In both cases, the quota must be respected. */
  160. if(p_query_params->order_by_asc)
  161. db_search(p_query_params, p_file_infos);
  162. if( p_query_params->results_buff->len < p_query_params->quota &&
  163. !terminate_logs_manag_query(p_query_params))
  164. circ_buff_search(p_query_params, p_file_infos);
  165. if(!p_query_params->order_by_asc &&
  166. p_query_params->results_buff->len < p_query_params->quota &&
  167. !terminate_logs_manag_query(p_query_params))
  168. db_search(p_query_params, p_file_infos);
  169. for(int pfi_off = 0; p_file_infos[pfi_off]; pfi_off++)
  170. uv_mutex_unlock(p_file_infos[pfi_off]->db_mut);
  171. getrusage(RUSAGE_THREAD, &ru_end);
  172. __atomic_add_fetch(&p_file_infos[0]->cpu_time_per_mib.user,
  173. p_query_params->results_buff->len ? ( ru_end.ru_utime.tv_sec * USEC_PER_SEC -
  174. ru_start.ru_utime.tv_sec * USEC_PER_SEC +
  175. ru_end.ru_utime.tv_usec -
  176. ru_start.ru_utime.tv_usec ) * (1 MiB) / p_query_params->results_buff->len : 0
  177. , __ATOMIC_RELAXED);
  178. __atomic_add_fetch(&p_file_infos[0]->cpu_time_per_mib.sys,
  179. p_query_params->results_buff->len ? ( ru_end.ru_stime.tv_sec * USEC_PER_SEC -
  180. ru_start.ru_stime.tv_sec * USEC_PER_SEC +
  181. ru_end.ru_stime.tv_usec -
  182. ru_start.ru_stime.tv_usec ) * (1 MiB) / p_query_params->results_buff->len : 0
  183. , __ATOMIC_RELAXED);
  184. /* If keyword has been sanitised, it needs to be freed - otherwise it's just a pointer to a substring */
  185. if(p_query_params->sanitize_keyword && p_query_params->keyword){
  186. freez(p_query_params->keyword);
  187. }
  188. if(terminate_logs_manag_query(p_query_params)){
  189. return (p_query_params->cancelled &&
  190. __atomic_load_n(p_query_params->cancelled, __ATOMIC_RELAXED)) ?
  191. &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_CANCELLED] /* cancelled */ :
  192. &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_TIMEOUT] /* timed out */ ;
  193. }
  194. if(!p_query_params->results_buff->len)
  195. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_NOT_FOUND_ERR];
  196. return &logs_qry_res_err[LOGS_QRY_RES_ERR_CODE_OK];
  197. }