зеркало из https://github.com/mozilla/pjs.git
262 строки
8.2 KiB
Python
262 строки
8.2 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# ***** BEGIN LICENSE BLOCK *****
|
||
|
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
#
|
||
|
# The contents of this file are subject to the Mozilla Public License Version
|
||
|
# 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
# the License. You may obtain a copy of the License at
|
||
|
# http://www.mozilla.org/MPL/
|
||
|
#
|
||
|
# Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
# for the specific language governing rights and limitations under the
|
||
|
# License.
|
||
|
#
|
||
|
# The Original Code is standalone Firefox Windows performance test.
|
||
|
#
|
||
|
# The Initial Developer of the Original Code is Google Inc.
|
||
|
# Portions created by the Initial Developer are Copyright (C) 2006
|
||
|
# the Initial Developer. All Rights Reserved.
|
||
|
#
|
||
|
# Contributor(s):
|
||
|
# Annie Sullivan <annie.sullivan@gmail.com> (original author)
|
||
|
# Ben Hearsum <bhearsum@wittydomain.com> (ported to linux)
|
||
|
#
|
||
|
# Alternatively, the contents of this file may be used under the terms of
|
||
|
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
|
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
# of those above. If you wish to allow use of your version of this file only
|
||
|
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
# use your version of this file under the terms of the MPL, indicate your
|
||
|
# decision by deleting the provisions above and replace them with the notice
|
||
|
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
# the provisions above, a recipient may use your version of this file under
|
||
|
# the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
#
|
||
|
# ***** END LICENSE BLOCK *****
|
||
|
|
||
|
"""A set of functions to run the Tp test.
|
||
|
|
||
|
The Tp test measures page load times in Firefox. It does this with a
|
||
|
JavaScript script that opens a new window and cycles through page loads
|
||
|
from the local disk, timing each one. The script dumps the sum of the
|
||
|
mean times to open each page, and the standard deviation, to standard out.
|
||
|
We can also measure performance attributes during the test. See below for
|
||
|
what can be monitored
|
||
|
"""
|
||
|
|
||
|
__author__ = 'annie.sullivan@gmail.com (Annie Sullivan)'
|
||
|
|
||
|
|
||
|
import os
|
||
|
import time
|
||
|
import threading
|
||
|
|
||
|
import ffprocess
|
||
|
|
||
|
def GetPrivateBytes(pid):
|
||
|
"""Calculate the amount of private, writeable memory allocated to a process.
|
||
|
This code was adapted from 'pmap.c', part of the procps project.
|
||
|
"""
|
||
|
mapfile = '/proc/%s/maps' % pid
|
||
|
maps = open(mapfile)
|
||
|
|
||
|
private = 0
|
||
|
|
||
|
for line in maps:
|
||
|
# split up
|
||
|
(range,line) = line.split(" ", 1)
|
||
|
|
||
|
(start,end) = range.split("-")
|
||
|
flags = line.split(" ", 1)[0]
|
||
|
|
||
|
size = int(end, 16) - int(start, 16)
|
||
|
|
||
|
if flags.find("p") >= 0:
|
||
|
if flags.find("w") >= 0:
|
||
|
private += size
|
||
|
|
||
|
return private
|
||
|
|
||
|
|
||
|
def GetResidentSize(pid):
|
||
|
"""Retrieve the current resident memory for a given process"""
|
||
|
# for some reason /proc/PID/stat doesn't give accurate information
|
||
|
# so we use status instead
|
||
|
file = '/proc/%s/status' % pid
|
||
|
|
||
|
status = open(file)
|
||
|
|
||
|
for line in status:
|
||
|
if line.find("VmRSS") >= 0:
|
||
|
return int(line.split()[1]) * 1024
|
||
|
|
||
|
|
||
|
def GetCpuTime(pid, sampleTime=1):
|
||
|
"""Calculates the percent of time a process spent executing
|
||
|
This function samples /proc/PID/status at a rate of 20samples/sec to
|
||
|
check whether a process is active or idle.
|
||
|
|
||
|
Args:
|
||
|
pid: The PID of process to calculate time for
|
||
|
sampleTime: The length of time to monitor the process for. If this
|
||
|
argument is unspecified or 0 it will be monitored until
|
||
|
it terminates
|
||
|
Returns:
|
||
|
A percent of time a PID was idle (between 0.00 and 100.00)
|
||
|
"""
|
||
|
file = '/proc/%s/status' % pid
|
||
|
|
||
|
sampleInterval = .05
|
||
|
totalSamples = (1 / sampleInterval) * sampleTime
|
||
|
|
||
|
states = []
|
||
|
|
||
|
while os.path.exists(file):
|
||
|
status = open(file)
|
||
|
|
||
|
for line in status:
|
||
|
if line.find("State") >= 0:
|
||
|
states.append(line.split()[1])
|
||
|
|
||
|
# check to see if we've reached the maximum number of samples
|
||
|
if totalSamples != 0 and len(states) >= totalSamples:
|
||
|
break;
|
||
|
|
||
|
time.sleep(sampleInterval);
|
||
|
|
||
|
# non-idle cpu time = active cpu time / total cpu time
|
||
|
# ex: 10 total cycles, 8 idle cycles, 2 active cycles
|
||
|
# non-idle cpu time = 2 / 10
|
||
|
# non-idle cpu time = .2 (20%)
|
||
|
try:
|
||
|
activeCpuTime = float(states.count("R")) / float(len(states))
|
||
|
except ZeroDivisionError:
|
||
|
activeCpuTime = -1
|
||
|
|
||
|
return float("%.2lf" % (activeCpuTime * 100))
|
||
|
|
||
|
|
||
|
counterDict = {}
|
||
|
counterDict["Private Bytes"] = GetPrivateBytes
|
||
|
counterDict["RSS"] = GetResidentSize
|
||
|
counterDict["% Processor Time"] = GetCpuTime
|
||
|
|
||
|
class CounterManager(threading.Thread):
|
||
|
"""This class manages the monitoring of a process with any number of
|
||
|
counters.
|
||
|
|
||
|
A counter can be any function that takes an argument of one pid and
|
||
|
returns a piece of data about that process.
|
||
|
Some examples are: CalcCPUTime, GetResidentSize, and GetPrivateBytes
|
||
|
"""
|
||
|
|
||
|
pollInterval = .25
|
||
|
|
||
|
def __init__(self, process, counters=None):
|
||
|
"""Args:
|
||
|
counters: A list of counters to monitor. Any counters whose name does
|
||
|
not match a key in 'counterDict' will be ignored.
|
||
|
"""
|
||
|
self.allCounters = {}
|
||
|
self.registeredCounters = {}
|
||
|
self.process = process
|
||
|
self.runThread = False
|
||
|
self.pid = -1
|
||
|
|
||
|
self._loadCounters()
|
||
|
self.registerCounters(counters)
|
||
|
|
||
|
threading.Thread.__init__(self)
|
||
|
|
||
|
def _loadCounters(self):
|
||
|
"""Loads all of the counters defined in the counterDict"""
|
||
|
for counter in counterDict.keys():
|
||
|
self.allCounters[counter] = counterDict[counter]
|
||
|
|
||
|
def registerCounters(self, counters):
|
||
|
"""Registers a list of counters that will be monitoring.
|
||
|
Only counters whose names are found in allCounters will be added
|
||
|
"""
|
||
|
for counter in counters:
|
||
|
if counter in self.allCounters:
|
||
|
self.registeredCounters[counter] = \
|
||
|
[self.allCounters[counter], []]
|
||
|
|
||
|
def unregisterCounters(self, counters):
|
||
|
"""Unregister a list of counters.
|
||
|
Only counters whose names are found in registeredCounters will be
|
||
|
paid attention to
|
||
|
"""
|
||
|
for counter in counters:
|
||
|
if counter in self.registeredCounters:
|
||
|
del self.registeredCounters[counter]
|
||
|
|
||
|
def getRegisteredCounters(self):
|
||
|
"""Returns a list of the registered counters."""
|
||
|
return keys(self.registeredCounters)
|
||
|
|
||
|
def getCounterValue(self, counterName):
|
||
|
"""Returns the last value of the counter 'counterName'"""
|
||
|
try:
|
||
|
if counterName is "% Processor Time":
|
||
|
return self._getCounterAverage(counterName)
|
||
|
else:
|
||
|
return self.registeredCounters[counterName][1][-1]
|
||
|
except:
|
||
|
return None
|
||
|
|
||
|
def _getCounterAverage(self, counterName):
|
||
|
"""Returns the average value of the counter 'counterName'"""
|
||
|
try:
|
||
|
total = 0
|
||
|
for v in self.registeredCounters[counterName][1]:
|
||
|
total += v
|
||
|
return total / len(self.registeredCounters[counterName][1])
|
||
|
except:
|
||
|
return None
|
||
|
|
||
|
def getProcess(self):
|
||
|
"""Returns the process currently associated with this CounterManager"""
|
||
|
return self.process
|
||
|
|
||
|
def startMonitor(self):
|
||
|
"""Starts the monitoring process.
|
||
|
Throws an exception if any error occurs
|
||
|
"""
|
||
|
# TODO: make this function less ugly
|
||
|
try:
|
||
|
# the last process is the useful one
|
||
|
self.pid = ffprocess.GetPidsByName(self.process)[-1]
|
||
|
os.stat('/proc/%s' % self.pid)
|
||
|
self.runThread = True
|
||
|
self.start()
|
||
|
except:
|
||
|
raise
|
||
|
|
||
|
def stopMonitor(self):
|
||
|
"""Stops the monitor"""
|
||
|
# TODO: should probably wait until we know run() is completely stopped
|
||
|
# before setting self.pid to None. Use a lock?
|
||
|
self.runThread = False
|
||
|
|
||
|
def run(self):
|
||
|
"""Performs the actual monitoring of the process. Will keep running
|
||
|
until stopMonitor() is called
|
||
|
"""
|
||
|
while self.runThread:
|
||
|
for counter in self.registeredCounters.keys():
|
||
|
# counter[0] is a function that gets the current value for
|
||
|
# a counter
|
||
|
# counter[1] is a list of recorded values
|
||
|
try:
|
||
|
self.registeredCounters[counter][1].append(
|
||
|
self.registeredCounters[counter][0](self.pid))
|
||
|
except:
|
||
|
# if a counter throws an exception, remove it
|
||
|
self.unregisterCounters([counter])
|
||
|
|
||
|
time.sleep(self.pollInterval)
|