diff --git a/src/Val.cc b/src/Val.cc index 1d0055f71b..d2d5079699 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -1322,6 +1322,18 @@ ValPtr ListVal::DoClone(CloneState* state) return lv; } +unsigned int ListVal::Footprint(bool count_entries) const + { + unsigned int fp = 0; + for ( const auto& val : vals ) + fp += val->Footprint(count_entries); + + if ( count_entries ) + fp += vals.size(); + + return fp; + } + unsigned int ListVal::MemoryAllocation() const { #pragma GCC diagnostic push @@ -2673,6 +2685,27 @@ ValPtr TableVal::DoClone(CloneState* state) return tv; } +unsigned int TableVal::Footprint(bool count_entries) const + { + unsigned int fp = 0; + + for ( const auto& iter : *table_val ) + { + auto k = iter.GetHashKey(); + auto vl = table_hash->RecoverVals(*k); + auto v = iter.GetValue()->GetVal(); + + fp += vl->Footprint(count_entries); + if ( v ) + fp += v->Footprint(count_entries); + } + + if ( count_entries ) + fp += table_val->Length(); + + return fp; + } + unsigned int TableVal::MemoryAllocation() const { unsigned int size = 0; @@ -3038,6 +3071,27 @@ ValPtr RecordVal::DoClone(CloneState* state) return rv; } +unsigned int RecordVal::Footprint(bool count_entries) const + { + unsigned int fp = 0; + int n = NumFields(); + + for ( auto i = 0; i < n; ++i ) + { + if ( ! HasField(i) ) + continue; + + auto f_i = GetField(i); + if ( f_i ) + fp += f_i->Footprint(count_entries); + } + + if ( count_entries ) + fp += n; + + return fp; + } + unsigned int RecordVal::MemoryAllocation() const { unsigned int size = 0; @@ -3561,6 +3615,24 @@ bool VectorVal::Concretize(const TypePtr& t) return true; } +unsigned int VectorVal::Footprint(bool count_entries) const + { + unsigned int fp = 0; + auto n = vector_val->size(); + + for ( auto i = 0U; i < n; ++i ) + { + auto v = At(i); + if ( v ) + fp += v->Footprint(count_entries); + } + + if ( count_entries ) + fp += n; + + return fp; + } + unsigned int VectorVal::Resize(unsigned int new_num_elements) { unsigned int oldsize = vector_val->size(); diff --git a/src/Val.h b/src/Val.h index b7cae59690..74f5295c25 100644 --- a/src/Val.h +++ b/src/Val.h @@ -121,6 +121,18 @@ public: // size depends on the Val's type. virtual ValPtr SizeVal() const; + /** + * Returns the Val's "footprint", i.e., how many atomic (non-container) + * values it includes, either directly or indirectly. + * + * @param count_entries If true, (recursively) include in the + * footprint the count of the number of container elements as well + * as each element's footprint. + * + * @return The total footprint, optionally including element counts. + */ + virtual unsigned int Footprint(bool count_entries) const { return 1; } + // Bytes in total value object. [[deprecated("Remove in v5.1. MemoryAllocation() is deprecated and will be removed. See " "GHI-572.")]] virtual unsigned int @@ -666,6 +678,8 @@ public: void Describe(ODesc* d) const override; + unsigned int Footprint(bool count_entries) const override; + [[deprecated("Remove in v5.1. MemoryAllocation() is deprecated and will be removed. See " "GHI-572.")]] unsigned int MemoryAllocation() const override; @@ -941,6 +955,8 @@ public: // the function in the frame allowing it to capture its closure. void InitDefaultFunc(detail::Frame* f); + unsigned int Footprint(bool count_entries) const override; + [[deprecated("Remove in v5.1. MemoryAllocation() is deprecated and will be removed. See " "GHI-572.")]] unsigned int MemoryAllocation() const override; @@ -1367,6 +1383,8 @@ public: } RecordValPtr CoerceTo(RecordTypePtr other, bool allow_orphaning = false); + unsigned int Footprint(bool count_entries) const override; + [[deprecated("Remove in v5.1. MemoryAllocation() is deprecated and will be removed. See " "GHI-572.")]] unsigned int MemoryAllocation() const override; @@ -1624,6 +1642,8 @@ public: const auto& RawYieldType() const { return yield_type; } const auto& RawYieldTypes() const { return yield_types; } + unsigned int Footprint(bool count_entries) const override; + protected: /** * Returns the element at a given index or nullptr if it does not exist. diff --git a/src/zeek.bif b/src/zeek.bif index 4665831d65..6df100aa8a 100644 --- a/src/zeek.bif +++ b/src/zeek.bif @@ -1985,6 +1985,49 @@ function global_sizes%(%): var_sizes &deprecated="Remove in v5.1. MemoryAllocati return sizes; %} +## Generates a table of the footprint of all global container variables. +## The table index is the variable name and the value is the footprint. +## The argument specifies whether the footprint should (recursively) include +## the number of entries in each global. +## +## Returns: A table that maps variable names to their footprints. +## +## .. zeek:see:: value_footprint +function global_container_footprints%(count_entries: bool%): var_sizes + %{ + auto sizes = zeek::make_intrusive(IntrusivePtr{zeek::NewRef{}, var_sizes}); + const auto& globals = zeek::detail::global_scope()->Vars(); + + for ( const auto& global : globals ) + { + auto& id = global.second; + auto v = id->GetVal(); + + if ( ! v || ! IsAggr(v->GetType()) ) + continue; + + auto id_name = zeek::make_intrusive(id->Name()); + auto fp = zeek::val_mgr->Count(v->Footprint(count_entries)); + sizes->Assign(std::move(id_name), std::move(fp)); + } + + return sizes; + %} + +## Computes a value's "footprint" (number of atomic elements included, either +## directly or indirectly). The argument, if true, specifies that the +## footprint should also include the number of entries in each included +## container. +## +## Returns: the total number of atomic elements plus (optionally) the +## number of entries in included containers. +## +## .. zeek:see:: global_container_footprints +function value_footprint%(v: any, count_entries: bool%): count + %{ + return zeek::val_mgr->Count(v->Footprint(count_entries)); + %} + ## Generates a table with information about all global identifiers. The table ## value is a record containing the type name of the identifier, whether it is ## exported, a constant, an enum constant, redefinable, and its value (if it diff --git a/testing/btest/Baseline/bifs.footprint/out b/testing/btest/Baseline/bifs.footprint/out new file mode 100644 index 0000000000..3d0f6d4b9b --- /dev/null +++ b/testing/btest/Baseline/bifs.footprint/out @@ -0,0 +1,49 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +bool, 1 +bool, 1 +count, 1 +count, 1 +int, 1 +int, 1 +double, 1 +double, 1 +string, 1 +string, 1 +pattern, 1 +pattern, 1 +addr, 1 +addr, 1 +subnet, 1 +subnet, 1 +port, 1 +port, 1 +l1, 0 +l1, 3 +l1b, 3 +l1b, 6 +l2, 2 +l2, 7 +l2b, 4 +l2b, 9 +v1, 4 +v1, 8 +v2, 8 +v2, 18 +v3, 3 +v3, 11 +t1, 6 +t1, 12 +t2, 9 +t2, 18 +t3, 20 +t3, 46 +t4, 7 +t4, 19 +s1, 3 +s1, 9 +s2, 6 +s2, 15 +s3, 8 +s3, 20 +s4, 3 +s4, 9 diff --git a/testing/btest/bifs/footprint.zeek b/testing/btest/bifs/footprint.zeek new file mode 100644 index 0000000000..6f9666a2a8 --- /dev/null +++ b/testing/btest/bifs/footprint.zeek @@ -0,0 +1,103 @@ +# @TEST-EXEC: zeek -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type r1: record { + a: count; + b: double; + c: string; +}; + +type r2: record { + a: count; + b1: double &default = 1.0; + b2: double &default = 2.0; + c: string &optional; + d: string &optional; +}; + +event zeek_init() + { + print "bool", value_footprint(T, F); + print "bool", value_footprint(T, T); + print "count", value_footprint(3, F); + print "count", value_footprint(4, T); + print "int", value_footprint(-3, F); + print "int", value_footprint(-4, T); + print "double", value_footprint(-3.0, F); + print "double", value_footprint(4e99, T); + print "string", value_footprint("short", F); + print "string", value_footprint("longlonglong", T); + print "pattern", value_footprint(/short/, F); + print "pattern", value_footprint(/longlonglong/, T); + print "addr", value_footprint(1.2.3.4, F); + print "addr", value_footprint([ffff::ffff], T); + print "subnet", value_footprint(1.2.3.4/22, F); + print "subnet", value_footprint([ffff::ffff]/99, T); + print "port", value_footprint(123/tcp, F); + print "port", value_footprint(9999/udp, T); + + local l1: r1; + print "l1", value_footprint(l1, F); + print "l1", value_footprint(l1, T); + + local l1b = r1($a=3, $b=3.0, $c="3"); + print "l1b", value_footprint(l1b, F); + print "l1b", value_footprint(l1b, T); + + local l2: r2; + print "l2", value_footprint(l2, F); + print "l2", value_footprint(l2, T); + + local l2b = r2($a=3, $b1=99.0, $c="I'm here"); + print "l2b", value_footprint(l2b, F); + print "l2b", value_footprint(l2b, T); + + local v1 = vector(9, 7, 3, 1); + print "v1", value_footprint(v1, F); + print "v1", value_footprint(v1, T); + + local v2 = vector(v1, v1); + print "v2", value_footprint(v2, F); + print "v2", value_footprint(v2, T); + + local v3 = vector(l1, l1b); + print "v3", value_footprint(v3, F); + print "v3", value_footprint(v3, T); + + local t1 = table([1] = 1, [2] = 4, [3] = 9); + print "t1", value_footprint(t1, F); + # Note, table and set footprint values using count_entries=T because + # table indices are ListVal's, so those add their own container + # entry counts into the sum. + print "t1", value_footprint(t1, T); + + local t2 = table([1, 3] = 1, [2, 3] = 4, [3, 3] = 9); + print "t2", value_footprint(t2, F); + print "t2", value_footprint(t2, T); + + local t3 = table([1, 3] = v2, [2, 3] = v2); + print "t3", value_footprint(t3, F); + print "t3", value_footprint(t3, T); + + local t4 = table([1, 3] = l1, [2, 3] = l1b); + print "t4", value_footprint(t4, F); + print "t4", value_footprint(t4, T); + + local s1 = set(1, 4, 9); + print "s1", value_footprint(s1, F); + print "s1", value_footprint(s1, T); + + local s2 = set([1, 3], [2, 3], [3, 3]); + print "s2", value_footprint(s2, F); + print "s2", value_footprint(s2, T); + + local s3: set[r1, count]; + add s3[l1b, 9]; + add s3[l1b, 12]; + print "s3", value_footprint(s3, F); + print "s3", value_footprint(s3, T); + + local s4 = set(vector(l1b), vector(l1b), vector(l1b)); + print "s4", value_footprint(s4, F); + print "s4", value_footprint(s4, T); + }