Merge remote-tracking branch 'origin/topic/vern/if-coverage'

* origin/topic/vern/if-coverage:
  extend script coverage profiling to track whether conditionals evaluate to true/false
This commit is contained in:
Arne Welzel 2025-06-26 18:49:31 +02:00
commit 3cd6e1ca06
9 changed files with 131 additions and 39 deletions

View file

@ -1,3 +1,7 @@
8.0.0-dev.548 | 2025-06-26 18:49:31 +0200
* extend script coverage profiling to track whether conditionals evaluate to true/false (Vern Paxson, Corelight)
8.0.0-dev.546 | 2025-06-26 14:30:37 +0200 8.0.0-dev.546 | 2025-06-26 14:30:37 +0200
* btest/cluster/telemetry: Add smoke testing for telemetry (Arne Welzel, Corelight) * btest/cluster/telemetry: Add smoke testing for telemetry (Arne Welzel, Corelight)

View file

@ -1 +1 @@
8.0.0-dev.546 8.0.0-dev.548

View file

@ -20,6 +20,8 @@ using namespace std;
namespace zeek::detail { namespace zeek::detail {
ScriptCoverageManager::ScriptCoverageManager() { pf = getenv("ZEEK_PROFILER_FILE"); }
void ScriptCoverageManager::AddStmt(Stmt* s) { void ScriptCoverageManager::AddStmt(Stmt* s) {
if ( ignoring != 0 || analysis_options.gen_ZAM ) if ( ignoring != 0 || analysis_options.gen_ZAM )
return; return;
@ -34,14 +36,16 @@ void ScriptCoverageManager::AddFunction(IDPtr func_id, StmtPtr body) {
func_instances.emplace_back(func_id, body); func_instances.emplace_back(func_id, body);
} }
bool ScriptCoverageManager::ReadStats() { void ScriptCoverageManager::AddConditional(Location cond_loc, std::string_view text, bool was_true) {
char* bf = getenv("ZEEK_PROFILER_FILE"); cond_instances.push_back({cond_loc, std::string(text), was_true});
}
if ( ! bf ) bool ScriptCoverageManager::ReadStats() {
if ( ! IsActive() )
return false; return false;
std::ifstream ifs; std::ifstream ifs;
ifs.open(bf, std::ifstream::in); ifs.open(pf, std::ifstream::in);
if ( ! ifs ) if ( ! ifs )
return false; return false;
@ -82,38 +86,43 @@ bool ScriptCoverageManager::ReadStats() {
} }
bool ScriptCoverageManager::WriteStats() { bool ScriptCoverageManager::WriteStats() {
char* bf = getenv("ZEEK_PROFILER_FILE"); if ( ! IsActive() )
if ( ! bf )
return false; return false;
util::SafeDirname dirname{bf}; util::SafeDirname dirname{pf};
if ( ! util::detail::ensure_intermediate_dirs(dirname.result.data()) ) { if ( ! util::detail::ensure_intermediate_dirs(dirname.result.data()) ) {
reporter->Error("Failed to open ZEEK_PROFILER_FILE destination '%s' for writing", bf); reporter->Error("Failed to open ZEEK_PROFILER_FILE destination '%s' for writing", pf);
return false; return false;
} }
FILE* f; FILE* f;
const char* p = strstr(bf, "XXXXXX"); const char* p = strstr(pf, "XXXXXX");
if ( p && ! p[6] ) { if ( p && ! p[6] ) {
mode_t old_umask = umask(S_IXUSR | S_IRWXO | S_IRWXG); mode_t old_umask = umask(S_IXUSR | S_IRWXO | S_IRWXG);
int fd = mkstemp(bf); auto pf_copy = strdup(pf);
if ( ! pf_copy ) {
reporter->InternalError("Memory exhausted in ScriptCoverageManager::WriteStats");
return false;
}
int fd = mkstemp(pf_copy);
free(pf_copy);
umask(old_umask); umask(old_umask);
if ( fd == -1 ) { if ( fd == -1 ) {
reporter->Error("Failed to generate unique file name from ZEEK_PROFILER_FILE: %s", bf); reporter->Error("Failed to generate unique file name from ZEEK_PROFILER_FILE: %s", pf);
return false; return false;
} }
f = fdopen(fd, "w"); f = fdopen(fd, "w");
} }
else { else {
f = fopen(bf, "w"); f = fopen(pf, "w");
} }
if ( ! f ) { if ( ! f ) {
reporter->Error("Failed to open ZEEK_PROFILER_FILE destination '%s' for writing", bf); reporter->Error("Failed to open ZEEK_PROFILER_FILE destination '%s' for writing", pf);
return false; return false;
} }
@ -130,6 +139,9 @@ bool ScriptCoverageManager::WriteStats() {
TrackUsage(body, desc, body->GetAccessCount()); TrackUsage(body, desc, body->GetAccessCount());
} }
for ( const auto& [cond_loc, text, was_true] : cond_instances )
TrackUsage(&cond_loc, text, was_true ? 1 : 0);
for ( auto& [location_info, cnt] : usage_map ) for ( auto& [location_info, cnt] : usage_map )
Report(f, cnt, location_info.first, location_info.second); Report(f, cnt, location_info.first, location_info.second);
@ -137,9 +149,9 @@ bool ScriptCoverageManager::WriteStats() {
return true; return true;
} }
void ScriptCoverageManager::TrackUsage(const ObjPtr& obj, std::string desc, uint64_t cnt) { void ScriptCoverageManager::TrackUsage(const Location* loc, std::string desc, uint64_t cnt) {
ODesc location_info; ODesc location_info;
obj->GetLocationInfo()->Describe(&location_info); loc->Describe(&location_info);
static canonicalize_desc cd{delim}; static canonicalize_desc cd{delim};
for_each(desc.begin(), desc.end(), cd); for_each(desc.begin(), desc.end(), cd);

View file

@ -15,10 +15,19 @@ namespace zeek::detail {
using ObjPtr = IntrusivePtr<Obj>; using ObjPtr = IntrusivePtr<Obj>;
/** /**
* A simple class for managing stats of Zeek script coverage across Zeek runs. * A class for managing stats of Zeek script coverage across Zeek runs.
*/ */
class ScriptCoverageManager { class ScriptCoverageManager {
public: public:
ScriptCoverageManager();
/**
* Returns true if the manager is active (will do work), false if not.
*
* @return: true if active, false if not.
*/
bool IsActive() const { return pf != nullptr; }
/** /**
* Imports Zeek script Stmt usage information from file pointed to by * Imports Zeek script Stmt usage information from file pointed to by
* environment variable ZEEK_PROFILER_FILE. * environment variable ZEEK_PROFILER_FILE.
@ -45,8 +54,14 @@ public:
void AddStmt(Stmt* s); void AddStmt(Stmt* s);
void AddFunction(IDPtr func_id, StmtPtr body); void AddFunction(IDPtr func_id, StmtPtr body);
void AddConditional(Location cond_loc, std::string_view text, bool was_true);
private: private:
/**
* The name of the profile file, or nil if we're not profiling.
*/
const char* pf;
/** /**
* The current, global ScriptCoverageManager instance creates this list at parse-time. * The current, global ScriptCoverageManager instance creates this list at parse-time.
*/ */
@ -57,6 +72,20 @@ private:
*/ */
std::list<std::pair<IDPtr, StmtPtr>> func_instances; std::list<std::pair<IDPtr, StmtPtr>> func_instances;
/**
* Helper struct for tracking the result of @-directives.
*/
struct Conditional {
Location loc;
std::string text;
bool result;
};
/**
* A similar list for tracking conditionals and whether they were true.
*/
std::list<Conditional> cond_instances;
/** /**
* Indicates whether new statements will not be considered as part of * Indicates whether new statements will not be considered as part of
* coverage statistics because it was marked with the @no-test tag. * coverage statistics because it was marked with the @no-test tag.
@ -95,7 +124,10 @@ private:
* Tracks the usage of a given object with a given description * Tracks the usage of a given object with a given description
* and a given coverage count. * and a given coverage count.
*/ */
void TrackUsage(const ObjPtr& obj, std::string desc, uint64_t cnt); void TrackUsage(const ObjPtr& obj, std::string desc, uint64_t cnt) {
TrackUsage(obj->GetLocationInfo(), std::move(desc), cnt);
}
void TrackUsage(const Location* loc, std::string desc, uint64_t cnt);
/** /**
* Reports a single coverage instance. * Reports a single coverage instance.

View file

@ -27,6 +27,7 @@
#include "zeek/Scope.h" #include "zeek/Scope.h"
#include "zeek/ZeekString.h" #include "zeek/ZeekString.h"
#include "zeek/DNS_Mgr.h" #include "zeek/DNS_Mgr.h"
#include "zeek/Desc.h"
#include "zeek/Expr.h" #include "zeek/Expr.h"
#include "zeek/Func.h" #include "zeek/Func.h"
#include "zeek/Stmt.h" #include "zeek/Stmt.h"
@ -38,6 +39,7 @@
#include "zeek/Reporter.h" #include "zeek/Reporter.h"
#include "zeek/RE.h" #include "zeek/RE.h"
#include "zeek/RunState.h" #include "zeek/RunState.h"
#include "zeek/ScriptCoverageManager.h"
#include "zeek/Traverse.h" #include "zeek/Traverse.h"
#include "zeek/module_util.h" #include "zeek/module_util.h"
#include "zeek/ScannedFile.h" #include "zeek/ScannedFile.h"
@ -891,10 +893,24 @@ public:
std::vector<const zeek::detail::NameExpr*> local_names; std::vector<const zeek::detail::NameExpr*> local_names;
}; };
static void begin_ignoring() { static void set_ignoring(bool start_ignoring, std::string_view directive, std::optional<std::string_view> value = std::nullopt) {
if ( script_coverage_mgr.IsActive() ) {
std::string text = std::string(directive);
if ( value ) {
text += " ( ";
text += *value;
text += " )";
}
script_coverage_mgr.AddConditional(GetCurrentLocation(), text, ! start_ignoring);
}
if ( start_ignoring ) {
if_stack.push_back(conditional_depth); if_stack.push_back(conditional_depth);
BEGIN(IGNORE); BEGIN(IGNORE);
} }
}
static void resume_processing() { static void resume_processing() {
if_stack.pop_back(); if_stack.pop_back();
@ -920,8 +936,7 @@ void do_atif(zeek::detail::Expr* expr) {
return; return;
} }
if ( ! val->AsBool() ) set_ignoring(! val->AsBool(), "@if", zeek::obj_desc_short(expr));
begin_ignoring();
} }
void do_atifdef(const char* id) { void do_atifdef(const char* id) {
@ -934,8 +949,7 @@ void do_atifdef(const char* id) {
if ( ! found ) if ( ! found )
found = (zeek::detail::module_names().count(id) != 0); found = (zeek::detail::module_names().count(id) != 0);
if ( ! found ) set_ignoring(! found, "@ifdef", id);
begin_ignoring();
} }
void do_atifndef(const char* id) { void do_atifndef(const char* id) {
@ -948,8 +962,7 @@ void do_atifndef(const char* id) {
if ( ! found ) if ( ! found )
found = (zeek::detail::module_names().count(id) != 0); found = (zeek::detail::module_names().count(id) != 0);
if ( found ) set_ignoring(found, "@ifndef", id);
begin_ignoring();
} }
void do_atelse() { void do_atelse() {
@ -960,10 +973,12 @@ void do_atelse() {
return; return;
if ( YY_START == INITIAL ) if ( YY_START == INITIAL )
begin_ignoring(); set_ignoring(true, "@else");
else else {
set_ignoring(false, "@else");
resume_processing(); resume_processing();
} }
}
void do_atendif() { void do_atendif() {
if ( conditional_depth <= entry_cond_depth.back() ) if ( conditional_depth <= entry_cond_depth.back() )

View file

@ -1,3 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
1 ./profiling-test1.zeek, line 2 print new conn; 1 ./profiling-test1.zeek, line 2 @if ( T )
1 ./profiling-test1.zeek, lines 1-2 event new_connection BODY 1 ./profiling-test1.zeek, line 4 print new conn;
1 ./profiling-test1.zeek, lines 3-4 event new_connection BODY

View file

@ -1,3 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
2 ./profiling-test1.zeek, line 2 print new conn; 2 ./profiling-test1.zeek, line 2 @if ( T )
2 ./profiling-test1.zeek, lines 1-2 event new_connection BODY 2 ./profiling-test1.zeek, line 4 print new conn;
2 ./profiling-test1.zeek, lines 3-4 event new_connection BODY

View file

@ -1,5 +1,16 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
2 ./profiling-test1.zeek, line 2 print new conn; 2 ./profiling-test1.zeek, line 2 @if ( T )
2 ./profiling-test1.zeek, lines 1-2 event new_connection BODY 2 ./profiling-test1.zeek, line 4 print new conn;
1 ./profiling-test2.zeek, line 2 print new conn; 2 ./profiling-test1.zeek, lines 3-4 event new_connection BODY
1 ./profiling-test2.zeek, lines 1-2 event new_connection BODY 0 ./profiling-test2.zeek, line 1 @if ( F )
0 ./profiling-test2.zeek, line 11 @ifndef ( Conn::Info )
1 ./profiling-test2.zeek, line 13 @else
1 ./profiling-test2.zeek, line 14 event zeek_init BODY
1 ./profiling-test2.zeek, line 14 print Conn::Info;
1 ./profiling-test2.zeek, line 2 @else
1 ./profiling-test2.zeek, line 4 print new conn;
1 ./profiling-test2.zeek, line 6 @ifdef ( Conn::Info )
1 ./profiling-test2.zeek, line 7 event zeek_init BODY
1 ./profiling-test2.zeek, line 7 print Conn::Info;
0 ./profiling-test2.zeek, line 8 @else
1 ./profiling-test2.zeek, lines 3-4 event new_connection BODY

View file

@ -13,11 +13,27 @@
# @TEST-EXEC: btest-diff step3.out # @TEST-EXEC: btest-diff step3.out
# @TEST-START-FILE profiling-test1.zeek # @TEST-START-FILE profiling-test1.zeek
@if ( T )
event new_connection(c: connection) event new_connection(c: connection)
{ print "new conn"; } { print "new conn"; }
@endif
# @TEST-END-FILE # @TEST-END-FILE
# @TEST-START-FILE profiling-test2.zeek # @TEST-START-FILE profiling-test2.zeek
@if ( F )
@else
event new_connection(c: connection) event new_connection(c: connection)
{ print "new conn"; } { print "new conn"; }
@endif
@ifdef ( Conn::Info )
event zeek_init() { print Conn::Info; }
@else
event zeek_init() { print "No Conn::Info"; }
@endif
@ifndef ( Conn::Info )
event zeek_init() { print "No Conn::Info"; }
@else
event zeek_init() { print Conn::Info; }
@endif
# @TEST-END-FILE # @TEST-END-FILE