mirror of
https://github.com/zeek/zeek.git
synced 2025-10-06 08:38:20 +00:00
Merge remote-tracking branch 'origin/topic/seth/metrics-merge'
* origin/topic/seth/metrics-merge: (70 commits) Added protocol to the traceroute detection script. Added an automatic state limiter for threshold based SumStats. Removed some dead code in scan.bro Renamed a plugin hook in sumstats framework. Move loading variance back to where it should be alphabetically. Fix a bug with path building in FTP. Came up when changing the path utils. Fix a few tests. SumStats test checkpoint. SumStats tests pass. Checkpoint for SumStats rename. Fix another occasional reporter error. Small updates to hopefully correct reporter errors leading to lost memory. Trying to fix a state maintenance issue. Updating DocSourcesList Updated FTP bruteforce detection and a few other small changes. Test updates and cleanup. Fixed the measurement "sample" plugin. Fix path compression to include removing "/./". Removed the example metrics scripts. Better real world examples exist now. Measurement framework is ready for testing. ...
This commit is contained in:
commit
1e40a2f88c
62 changed files with 2388 additions and 1278 deletions
|
@ -1,78 +0,0 @@
|
|||
# @TEST-SERIALIZE: comm
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager-1 BROPATH=$BROPATH:.. CLUSTER_NODE=manager-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run proxy-1 BROPATH=$BROPATH:.. CLUSTER_NODE=proxy-1 bro %INPUT
|
||||
# @TEST-EXEC: sleep 1
|
||||
# @TEST-EXEC: btest-bg-run worker-1 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run worker-2 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-2 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff manager-1/metrics.log
|
||||
|
||||
@TEST-START-FILE cluster-layout.bro
|
||||
redef Cluster::nodes = {
|
||||
["manager-1"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=37757/tcp, $workers=set("worker-1", "worker-2")],
|
||||
["proxy-1"] = [$node_type=Cluster::PROXY, $ip=127.0.0.1, $p=37758/tcp, $manager="manager-1", $workers=set("worker-1", "worker-2")],
|
||||
["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37760/tcp, $manager="manager-1", $proxy="proxy-1", $interface="eth0"],
|
||||
["worker-2"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37761/tcp, $manager="manager-1", $proxy="proxy-1", $interface="eth1"],
|
||||
};
|
||||
@TEST-END-FILE
|
||||
|
||||
redef Log::default_rotation_interval = 0secs;
|
||||
|
||||
redef enum Metrics::ID += {
|
||||
TEST_METRIC,
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Metrics::add_filter(TEST_METRIC,
|
||||
[$name="foo-bar",
|
||||
$break_interval=3secs]);
|
||||
}
|
||||
|
||||
event remote_connection_closed(p: event_peer)
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
global ready_for_data: event();
|
||||
|
||||
redef Cluster::manager2worker_events += /ready_for_data/;
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::WORKER )
|
||||
|
||||
event ready_for_data()
|
||||
{
|
||||
Metrics::add_data(TEST_METRIC, [$host=1.2.3.4], 3);
|
||||
Metrics::add_data(TEST_METRIC, [$host=6.5.4.3], 2);
|
||||
Metrics::add_data(TEST_METRIC, [$host=7.2.1.5], 1);
|
||||
}
|
||||
|
||||
@endif
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
|
||||
global n = 0;
|
||||
global peer_count = 0;
|
||||
|
||||
event Metrics::log_metrics(rec: Metrics::Info)
|
||||
{
|
||||
n = n + 1;
|
||||
if ( n == 3 )
|
||||
{
|
||||
terminate_communication();
|
||||
terminate();
|
||||
}
|
||||
}
|
||||
|
||||
event remote_connection_handshake_done(p: event_peer)
|
||||
{
|
||||
print p;
|
||||
peer_count = peer_count + 1;
|
||||
if ( peer_count == 3 )
|
||||
{
|
||||
event ready_for_data();
|
||||
}
|
||||
}
|
||||
|
||||
@endif
|
|
@ -1,16 +0,0 @@
|
|||
# @TEST-EXEC: bro %INPUT
|
||||
# @TEST-EXEC: btest-diff metrics.log
|
||||
|
||||
redef enum Metrics::ID += {
|
||||
TEST_METRIC,
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Metrics::add_filter(TEST_METRIC,
|
||||
[$name="foo-bar",
|
||||
$break_interval=3secs]);
|
||||
Metrics::add_data(TEST_METRIC, [$host=1.2.3.4], 3);
|
||||
Metrics::add_data(TEST_METRIC, [$host=6.5.4.3], 2);
|
||||
Metrics::add_data(TEST_METRIC, [$host=7.2.1.5], 1);
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
# @TEST-SERIALIZE: comm
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager-1 BROPATH=$BROPATH:.. CLUSTER_NODE=manager-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run proxy-1 BROPATH=$BROPATH:.. CLUSTER_NODE=proxy-1 bro %INPUT
|
||||
# @TEST-EXEC: sleep 1
|
||||
# @TEST-EXEC: btest-bg-run worker-1 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run worker-2 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-2 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-wait 20
|
||||
# @TEST-EXEC: btest-diff manager-1/notice.log
|
||||
|
||||
@TEST-START-FILE cluster-layout.bro
|
||||
redef Cluster::nodes = {
|
||||
["manager-1"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=37757/tcp, $workers=set("worker-1")],
|
||||
["proxy-1"] = [$node_type=Cluster::PROXY, $ip=127.0.0.1, $p=37758/tcp, $manager="manager-1", $workers=set("worker-1")],
|
||||
["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37760/tcp, $manager="manager-1", $proxy="proxy-1", $interface="eth0"],
|
||||
["worker-2"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37761/tcp, $manager="manager-1", $proxy="proxy-1", $interface="eth1"],
|
||||
};
|
||||
@TEST-END-FILE
|
||||
|
||||
redef Log::default_rotation_interval = 0secs;
|
||||
|
||||
redef enum Notice::Type += {
|
||||
Test_Notice,
|
||||
};
|
||||
|
||||
redef enum Metrics::ID += {
|
||||
TEST_METRIC,
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Metrics::add_filter(TEST_METRIC,
|
||||
[$name="foo-bar",
|
||||
$break_interval=1hr,
|
||||
$note=Test_Notice,
|
||||
$notice_threshold=100,
|
||||
$log=T]);
|
||||
}
|
||||
|
||||
event remote_connection_closed(p: event_peer)
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
|
||||
event Notice::log_notice(rec: Notice::Info)
|
||||
{
|
||||
terminate_communication();
|
||||
terminate();
|
||||
}
|
||||
|
||||
@endif
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::WORKER )
|
||||
|
||||
event do_metrics(i: count)
|
||||
{
|
||||
# Worker-1 will trigger an intermediate update and then if everything
|
||||
# works correctly, the data from worker-2 will hit the threshold and
|
||||
# should trigger the notice.
|
||||
Metrics::add_data(TEST_METRIC, [$host=1.2.3.4], i);
|
||||
}
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
if ( Cluster::node == "worker-1" )
|
||||
schedule 2sec { do_metrics(99) };
|
||||
if ( Cluster::node == "worker-2" )
|
||||
event do_metrics(1);
|
||||
}
|
||||
|
||||
@endif
|
|
@ -0,0 +1,82 @@
|
|||
# @TEST-SERIALIZE: comm
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager-1 BROPATH=$BROPATH:.. CLUSTER_NODE=manager-1 bro %INPUT
|
||||
# @TEST-EXEC: sleep 1
|
||||
# @TEST-EXEC: btest-bg-run worker-1 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run worker-2 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-2 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-wait 15
|
||||
|
||||
# @TEST-EXEC: btest-diff manager-1/.stdout
|
||||
|
||||
@TEST-START-FILE cluster-layout.bro
|
||||
redef Cluster::nodes = {
|
||||
["manager-1"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=37757/tcp, $workers=set("worker-1", "worker-2")],
|
||||
["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37760/tcp, $manager="manager-1", $interface="eth0"],
|
||||
["worker-2"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37761/tcp, $manager="manager-1", $interface="eth1"],
|
||||
};
|
||||
@TEST-END-FILE
|
||||
|
||||
redef Log::default_rotation_interval = 0secs;
|
||||
|
||||
global n = 0;
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
local r1: SumStats::Reducer = [$stream="test", $apply=set(SumStats::SUM, SumStats::MIN, SumStats::MAX, SumStats::AVERAGE, SumStats::STD_DEV, SumStats::VARIANCE, SumStats::UNIQUE)];
|
||||
SumStats::create([$epoch=5secs,
|
||||
$reducers=set(r1),
|
||||
$epoch_finished(rt: SumStats::ResultTable) =
|
||||
{
|
||||
for ( key in rt )
|
||||
{
|
||||
local r = rt[key]["test"];
|
||||
print fmt("Host: %s - num:%d - sum:%.1f - avg:%.1f - max:%.1f - min:%.1f - var:%.1f - std_dev:%.1f - unique:%d", key$host, r$num, r$sum, r$average, r$max, r$min, r$variance, r$std_dev, r$unique);
|
||||
}
|
||||
|
||||
terminate();
|
||||
}]);
|
||||
}
|
||||
|
||||
event remote_connection_closed(p: event_peer)
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
global ready_for_data: event();
|
||||
redef Cluster::manager2worker_events += /^ready_for_data$/;
|
||||
|
||||
event ready_for_data()
|
||||
{
|
||||
if ( Cluster::node == "worker-1" )
|
||||
{
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=34]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=30]);
|
||||
SumStats::observe("test", [$host=6.5.4.3], [$num=1]);
|
||||
SumStats::observe("test", [$host=7.2.1.5], [$num=54]);
|
||||
}
|
||||
if ( Cluster::node == "worker-2" )
|
||||
{
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=75]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=30]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=3]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=57]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=52]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=61]);
|
||||
SumStats::observe("test", [$host=1.2.3.4], [$num=95]);
|
||||
SumStats::observe("test", [$host=6.5.4.3], [$num=5]);
|
||||
SumStats::observe("test", [$host=7.2.1.5], [$num=91]);
|
||||
SumStats::observe("test", [$host=10.10.10.10], [$num=5]);
|
||||
}
|
||||
}
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
|
||||
global peer_count = 0;
|
||||
event remote_connection_handshake_done(p: event_peer) &priority=-5
|
||||
{
|
||||
++peer_count;
|
||||
if ( peer_count == 2 )
|
||||
event ready_for_data();
|
||||
}
|
||||
|
||||
@endif
|
34
testing/btest/scripts/base/frameworks/sumstats/basic.bro
Normal file
34
testing/btest/scripts/base/frameworks/sumstats/basic.bro
Normal file
|
@ -0,0 +1,34 @@
|
|||
# @TEST-EXEC: bro %INPUT
|
||||
# @TEST-EXEC: btest-diff .stdout
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
local r1: SumStats::Reducer = [$stream="test.metric",
|
||||
$apply=set(SumStats::SUM,
|
||||
SumStats::VARIANCE,
|
||||
SumStats::AVERAGE,
|
||||
SumStats::MAX,
|
||||
SumStats::MIN,
|
||||
SumStats::STD_DEV,
|
||||
SumStats::UNIQUE)];
|
||||
SumStats::create([$epoch=3secs,
|
||||
$reducers=set(r1),
|
||||
$epoch_finished(data: SumStats::ResultTable) =
|
||||
{
|
||||
for ( key in data )
|
||||
{
|
||||
local r = data[key]["test.metric"];
|
||||
print fmt("Host: %s - num:%d - sum:%.1f - var:%.1f - avg:%.1f - max:%.1f - min:%.1f - std_dev:%.1f - unique:%d", key$host, r$num, r$sum, r$variance, r$average, r$max, r$min, r$std_dev, r$unique);
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=5]);
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=22]);
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=94]);
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=50]);
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=50]);
|
||||
|
||||
SumStats::observe("test.metric", [$host=6.5.4.3], [$num=2]);
|
||||
SumStats::observe("test.metric", [$host=7.2.1.5], [$num=1]);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
# @TEST-SERIALIZE: comm
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager-1 BROPATH=$BROPATH:.. CLUSTER_NODE=manager-1 bro %INPUT
|
||||
# @TEST-EXEC: sleep 3
|
||||
# @TEST-EXEC: btest-bg-run worker-1 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-1 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-run worker-2 BROPATH=$BROPATH:.. CLUSTER_NODE=worker-2 bro %INPUT
|
||||
# @TEST-EXEC: btest-bg-wait 10
|
||||
# @TEST-EXEC: btest-diff manager-1/.stdout
|
||||
|
||||
@TEST-START-FILE cluster-layout.bro
|
||||
redef Cluster::nodes = {
|
||||
["manager-1"] = [$node_type=Cluster::MANAGER, $ip=127.0.0.1, $p=37757/tcp, $workers=set("worker-1", "worker-2")],
|
||||
["worker-1"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37760/tcp, $manager="manager-1", $interface="eth0"],
|
||||
["worker-2"] = [$node_type=Cluster::WORKER, $ip=127.0.0.1, $p=37761/tcp, $manager="manager-1", $interface="eth1"],
|
||||
};
|
||||
@TEST-END-FILE
|
||||
|
||||
redef Log::default_rotation_interval = 0secs;
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
local r1: SumStats::Reducer = [$stream="test.metric", $apply=set(SumStats::SUM)];
|
||||
SumStats::create([$epoch=1hr,
|
||||
$reducers=set(r1),
|
||||
$threshold_val(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
return double_to_count(result["test.metric"]$sum);
|
||||
},
|
||||
$threshold=100,
|
||||
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
print fmt("A test metric threshold was crossed with a value of: %.1f", result["test.metric"]$sum);
|
||||
terminate();
|
||||
}]);
|
||||
}
|
||||
|
||||
event remote_connection_closed(p: event_peer)
|
||||
{
|
||||
terminate();
|
||||
}
|
||||
|
||||
event do_stats(i: count)
|
||||
{
|
||||
# Worker-1 will trigger an intermediate update and then if everything
|
||||
# works correctly, the data from worker-2 will hit the threshold and
|
||||
# should trigger the notice.
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=i]);
|
||||
}
|
||||
|
||||
event remote_connection_handshake_done(p: event_peer)
|
||||
{
|
||||
if ( p$descr == "manager-1" )
|
||||
{
|
||||
if ( Cluster::node == "worker-1" )
|
||||
schedule 0.1sec { do_stats(1) };
|
||||
if ( Cluster::node == "worker-2" )
|
||||
schedule 0.5sec { do_stats(99) };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
# @TEST-EXEC: bro %INPUT
|
||||
# @TEST-EXEC: btest-diff .stdout
|
||||
|
||||
redef enum Notice::Type += {
|
||||
Test_Notice,
|
||||
};
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
local r1: SumStats::Reducer = [$stream="test.metric", $apply=set(SumStats::SUM)];
|
||||
SumStats::create([$epoch=3secs,
|
||||
$reducers=set(r1),
|
||||
#$threshold_val = SumStats::sum_threshold("test.metric"),
|
||||
$threshold_val(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
return double_to_count(result["test.metric"]$sum);
|
||||
},
|
||||
$threshold=5,
|
||||
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
local r = result["test.metric"];
|
||||
print fmt("THRESHOLD: hit a threshold value at %.0f for %s", r$sum, SumStats::key2str(key));
|
||||
}
|
||||
]);
|
||||
|
||||
local r2: SumStats::Reducer = [$stream="test.metric", $apply=set(SumStats::SUM)];
|
||||
SumStats::create([$epoch=3secs,
|
||||
$reducers=set(r2),
|
||||
#$threshold_val = SumStats::sum_threshold("test.metric"),
|
||||
$threshold_val(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
return double_to_count(result["test.metric"]$sum);
|
||||
},
|
||||
$threshold_series=vector(3,6,800),
|
||||
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
local r = result["test.metric"];
|
||||
print fmt("THRESHOLD_SERIES: hit a threshold series value at %.0f for %s", r$sum, SumStats::key2str(key));
|
||||
}
|
||||
]);
|
||||
|
||||
local r3: SumStats::Reducer = [$stream="test.metric", $apply=set(SumStats::SUM)];
|
||||
local r4: SumStats::Reducer = [$stream="test.metric2", $apply=set(SumStats::SUM)];
|
||||
SumStats::create([$epoch=3secs,
|
||||
$reducers=set(r3, r4),
|
||||
$threshold_val(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
# Calculate a ratio between sums of two reducers.
|
||||
if ( "test.metric2" in result && "test.metric" in result &&
|
||||
result["test.metric"]$sum > 0 )
|
||||
return double_to_count(result["test.metric2"]$sum / result["test.metric"]$sum);
|
||||
else
|
||||
return 0;
|
||||
},
|
||||
# Looking for metric2 sum to be 5 times the sum of metric
|
||||
$threshold=5,
|
||||
$threshold_crossed(key: SumStats::Key, result: SumStats::Result) =
|
||||
{
|
||||
local thold = result["test.metric2"]$sum / result["test.metric"]$sum;
|
||||
print fmt("THRESHOLD WITH RATIO BETWEEN REDUCERS: hit a threshold value at %.0fx for %s", thold, SumStats::key2str(key));
|
||||
}
|
||||
]);
|
||||
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=3]);
|
||||
SumStats::observe("test.metric", [$host=6.5.4.3], [$num=2]);
|
||||
SumStats::observe("test.metric", [$host=7.2.1.5], [$num=1]);
|
||||
SumStats::observe("test.metric", [$host=1.2.3.4], [$num=3]);
|
||||
SumStats::observe("test.metric", [$host=7.2.1.5], [$num=1000]);
|
||||
SumStats::observe("test.metric2", [$host=7.2.1.5], [$num=10]);
|
||||
SumStats::observe("test.metric2", [$host=7.2.1.5], [$num=1000]);
|
||||
SumStats::observe("test.metric2", [$host=7.2.1.5], [$num=54321]);
|
||||
|
||||
}
|
33
testing/btest/scripts/base/utils/queue.test
Normal file
33
testing/btest/scripts/base/utils/queue.test
Normal file
|
@ -0,0 +1,33 @@
|
|||
# @TEST-EXEC: bro -b %INPUT > output
|
||||
# @TEST-EXEC: btest-diff output
|
||||
|
||||
# This is loaded by default
|
||||
@load base/utils/queue
|
||||
|
||||
event bro_init()
|
||||
{
|
||||
local q = Queue::init([$max_len=2]);
|
||||
Queue::put(q, 1);
|
||||
Queue::put(q, 2);
|
||||
Queue::put(q, 3);
|
||||
Queue::put(q, 4);
|
||||
local test1: vector of count = vector();
|
||||
Queue::get_vector(q, test1);
|
||||
for ( i in test1 )
|
||||
print fmt("This is a get_vector test: %d", test1[i]);
|
||||
|
||||
local test_val = Queue::get(q);
|
||||
print fmt("Testing get: %s", test_val);
|
||||
print fmt("Length after get: %d", Queue::len(q));
|
||||
|
||||
local q2 = Queue::init([]);
|
||||
Queue::put(q2, "test 1");
|
||||
Queue::put(q2, "test 2");
|
||||
Queue::put(q2, "test 2");
|
||||
Queue::put(q2, "test 1");
|
||||
print fmt("Size of q2: %d", Queue::len(q2));
|
||||
local test3: vector of string = vector();
|
||||
Queue::get_vector(q2, test3);
|
||||
for ( i in test3 )
|
||||
print fmt("String queue value: %s", test3[i]);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue