/
BrowserProfiler.rb
173 lines (151 loc) · 4.74 KB
/
BrowserProfiler.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#
# Profile the current browser (currently hacked )
#
class BrowserProfiler
# Reasonable number of samples, so we don't use too much memory
@@MAX_SAMPLES = 1000
# Constructor
def initialize(args)
@takingSamples = false
@sampleList = []
@maxSamples = 0
@sampleNumber = 0
end
#
# Start the sampling process. At most 1,000 samples are taken. If no callback
# is provided, the samples are stored and return after this method completes
# or 1,000 samples are taken.
#
def start(bp, args)
if @takingSamples == true
bp.error("alreadyStarted", "you tried to start another sampling process, but we're already sampling")
return
end
interval = args['interval'] || 1.0
if (interval < 0.1)
interval = 0.1
end
@startTime = Time.now
@takingSamples = true
@maxSamples = @@MAX_SAMPLES
@sampleList = []
@sampleNumber = 0
Thread.new(bp, args['callback'], interval) do | bp,callback,interval |
while @takingSamples
@sampleNumber += 1
s = _get_sample()
if (callback)
callback.invoke(s)
else
@sampleList.push(s)
end
@maxSamples -= 1
if @maxSamples > 0
sleep interval
else
break
end
end
bp.complete(@sampleList)
end
end
#
# Take 1 sample.
#
def sample(bp, args)
bp.complete(_get_sample())
end
#
# Stop the sampling. Return the sampleList if no callback was provided to start.
#
def stop(bp, args)
@takingSamples = false
bp.complete(@sampleList)
end
#
# Get sample data for the given browser.
#
def _ps_browser(pat)
x = `ps -ocomm,pcpu,rss -xwwc | grep -i #{pat}`
if x.length > 0
cpu = Float(/\d+\.\d+/.match(x)[0])
mem = Float(/\d+$/.match(x)[0])*1024.0
else
cpu = -1.0
mem = -1.0
end
return [cpu, mem]
end
#
# Get a single sample. Three system calls are made, 1 to iostat and 2 to ps
#
def _get_sample
time = (Time.now - @startTime).to_f
x = `iostat -n 0 | tail -1`
x = x.scan(/\d+/)
user = Float(x[0])
sys = Float(x[1])
x = _ps_browser("firefox-bin")
fcpu = x[0]
fmem = x[1]
x = _ps_browser("safari")
scpu = x[0]
smem = x[1]
return {'sample' => @sampleNumber, 'time' => time, 'ffxcpu' => fcpu, 'ffxmem' => fmem, 'safcpu' => scpu, 'safmem' => smem, 'sys'=>sys, 'user'=>user}
end
end
rubyCoreletDefinition = {
'class' => "BrowserProfiler",
'name' => "BrowserProfiler",
'major_version' => 0,
'minor_version' => 0,
'micro_version' => 5,
'documentation' =>
'A service that analyzes the memory and cpu usage of a web browser. ' +
'The service can take 1 sample or multiple samples at a specified interval. ' +
'When sampling at intervals, at most 1,000 samples are taken. If you provide ' +
'a callback function, your javascript will be called after every sample is taken. ' +
'If no callback is provided, all samples are stored in an array and returned after start() ' +
'completes or stop() is called.\n' +
'The sample object is a map with the following keys (most values are floats):\n' +
'[sample] - the sample number (1-1,000)\n' +
'[time] - the offset time in seconds of when the sample was taken\n' +
'[sys] - the percentage CPU "sys" processes are using\n' +
'[user] - the percentage CPU "user" processes are using\n' +
'[ffxcpu] - the percentage CPU Firefox is using, or -1.0 if it is not running\n' +
'[ffxmem] - the amout of memory Firefox is using, or -1.0 if it is not running\n' +
'[safcpu] - the percentage CPU Safari is using, or -1.0 if it is not running\n' +
'[safmem] - the amout of memory Safari is using, or -1.0 if it is not running\n',
'functions' =>
[
{
'name' => 'start',
'documentation' => "Takes samples of memory/cpu every 'interval' seconds. Calls supplied callback with map of {memory, cpu}. Horrid - osx only - assumes ffx - broken for multiple instances. very much proof of concept, people.",
'arguments' =>
[
{
'name' => 'interval',
'type' => 'double',
'required' => true,
'documentation' => 'The sample time in seconds.'
},
{
'name' => 'callback',
'type' => 'callback',
'required' => false,
'documentation' => 'the callback to send a hello message to'
}
]
},
{
'name' => 'stop',
'documentation' => "Stop taking samples. Passes back the array of all samples.",
'arguments' => []
},
{
'name' => 'sample',
'documentation' => "Returns a single sample, map of {memory, cpu}",
'arguments' => []
}
]
}