29#include <unordered_map>
32#include <libdap/DDS.h>
33#include <libdap/DMR.h>
34#include <libdap/D4Group.h>
35#include <libdap/Vector.h>
36#include <libdap/Array.h>
37#include <libdap/Constructor.h>
38#include <libdap/XMLWriter.h>
40#include "TheBESKeys.h"
41#include "BESContextManager.h"
45#include "BESStopWatch.h"
46#include "BESSyntaxUserError.h"
50#define prolog std::string("dap_utils::").append(__func__).append("() - ")
56constexpr auto BES_KEYS_MAX_RESPONSE_SIZE_KEY =
"BES.MaxResponseSize.bytes";
57constexpr auto BES_KEYS_MAX_VAR_SIZE_KEY =
"BES.MaxVariableSize.bytes";
58constexpr auto BES_CONTEXT_MAX_RESPONSE_SIZE_KEY =
"max_response_size";
59constexpr auto BES_CONTEXT_MAX_VAR_SIZE_KEY =
"max_variable_size";
60constexpr uint64_t twoGB = 2147483648;
61constexpr uint64_t fourGB = 4294967296;
65constexpr auto MODULE =
"dap_utils";
66constexpr auto MODULE_VERBOSE =
"dap_utils_verbose";
73static void log_response_and_memory_size_helper(
const std::string &caller_id,
long response_size) {
76 INFO_LOG(caller_id +
"response size: " + std::to_string(response_size) +
"KB"+BESLog::mark+
"memory used by process: " +
77 std::to_string(mem_size) +
"KB");
80 INFO_LOG(caller_id +
"response size: " + std::to_string(response_size) +
"KB");
92void log_response_and_memory_size(
const std::string &caller_id, DDS *
const *dds)
94 auto response_size = (long)(*dds)->get_request_size_kb(
true);
95 log_response_and_memory_size_helper(caller_id, response_size);
106void log_response_and_memory_size(
const std::string &caller_id, DMR &dmr)
109 auto response_size = (long)dmr.request_size_kb(
true);
110 log_response_and_memory_size_helper(caller_id, response_size);
121void log_response_and_memory_size(
const std::string &caller_id, libdap::XMLWriter &dmrpp_writer)
123 auto response_size = (long)dmrpp_writer.get_doc_size() / 1000;
124 log_response_and_memory_size_helper(caller_id, response_size);
133std::string mk_model_incompatibility_message(
const std::vector<std::string> &inventory){
136 msg <<
"ERROR: Your have asked this service to utilize the DAP2 data model\n";
137 msg <<
"to process your request. Unfortunately the requested dataset contains\n";
138 msg <<
"data types that cannot be represented in DAP2.\n ";
140 msg <<
"There are " << inventory.size() <<
" incompatible variables and/or attributes referenced \n";
141 msg <<
"in your request.\n";
142 msg <<
"Incompatible variables: \n";
144 for(
const auto &entry: inventory){ msg <<
" " << entry <<
"\n"; }
146 msg <<
"You may resolve these issues by asking the service to use\n";
147 msg <<
"the DAP4 data model instead of the DAP2 model.\n";
149 msg <<
" - NetCDF If you wish to receive your response encoded as a\n";
150 msg <<
" netcdf file please note that netcdf-3 has similar representational\n";
151 msg <<
" constraints as DAP2, while netcdf-4 does not. In order to request\n";
152 msg <<
" a DAP4 model nectdf-4 response, change your request URL from \n";
153 msg <<
" dataset_url.nc to dataset_url.dap.nc4\n";
155 msg <<
" - DAP Clients If you are using a specific DAP client like pyDAP or\n";
156 msg <<
" Panoply you may be able to signal the tool to use DAP4 by changing\n";
157 msg <<
" the protocol of the dataset_url from https:// to dap4:// \n";
159 msg <<
" - If you are using the service's Data Request Form for your dataset\n";
160 msg <<
" you can find the DAP4 version by changing form_url.html to form_url.dmr.html\n";
172void throw_for_dap4_typed_vars_or_attrs(DDS *dds,
const std::string &file,
unsigned int line)
174 vector<string> inventory;
175 if(dds->is_dap4_projected(inventory)){
176 string msg = mk_model_incompatibility_message(inventory);
177 throw BESSyntaxUserError(msg, file, line);
187void throw_for_dap4_typed_attrs(DAS *das,
const std::string &file,
unsigned int line)
189 vector<string> inventory;
190 if(das->get_top_level_attributes()->has_dap4_types(
"/",inventory)){
191 string msg = mk_model_incompatibility_message(inventory);
192 throw BESSyntaxUserError(msg, file, line);
203uint64_t count_requested_elements(
const D4Dimension *d4dim){
204 uint64_t elements = 0;
205 if(d4dim->constrained()){
206 elements = (d4dim->c_stop() - d4dim->c_start());
207 if(d4dim->c_stride()){
208 elements = elements / d4dim->c_stride();
211 elements = d4dim->size();
225uint64_t count_requested_elements(
const Array::dimension &dim){
227 elements = (dim.stop - dim.start) / dim.stride;
237std::string get_dap_array_dims_str(libdap::Array &a){
238 stringstream my_dims;
239 for (
auto dim_iter = a.dim_begin(), end_iter = a.dim_end(); dim_iter != end_iter; ++dim_iter) { stringstream ce;
240 const auto &dim = *dim_iter;
241 ce << dim.start <<
":";
243 ce << dim.stride <<
":";
246 my_dims <<
"[" << ce.str() <<
"]";
248 return my_dims.str();
256std::string get_dap_decl(libdap::BaseType *var) {
259 if(var->is_vector_type()){
260 auto myArray =
dynamic_cast<libdap::Array *
>(var);
262 ss << myArray->prototype()->type_name() <<
" " << var->FQN();
263 ss << get_dap_array_dims_str(*myArray);
266 auto myVec =
dynamic_cast<libdap::Vector *
>(var);
268 ss << myVec->prototype()->type_name() <<
" " << var->FQN();
269 ss <<
"[" << myVec->length() <<
"]";
274 ss << var->type_name() << var->FQN();
282uint64_t crsaibv_process_ctor(
const libdap::Constructor *ctor,
283 const uint64_t max_var_size,
284 std::vector<std::string> &too_big );
296uint64_t crsaibv_process_variable(
298 const uint64_t max_var_size,
299 std::vector<std::string> &too_big
302 uint64_t response_size = 0;
305 if (var->is_constructor_type()) {
306 response_size += crsaibv_process_ctor(
dynamic_cast<libdap::Constructor *
>(var), max_var_size, too_big);
310 uint64_t vsize = var->width_ll(
true);
311 response_size += vsize;
313 BESDEBUG(MODULE_VERBOSE, prolog <<
" " << get_dap_decl(var) <<
"(" << vsize <<
" bytes)" << endl);
314 if ( (max_var_size > 0) && (vsize > max_var_size) ) {
315 string entry = get_dap_decl(var) +
" (" + to_string(vsize) +
" bytes)";
316 too_big.emplace_back(entry);
318 prolog << get_dap_decl(var) <<
"(" << vsize
319 <<
" bytes) is bigger than the max_var_size of "
320 << max_var_size <<
" bytes. too_big.size(): " << too_big.size() << endl);
324 return response_size;
336 uint64_t crsaibv_process_ctor(
const libdap::Constructor *ctor,
337 const uint64_t max_var_size,
338 std::vector<std::string> &too_big
340 uint64_t response_size = 0;
342 for (
auto dap_var: ctor->variables()) {
343 response_size += crsaibv_process_variable(dap_var, max_var_size, too_big);
348 prolog <<
"ERROR Received a null pointer to Constructor. " <<
349 "It is likely that a dynamic_cast failed.." << endl);
351 return response_size;
364uint64_t compute_response_size_and_inv_big_vars(
365 const libdap::D4Group *grp,
366 const uint64_t max_var_size,
367 std::vector<std::string> &too_big)
369 BESDEBUG(MODULE_VERBOSE, prolog <<
"BEGIN " << grp->type_name() <<
" " << grp->FQN() << endl);
371 uint64_t response_size = 0;
373 for(
auto dap_var:grp->variables()){
374 response_size += crsaibv_process_variable(dap_var, max_var_size, too_big);
378 for (
const auto child_grp: grp->groups()) {
379 if (child_grp->send_p()) {
380 response_size += compute_response_size_and_inv_big_vars(child_grp, max_var_size, too_big);
383 BESDEBUG(MODULE_VERBOSE, prolog <<
"SKIPPING: " << grp->type_name() <<
384 " " << child_grp->FQN() <<
" (No child selected.)" << endl);
387 BESDEBUG(MODULE_VERBOSE, prolog <<
"END " << grp->type_name() <<
" " << grp->FQN() <<
" ("
388 "response_size: " << response_size <<
", "<<
389 "too_big_vars: " << too_big.size() <<
")" << endl);
390 return response_size;
404uint64_t compute_response_size_and_inv_big_vars(
406 const uint64_t max_var_size,
407 std::vector<std::string> &too_big)
409 BES_STOPWATCH_START(MODULE, prolog +
"DMR");
410 return compute_response_size_and_inv_big_vars(dmr.root(), max_var_size,too_big);
423uint64_t compute_response_size_and_inv_big_vars(
424 const libdap::DDS &dds,
425 const uint64_t max_var_size,
426 std::vector<std::string> &too_big)
428 BES_STOPWATCH_START(MODULE, prolog +
"DDS");
429 uint64_t response_size = 0;
431 for(
auto dap_var:dds.variables()){
432 response_size += crsaibv_process_variable(dap_var, max_var_size, too_big);
434 return response_size;
448void get_max_sizes_bytes(uint64_t &max_response_size_bytes, uint64_t &max_var_size_bytes,
bool is_dap2)
450 BES_STOPWATCH_START(MODULE, prolog + (is_dap2?
"DAP2":
"DAP4"));
454 BESDEBUG(MODULE, prolog <<
"config_max_resp_size: " << config_max_resp_size <<
"\n");
455 max_response_size_bytes = config_max_resp_size;
457 uint64_t cmd_context_max_resp_size;
459 cmd_context_max_resp_size = BESContextManager::TheManager()->get_context_uint64(BES_CONTEXT_MAX_RESPONSE_SIZE_KEY, found);
462 prolog <<
"Did not locate BESContext key: " << BES_CONTEXT_MAX_RESPONSE_SIZE_KEY <<
" SKIPPING."
466 BESDEBUG(MODULE, prolog <<
"cmd_context_max_resp_size: " << cmd_context_max_resp_size <<
"\n");
471 if(cmd_context_max_resp_size != 0 && (cmd_context_max_resp_size < config_max_resp_size || config_max_resp_size == 0) ){
473 max_response_size_bytes = cmd_context_max_resp_size;
476 BESDEBUG(MODULE, prolog <<
"max_response_size_bytes: " << max_response_size_bytes <<
"\n");
480 BESDEBUG(MODULE, prolog <<
"config_max_var_size: " << config_max_var_size <<
"\n");
481 max_var_size_bytes = config_max_var_size;
483 uint64_t cmd_context_max_var_size=0;
485 cmd_context_max_var_size = BESContextManager::TheManager()->get_context_uint64(BES_CONTEXT_MAX_VAR_SIZE_KEY, found);
487 max_var_size_bytes = config_max_var_size;
488 BESDEBUG(MODULE, prolog <<
"Did not locate BESContext key: " << BES_CONTEXT_MAX_VAR_SIZE_KEY <<
" SKIPPING." <<
"\n");
490 else if( (cmd_context_max_var_size != 0) && (cmd_context_max_var_size < config_max_var_size || config_max_var_size == 0) ){
492 max_var_size_bytes = cmd_context_max_var_size;
497 if (max_var_size_bytes == 0 || max_var_size_bytes > twoGB) {
498 max_var_size_bytes = twoGB;
499 BESDEBUG(MODULE, prolog <<
"Adjusted max_var_size_bytes to DAP2 limit.\n");
501 if (max_response_size_bytes == 0 || max_response_size_bytes > fourGB) {
502 max_response_size_bytes = fourGB;
503 BESDEBUG(MODULE, prolog <<
"Adjusted max_response_size_bytes to DAP2 limit.\n");
506 BESDEBUG(MODULE, prolog <<
"max_var_size_bytes: " << max_var_size_bytes <<
"\n");
515std::string too_big_error_prolog(
const uint64_t max_response_size_bytes,
const uint64_t max_var_size_bytes){
517 msg <<
"\nYou asked for too much! \n";
518 msg <<
" Maximum allowed response size: ";
519 if(max_response_size_bytes == 0){
520 msg <<
"unlimited\n";
523 msg << max_response_size_bytes <<
" bytes.\n";
525 msg <<
" Maximum allowed variable size: ";
526 if(max_var_size_bytes == 0){
527 msg <<
"unlimited\n";
530 msg << max_var_size_bytes <<
" bytes.\n";
550 const uint64_t max_response_size_bytes,
551 const uint64_t response_size_bytes,
552 const uint64_t max_var_size_bytes,
553 const std::vector<string> &too_big_vars,
557 BESDEBUG(MODULE, prolog <<
"max_response_size_bytes: " << max_response_size_bytes <<
"\n");
558 BESDEBUG(MODULE, prolog <<
"max_var_size_bytes: " << max_var_size_bytes <<
"\n");
559 BESDEBUG(MODULE, prolog <<
"response_size_bytes: " << response_size_bytes <<
"\n");
560 BESDEBUG(MODULE, prolog <<
"too_big_vars.size(): " << too_big_vars.size() <<
"\n");
563 bool response_too_big = (max_response_size_bytes > 0) && (response_size_bytes > max_response_size_bytes);
564 if(response_too_big){
565 msg << too_big_error_prolog(max_response_size_bytes, max_var_size_bytes);
566 msg <<
"The submitted DAP" << (is_dap2?
"2":
"4") <<
" request will generate a ";
567 msg << response_size_bytes <<
" byte\n";
568 msg <<
"response, which is larger than the maximum allowed response size.\n";
572 if(!too_big_vars.empty()){
573 if(response_too_big){
576 msg <<
"\nIn addition to the overall response being too large for the\n";
577 msg <<
"service to produce, the request references the following\n";
578 msg <<
"variable(s) ";
582 msg << too_big_error_prolog(max_response_size_bytes, max_var_size_bytes);
583 msg <<
"The following is a list of variable(s), identified\n";
584 msg <<
"in the request, ";
587 msg <<
"that are each too large for the service\n";
588 msg <<
"to process.\n";
589 msg <<
"\nOversized Variable(s): \n";
590 for(
const auto& var_entry:too_big_vars){
591 msg <<
" " << var_entry <<
"\n";
594 response_too_big =
true;
597 if(response_too_big) {
599 msg <<
"You can resolve these issues by requesting less.\n";
600 msg <<
" - Consider asking for fewer variables (do you need them all?)\n";
601 msg <<
" - If individual variables are too large you can also subset\n";
602 msg <<
" them using an index based array subset expression \n";
603 msg <<
" to request a smaller area or to decimate the variable.\n";
605 msg <<
"You can find detailed information about DAP2 variable sub-setting\n";
606 msg <<
"expressions in section 4.4 of the DAP2 User Guide located here:\n";
607 msg <<
"https://www.opendap.org/documentation/UserGuideComprehensive.pdf\n";
611 msg <<
"You can find detailed information about DAP4 variable sub-setting here:\n";
612 msg <<
"https://github.com/OPENDAP/dap4-specification/blob/main/";
613 msg <<
"01_data-model-and-serialized-rep.md#8-constraints\n";
627void throw_if_too_big(libdap::DMR &dmr,
const string &file,
const unsigned int line)
629 BES_STOPWATCH_START(MODULE, prolog +
"DMR");
631 uint64_t max_var_size_bytes=0;
632 uint64_t max_response_size_bytes=0;
633 std::vector<std::string> too_big_vars;
635 get_max_sizes_bytes(max_response_size_bytes, max_var_size_bytes);
636 BESDEBUG(MODULE, prolog <<
"max_var_size_bytes: " << max_var_size_bytes <<
"\n");
637 BESDEBUG(MODULE, prolog <<
"max_response_size_bytes: " << max_response_size_bytes <<
"\n");
639 auto response_size_bytes = compute_response_size_and_inv_big_vars(dmr, max_var_size_bytes, too_big_vars);
640 BESDEBUG(MODULE, prolog <<
"response_size_bytes: " << response_size_bytes <<
"\n");
641 BESDEBUG(MODULE, prolog <<
"too_big_vars: " << too_big_vars.size() <<
"\n");
643 stringstream too_big_message;
646 max_response_size_bytes,
650 BESDEBUG(MODULE, prolog <<
"It's TOO BIG:\n" << too_big_message.str() <<
"\n");
651 throw BESSyntaxUserError(too_big_message.str(), file, line);
663void throw_if_too_big(
const libdap::DDS &dds,
const std::string &file,
const unsigned int line)
665 BES_STOPWATCH_START(MODULE, prolog +
"DDS");
667 uint64_t max_var_size_bytes=0;
668 uint64_t max_response_size_bytes=0;
669 std::vector<std::string> too_big_vars;
671 get_max_sizes_bytes(max_response_size_bytes, max_var_size_bytes,
true);
672 BESDEBUG(MODULE, prolog <<
"max_var_size_bytes: " << max_var_size_bytes <<
"\n");
673 BESDEBUG(MODULE, prolog <<
"max_response_size_bytes: " << max_response_size_bytes <<
"\n");
675 auto response_size_bytes = compute_response_size_and_inv_big_vars(dds, max_var_size_bytes, too_big_vars);
676 BESDEBUG(MODULE, prolog <<
"response_size_bytes: " << response_size_bytes <<
"\n");
677 BESDEBUG(MODULE, prolog <<
"too_big_vars: " << too_big_vars.size() <<
"\n");
679 stringstream too_big_message;
682 max_response_size_bytes,
687 BESDEBUG(MODULE, prolog <<
"It's TOO BIG:\n" << too_big_message.str() <<
"\n");
688 throw BESSyntaxUserError(too_big_message.str(), file, line);
static long get_current_memory_usage() noexcept
Get the Resident Set Size in KB.
static TheBESKeys * TheKeys()
Access to the singleton.
static uint64_t read_uint64_key(const std::string &key, uint64_t default_value)
Read an integer-valued key from the bes.conf file.