dorothy2 0.0.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +30 -6
- data/TODO +21 -0
- data/bin/dorothy_start +7 -6
- data/bin/dparser_start +13 -1
- data/etc/ddl/dorothive.ddl +2 -31
- data/lib/doroParser.rb +30 -23
- data/lib/dorothy2/BFM.rb +5 -13
- data/lib/dorothy2/do-utils.rb +4 -5
- data/lib/dorothy2/version.rb +1 -1
- data/lib/dorothy2.rb +5 -8
- data/lib/mu/xtractr/about.rb +57 -0
- data/lib/mu/xtractr/content.rb +68 -0
- data/lib/mu/xtractr/field.rb +178 -0
- data/lib/mu/xtractr/flow.rb +162 -0
- data/lib/mu/xtractr/flows.rb +118 -0
- data/lib/mu/xtractr/host.rb +87 -0
- data/lib/mu/xtractr/packet.rb +138 -0
- data/lib/mu/xtractr/packets.rb +122 -0
- data/lib/mu/xtractr/service.rb +77 -0
- data/lib/mu/xtractr/stream/http.rb +103 -0
- data/lib/mu/xtractr/stream.rb +132 -0
- data/lib/mu/xtractr/term.rb +73 -0
- data/lib/mu/xtractr/test/stream/tc_http.rb +53 -0
- data/lib/mu/xtractr/test/tc_field.rb +140 -0
- data/lib/mu/xtractr/test/tc_flow.rb +79 -0
- data/lib/mu/xtractr/test/tc_flows.rb +94 -0
- data/lib/mu/xtractr/test/tc_host.rb +116 -0
- data/lib/mu/xtractr/test/tc_packet.rb +110 -0
- data/lib/mu/xtractr/test/tc_packets.rb +84 -0
- data/lib/mu/xtractr/test/tc_service.rb +66 -0
- data/lib/mu/xtractr/test/tc_stream.rb +56 -0
- data/lib/mu/xtractr/test/tc_term.rb +59 -0
- data/lib/mu/xtractr/test/tc_views.rb +118 -0
- data/lib/mu/xtractr/test/tc_xtractr.rb +151 -0
- data/lib/mu/xtractr/test/test.rb +19 -0
- data/lib/mu/xtractr/views.rb +204 -0
- data/lib/mu/xtractr.rb +257 -0
- metadata +32 -4
@@ -0,0 +1,118 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'mu/xtractr'
|
16
|
+
require 'test/unit'
|
17
|
+
|
18
|
+
module Mu
|
19
|
+
class Xtractr
|
20
|
+
class Views
|
21
|
+
class Count
|
22
|
+
class Test < Test::Unit::TestCase
|
23
|
+
attr_reader :xtractr
|
24
|
+
attr_reader :count
|
25
|
+
|
26
|
+
def setup
|
27
|
+
@xtractr = Xtractr.new
|
28
|
+
@count = xtractr.flows('flow.service:DNS').count('dns.qry.name').first
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_attributes
|
32
|
+
assert_kind_of(Field, count.field)
|
33
|
+
assert_equal('ax.search.itunes.apple.com', count.value)
|
34
|
+
assert_equal(8, count.count)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_object
|
38
|
+
object = count.object
|
39
|
+
assert_kind_of(Field::Value, object)
|
40
|
+
assert_equal('dns.qry.name', object.field.name)
|
41
|
+
assert_equal('ax.search.itunes.apple.com', object.value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_packets
|
45
|
+
packets = count.packets
|
46
|
+
assert_equal("dns.qry.name:\"ax.search.itunes.apple.com\"", packets.q)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_each_packet
|
50
|
+
count.each_packet do |pkt|
|
51
|
+
assert_kind_of(Packet, pkt)
|
52
|
+
values = pkt['dns.qry.name']
|
53
|
+
assert_equal(1, values.size)
|
54
|
+
assert_equal('ax.search.itunes.apple.com', values[0])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_sum
|
59
|
+
sums = count.object.sum('pkt.src', 'pkt.length')
|
60
|
+
assert_equal(2, sums.length)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_inspect
|
64
|
+
assert_nothing_raised { count.inspect }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # Count
|
68
|
+
|
69
|
+
class Sum
|
70
|
+
class Test < Test::Unit::TestCase
|
71
|
+
attr_reader :xtractr
|
72
|
+
attr_reader :sum
|
73
|
+
|
74
|
+
def setup
|
75
|
+
@xtractr = Xtractr.new
|
76
|
+
@sum = xtractr.flows('flow.service:DNS').sum('dns.qry.name', 'flow.bytes').first
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_attributes
|
80
|
+
assert_kind_of(Field, sum.field)
|
81
|
+
assert_equal('ax.search.itunes.apple.com', sum.value)
|
82
|
+
assert_equal(1220, sum.sum)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_object
|
86
|
+
object = sum.object
|
87
|
+
assert_kind_of(Field::Value, object)
|
88
|
+
assert_equal('dns.qry.name', object.field.name)
|
89
|
+
assert_equal('ax.search.itunes.apple.com', object.value)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_packets
|
93
|
+
packets = sum.packets
|
94
|
+
assert_equal("dns.qry.name:\"ax.search.itunes.apple.com\"", packets.q)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_each_packet
|
98
|
+
sum.each_packet do |pkt|
|
99
|
+
assert_kind_of(Packet, pkt)
|
100
|
+
values = pkt['dns.qry.name']
|
101
|
+
assert_equal(1, values.size)
|
102
|
+
assert_equal('ax.search.itunes.apple.com', values[0])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_count
|
107
|
+
counts = sum.object.count('pkt.service')
|
108
|
+
assert_equal(1, counts.length)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_inspect
|
112
|
+
assert_nothing_raised { sum.inspect }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end # Sum
|
116
|
+
end # Views
|
117
|
+
end # Xtractr
|
118
|
+
end # Mu
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'mu/xtractr'
|
16
|
+
require 'test/unit'
|
17
|
+
|
18
|
+
module Mu
|
19
|
+
class Xtractr
|
20
|
+
class Test < Test::Unit::TestCase
|
21
|
+
attr_reader :xtractr
|
22
|
+
|
23
|
+
def setup
|
24
|
+
@xtractr = Xtractr.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_about
|
28
|
+
about = xtractr.about
|
29
|
+
assert_equal(1778, about.packets)
|
30
|
+
assert_equal(32, about.flows)
|
31
|
+
assert_equal(12, about.hosts)
|
32
|
+
assert_equal(5, about.services)
|
33
|
+
assert_equal(171.172, about.duration)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_hosts
|
37
|
+
hosts = xtractr.hosts
|
38
|
+
assert_equal(12, hosts.size)
|
39
|
+
hosts.each { |host| assert_instance_of(Host, host) }
|
40
|
+
assert_equal(8, xtractr.hosts(/^8.18/).size)
|
41
|
+
assert_equal(8, xtractr.hosts('8.18').size)
|
42
|
+
assert_equal(1, xtractr.hosts(/^4.2/).size)
|
43
|
+
assert_equal(1, xtractr.hosts('4.2').size)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_host
|
47
|
+
[
|
48
|
+
'4.2.2.1',
|
49
|
+
'8.18.65.67',
|
50
|
+
'8.18.65.32',
|
51
|
+
'8.18.65.58',
|
52
|
+
'8.18.65.82',
|
53
|
+
'8.18.65.27',
|
54
|
+
'8.18.65.10',
|
55
|
+
'8.18.65.88',
|
56
|
+
'8.18.65.89',
|
57
|
+
'66.235.132.121',
|
58
|
+
'192.168.1.10',
|
59
|
+
'224.0.0.251',
|
60
|
+
].each do |address|
|
61
|
+
assert_nothing_raised { xtractr.host address }
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_raise(ArgumentError) { xtractr.host '1.1.1.1' }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_services
|
68
|
+
services = xtractr.services
|
69
|
+
assert_equal(5, services.size)
|
70
|
+
services.each { |service| assert_instance_of(Service, service) }
|
71
|
+
assert_equal(2, xtractr.services(/HTTP/).size)
|
72
|
+
assert_equal(2, xtractr.services('HTTP').size)
|
73
|
+
assert_equal(2, xtractr.services('http').size)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_service
|
77
|
+
[ 'DNS', 'TCP', 'HTTP', 'HTTP/XML', 'MDNS' ].each do |name|
|
78
|
+
assert_nothing_raised { xtractr.service name }
|
79
|
+
assert_nothing_raised { xtractr.service name.downcase }
|
80
|
+
end
|
81
|
+
|
82
|
+
assert_raise(ArgumentError) { xtractr.service 'blah' }
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_fields
|
86
|
+
fields = xtractr.fields
|
87
|
+
assert_equal(170, fields.size)
|
88
|
+
fields.each { |field| assert_instance_of(Field, field) }
|
89
|
+
assert_equal(12, xtractr.fields(/^pkt\./).size)
|
90
|
+
assert_equal(12, xtractr.fields("PKT.").size)
|
91
|
+
assert_equal(12, xtractr.fields("pkt.").size)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_field
|
95
|
+
[
|
96
|
+
'pkt.src', 'pkt.dst', 'pkt.flow', 'pkt.id', 'pkt.pcap', 'pkt.first',
|
97
|
+
'pkt.dir', 'pkt.time', 'pkt.offset', 'pkt.length', 'pkt.service',
|
98
|
+
'pkt.title'
|
99
|
+
].each do |name|
|
100
|
+
assert_nothing_raised { xtractr.field name }
|
101
|
+
end
|
102
|
+
assert_raise(ArgumentError) { xtractr.field 'blah' }
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_flows
|
106
|
+
flows = xtractr.flows
|
107
|
+
assert_kind_of(Flows, flows)
|
108
|
+
assert_equal('*', flows.q)
|
109
|
+
|
110
|
+
flows = xtractr.flows 'blah:foo'
|
111
|
+
assert_equal('blah:foo', flows.q)
|
112
|
+
|
113
|
+
flows = xtractr.flows 1..10
|
114
|
+
assert_equal('flow.id:[1 10]', flows.q)
|
115
|
+
|
116
|
+
flows = xtractr.flows 1...10
|
117
|
+
assert_equal('flow.id:[1 9]', flows.q)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_flow
|
121
|
+
flow = xtractr.flow 1
|
122
|
+
assert_kind_of(Flow, flow)
|
123
|
+
|
124
|
+
assert_raise(ArgumentError) do
|
125
|
+
flow = xtractr.flow xtractr.about.flows+1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_packets
|
130
|
+
packets = xtractr.packets
|
131
|
+
assert_kind_of(Packets, packets)
|
132
|
+
assert_equal('*', packets.q)
|
133
|
+
|
134
|
+
packets = xtractr.packets 'blah:foo'
|
135
|
+
assert_equal('blah:foo', packets.q)
|
136
|
+
|
137
|
+
packets = xtractr.packets 1..10
|
138
|
+
assert_equal('pkt.id:[1 10]', packets.q)
|
139
|
+
|
140
|
+
packets = xtractr.packets 1...10
|
141
|
+
assert_equal('pkt.id:[1 9]', packets.q)
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_packet
|
145
|
+
pkt = xtractr.packet 1
|
146
|
+
assert_kind_of(Packet, pkt)
|
147
|
+
assert_equal(1, pkt.id)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end # Xtractr
|
151
|
+
end # Mu
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
require 'test/unit'
|
16
|
+
|
17
|
+
Dir.glob('mu/xtractr/test/**/tc*.rb').each do |file|
|
18
|
+
require file
|
19
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# "THE BEER-WARE LICENSE" (Revision 42):
|
2
|
+
# Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
|
3
|
+
# notice you can do whatever you want with this stuff. If we meet some day,
|
4
|
+
# and you think this stuff is worth it, you can buy us a beer in return.
|
5
|
+
#
|
6
|
+
# All about pcapr
|
7
|
+
# * http://www.pcapr.net
|
8
|
+
# * http://groups.google.com/group/pcapr-forum
|
9
|
+
# * http://twitter.com/pcapr
|
10
|
+
#
|
11
|
+
# Mu Dynamics
|
12
|
+
# * http://www.mudynamics.com
|
13
|
+
# * http://labs.mudynamics.com
|
14
|
+
|
15
|
+
module Mu
|
16
|
+
class Xtractr
|
17
|
+
# See http://labs.mudynamics.com/2009/04/03/interactive-couchdb/ for a quick
|
18
|
+
# tutorial on how Map/Reduce works.
|
19
|
+
class Views # :nodoc:
|
20
|
+
# = Count
|
21
|
+
# Count contains the results of doing a map/reduce on either flows or
|
22
|
+
# packets. Each count contains the field on which the map/reduce was
|
23
|
+
# performed, the unique value as all as the count of that value in the
|
24
|
+
# flows or packets. For example to count the unique source IP address of
|
25
|
+
# HTTP flows in the first five minutes of the index, you would do:
|
26
|
+
#
|
27
|
+
# xtractr.flows('flow.service:HTTP flow.duration:[1 300]').count('flow.src')
|
28
|
+
class Count
|
29
|
+
attr_reader :xtractr # :nodoc:
|
30
|
+
|
31
|
+
# Returns the field used for counting.
|
32
|
+
attr_reader :field
|
33
|
+
|
34
|
+
# Returns the unique value of the field.
|
35
|
+
attr_reader :value
|
36
|
+
|
37
|
+
# Returns the count of the field/value.
|
38
|
+
attr_reader :count
|
39
|
+
|
40
|
+
def initialize xtractr, field, value, count # :nodoc:
|
41
|
+
@xtractr = xtractr
|
42
|
+
@field = field
|
43
|
+
@value = value
|
44
|
+
@count = count
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a Field::Value object that can be used for further method
|
48
|
+
# chaining.
|
49
|
+
# xtractr.flows.count('flow.src').first.object.count('flow.service')
|
50
|
+
def object
|
51
|
+
Field::Value.new xtractr, "key" => field.name, "value" => value
|
52
|
+
end
|
53
|
+
|
54
|
+
# Fetch the list of packets that contain this field value.
|
55
|
+
# xtractr.flows.count('flow.src').first.packets.each { |pkt ... }
|
56
|
+
def packets q=nil
|
57
|
+
object.packets q
|
58
|
+
end
|
59
|
+
|
60
|
+
# Iterate over each packet that contains this field value.
|
61
|
+
# xtractr.flows.count('flow.src').first.each_packet { |pkt ... }
|
62
|
+
def each_packet(q=nil, &blk) # :yields: packet
|
63
|
+
packets(q).each(&blk)
|
64
|
+
return self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sum the numeric values of vfield, keyed by the unique values of
|
68
|
+
# kfield. This is used for method chaining.
|
69
|
+
# xtractr.flows.count('flow.src').first.sum('flow.service', 'flow.bytes')
|
70
|
+
def sum kfield, vfield
|
71
|
+
object.sum kfield, vfield
|
72
|
+
end
|
73
|
+
|
74
|
+
def inspect # :nodoc:
|
75
|
+
"#<count #{value} #{count}>"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# = Sum
|
80
|
+
# Sum contains the results of doing a map/reduce on either flows or
|
81
|
+
# packets. Each sum contains the field on which the map/reduce was
|
82
|
+
# performed, the unique value as all as the sum of that value in the
|
83
|
+
# flows or packets. For example to count the bytes sent in HTTP flows
|
84
|
+
# keyed by the source IP address, you would do:
|
85
|
+
#
|
86
|
+
# xtractr.flows('flow.service:HTTP').count('flow.src', 'flow.bytes')
|
87
|
+
class Sum
|
88
|
+
attr_reader :xtractr # :nodoc:
|
89
|
+
|
90
|
+
# Returns the field used for summing.
|
91
|
+
attr_reader :field
|
92
|
+
|
93
|
+
# Returns the unique value used as the map/reduce key.
|
94
|
+
attr_reader :value
|
95
|
+
|
96
|
+
# Returns the aggregate computed sum.
|
97
|
+
attr_reader :sum
|
98
|
+
|
99
|
+
def initialize xtractr, field, value, sum # :nodoc:
|
100
|
+
@xtractr = xtractr
|
101
|
+
@field = field
|
102
|
+
@value = value
|
103
|
+
@sum = sum
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a Field::Value object that can be used for further method
|
107
|
+
# chaining. In the following example, we first compute the top talkers
|
108
|
+
# (based on the bytes sent) and then use the topmost talker to count
|
109
|
+
# the list of unique services.
|
110
|
+
# xtractr.flows.sum('flow.src', 'flow.bytes').first.object.count('flow.service')
|
111
|
+
def object
|
112
|
+
Field::Value.new xtractr, "key" => field.name, "value" => value
|
113
|
+
end
|
114
|
+
|
115
|
+
# Fetch the list of packets that contain this field value.
|
116
|
+
# xtractr.flows.sum('flow.src', 'flow.bytes').first.packets.each { |pkt ... }
|
117
|
+
def packets q=nil
|
118
|
+
object.packets q
|
119
|
+
end
|
120
|
+
|
121
|
+
# Iterate over each packet that contains this field value.
|
122
|
+
# xtractr.flows.sum('flow.src', 'flow.bytes').first.each_packet { |pkt ... }
|
123
|
+
def each_packet q=nil, &blk
|
124
|
+
packets(q).each(&blk)
|
125
|
+
return self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Count the unique values of the specified field amongst all the packets
|
129
|
+
# that matched the query.
|
130
|
+
# xtractr.flows.sum('flow.src', 'flow.bytes').first.count('flow.service')
|
131
|
+
def count _field
|
132
|
+
object.count _field
|
133
|
+
end
|
134
|
+
|
135
|
+
def inspect # :nodoc:
|
136
|
+
"#<sum #{value} #{sum}>"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.count xtractr, field, url, opts={} # :nodoc:
|
141
|
+
field = Field.new(xtractr, field) if field.is_a? String
|
142
|
+
name = field.name.gsub /^(pkt|flow)\./, ''
|
143
|
+
_opts = opts.dup
|
144
|
+
_opts[:r] = <<-EOS
|
145
|
+
({
|
146
|
+
map: function(_pf) {
|
147
|
+
_pf.values("#{name}", function(_value) {
|
148
|
+
if (_value) {
|
149
|
+
if (typeof(_value) === 'string') {
|
150
|
+
if (_value.length > 1024) {
|
151
|
+
_value = _value.slice(0,1024);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
emit(_value, 1);
|
155
|
+
}
|
156
|
+
});
|
157
|
+
},
|
158
|
+
reduce: function(_key, _values) {
|
159
|
+
return sum(_values);
|
160
|
+
}
|
161
|
+
})
|
162
|
+
EOS
|
163
|
+
result = xtractr.json url, _opts
|
164
|
+
result['rows'].map do |row|
|
165
|
+
Views::Count.new(xtractr, field, row['key'], row['value'])
|
166
|
+
end.sort { |a, b| b.count <=> a.count }
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.sum xtractr, kfield, vfield, url, opts={} # :nodoc:
|
170
|
+
kfield = Field.new(xtractr, kfield) if kfield.is_a? String
|
171
|
+
vfield = Field.new(xtractr, vfield) if vfield.is_a? String
|
172
|
+
kname = kfield.name.gsub /^(pkt|flow)\./, ''
|
173
|
+
vname = vfield.name.gsub /^(pkt|flow)\./, ''
|
174
|
+
_opts = opts.dup
|
175
|
+
_opts[:r] = <<-EOS
|
176
|
+
({
|
177
|
+
map: function(_pf) {
|
178
|
+
var _key = _pf["#{kname}"];
|
179
|
+
if (_key) {
|
180
|
+
if (typeof(_key) === 'string') {
|
181
|
+
if (_key.length > 1024) {
|
182
|
+
_key = _key.slice(0,1024);
|
183
|
+
}
|
184
|
+
}
|
185
|
+
_pf.values("#{vname}", function(_val) {
|
186
|
+
if (typeof(_val) === 'number') {
|
187
|
+
emit(_key, _val);
|
188
|
+
}
|
189
|
+
});
|
190
|
+
}
|
191
|
+
},
|
192
|
+
reduce: function(_key, _values) {
|
193
|
+
return sum(_values);
|
194
|
+
}
|
195
|
+
})
|
196
|
+
EOS
|
197
|
+
result = xtractr.json url, _opts
|
198
|
+
result['rows'].map do |row|
|
199
|
+
Views::Sum.new(xtractr, kfield, row['key'], row['value'])
|
200
|
+
end.sort { |a, b| b.sum <=> a.sum }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end # Xtractr
|
204
|
+
end # Mu
|