diff --git a/agents/grpc/proto/reconfigure.proto b/agents/grpc/proto/reconfigure.proto index 838c8f10d2..52b0eb2e02 100644 --- a/agents/grpc/proto/reconfigure.proto +++ b/agents/grpc/proto/reconfigure.proto @@ -17,6 +17,7 @@ message ReconfigureBody { optional bool tracingEnabled = 10; optional uint32 tracingModulesBlacklist = 11; optional bool contCpuProfile = 12; + optional bool assetsEnabled = 13; } message ReconfigureEvent { diff --git a/agents/grpc/src/grpc_agent.cc b/agents/grpc/src/grpc_agent.cc index 9f827f77ec..a80b7e4968 100644 --- a/agents/grpc/src/grpc_agent.cc +++ b/agents/grpc/src/grpc_agent.cc @@ -392,6 +392,10 @@ void PopulateReconfigureEvent(grpcagent::ReconfigureEvent* reconfigure_event, if (it != config.end()) { body->set_contcpuprofile(*it); } + it = config.find("assetsEnabled"); + if (it != config.end()) { + body->set_assetsenabled(*it); + } } void PopulateStartupTimesEvent(grpcagent::StartupTimesEvent* st_events, @@ -440,6 +444,7 @@ GrpcAgent::GrpcAgent(): hooks_init_(false), agent_id_(GetAgentId()), auth_retries_(0), unauthorized_(false), + assets_enabled_(true), profile_on_exit_(false) { ASSERT_EQ(0, uv_loop_init(&loop_)); ASSERT_EQ(0, uv_cond_init(&start_cond_)); @@ -1186,12 +1191,12 @@ int GrpcAgent::config(const json& config) { ret = setup_metrics_timer(period); } - // uint64_t period = NSOLID_SPANS_FLUSH_INTERVAL; - // auto spans_flush_interval = - // per_process::system_environment->Get(kNSOLID_SPANS_FLUSH_INTERVAL); - // if (spans_flush_interval.has_value()) { - // period = std::stoull(spans_flush_interval.value()); - // } + { + auto it = config_.find("assetsEnabled"); + if (it != config_.end()) { + assets_enabled_ = *it; + } + } return ret; } @@ -1747,6 +1752,10 @@ void GrpcAgent::reconfigure(const grpcagent::CommandRequest& request) { out["contCpuProfile"] = body.contcpuprofile(); } + if (body.has_assetsenabled()) { + out["assetsEnabled"] = body.assetsenabled(); + } + DebugJSON("Reconfigure out: \n%s\n", out); UpdateConfig(out.dump()); @@ -2084,6 +2093,11 @@ ErrorType GrpcAgent::do_start_prof_init( const grpcagent::CommandRequest& req, const ProfileType& type, ProfileOptions& options) { + + if (!assets_enabled_) { + return ErrorType::EAssetsDisabled; + } + const grpcagent::ProfileArgs& args = req.args().profile(); uint64_t thread_id = args.thread_id(); uint64_t duration = args.duration(); diff --git a/agents/grpc/src/grpc_agent.h b/agents/grpc/src/grpc_agent.h index 4b3516dcdb..1d57acc4ee 100644 --- a/agents/grpc/src/grpc_agent.h +++ b/agents/grpc/src/grpc_agent.h @@ -349,6 +349,7 @@ class GrpcAgent: public std::enable_shared_from_this, log_exporter_; // Profiling + std::atomic assets_enabled_; nsuv::ns_mutex profile_state_lock_; ProfileState profile_state_[ProfileType::kNumberOfProfileTypes]; std::atomic profile_on_exit_; diff --git a/agents/grpc/src/grpc_errors.h b/agents/grpc/src/grpc_errors.h index 84db558eab..ea9d2c8436 100644 --- a/agents/grpc/src/grpc_errors.h +++ b/agents/grpc/src/grpc_errors.h @@ -9,7 +9,8 @@ X(ESnapshotDisabled, 500, "Heap Snapshots disabled", 1004) \ X(ENoMemory, 500, "Internal Runtime Error", 1005) \ X(ENotAvailable, 404, "Resource not available", 1006) \ - X(ESourceCodeFileError, 500, "Internal Runtime Error", 1007) + X(ESourceCodeFileError, 500, "Internal Runtime Error", 1007) \ + X(EAssetsDisabled, 500, "Assets collection disabled", 1008) namespace node { namespace nsolid { diff --git a/agents/grpc/src/proto/reconfigure.pb.cc b/agents/grpc/src/proto/reconfigure.pb.cc index 62d7cddf5c..34307f77cd 100644 --- a/agents/grpc/src/proto/reconfigure.pb.cc +++ b/agents/grpc/src/proto/reconfigure.pb.cc @@ -46,7 +46,8 @@ inline constexpr ReconfigureBody::Impl_::Impl_( redactsnapshots_{false}, tracingenabled_{false}, tracingmodulesblacklist_{0u}, - contcpuprofile_{false} {} + contcpuprofile_{false}, + assetsenabled_{false} {} template PROTOBUF_CONSTEXPR ReconfigureBody::ReconfigureBody(::_pbi::ConstantInitialized) @@ -103,7 +104,7 @@ const ::uint32_t protodesc_cold) = { 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_._has_bits_), - 15, // hasbit index offset + 16, // hasbit index offset PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.blockedloopthreshold_), PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.interval_), PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.pausemetrics_), @@ -116,6 +117,7 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.tracingenabled_), PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.tracingmodulesblacklist_), PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.contcpuprofile_), + PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureBody, _impl_.assetsenabled_), 3, 4, 5, @@ -128,6 +130,7 @@ const ::uint32_t 8, 9, 10, + 11, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::grpcagent::ReconfigureEvent, _impl_._has_bits_), 5, // hasbit index offset @@ -140,7 +143,7 @@ const ::uint32_t static const ::_pbi::MigrationSchema schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { {0, sizeof(::grpcagent::ReconfigureBody)}, - {27, sizeof(::grpcagent::ReconfigureEvent)}, + {29, sizeof(::grpcagent::ReconfigureEvent)}, }; static const ::_pb::Message* PROTOBUF_NONNULL const file_default_instances[] = { &::grpcagent::_ReconfigureBody_default_instance_._instance, @@ -149,7 +152,7 @@ static const ::_pb::Message* PROTOBUF_NONNULL const file_default_instances[] = { const char descriptor_table_protodef_reconfigure_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( protodesc_cold) = { "\n\021reconfigure.proto\022\tgrpcagent\032\014common.p" - "roto\"\245\004\n\017ReconfigureBody\022!\n\024blockedLoopT" + "roto\"\323\004\n\017ReconfigureBody\022!\n\024blockedLoopT" "hreshold\030\001 \001(\004H\000\210\001\001\022\025\n\010interval\030\002 \001(\004H\001\210" "\001\001\022\031\n\014pauseMetrics\030\003 \001(\010H\002\210\001\001\022\034\n\017promise" "Tracking\030\004 \001(\010H\003\210\001\001\022\034\n\017redactSnapshots\030\005" @@ -157,15 +160,16 @@ const char descriptor_table_protodef_reconfigure_2eproto[] ABSL_ATTRIBUTE_SECTIO "Bucket\030\007 \001(\tH\006\210\001\001\022\027\n\nstatsdTags\030\010 \001(\tH\007\210" "\001\001\022\014\n\004tags\030\t \003(\t\022\033\n\016tracingEnabled\030\n \001(\010" "H\010\210\001\001\022$\n\027tracingModulesBlacklist\030\013 \001(\rH\t" - "\210\001\001\022\033\n\016contCpuProfile\030\014 \001(\010H\n\210\001\001B\027\n\025_blo" - "ckedLoopThresholdB\013\n\t_intervalB\017\n\r_pause" - "MetricsB\022\n\020_promiseTrackingB\022\n\020_redactSn" - "apshotsB\t\n\007_statsdB\017\n\r_statsdBucketB\r\n\013_" - "statsdTagsB\021\n\017_tracingEnabledB\032\n\030_tracin" - "gModulesBlacklistB\021\n\017_contCpuProfile\"g\n\020" - "ReconfigureEvent\022)\n\006common\030\001 \001(\0132\031.grpca" - "gent.CommonResponse\022(\n\004body\030\002 \001(\0132\032.grpc" - "agent.ReconfigureBodyb\006proto3" + "\210\001\001\022\033\n\016contCpuProfile\030\014 \001(\010H\n\210\001\001\022\032\n\rasse" + "tsEnabled\030\r \001(\010H\013\210\001\001B\027\n\025_blockedLoopThre" + "sholdB\013\n\t_intervalB\017\n\r_pauseMetricsB\022\n\020_" + "promiseTrackingB\022\n\020_redactSnapshotsB\t\n\007_" + "statsdB\017\n\r_statsdBucketB\r\n\013_statsdTagsB\021" + "\n\017_tracingEnabledB\032\n\030_tracingModulesBlac" + "klistB\021\n\017_contCpuProfileB\020\n\016_assetsEnabl" + "ed\"g\n\020ReconfigureEvent\022)\n\006common\030\001 \001(\0132\031" + ".grpcagent.CommonResponse\022(\n\004body\030\002 \001(\0132" + "\032.grpcagent.ReconfigureBodyb\006proto3" }; static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const descriptor_table_reconfigure_2eproto_deps[1] = { @@ -175,7 +179,7 @@ static ::absl::once_flag descriptor_table_reconfigure_2eproto_once; PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_reconfigure_2eproto = { false, false, - 709, + 755, descriptor_table_protodef_reconfigure_2eproto, "reconfigure.proto", &descriptor_table_reconfigure_2eproto_once, @@ -236,9 +240,9 @@ ReconfigureBody::ReconfigureBody( offsetof(Impl_, blockedloopthreshold_), reinterpret_cast(&from._impl_) + offsetof(Impl_, blockedloopthreshold_), - offsetof(Impl_, contcpuprofile_) - + offsetof(Impl_, assetsenabled_) - offsetof(Impl_, blockedloopthreshold_) + - sizeof(Impl_::contcpuprofile_)); + sizeof(Impl_::assetsenabled_)); // @@protoc_insertion_point(copy_constructor:grpcagent.ReconfigureBody) } @@ -256,9 +260,9 @@ inline void ReconfigureBody::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { ::memset(reinterpret_cast(&_impl_) + offsetof(Impl_, blockedloopthreshold_), 0, - offsetof(Impl_, contcpuprofile_) - + offsetof(Impl_, assetsenabled_) - offsetof(Impl_, blockedloopthreshold_) + - sizeof(Impl_::contcpuprofile_)); + sizeof(Impl_::assetsenabled_)); } ReconfigureBody::~ReconfigureBody() { // @@protoc_insertion_point(destructor:grpcagent.ReconfigureBody) @@ -332,16 +336,16 @@ ReconfigureBody::GetClassData() const { return ReconfigureBody_class_data_.base(); } PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 -const ::_pbi::TcParseTable<4, 12, 0, 74, 2> +const ::_pbi::TcParseTable<4, 13, 0, 74, 2> ReconfigureBody::_table_ = { { PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_._has_bits_), 0, // no _extensions_ - 12, 120, // max_field_number, fast_idx_mask + 13, 120, // max_field_number, fast_idx_mask offsetof(decltype(_table_), field_lookup_table), - 4294963200, // skipmap + 4294959104, // skipmap offsetof(decltype(_table_), field_entries), - 12, // num_field_entries + 13, // num_field_entries 0, // num_aux_entries offsetof(decltype(_table_), field_names), // no aux_entries ReconfigureBody_class_data_.base(), @@ -388,7 +392,9 @@ ReconfigureBody::_table_ = { // optional bool contCpuProfile = 12; {::_pbi::TcParser::SingularVarintNoZag1(), {96, 10, 0, PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.contcpuprofile_)}}, - {::_pbi::TcParser::MiniParse, {}}, + // optional bool assetsEnabled = 13; + {::_pbi::TcParser::SingularVarintNoZag1(), + {104, 11, 0, PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.assetsenabled_)}}, {::_pbi::TcParser::MiniParse, {}}, {::_pbi::TcParser::MiniParse, {}}, }}, {{ @@ -418,6 +424,8 @@ ReconfigureBody::_table_ = { {PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.tracingmodulesblacklist_), _Internal::kHasBitsOffset + 9, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, // optional bool contCpuProfile = 12; {PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.contcpuprofile_), _Internal::kHasBitsOffset + 10, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // optional bool assetsEnabled = 13; + {PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.assetsenabled_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, }}, // no aux_entries {{ @@ -454,10 +462,10 @@ PROTOBUF_NOINLINE void ReconfigureBody::Clear() { reinterpret_cast(&_impl_.redactsnapshots_) - reinterpret_cast(&_impl_.blockedloopthreshold_)) + sizeof(_impl_.redactsnapshots_)); } - if ((cached_has_bits & 0x00000700U) != 0) { + if ((cached_has_bits & 0x00000f00U) != 0) { ::memset(&_impl_.tracingenabled_, 0, static_cast<::size_t>( - reinterpret_cast(&_impl_.contcpuprofile_) - - reinterpret_cast(&_impl_.tracingenabled_)) + sizeof(_impl_.contcpuprofile_)); + reinterpret_cast(&_impl_.assetsenabled_) - + reinterpret_cast(&_impl_.tracingenabled_)) + sizeof(_impl_.assetsenabled_)); } _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); @@ -570,6 +578,13 @@ ::uint8_t* PROTOBUF_NONNULL ReconfigureBody::_InternalSerialize( 12, this_._internal_contcpuprofile(), target); } + // optional bool assetsEnabled = 13; + if ((cached_has_bits & 0x00000800U) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 13, this_._internal_assetsenabled(), target); + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -606,7 +621,7 @@ ::size_t ReconfigureBody::ByteSizeLong() const { } } cached_has_bits = this_._impl_._has_bits_[0]; - total_size += ::absl::popcount(0x000005e0U & cached_has_bits) * 2; + total_size += ::absl::popcount(0x00000de0U & cached_has_bits) * 2; if ((cached_has_bits & 0x0000001fU) != 0) { // optional string statsd = 6; if ((cached_has_bits & 0x00000001U) != 0) { @@ -684,7 +699,7 @@ void ReconfigureBody::MergeImpl(::google::protobuf::MessageLite& to_msg, const : _this->_impl_.redactsnapshots_ = from._impl_.redactsnapshots_; } } - if ((cached_has_bits & 0x00000700U) != 0) { + if ((cached_has_bits & 0x00000f00U) != 0) { if ((cached_has_bits & 0x00000100U) != 0) { _this->_impl_.tracingenabled_ = from._impl_.tracingenabled_; } @@ -694,6 +709,9 @@ void ReconfigureBody::MergeImpl(::google::protobuf::MessageLite& to_msg, const : if ((cached_has_bits & 0x00000400U) != 0) { _this->_impl_.contcpuprofile_ = from._impl_.contcpuprofile_; } + if ((cached_has_bits & 0x00000800U) != 0) { + _this->_impl_.assetsenabled_ = from._impl_.assetsenabled_; + } } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>(from._internal_metadata_); @@ -718,8 +736,8 @@ void ReconfigureBody::InternalSwap(ReconfigureBody* PROTOBUF_RESTRICT PROTOBUF_N ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.statsdbucket_, &other->_impl_.statsdbucket_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.statsdtags_, &other->_impl_.statsdtags_, arena); ::google::protobuf::internal::memswap< - PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.contcpuprofile_) - + sizeof(ReconfigureBody::_impl_.contcpuprofile_) + PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.assetsenabled_) + + sizeof(ReconfigureBody::_impl_.assetsenabled_) - PROTOBUF_FIELD_OFFSET(ReconfigureBody, _impl_.blockedloopthreshold_)>( reinterpret_cast(&_impl_.blockedloopthreshold_), reinterpret_cast(&other->_impl_.blockedloopthreshold_)); diff --git a/agents/grpc/src/proto/reconfigure.pb.h b/agents/grpc/src/proto/reconfigure.pb.h index 1d01c13b65..fe341bc98a 100644 --- a/agents/grpc/src/proto/reconfigure.pb.h +++ b/agents/grpc/src/proto/reconfigure.pb.h @@ -230,6 +230,7 @@ class ReconfigureBody final : public ::google::protobuf::Message kTracingEnabledFieldNumber = 10, kTracingModulesBlacklistFieldNumber = 11, kContCpuProfileFieldNumber = 12, + kAssetsEnabledFieldNumber = 13, }; // repeated string tags = 9; int tags_size() const; @@ -388,12 +389,23 @@ class ReconfigureBody final : public ::google::protobuf::Message bool _internal_contcpuprofile() const; void _internal_set_contcpuprofile(bool value); + public: + // optional bool assetsEnabled = 13; + bool has_assetsenabled() const; + void clear_assetsenabled() ; + bool assetsenabled() const; + void set_assetsenabled(bool value); + + private: + bool _internal_assetsenabled() const; + void _internal_set_assetsenabled(bool value); + public: // @@protoc_insertion_point(class_scope:grpcagent.ReconfigureBody) private: class _Internal; friend class ::google::protobuf::internal::TcParser; - static const ::google::protobuf::internal::TcParseTable<4, 12, + static const ::google::protobuf::internal::TcParseTable<4, 13, 0, 74, 2> _table_; @@ -427,6 +439,7 @@ class ReconfigureBody final : public ::google::protobuf::Message bool tracingenabled_; ::uint32_t tracingmodulesblacklist_; bool contcpuprofile_; + bool assetsenabled_; PROTOBUF_TSAN_DECLARE_MEMBER }; union { Impl_ _impl_; }; @@ -1159,6 +1172,34 @@ inline void ReconfigureBody::_internal_set_contcpuprofile(bool value) { _impl_.contcpuprofile_ = value; } +// optional bool assetsEnabled = 13; +inline bool ReconfigureBody::has_assetsenabled() const { + bool value = (_impl_._has_bits_[0] & 0x00000800U) != 0; + return value; +} +inline void ReconfigureBody::clear_assetsenabled() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.assetsenabled_ = false; + _impl_._has_bits_[0] &= ~0x00000800U; +} +inline bool ReconfigureBody::assetsenabled() const { + // @@protoc_insertion_point(field_get:grpcagent.ReconfigureBody.assetsEnabled) + return _internal_assetsenabled(); +} +inline void ReconfigureBody::set_assetsenabled(bool value) { + _internal_set_assetsenabled(value); + _impl_._has_bits_[0] |= 0x00000800U; + // @@protoc_insertion_point(field_set:grpcagent.ReconfigureBody.assetsEnabled) +} +inline bool ReconfigureBody::_internal_assetsenabled() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.assetsenabled_; +} +inline void ReconfigureBody::_internal_set_assetsenabled(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.assetsenabled_ = value; +} + // ------------------------------------------------------------------- // ReconfigureEvent diff --git a/agents/zmq/src/zmq_agent.cc b/agents/zmq/src/zmq_agent.cc index cdcfc9dc38..db633429e7 100644 --- a/agents/zmq/src/zmq_agent.cc +++ b/agents/zmq/src/zmq_agent.cc @@ -1279,6 +1279,13 @@ int ZmqAgent::config(const json& config) { setup_blocked_loop_hooks(); } + { + auto it = config_.find("assetsEnabled"); + if (it != config_.end()) { + assets_enabled_ = *it; + } + } + if (utils::find_any_fields_in_diff(diff, { "/app" })) { auto it = config_.find("app"); if (it != config_.end()) { @@ -2105,6 +2112,10 @@ void ZmqAgent::do_got_prof(ProfileType type, int ZmqAgent::do_start_prof_init(const nlohmann::json& message, ProfileType type, ProfileOptions& options) { + if (!assets_enabled_) { + return UV_EINVAL; + } + StartProfiling start_profiling = nullptr; switch (type) { case ProfileType::kCpu: diff --git a/agents/zmq/src/zmq_agent.h b/agents/zmq/src/zmq_agent.h index eed1aa48b5..efadca4033 100644 --- a/agents/zmq/src/zmq_agent.h +++ b/agents/zmq/src/zmq_agent.h @@ -728,6 +728,7 @@ class ZmqAgent { // Profiling ProfileState profile_state_[ProfileType::kNumberOfProfileTypes]; std::atomic profile_on_exit_; + std::atomic assets_enabled_; std::shared_ptr profile_collector_; nsuv::ns_async start_profiling_msg_; TSQueue start_profiling_msg_q_; diff --git a/lib/nsolid.js b/lib/nsolid.js index 0c7f296d85..27648de839 100644 --- a/lib/nsolid.js +++ b/lib/nsolid.js @@ -130,6 +130,10 @@ ObjectAssign(start, { heapSamplingStream, heapSampling, heapSamplingEnd, + enableAssets, + disableAssets, + enableTraces, + disableTraces, logger: { debug: (msg) => { writeLog(msg, 5); }, info: (msg) => { writeLog(msg, 9); }, @@ -188,6 +192,18 @@ const assignObj = assignGetters(start, { * @static */ metricsPaused: () => getConfig('pauseMetrics') || false, + /** + * @member {boolean} assetsEnabled + * @description whether the agent is currently retrieving assets or not. + * @static + */ + assetsEnabled: () => getConfig('assetsEnabled') ?? true, + /** + * @member {boolean} tracingEnabled + * @description whether tracing is currently enabled for the agent. + * @static + */ + tracingEnabled: () => getConfig('tracingEnabled') ?? false, }); // If the return value is undefined then there was an issue. Return early. @@ -394,7 +410,10 @@ function heapProfileEnd(cb) { * {ERR_NSOLID_HEAP_PROFILE_START} If there is an error starting the heap profile. * @alias module:nsolid.heapProfileStream */ -function heapProfileStream(threadId, duration, trackAllocations) { +function heapProfileStream(threadId, duration, trackAllocations, options) { + if (getConfig('assetsEnabled') === false) + throw new ERR_NSOLID_HEAP_PROFILE_START(); + validateNumber(threadId, 'threadId'); validateNumber(duration, 'duration'); validateBoolean(trackAllocations, 'trackAllocations'); @@ -475,6 +494,9 @@ function heapSamplingEnd(cb) { * @alias module:nsolid.heapSamplingStream */ function heapSamplingStream(threadId, duration, options) { + if (getConfig('assetsEnabled') === false) + throw new ERR_NSOLID_HEAP_SAMPLING_START(); + validateNumber(threadId, 'threadId'); validateNumber(duration, 'duration', 1); options ||= {}; @@ -579,8 +601,41 @@ function pauseMetrics() { } +function enableAssets() { + if (getConfig('assetsEnabled') === true) + return; + + updateConfig({ assetsEnabled: true }); +} + + +function disableAssets() { + if (getConfig('assetsEnabled') === false) + return; + + updateConfig({ assetsEnabled: false }); +} + + +function enableTraces() { + if (getConfig('tracingEnabled') === true) + return; + + updateConfig({ tracingEnabled: true }); +} + + +function disableTraces() { + if (getConfig('tracingEnabled') === false) + return; + + updateConfig({ tracingEnabled: false }); +} + + /** - * It resumes the process metrics collection. It works only if called from the + * It resumes the process metrics collection when exiting. + * works only if called from the * main thread. * @alias module:nsolid.resumeMetrics */ @@ -703,6 +758,11 @@ function updateConfig(config = {}) { nsolidConfig.contCpuProfile = envToBool(config.contCpuProfile); } else if (key === 'contCpuProfileInterval') { nsolidConfig.contCpuProfileInterval = +config.contCpuProfileInterval; + } else if (key === 'assetsEnabled') { + const normalized = optionToBool(config.assetsEnabled); + if (normalized !== undefined) { + nsolidConfig.assetsEnabled = normalized; + } } else { nsolidConfig[key] = config[key]; } @@ -884,6 +944,17 @@ function initializeConfig(nsolidConfig) { parseTracingModulesList(process.env.NSOLID_TRACING_MODULES_BLACKLIST || pkgConfig.nsolid.tracingModulesBlacklist); + const envAssetsEnabled = optionToBool(process.env.NSOLID_ASSETS_ENABLED); + const pkgAssetsEnabled = optionToBool(pkgConfig.nsolid.assetsEnabled); + + if (envAssetsEnabled !== undefined) { + nsolidConfig.assetsEnabled = envAssetsEnabled; + } else if (pkgAssetsEnabled !== undefined) { + nsolidConfig.assetsEnabled = pkgAssetsEnabled; + } else { + nsolidConfig.assetsEnabled = true; + } + // Promise Tracking nsolidConfig.promiseTracking = envToBool(process.env.NSOLID_PROMISE_TRACKING) || @@ -1032,6 +1103,19 @@ function envToBool(str) { } +function optionToBool(value) { + if (value === undefined || value === null) + return undefined; + if (typeof value === 'boolean') + return value; + if (typeof value === 'number') + return value !== 0; + if (typeof value === 'string') + return envToBool(value); + return undefined; +} + + function genPackageList() { let main_path; let last_path; diff --git a/src/nsolid/nsolid_api.cc b/src/nsolid/nsolid_api.cc index 1e05348237..7190f02dfb 100644 --- a/src/nsolid/nsolid_api.cc +++ b/src/nsolid/nsolid_api.cc @@ -1162,19 +1162,28 @@ void EnvList::UpdateConfig(const nlohmann::json& config) { } if (old.empty() || - utils::find_any_fields_in_diff(diff, { "/contCpuProfile", + utils::find_any_fields_in_diff(diff, { "/assetsEnabled", + "/contCpuProfile", "/contCpuProfileInterval" })) { + bool assetsEnabled = true; bool contCpuProfile = false; uint64_t contCpuProfileInterval = 60000; // Default: 1 minute - it = curr.find("contCpuProfile"); + it = curr.find("assetsEnabled"); if (it != curr.end() && !it->is_null()) { - contCpuProfile = it->get(); + assetsEnabled = it->get(); } - it = curr.find("contCpuProfileInterval"); - if (it != curr.end() && !it->is_null()) { - contCpuProfileInterval = it->get(); + if (assetsEnabled) { + it = curr.find("contCpuProfile"); + if (it != curr.end() && !it->is_null()) { + contCpuProfile = it->get(); + } + + it = curr.find("contCpuProfileInterval"); + if (it != curr.end() && !it->is_null()) { + contCpuProfileInterval = it->get(); + } } // Update the continuous profiler with the new configuration diff --git a/test/agents/test-grpc-continuous-profile.mjs b/test/agents/test-grpc-continuous-profile.mjs index 15325d1237..0854d5d4cf 100644 --- a/test/agents/test-grpc-continuous-profile.mjs +++ b/test/agents/test-grpc-continuous-profile.mjs @@ -1,6 +1,7 @@ // Flags: --expose-internals import { mustSucceed } from '../common/index.mjs'; import assert from 'node:assert'; +import { setTimeout } from 'node:timers/promises'; import validators from 'internal/validators'; import { GRPCServer, @@ -76,6 +77,7 @@ function checkProfileError(profile, metadata, requestId, agentId, code, msg) { assert.strictEqual(metadata['nsolid-agent-id'][0], agentId); } + const tests = []; tests.push({ @@ -116,6 +118,132 @@ tests.push({ }, }); +tests.push({ + name: 'should not emit continuous profiles when assets are disabled via env', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = { + ...getEnv(port), + NSOLID_ASSETS_ENABLED: '0', + }; + + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + + const child = new TestClient([], opts); + const agentId = await child.id(); + + let profileCount = 0; + grpcServer.on('profile', (data) => { + assert.strictEqual(data.metadata['nsolid-agent-id'][0], agentId); + profileCount++; + }); + + // Wait slightly longer than two intervals (100ms) to see if any profile arrives. + await setTimeout(300); + assert.strictEqual(profileCount, 0); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should stop continuous profiling after disabling assets via start config', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + + const child = new TestClient([], opts); + await child.id(); + await child.config({ + contCpuProfile: true, + contCpuProfileInterval: 100, + }); + + let profileCount = 0; + grpcServer.on('profile', () => { + profileCount++; + }); + + await setTimeout(250); + assert.ok(profileCount >= 1); + + await child.config({ assetsEnabled: false }); + const countAfterDisable = profileCount; + + await setTimeout(250); + assert.ok(profileCount - countAfterDisable <= 1); + + await child.config({ assetsEnabled: true }); + await setTimeout(250); + assert.ok(profileCount > countAfterDisable); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should stop continuous profiling after disableAssets()/enableAssets()', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + + const child = new TestClient([], opts); + await child.id(); + + let profileCount = 0; + grpcServer.on('profile', () => { + profileCount++; + }); + + await setTimeout(250); + assert.ok(profileCount >= 1); + + await child.disableAssets(); + const countAfterDisable = profileCount; + + await setTimeout(250); + assert.ok(profileCount - countAfterDisable <= 1); + + await child.enableAssets(); + await setTimeout(250); + assert.ok(profileCount > countAfterDisable); + + const currentConfig = await child.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + tests.push({ name: 'should also work with worker threads', test: async (getEnv) => { @@ -124,6 +252,19 @@ tests.push({ grpcServer.start(mustSucceed(async (port) => { let timesMainThread = 0; let timesWorker = 0; + const env = getEnv(port); + + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + + const startTime = process.hrtime(); + const child = new TestClient([ '-w', 1 ], opts); + const agentId = await child.id(); + const workers = await child.workers(); + const wid = workers[0]; + grpcServer.on('profile', async (data) => { if (data.msg.threadId === '0') { timesMainThread++; @@ -147,18 +288,6 @@ tests.push({ resolve(); } }); - const env = getEnv(port); - - const opts = { - stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env, - }; - - let startTime = process.hrtime(); - const child = new TestClient([ '-w', 1 ], opts); - const agentId = await child.id(); - const workers = await child.workers(); - const wid = workers[0]; })); }); }, @@ -248,14 +377,13 @@ tests.push({ }); // Wait a short time to ensure the profile has started - setTimeout(async () => { - console.log('Enabling continuous profiling during CPU profile'); - // Enable continuous profiling by updating the configuration - await child.config({ - contCpuProfile: true, - contCpuProfileInterval: 100, // 100ms for faster testing - }); - }, 200); + await setTimeout(200); + console.log('Enabling continuous profiling during CPU profile'); + // Enable continuous profiling by updating the configuration + await child.config({ + contCpuProfile: true, + contCpuProfileInterval: 100, // 100ms for faster testing + }); })); }); }, @@ -299,27 +427,26 @@ tests.push({ let profileErrorTested = false; // Wait for continuous profiling to start - setTimeout(async () => { - console.log('Attempting manual CPU profile while continuous profiling is active'); - // Try to perform a manual CPU profile - this should fail with EInProgressError - const options = { - duration: 100, - threadId: 0, - }; - const { data, requestId } = await grpcServer.cpuProfile(agentId, options); - // Verify the error response - checkProfileError( - data.msg, - data.metadata, - requestId, - agentId, - 409, // 409 Conflict - Operation already in progress - 'Operation already in progress(1001)', - ); - - console.log('Received expected error for manual CPU profile'); - profileErrorTested = true; - }, 400); + await setTimeout(400); + console.log('Attempting manual CPU profile while continuous profiling is active'); + // Try to perform a manual CPU profile - this should fail with EInProgressError + const options = { + duration: 100, + threadId: 0, + }; + const { data, requestId } = await grpcServer.cpuProfile(agentId, options); + // Verify the error response + checkProfileError( + data.msg, + data.metadata, + requestId, + agentId, + 409, // 409 Conflict - Operation already in progress + 'Operation already in progress(1001)', + ); + + console.log('Received expected error for manual CPU profile'); + profileErrorTested = true; })); }); }, diff --git a/test/agents/test-grpc-heap-profile.mjs b/test/agents/test-grpc-heap-profile.mjs index 2535b3acdc..63d3bb5f2c 100644 --- a/test/agents/test-grpc-heap-profile.mjs +++ b/test/agents/test-grpc-heap-profile.mjs @@ -256,6 +256,138 @@ tests.push({ }, }); +tests.push({ + name: 'should return 500 if assets collection is disabled', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = { + ...getEnv(port), + NSOLID_ASSETS_ENABLED: '0', + }; + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const options = { + duration: 100, + threadId: 0, + heapProfile: { + trackAllocations: false, + }, + }; + + const { data, requestId } = await grpcServer.heapProfile(agentId, options); + checkProfileError(data.msg, + data.metadata, + requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + heapProfile: { + trackAllocations: false, + }, + }; + + const disabledConfig = await child.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + const disabledResult = await grpcServer.heapProfile(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + const enabledConfig = await child.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + const { data, requestId } = await grpcServer.heapProfile(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect enableAssets()/disableAssets() helpers', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + heapProfile: { + trackAllocations: false, + }, + }; + + await child.disableAssets(); + + const disabledResult = await grpcServer.heapProfile(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + await child.enableAssets(); + + const { data, requestId } = await grpcServer.heapProfile(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + const currentConfig = await child.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + tests.push({ name: 'should end an ongoing profile before exiting', test: async (getEnv) => { diff --git a/test/agents/test-grpc-heap-sampling.mjs b/test/agents/test-grpc-heap-sampling.mjs index e0331d5b52..72ab4259a1 100644 --- a/test/agents/test-grpc-heap-sampling.mjs +++ b/test/agents/test-grpc-heap-sampling.mjs @@ -245,6 +245,138 @@ tests.push({ }, }); +tests.push({ + name: 'should return 500 if assets collection is disabled', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = { + ...getEnv(port), + NSOLID_ASSETS_ENABLED: '0', + }; + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const options = { + duration: 100, + threadId: 0, + heapSampling: { + sampleInterval: 50, + }, + }; + + const { data, requestId } = await grpcServer.heapSampling(agentId, options); + checkProfileError(data.msg, + data.metadata, + requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + heapSampling: { + sampleInterval: 50, + }, + }; + + const disabledConfig = await child.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + const disabledResult = await grpcServer.heapSampling(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + const enabledConfig = await child.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + const { data, requestId } = await grpcServer.heapSampling(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect enableAssets()/disableAssets() helpers', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + heapSampling: { + sampleInterval: 50, + }, + }; + + await child.disableAssets(); + + const disabledResult = await grpcServer.heapSampling(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + await child.enableAssets(); + + const { data, requestId } = await grpcServer.heapSampling(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + const currentConfig = await child.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + tests.push({ name: 'should end an ongoing profile before exiting', test: async (getEnv) => { diff --git a/test/agents/test-grpc-profile.mjs b/test/agents/test-grpc-profile.mjs index 3b621c5e8f..aa04c06417 100644 --- a/test/agents/test-grpc-profile.mjs +++ b/test/agents/test-grpc-profile.mjs @@ -240,6 +240,129 @@ tests.push({ }, }); +tests.push({ + name: 'should return 500 if assets collection is disabled', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = { + ...getEnv(port), + NSOLID_ASSETS_ENABLED: '0', + }; + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const options = { + duration: 100, + threadId: 0, + }; + + const { data, requestId } = await grpcServer.cpuProfile(agentId, options); + checkProfileError(data.msg, + data.metadata, + requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + }; + + const disabledConfig = await child.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + const disabledResult = await grpcServer.cpuProfile(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + const enabledConfig = await child.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + const { data, requestId } = await grpcServer.cpuProfile(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect enableAssets()/disableAssets() helpers', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const profileOpts = { + duration: 100, + threadId: 0, + }; + + await child.disableAssets(); + + const disabledResult = await grpcServer.cpuProfile(agentId, profileOpts); + checkProfileError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + await child.enableAssets(); + + const { data, requestId } = await grpcServer.cpuProfile(agentId, profileOpts); + checkProfileData(data.msg, data.metadata, requestId, agentId, profileOpts, true); + + const currentConfig = await child.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + tests.push({ name: 'should end an ongoing profile before exiting', test: async (getEnv) => { diff --git a/test/agents/test-grpc-snapshot.mjs b/test/agents/test-grpc-snapshot.mjs index db5c08289e..da6d5a6997 100644 --- a/test/agents/test-grpc-snapshot.mjs +++ b/test/agents/test-grpc-snapshot.mjs @@ -243,6 +243,129 @@ tests.push({ }, }); +tests.push({ + name: 'should return 500 if assets collection is disabled', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = { + ...getEnv(port), + NSOLID_ASSETS_ENABLED: '0', + }; + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const options = { + duration: 100, + threadId: 0, + }; + + const { data, requestId } = await grpcServer.heapSnapshot(agentId, options); + checkSnapshotError(data.msg, + data.metadata, + requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const snapshotOpts = { + duration: 100, + threadId: 0, + }; + + const disabledConfig = await child.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + const disabledResult = await grpcServer.heapSnapshot(agentId, snapshotOpts); + checkSnapshotError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + const enabledConfig = await child.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + const { data, requestId } = await grpcServer.heapSnapshot(agentId, snapshotOpts); + checkSnapshotData(data.msg, data.metadata, requestId, agentId, snapshotOpts, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + +tests.push({ + name: 'should respect enableAssets()/disableAssets() helpers', + test: async (getEnv) => { + return new Promise((resolve) => { + const grpcServer = new GRPCServer(); + grpcServer.start(mustSucceed(async (port) => { + const env = getEnv(port); + const opts = { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + env, + }; + const child = new TestClient([], opts); + const agentId = await child.id(); + const snapshotOpts = { + duration: 100, + threadId: 0, + }; + + await child.disableAssets(); + + const disabledResult = await grpcServer.heapSnapshot(agentId, snapshotOpts); + checkSnapshotError(disabledResult.data.msg, + disabledResult.data.metadata, + disabledResult.requestId, + agentId, + 500, + 'Assets collection disabled(1008)'); + + await child.enableAssets(); + + const { data, requestId } = await grpcServer.heapSnapshot(agentId, snapshotOpts); + checkSnapshotData(data.msg, data.metadata, requestId, agentId, snapshotOpts, true); + + const currentConfig = await child.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + + await child.shutdown(0); + grpcServer.close(); + resolve(); + })); + }); + }, +}); + tests.push({ name: 'should also work from the JS api', test: async (getEnv) => { diff --git a/test/agents/test-grpc-tracing.mjs b/test/agents/test-grpc-tracing.mjs index 3fe474b4b7..6bee3d2cab 100644 --- a/test/agents/test-grpc-tracing.mjs +++ b/test/agents/test-grpc-tracing.mjs @@ -2,6 +2,7 @@ import { mustCall, mustCallAtLeast, mustSucceed } from '../common/index.mjs'; import assert from 'node:assert'; import { threadId } from 'node:worker_threads'; +import { setTimeout as delay } from 'node:timers/promises'; import { checkExitData, GRPCServer, @@ -201,20 +202,49 @@ tests.push({ const child = new TestClient(['-t', 'http'], opts); const agentId = await child.id(); const resourceSpans = []; + let phase = 'initial'; grpcServer.on('spans', mustCallAtLeast(async (spans) => { + if (phase === 'done') + return; + mergeResourceSpans(spans, resourceSpans); - if (resourceSpans.length === 1 && + + if (phase === 'initial' && + resourceSpans.length === 1 && resourceSpans[0].scopeSpans.length === 1 && resourceSpans[0].scopeSpans[0].spans.length === 2) { - await child.shutdown(0); console.dir(resourceSpans, { depth: null }); const resourceSpan = resourceSpans[0]; const scopeSpans = resourceSpan.scopeSpans[0].spans; checkResource(resourceSpan.resource); checkHttpSpans(scopeSpans, threadId, 0); + + resourceSpans.length = 0; + phase = 'disabled'; + + await child.disableTraces(); + await child.trace('http'); + await delay(200); + assert.strictEqual(resourceSpans.length, 0); + + phase = 'reenabled'; + await child.enableTraces(); + await child.trace('http'); + } else if (phase === 'reenabled' && + resourceSpans.length === 1 && + resourceSpans[0].scopeSpans.length === 1 && + resourceSpans[0].scopeSpans[0].spans.length === 2) { + console.dir(resourceSpans, { depth: null }); + const resourceSpan = resourceSpans[0]; + const scopeSpans = resourceSpan.scopeSpans[0].spans; + checkResource(resourceSpan.resource); + checkHttpSpans(scopeSpans, threadId, 0); + + phase = 'done'; + await child.shutdown(0); resolve(); } - }, 1)); + }, 2)); })); }); }, @@ -240,20 +270,49 @@ tests.push({ const child = new TestClient(['-t', 'custom'], opts); const agentId = await child.id(); const resourceSpans = []; + let phase = 'initial'; grpcServer.on('spans', mustCallAtLeast(async (spans) => { + if (phase === 'done') + return; + mergeResourceSpans(spans, resourceSpans); - if (resourceSpans.length === 1 && + + if (phase === 'initial' && + resourceSpans.length === 1 && resourceSpans[0].scopeSpans.length === 1 && resourceSpans[0].scopeSpans[0].spans.length === 1) { - await child.shutdown(0); console.dir(resourceSpans, { depth: null }); const resourceSpan = resourceSpans[0]; const scopeSpans = resourceSpan.scopeSpans[0].spans; checkResource(resourceSpan.resource); checkCustomSpans(scopeSpans, threadId, 0); + + resourceSpans.length = 0; + phase = 'disabled'; + + await child.disableTraces(); + await child.trace('custom'); + await delay(200); + assert.strictEqual(resourceSpans.length, 0); + + phase = 'reenabled'; + await child.enableTraces(); + await child.trace('custom'); + } else if (phase === 'reenabled' && + resourceSpans.length === 1 && + resourceSpans[0].scopeSpans.length === 1 && + resourceSpans[0].scopeSpans[0].spans.length === 1) { + console.dir(resourceSpans, { depth: null }); + const resourceSpan = resourceSpans[0]; + const scopeSpans = resourceSpan.scopeSpans[0].spans; + checkResource(resourceSpan.resource); + checkCustomSpans(scopeSpans, threadId, 0); + + phase = 'done'; + await child.shutdown(0); resolve(); } - }, 1)); + }, 2)); })); }); }, diff --git a/test/agents/test-zmq-heap-profile.mjs b/test/agents/test-zmq-heap-profile.mjs index 8bf5f19b57..e215882118 100644 --- a/test/agents/test-zmq-heap-profile.mjs +++ b/test/agents/test-zmq-heap-profile.mjs @@ -339,6 +339,74 @@ tests.push({ }, }); +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (playground) => { + return new Promise((resolve) => { + let events = 0; + let profile = ''; + let requestId; + let resolved = false; + const options = { + duration: 100, + threadId: 0, + trackAllocations: false, + }; + + const bootstrapOpts = { + // Just to be sure we don't receive the loop_blocked event + opts: { env: { NSOLID_BLOCKED_LOOP_THRESHOLD: 10000 } }, + }; + + playground.bootstrap(bootstrapOpts, mustSucceed(async (agentId) => { + const disabledConfig = await playground.client.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + await new Promise((done) => { + playground.zmqAgentBus.agentHeapProfileStart(agentId, options, mustCall((err) => { + assert.strictEqual(err.code, 422); + assert.strictEqual(err.message, 'Invalid arguments'); + done(); + })); + }); + + const enabledConfig = await playground.client.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + requestId = playground.zmqAgentBus.agentHeapProfileStart(agentId, options); + }), async (eventType, agentId, data) => { + if (resolved) + return; + + switch (++events) { + case 1: + assert.strictEqual(eventType, 'asset-data-packet'); + if (data.packet.length > 0) { + checkProfileData(requestId, options, agentId, data.metadata, false); + profile += data.packet; + --events; + } else { + checkProfileData(requestId, options, agentId, data.metadata, true); + } + break; + case 2: { + assert.strictEqual(eventType, 'asset-received'); + checkProfileData(requestId, options, agentId, data, true); + const heapProf = JSON.parse(profile); + assert.strictEqual(heapProf.snapshot.trace_function_count, 0); + assert.strictEqual(heapProf.trace_function_infos.length, 0); + assert.strictEqual(heapProf.trace_tree.length, 0); + resolved = true; + const currentConfig = await playground.client.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + resolve(); + } + } + }); + }); + }, +}); + tests.push({ name: 'should end an ongoing profile before exiting', test: async (playground) => { diff --git a/test/agents/test-zmq-heap-sampling.mjs b/test/agents/test-zmq-heap-sampling.mjs index 4b7070e7fe..a1e74183dd 100644 --- a/test/agents/test-zmq-heap-sampling.mjs +++ b/test/agents/test-zmq-heap-sampling.mjs @@ -132,6 +132,74 @@ tests.push({ }, }); +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (playground) => { + return new Promise((resolve) => { + let events = 0; + let profile = ''; + let requestId; + let resolved = false; + const options = { + duration: 100, + threadId: 0, + }; + + const bootstrapOpts = { + // Just to be sure we don't receive the loop_blocked event + opts: { env: { NSOLID_BLOCKED_LOOP_THRESHOLD: 10000 } }, + }; + + playground.bootstrap(bootstrapOpts, mustSucceed(async (agentId) => { + const disabledConfig = await playground.client.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + await new Promise((done) => { + playground.zmqAgentBus.agentHeapSamplingStart(agentId, options, mustCall((err) => { + assert.strictEqual(err.code, 422); + assert.strictEqual(err.message, 'Invalid arguments'); + done(); + })); + }); + + const enabledConfig = await playground.client.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + requestId = playground.zmqAgentBus.agentHeapSamplingStart(agentId, options); + }), async (eventType, agentId, data) => { + if (resolved) + return; + + switch (++events) { + case 1: + assert.strictEqual(eventType, 'asset-data-packet'); + if (data.packet.length > 0) { + checkProfileData(requestId, options, agentId, data.metadata, false); + profile += data.packet; + --events; + } else { + checkProfileData(requestId, options, agentId, data.metadata, true); + } + break; + case 2: + { + assert.strictEqual(eventType, 'asset-received'); + checkProfileData(requestId, options, agentId, data, true); + const heapProf = JSON.parse(profile); + assert.ok(heapProf.head); + assert.ok(heapProf.head.callFrame); + assert.ok(heapProf.samples); + resolved = true; + const currentConfig = await playground.client.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + resolve(); + } + } + }); + }); + }, +}); + tests.push({ name: 'should return 410 if sent to a non-existant thread', test: async (playground) => { diff --git a/test/agents/test-zmq-profile.mjs b/test/agents/test-zmq-profile.mjs index ab94bc47ec..7d06597e96 100644 --- a/test/agents/test-zmq-profile.mjs +++ b/test/agents/test-zmq-profile.mjs @@ -117,6 +117,65 @@ tests.push({ }, }); +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (playground) => { + return new Promise((resolve) => { + let events = 0; + let profile = ''; + let requestId; + let resolved = false; + const options = { + duration: 100, + threadId: 0, + }; + + playground.bootstrap(mustSucceed(async (agentId) => { + const disabledConfig = await playground.client.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + await new Promise((done) => { + playground.zmqAgentBus.agentProfileStart(agentId, options, mustCall((err) => { + assert.strictEqual(err.code, 422); + assert.strictEqual(err.message, 'Invalid arguments'); + done(); + })); + }); + + const enabledConfig = await playground.client.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + requestId = playground.zmqAgentBus.agentProfileStart(agentId, options); + }), async (eventType, agentId, data) => { + if (resolved) + return; + + switch (++events) { + case 1: + assert.strictEqual(eventType, 'asset-data-packet'); + if (data.packet.length > 0) { + checkProfileData(requestId, options, agentId, data.metadata, false); + profile += data.packet; + --events; + } else { + checkProfileData(requestId, options, agentId, data.metadata, true); + } + break; + case 2: { + assert.strictEqual(eventType, 'asset-received'); + checkProfileData(requestId, options, agentId, data, true); + JSON.parse(profile); + resolved = true; + const currentConfig = await playground.client.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + resolve(); + } + } + }); + }); + }, +}); + tests.push({ name: 'should return 410 if sent to a non-existant thread', test: async (playground) => { diff --git a/test/agents/test-zmq-snapshot.mjs b/test/agents/test-zmq-snapshot.mjs index 8f609cbd3e..fb1c5b2056 100644 --- a/test/agents/test-zmq-snapshot.mjs +++ b/test/agents/test-zmq-snapshot.mjs @@ -232,6 +232,69 @@ tests.push({ }, }); +tests.push({ + name: 'should respect assetsEnabled toggled via nsolid.start()', + test: async (playground) => { + return new Promise((resolve) => { + let events = 0; + let snapshot = ''; + let requestId; + let resolved = false; + const options = { + threadId: 0, + }; + + const bootstrapOpts = { + // Just to be sure we don't receive the loop_blocked event + opts: { env: { NSOLID_BLOCKED_LOOP_THRESHOLD: 10000 } }, + }; + + playground.bootstrap(bootstrapOpts, mustSucceed(async (agentId) => { + const disabledConfig = await playground.client.config({ assetsEnabled: false }); + assert.strictEqual(disabledConfig.assetsEnabled, false); + + await new Promise((done) => { + playground.zmqAgentBus.agentSnapshotRequest(agentId, options, mustCall((err) => { + assert.strictEqual(err.code, 422); + assert.strictEqual(err.message, 'Invalid arguments'); + done(); + })); + }); + + const enabledConfig = await playground.client.config({ assetsEnabled: true }); + assert.strictEqual(enabledConfig.assetsEnabled, true); + + requestId = playground.zmqAgentBus.agentSnapshotRequest(agentId, options); + }), async (eventType, agentId, data) => { + if (resolved) + return; + + switch (++events) { + case 1: + assert.strictEqual(eventType, 'asset-data-packet'); + if (data.packet.length > 0) { + checkSnapshotData(requestId, options, agentId, data.metadata, false); + snapshot += data.packet; + --events; + } else { + checkSnapshotData(requestId, options, agentId, data.metadata, true); + } + break; + case 2: { + assert.strictEqual(eventType, 'asset-received'); + checkSnapshotData(requestId, options, agentId, data, true); + JSON.parse(snapshot); + resolved = true; + const currentConfig = await playground.client.config(); + assert.strictEqual(currentConfig.assetsEnabled, true); + resolve(); + } + } + }); + }); + }, +}); + const config = { commandBindAddr: 'tcp://*:9001', diff --git a/test/agents/test-zmq-tracing.mjs b/test/agents/test-zmq-tracing.mjs index 9be640cacc..47e1af5caa 100644 --- a/test/agents/test-zmq-tracing.mjs +++ b/test/agents/test-zmq-tracing.mjs @@ -2,6 +2,7 @@ import { mustCall, mustCallAtLeast, mustSucceed } from '../common/index.mjs'; import assert from 'node:assert'; import { threadId } from 'node:worker_threads'; +import { setTimeout as delay } from 'node:timers/promises'; import validators from 'internal/validators'; import { TestPlayground } from '../common/nsolid-zmq-agent/index.js'; @@ -402,6 +403,74 @@ tests.push({ }, }); +tests.push({ + name: 'should respect tracingEnabled toggled via enableTraces/disableTraces', + test: async (playground) => { + return new Promise((resolve) => { + let totalSpans = 0; + let phase = 'initial'; + const opts = { + opts: { + env: { + NSOLID_TRACING_ENABLED: 1, + NSOLID_APPNAME: 'myapp', + }, + }, + }; + + playground.bootstrap(opts, mustSucceed(async (agentId) => { + // Initial trace with tracing enabled + await playground.client.tracing('http', threadId); + }), mustCallAtLeast(async (eventType, agentId, data) => { + if (phase === 'done') + return; + + console.log(`${eventType}, ${agentId}, phase: ${phase}`); + assert.strictEqual(eventType, 'agent-tracing'); + + // Fail immediately if spans arrive while tracing is disabled + assert.notStrictEqual(phase, 'disabled', 'Tracing emitted spans while disabled'); + + if (phase === 'initial') { + checkTracingData(data, null, agentId, threadId, 'myapp'); + const spanTypes = [ 'http_server', 'http_client']; + for (const span of data.body.spans) { + validateSpan(span, spanTypes[totalSpans], threadId); + totalSpans++; + } + + if (totalSpans === 2) { + totalSpans = 0; + phase = 'disabled'; + + // Disable tracing and verify no spans are emitted + await playground.client.disableTraces(); + await playground.client.tracing('http', threadId); + await delay(200); + + // Re-enable tracing + phase = 'reenabled'; + await playground.client.enableTraces(); + await playground.client.tracing('http', threadId); + } + } else if (phase === 'reenabled') { + checkTracingData(data, null, agentId, threadId, 'myapp'); + const spanTypes = [ 'http_server', 'http_client']; + for (const span of data.body.spans) { + validateSpan(span, spanTypes[totalSpans], threadId); + totalSpans++; + } + + if (totalSpans === 2) { + phase = 'done'; + resolve(); + } + } + }, 2)); + }); + }, +}); + const config = { commandBindAddr: 'tcp://*:9001', dataBindAddr: 'tcp://*:9002', diff --git a/test/common/nsolid-grpc-agent/client.js b/test/common/nsolid-grpc-agent/client.js index 500bbab082..f094e528b9 100644 --- a/test/common/nsolid-grpc-agent/client.js +++ b/test/common/nsolid-grpc-agent/client.js @@ -61,7 +61,12 @@ function execCustomTrace() { setTimeout(() => { const api = require(require.resolve('@opentelemetry/api', { paths: [fixturesDir] })); - nsolid.otel.register(api); + try { + nsolid.otel.register(api); + } catch (err) { + if (err.code !== 'ERR_NSOLID_OTEL_API_ALREADY_REGISTERED') + throw err; + } const tracer = api.trace.getTracer('test'); const span = tracer.startSpan('initial_name', { attributes: { a: 1, b: 2 }, kind: api.SpanKind.CLIENT }); @@ -117,6 +122,18 @@ if (isMainThread) { } process.send({ type: 'config', config: nsolid.config }); + } else if (msg.type === 'enable_assets') { + nsolid.enableAssets(); + process.send({ type: 'enable_assets' }); + } else if (msg.type === 'disable_assets') { + nsolid.disableAssets(); + process.send({ type: 'disable_assets' }); + } else if (msg.type === 'enable_traces') { + nsolid.enableTraces(); + process.send({ type: 'enable_traces' }); + } else if (msg.type === 'disable_traces') { + nsolid.disableTraces(); + process.send({ type: 'disable_traces' }); } else if (msg.type === 'heap_profile') { nsolid.heapProfile(msg.duration); } else if (msg.type === 'heap_sampling') { diff --git a/test/common/nsolid-grpc-agent/index.js b/test/common/nsolid-grpc-agent/index.js index f5c178db10..bb08bfaf36 100644 --- a/test/common/nsolid-grpc-agent/index.js +++ b/test/common/nsolid-grpc-agent/index.js @@ -344,17 +344,59 @@ class TestClient { } async config(config = null) { + if (!this.#child) + return null; + return this.#sendAndWait({ + type: 'config', + payload: { config }, + expect: 'config', + map: (msg) => msg.config, + }); + } + + async enableAssets() { + if (!this.#child) + return; + await this.#sendAndWait({ type: 'enable_assets' }); + } + + async disableAssets() { + if (!this.#child) + return; + await this.#sendAndWait({ type: 'disable_assets' }); + } + + async enableTraces() { + if (!this.#child) + return; + await this.#sendAndWait({ type: 'enable_traces' }); + } + + async disableTraces() { + if (!this.#child) + return; + await this.#sendAndWait({ type: 'disable_traces' }); + } + + async trace(kind, targetThreadId = 0) { + if (!this.#child) + return; + await new Promise((resolve) => { + this.#child.send({ type: 'trace', kind, threadId: targetThreadId }, resolve); + }); + } + + #sendAndWait({ type, payload = {}, expect = type, map }) { return new Promise((resolve) => { - if (this.#child) { - this.#child.send({ type: 'config', config }); - this.#child.on('message', (msg) => { - if (msg.type === 'config') { - resolve(msg.config); - } - }); - } else { - resolve(null); - } + const handler = (msg) => { + if (msg.type !== expect) + return; + this.#child.off('message', handler); + resolve(map ? map(msg) : undefined); + }; + + this.#child.on('message', handler); + this.#child.send({ type, ...payload }); }); } diff --git a/test/common/nsolid-zmq-agent/client.js b/test/common/nsolid-zmq-agent/client.js index 60bb14db91..1d507a257d 100644 --- a/test/common/nsolid-zmq-agent/client.js +++ b/test/common/nsolid-zmq-agent/client.js @@ -138,6 +138,14 @@ if (isMainThread) { } } else if (msg.type === 'workers') { process.send({ type: 'workers', ids: Array.from(workers.keys()) }); + } else if (msg.type === 'enableTraces') { + nsolid.enableTraces(); + } else if (msg.type === 'disableTraces') { + nsolid.disableTraces(); + } else if (msg.type === 'enableAssets') { + nsolid.enableAssets(); + } else if (msg.type === 'disableAssets') { + nsolid.disableAssets(); } }); diff --git a/test/common/nsolid-zmq-agent/index.js b/test/common/nsolid-zmq-agent/index.js index ce00672e95..ecd953c980 100644 --- a/test/common/nsolid-zmq-agent/index.js +++ b/test/common/nsolid-zmq-agent/index.js @@ -184,6 +184,54 @@ class TestClient { }); } + async enableTraces() { + return new Promise((resolve) => { + if (this.#child) { + this.#child.send({ type: 'enableTraces' }, () => { + resolve(); + }); + } else { + resolve(); + } + }); + } + + async disableTraces() { + return new Promise((resolve) => { + if (this.#child) { + this.#child.send({ type: 'disableTraces' }, () => { + resolve(); + }); + } else { + resolve(); + } + }); + } + + async enableAssets() { + return new Promise((resolve) => { + if (this.#child) { + this.#child.send({ type: 'enableAssets' }, () => { + resolve(); + }); + } else { + resolve(); + } + }); + } + + async disableAssets() { + return new Promise((resolve) => { + if (this.#child) { + this.#child.send({ type: 'disableAssets' }, () => { + resolve(); + }); + } else { + resolve(); + } + }); + } + async workers() { return new Promise((resolve) => { if (this.#child) { diff --git a/test/parallel/test-nsolid-config-tracing.js b/test/parallel/test-nsolid-config-tracing.js index c5b5b65255..4773e8fe24 100644 --- a/test/parallel/test-nsolid-config-tracing.js +++ b/test/parallel/test-nsolid-config-tracing.js @@ -20,3 +20,10 @@ nsolid.start({ }); assert.strictEqual(nsolid.config.tracingEnabled, true); + +// Runtime helpers should keep config in sync +nsolid.disableTraces(); +assert.strictEqual(nsolid.config.tracingEnabled, false); + +nsolid.enableTraces(); +assert.strictEqual(nsolid.config.tracingEnabled, true); diff --git a/test/parallel/test-nsolid-heap-profile-stream.js b/test/parallel/test-nsolid-heap-profile-stream.js index 2944e298cd..d31f432664 100644 --- a/test/parallel/test-nsolid-heap-profile-stream.js +++ b/test/parallel/test-nsolid-heap-profile-stream.js @@ -65,5 +65,62 @@ const { }); stream.on('end', common.mustCall(() => { assert(JSON.parse(profile)); + runHeapProfileStreamAssetsToggleTests(); })); } + +function runHeapProfileStreamAssetsToggleTests() { + // Disable assets via config update and ensure stream creation fails + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + }); + + setImmediate(() => { + assert.throws( + () => { + nsolid.heapProfileStream(0, 1000, true); + }, + { + message: 'Heap profile could not be started' + } + ); + + // Re-enable through helper and confirm stream succeeds again + nsolid.enableAssets(); + setImmediate(() => { + let profile = ''; + const enabledStream = nsolid.heapProfileStream(0, 1200, true); + enabledStream.on('data', (chunk) => { + profile += chunk; + }); + enabledStream.on('end', common.mustCall(() => { + assert(JSON.parse(profile)); + + // Disable assets via helper and ensure stream creation fails again + nsolid.disableAssets(); + setImmediate(() => { + assert.throws( + () => { + nsolid.heapProfileStream(0, 1000, true); + }, + { + message: 'Heap profile could not be started' + } + ); + + // Final re-enable to restore functionality + nsolid.enableAssets(); + setImmediate(() => { + const finalStream = nsolid.heapProfileStream(0, 1200, true); + finalStream.resume(); + finalStream.on('end', common.mustCall(() => { + assert.ok(true); + })); + }); + }); + })); + }); + }); +} diff --git a/test/parallel/test-nsolid-heap-profile.js b/test/parallel/test-nsolid-heap-profile.js index 8fee3145c2..10453d35fc 100644 --- a/test/parallel/test-nsolid-heap-profile.js +++ b/test/parallel/test-nsolid-heap-profile.js @@ -121,10 +121,84 @@ setTimeout(() => { assert.notStrictEqual(err.code, 0); assert.strictEqual(err.message, 'Heap profile could not be stopped'); + runHeapProfileAssetsToggleTests(); })); })); })); })); })); - }, 100); -}, 100); + }, common.platformTimeout(100)); +}, common.platformTimeout(100)); + +function runHeapProfileAssetsToggleTests() { + // Disable assets via config update + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + }); + + setTimeout(() => { + assert.throws( + () => { + nsolid.heapProfile(); + }, + { + message: 'Heap profile could not be started' + } + ); + + nsolid.heapProfile(common.mustCall((err) => { + assert.notStrictEqual(err.code, 0); + assert.strictEqual(err.message, 'Heap profile could not be started'); + })); + + assert.throws( + () => { + nsolid.heapProfileEnd(); + }, + { + message: 'Heap profile could not be stopped' + } + ); + + // Re-enable via helper + nsolid.enableAssets(); + setTimeout(() => { + // Start profile and wait for it to complete before toggling assets + nsolid.heapProfile(common.mustSucceed(() => { + nsolid.heapProfileEnd(common.mustSucceed(() => { + // Only disable assets after profile completes + nsolid.disableAssets(); + setTimeout(() => { + assert.throws( + () => { + nsolid.heapProfile(); + }, + { + message: 'Heap profile could not be started' + } + ); + + assert.throws( + () => { + nsolid.heapProfileEnd(); + }, + { + message: 'Heap profile could not be stopped' + } + ); + + // Only re-enable after errors are confirmed + nsolid.enableAssets(); + setTimeout(() => { + nsolid.heapProfile(common.mustSucceed(() => { + nsolid.heapProfileEnd(common.mustSucceed()); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); + })); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); +} diff --git a/test/parallel/test-nsolid-heap-sampling-stream.js b/test/parallel/test-nsolid-heap-sampling-stream.js index b64a975a20..4ca1148cc0 100644 --- a/test/parallel/test-nsolid-heap-sampling-stream.js +++ b/test/parallel/test-nsolid-heap-sampling-stream.js @@ -135,6 +135,7 @@ const { stream.on('end', common.mustCall(() => { assert(JSON.parse(profile)); testProfileSchema(JSON.parse(profile)); + runHeapSamplingStreamAssetsToggleTests(); })); } @@ -166,3 +167,62 @@ function testProfileSchema(profile) { assert(isTestDone); } } + +function runHeapSamplingStreamAssetsToggleTests() { + // Disable assets through config update + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + }); + + setImmediate(() => { + assert.throws( + () => { + nsolid.heapSamplingStream(0, 1000); + }, + { + code: 'ERR_NSOLID_HEAP_SAMPLING_START', + message: 'Heap sampling could not be started' + } + ); + + // Re-enable via helper and confirm success + nsolid.enableAssets(); + setImmediate(() => { + let profile = ''; + const enabledStream = nsolid.heapSamplingStream(0, 1200); + enabledStream.on('data', (chunk) => { + profile += chunk; + }); + enabledStream.on('end', common.mustCall(() => { + assert(JSON.parse(profile)); + testProfileSchema(JSON.parse(profile)); + + // Disable via helper and confirm failure again + nsolid.disableAssets(); + setImmediate(() => { + assert.throws( + () => { + nsolid.heapSamplingStream(0, 1000); + }, + { + code: 'ERR_NSOLID_HEAP_SAMPLING_START', + message: 'Heap sampling could not be started' + } + ); + + // Re-enable once more to restore functionality + nsolid.enableAssets(); + setImmediate(() => { + const finalStream = nsolid.heapSamplingStream(0, 1200); + finalStream.resume(); + finalStream.on('end', common.mustCall(() => { + assert.ok(true); + })); + }); + }); + })); + }); + }); +} diff --git a/test/parallel/test-nsolid-heap-sampling.js b/test/parallel/test-nsolid-heap-sampling.js index 5057e5d401..bde69a8e2c 100644 --- a/test/parallel/test-nsolid-heap-sampling.js +++ b/test/parallel/test-nsolid-heap-sampling.js @@ -161,12 +161,88 @@ setTimeout(() => { // profileEnd() should return an error if no ongoing profile nsolid.heapSamplingEnd(common.mustCall((err) => { assert.notStrictEqual(err.code, 0); - assert.strictEqual(err.message, - 'Heap sampling could not be stopped'); + assert.strictEqual( + err.message, + 'Heap sampling could not be stopped' + ); + runHeapSamplingAssetsToggleTests(); })); })); })); })); })); - }, 100); -}, 100); + }, common.platformTimeout(100)); +}, common.platformTimeout(100)); + +function runHeapSamplingAssetsToggleTests() { + // Disable assets through config update + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + }); + + setTimeout(() => { + assert.throws( + () => { + nsolid.heapSampling(); + }, + { + message: 'Heap sampling could not be started' + } + ); + + nsolid.heapSampling(common.mustCall((err) => { + assert.notStrictEqual(err.code, 0); + assert.strictEqual(err.message, 'Heap sampling could not be started'); + })); + + assert.throws( + () => { + nsolid.heapSamplingEnd(); + }, + { + message: 'Heap sampling could not be stopped' + } + ); + + // Re-enable via helper + nsolid.enableAssets(); + setTimeout(() => { + // Start sampling and wait for it to complete before toggling assets + nsolid.heapSampling(common.mustSucceed(() => { + nsolid.heapSamplingEnd(common.mustSucceed(() => { + // Only disable assets after sampling completes + nsolid.disableAssets(); + setTimeout(() => { + assert.throws( + () => { + nsolid.heapSampling(); + }, + { + message: 'Heap sampling could not be started' + } + ); + + assert.throws( + () => { + nsolid.heapSamplingEnd(); + }, + { + message: 'Heap sampling could not be stopped' + } + ); + + // Only re-enable after errors are confirmed + nsolid.enableAssets(); + setTimeout(() => { + nsolid.heapSampling(common.mustSucceed(() => { + nsolid.heapSamplingEnd(common.mustSucceed()); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); + })); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); +} diff --git a/test/parallel/test-nsolid-profile.js b/test/parallel/test-nsolid-profile.js index 795a07f4b6..d03c7e906d 100644 --- a/test/parallel/test-nsolid-profile.js +++ b/test/parallel/test-nsolid-profile.js @@ -96,8 +96,6 @@ setTimeout(() => { nsolid.profileEnd(common.mustCall((err) => { assert.notStrictEqual(err.code, 0); assert.strictEqual(err.message, 'CPU profile could not be stopped'); - // The same with callback versions - // profile() should return an error if ongoing profile nsolid.profile(common.mustSucceed(() => { nsolid.profile(common.mustCall((err) => { assert.notStrictEqual(err.code, 0); @@ -106,12 +104,79 @@ setTimeout(() => { // profileEnd() should return an error if no ongoing profile nsolid.profileEnd(common.mustCall((err) => { assert.notStrictEqual(err.code, 0); - assert.strictEqual(err.message, - 'CPU profile could not be stopped'); + assert.strictEqual( + err.message, + 'CPU profile could not be stopped' + ); + runAssetsToggleTests(); })); })); })); })); })); - }, 100); -}, 100); + }, common.platformTimeout(100)); +}, common.platformTimeout(100)); + +function runAssetsToggleTests() { + // Disable assets through config update + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + }); + + setTimeout(() => { + assert.throws( + () => { + nsolid.profile(); + }, + { + message: 'CPU profile could not be started' + } + ); + + nsolid.profile(common.mustCall((err) => { + assert.notStrictEqual(err.code, 0); + assert.strictEqual(err.message, 'CPU profile could not be started'); + })); + + assert.throws( + () => { + nsolid.profileEnd(); + }, + { + message: 'CPU profile could not be stopped' + } + ); + + // Re-enable through helper + nsolid.enableAssets(); + setTimeout(() => { + // Start profile and wait for it to complete before toggling assets + nsolid.profile(common.mustSucceed(() => { + nsolid.profileEnd(common.mustSucceed(() => { + // Only disable assets after profile completes + nsolid.disableAssets(); + setTimeout(() => { + assert.throws( + () => { + nsolid.profile(); + }, + { + message: 'CPU profile could not be started' + } + ); + + // Only re-enable after error is confirmed + nsolid.enableAssets(); + setTimeout(() => { + nsolid.profile(common.mustSucceed(() => { + nsolid.profileEnd(common.mustSucceed()); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); + })); + })); + }, common.platformTimeout(100)); + }, common.platformTimeout(100)); +} diff --git a/test/parallel/test-nsolid-snapshot.js b/test/parallel/test-nsolid-snapshot.js index fcf4e8d2e8..58dd2dcf91 100644 --- a/test/parallel/test-nsolid-snapshot.js +++ b/test/parallel/test-nsolid-snapshot.js @@ -53,5 +53,58 @@ setImmediate(() => { nsolid.snapshot(common.mustCall((err) => { assert.notStrictEqual(err.code, 0); assert.strictEqual(err.message, 'Heap snapshot could not be generated'); + runSnapshotAssetsToggleTests(); })); }); + +function runSnapshotAssetsToggleTests() { + nsolid.start({ + command: 'localhost:9001', + data: 'localhost:9002', + assetsEnabled: false, + disableSnapshots: false, + }); + + setImmediate(() => { + assert.throws( + () => { + nsolid.snapshot(); + }, + { + message: 'Heap snapshot could not be generated' + } + ); + + nsolid.snapshot(common.mustCall((err) => { + assert.notStrictEqual(err.code, 0); + assert.strictEqual(err.message, 'Heap snapshot could not be generated'); + + // Only enable assets after the error callback completes + nsolid.enableAssets(); + setImmediate(() => { + // Wait for snapshot to complete before disabling assets + nsolid.snapshot(common.mustSucceed(() => { + // Only disable assets after snapshot completes + nsolid.disableAssets(); + setImmediate(() => { + assert.throws( + () => { + nsolid.snapshot(); + }, + { + message: 'Heap snapshot could not be generated' + } + ); + + // Only enable assets after error is confirmed + nsolid.enableAssets(); + setImmediate(() => { + // Wait for final snapshot to complete + nsolid.snapshot(common.mustSucceed()); + }); + }); + })); + }); + })); + }); +}