strange subprocess behavior when calling ps

R

Roger Davis

Hi all,

I have encountered a strange problem with some code I am writing to
search the system process list for certain running processes. I am
using subprocess.Popen() to call '/bin/ps -e'. When I save my code to
the file pid.py (whose first line is #!/usr/bin/python) and run it
with the command

% ./pid.py

it works perfectly fine, retrieving lines from my pipe to the /bin/ps
output which look exactly as if I had typed the command '/bin/ps -e'
myself into a shell window. Here is a sample line from that output:

1891 ttys000 0:00.12 -tcsh

Now for the weird part -- when I run this code using the command

% python pid.py

I get entirely different output. It only prints out a very few
processes instead of the entire table, and each line also has lots of
environment variable values displayed. Here is the line from that
output which corresponds to the line immediately above:

1891 s000 S+ 0:00.12 -tcsh PATH=/usr/bin:/bin:/usr/sbin:/sbin
TMPDIR=/var/folders/3e/3e-TyTQIG-aOa4x37pbTbk++-H6/-Tmp-/ SHELL=/bin/
tcsh HOME=/Users/hmrgsoft USER=hmrgsoft LOGNAME=hmrgsoft DISPLAY=/tmp/
launch-c1YZNr/org.x:0 SSH_AUTH_SOCK=/tmp/launch-AJ9xbl/Listeners
Apple_PubSub_Socket_Render=/tmp/launch-BsRx5Y/Render
COMMAND_MODE=unix2003 __CF_USER_TEXT_ENCODING=0x532:0:0
TERM_PROGRAM=Apple_Terminal TERM_PROGRAM_VERSION=273 LANG=en_US.UTF-8
TERM=xterm-color

It's like it's calling up an entirely different ps, or passing it
different command arguments. In both cases, however, I am explicitly
calling /bin/ps with the same -e argument, and there appear to be no
other ps commands on my system, neither do I appear to have any ps
builtin command in any shell.

I am running 2.6.6 under MacOS 10.6.4 on a MacBook Pro Intel. I have
appended the code below. I am running both commands directly in a
Terminal window running tcsh.

Can anyone explain this? Thanks!

Roger Davis

##### code follows

#!/usr/bin/python
import sys
import subprocess

def main():

psargs= ["/bin/ps", "-e"]
try:
ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
psout= ps.communicate()[0]
pslines= psout.splitlines()
for line in pslines:
print "%s" % line
except KeyboardInterrupt:
print "Keyboard interrupt received -- terminating."
sys.stdout.flush()
sys.exit(-1)
except:
print "%s: unexpected error in generation of system process list" %
prognm
sys.stdout.flush()
sys.exit(-1)

main()
 
J

James Mills

Hi all,

I have encountered a strange problem with some code I am writing to
search the system process list for certain running processes. I am
using subprocess.Popen() to call '/bin/ps -e'. When I save my code to
the file pid.py (whose first line is #!/usr/bin/python) and run it
with the command

% ./pid.py

it works perfectly fine, retrieving lines from my pipe to the /bin/ps
output which look exactly as if I had typed the command '/bin/ps -e'
myself into a shell window. Here is a sample line from that output:

 1891 ttys000    0:00.12 -tcsh

Now for the weird part -- when I run this code using the command

% python pid.py

I get entirely different output. It only prints out a very few
processes instead of the entire table, and each line also has lots of
environment variable values displayed. Here is the line from that
output which corresponds to the line immediately above:

 1891 s000  S+     0:00.12 -tcsh PATH=/usr/bin:/bin:/usr/sbin:/sbin
TMPDIR=/var/folders/3e/3e-TyTQIG-aOa4x37pbTbk++-H6/-Tmp-/ SHELL=/bin/
tcsh HOME=/Users/hmrgsoft USER=hmrgsoft LOGNAME=hmrgsoft DISPLAY=/tmp/
launch-c1YZNr/org.x:0 SSH_AUTH_SOCK=/tmp/launch-AJ9xbl/Listeners
Apple_PubSub_Socket_Render=/tmp/launch-BsRx5Y/Render
COMMAND_MODE=unix2003 __CF_USER_TEXT_ENCODING=0x532:0:0
TERM_PROGRAM=Apple_Terminal TERM_PROGRAM_VERSION=273 LANG=en_US.UTF-8
TERM=xterm-color

It's like it's calling up an entirely different ps, or passing it
different command arguments. In both cases, however, I am explicitly
calling /bin/ps with the same -e argument, and there appear to be no
other ps commands on my system, neither do I appear to have any ps
builtin command in any shell.

I am running 2.6.6 under MacOS 10.6.4 on a MacBook Pro Intel. I have
appended the code below. I am running both commands directly in a
Terminal window running tcsh.

Can anyone explain this? Thanks!

Roger Davis

##### code follows

#!/usr/bin/python
import sys
import subprocess

def main():

       psargs= ["/bin/ps", "-e"]
       try:
               ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
               psout= ps.communicate()[0]
               pslines= psout.splitlines()
               for line in pslines:
                       print "%s" % line
       except KeyboardInterrupt:
               print "Keyboard interrupt received -- terminating."
               sys.stdout.flush()
               sys.exit(-1)
       except:
               print "%s: unexpected error in generation of system process list" %
prognm
               sys.stdout.flush()
               sys.exit(-1)

main()

Roger, why not use the nicely written and documented psutil module ?

http://pypi.python.org/pypi/psutil/0.2.0
http://code.google.com/p/psutil/

cheers
James
 
C

Chris Rebert

Hi all,

I have encountered a strange problem with some code I am writing to
search the system process list for certain running processes. I am
using subprocess.Popen() to call '/bin/ps -e'. When I save my code to
the file pid.py (whose first line is #!/usr/bin/python) and run it
with the command

% ./pid.py

it works perfectly fine, retrieving lines from my pipe to the /bin/ps
output which look exactly as if I had typed the command '/bin/ps -e'
myself into a shell window. Here is a sample line from that output:

 1891 ttys000    0:00.12 -tcsh

Now for the weird part -- when I run this code using the command

% python pid.py

I get entirely different output. It only prints out a very few
processes instead of the entire table, and each line also has lots of
environment variable values displayed.
It's like it's calling up an entirely different ps, or passing it
different command arguments. In both cases, however, I am explicitly
calling /bin/ps with the same -e argument, and there appear to be no
other ps commands on my system, neither do I appear to have any ps
builtin command in any shell.

I am running 2.6.6 under MacOS 10.6.4 on a MacBook Pro Intel. I have
appended the code below. I am running both commands directly in a
Terminal window running tcsh.

Can anyone explain this? Thanks!
##### code follows

#!/usr/bin/python
<snip>

Have you checked whether those commands are running under the same
Python? What output do you get from tcsh for the following?:
which python
python -V
/usr/bin/python -V
ls -l /usr/bin/python

Also, did you upgrade your system Python or something? I'm running Mac
OS 10.6.5 and the built-in /usr/bin/python is v2.6.1, so I find the
implied claim that your /usr/bin/python is v2.6.6 to be rather
bizarre.

I am unable to reproduce your problem with either my v2.6.1 system
Python or my v2.6.6 Python from Fink.

Cheers,
Chris
 
R

Roger Davis

Thanks, Chris, you're at least on the right track. I did upgrade from
python.org and the python in my shell PATH is /Library/Frameworks/
Python.framework/Versions/2.6/bin/python:

% python
Python 2.6.6 (r266:84374, Aug 31 2010, 11:00:51)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin

/usr/bin/python is the Apple-distributed 2.6.1:

% /usr/bin/python
Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin

This still doesn't explain the behavior to me, however. In either case
python is supposed to be forking a child process with a specific
executable (/bin/ps) and a specific argument list (-e) and reading
that command's output lines. Why should those output lines be
different whether I use 2.6.1, 2.6.6 or 8.9.10 for that matter? In
fact, this makes the problem that much worse -- the newer python 2.6.6
is the one producing the incorrect output. Changing the first line of
the script to read

#!/Library/Frameworks/Python.framework/Versions/2.6/bin/python

does not help, it still prints out the wrong output whether I do

% ./pid.py

or

% /Library/Frameworks/Python.framework/Versions/2.6/bin/python ./
pid.py

Any ideas?

Thanks!
 
R

Roger Davis

Hi James,

Thanks for the pointer to psutil. I actually did look around on
python.org before coding this up to see if there was such a package
available but there is not, at least not where I'm looking -- on the
other hand, I can't find my car keys most of the time. I would really
like to restrict my code dependencies to the standard Python
distribution (if there actually is such a thing, I'm new to the
language). Of course, that's assuming that I can actually get my code
to work in that fashion. If I can't get to the bottom of this issue
and figure out why my existing code does not work I will investigate
psutil further.

Thanks!

Roger
 
N

Ned Deily

I have encountered a strange problem with some code I am writing to
search the system process list for certain running processes. I am
using subprocess.Popen() to call '/bin/ps -e'. When I save my code to
the file pid.py (whose first line is #!/usr/bin/python) and run it
with the command

% ./pid.py

it works perfectly fine, retrieving lines from my pipe to the /bin/ps
output which look exactly as if I had typed the command '/bin/ps -e'
myself into a shell window. Here is a sample line from that output:

1891 ttys000 0:00.12 -tcsh

Now for the weird part -- when I run this code using the command

% python pid.py

I get entirely different output. It only prints out a very few
processes instead of the entire table, and each line also has lots of
environment variable values displayed. Here is the line from that
output which corresponds to the line immediately above:

1891 s000 S+ 0:00.12 -tcsh PATH=/usr/bin:/bin:/usr/sbin:/sbin
TMPDIR=/var/folders/3e/3e-TyTQIG-aOa4x37pbTbk++-H6/-Tmp-/ SHELL=/bin/
tcsh HOME=/Users/hmrgsoft USER=hmrgsoft LOGNAME=hmrgsoft DISPLAY=/tmp/
launch-c1YZNr/org.x:0 SSH_AUTH_SOCK=/tmp/launch-AJ9xbl/Listeners
Apple_PubSub_Socket_Render=/tmp/launch-BsRx5Y/Render
COMMAND_MODE=unix2003 __CF_USER_TEXT_ENCODING=0x532:0:0
TERM_PROGRAM=Apple_Terminal TERM_PROGRAM_VERSION=273 LANG=en_US.UTF-8
TERM=xterm-color

It's like it's calling up an entirely different ps, or passing it
different command arguments. In both cases, however, I am explicitly
calling /bin/ps with the same -e argument, and there appear to be no
other ps commands on my system, neither do I appear to have any ps
builtin command in any shell.

I am running 2.6.6 under MacOS 10.6.4 on a MacBook Pro Intel. I have
appended the code below. I am running both commands directly in a
Terminal window running tcsh.

See "man compat". What you are seeing is the difference between ps(1)
output in "legacy" mode, attempting to duplicate the old, non-POSIX
behavior from 10.3 days, or "unix2003" mode. Terminal login sessions
are normally automatically started with the COMMAND_MODE environment
variable set:

$ echo $COMMAND_MODE
unix2003

Adding an "env={"COMMAND_MODE": "unix2003"}" argument to your subprocess
Popen call should do the trick.
 
R

Roger Davis

Thanks, Ned! That really helps to explain what is going on. Now, just
a couple more questions and I think I will know all I need to know.

First, I *still* don't quite understand why this happens with my 2.6.6
interpreter but not my 2.6.1, and why another of the respondents to
this thread (Chris) could not duplicate this problem with his own
2.6.6 environment. Is there something defective with my Python
environment having to do with the 2.6.6 upgrade I installed directly
from python.org? (Chris used Fink, apparently.) If so, how can I clean
it up? I do have an easier time believing that this is a mangled
installation issue now that the problem has essentially been reduced
to the handling of an environment variable which I can well imagine
could be easily garbled somewhere in a version mismatch scenario.

Second, I do need to make this work on multiple platforms, primarily
Linux in addition to the Mac but also with a potential variety of
Python releases on both MacOS and Linux. I'm not sure if Linux will
object to this COMMAND_MODE business, I guess I'll just have to try
and see what happens. Any thoughts on that?

Finally, to split hairs a bit concerning your suggested solution, I
have a question about the additional env={'COMMAND_MODE': 'unix2003'}
argument. This works for me, but my reading of the subprocess
documentation leads me to believe that this will actually wipe out the
entire environment that would otherwise be inherited from the parent
and replace it with that single setting. I don't think this has any
ill effects here with regard to ps, at least none I can detect at the
moment, but wouldn't a perhaps safer solution be to do

os.environ['COMMAND_MODE']= 'unix2003'

prior to the Popen() to instead augment/correct the existing
environment which will then later be inherited by the child, assuming
no explicit env= optional argument is used? This also works for me,
and I think I'm more inclined to go this route unless you can think of
a good reason not to do so.

Thanks very much for your help!
 
N

Ned Deily

First, I *still* don't quite understand why this happens with my 2.6.6
interpreter but not my 2.6.1, and why another of the respondents to
this thread (Chris) could not duplicate this problem with his own
2.6.6 environment. Is there something defective with my Python
environment having to do with the 2.6.6 upgrade I installed directly
from python.org? (Chris used Fink, apparently.) If so, how can I clean
it up? I do have an easier time believing that this is a mangled
installation issue now that the problem has essentially been reduced
to the handling of an environment variable which I can well imagine
could be easily garbled somewhere in a version mismatch scenario.

Interesting. It appears that OS X 10.6 takes into account the
deployment target of the executable. Prior to 2.7, all recent
python.org installed Pythons have been built with
MACOSX_DEPLOYMENT_TARGET = 10.3, allowing the same build to work on all
releases from 10.3.9 on. So, when you launch a script with the
python.org interpreter (with a 10.3 deployment target), OS X
automatically sets COMMAND_MODE to 'legacy'. The Apple-supplied Python
2.6.1 in OS X 10.6 is built with a deployment target of 10.6, so it gets
a COMMAND_MODE of 'unix2003'. And, as far as I can tell, OS X 10.5
honors COMMAND_MODE but does not automatically set it or tailor it like
10.6 does. Live and learn!
Second, I do need to make this work on multiple platforms, primarily
Linux in addition to the Mac but also with a potential variety of
Python releases on both MacOS and Linux. I'm not sure if Linux will
object to this COMMAND_MODE business, I guess I'll just have to try
and see what happens. Any thoughts on that?

As far as I know, COMMAND_MODE has no special meaning on other platforms
so it should be ignored anywhere but on OS X. If you want to be extra
cautious, you could test for 'sys.platform == 'darwin'.
> Finally, to split hairs a bit concerning your suggested solution, I
have a question about the additional env={'COMMAND_MODE': 'unix2003'}
argument. This works for me, but my reading of the subprocess
documentation leads me to believe that this will actually wipe out the
entire environment that would otherwise be inherited from the parent
and replace it with that single setting. I don't think this has any
ill effects here with regard to ps, at least none I can detect at the
moment, but wouldn't a perhaps safer solution be to do

os.environ['COMMAND_MODE']= 'unix2003'

prior to the Popen() to instead augment/correct the existing
environment which will then later be inherited by the child, assuming
no explicit env= optional argument is used? This also works for me,
and I think I'm more inclined to go this route unless you can think of
a good reason not to do so.

That analysis seems correct. So it comes down to whether there are any
environment variable settings the user could make that would positively
or negatively affect the operation of your script.
 
J

Jean-Michel Pichavant

Roger said:
Hi all,
[snip]

Roger Davis

##### code follows

#!/usr/bin/python
import sys
import subprocess

def main():

psargs= ["/bin/ps", "-e"]
try:
ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
psout= ps.communicate()[0]
pslines= psout.splitlines()
for line in pslines:
print "%s" % line
except KeyboardInterrupt:
print "Keyboard interrupt received -- terminating."
sys.stdout.flush()
sys.exit(-1)
except:
print "%s: unexpected error in generation of system process list" %
prognm
sys.stdout.flush()
sys.exit(-1)

main()


Completely off topic but I think the try clause could be rewritten that way:

try:
ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
psout= ps.communicate()[0]
pslines= psout.splitlines()
for line in pslines:
print "%s" % line
except KeyboardInterrupt:
print "Keyboard interrupt received -- terminating."
finally:
sys.stdout.flush()


Don't use bare except clause, you're masking syntax errors for instance,
which will be flagged as 'unexpected error in generation ...".
In a more general manner, if something unexpected happens it's better to
just let the exception raise uncought. If you want to handle some
errors, meaning you're kindof expecting them then add a explicit clause
(like you did with KeyboardInterrupt).

JM


PS : "except Exception :" will catch most of the exceptions (all
inheriting from that class). It's better than using a bare "except :"
clause. (Exception won't catch SyntaxError)
 
R

Roger Davis

Completely off topic but I think the try clause could be rewritten that way:
...
Don't use bare except clause, you're masking syntax errors for instance,
which will be flagged as 'unexpected error in generation ...".
In a more general manner, if something unexpected happens it's better to
just let the exception raise uncought. If you want to handle some
errors, meaning you're kindof expecting them then add a explicit clause
(like you did with KeyboardInterrupt).

JM

PS : "except Exception :" will catch most of the exceptions (all
inheriting from that class). It's better than using a bare "except :"
clause. (Exception won't catch SyntaxError)

Thanks for the suggestion JM, it is off-topic and, although I will
first just say that the exception mechanism is *not* one of the
reasons I use Python (and stop there with regard to the whole
exception mechanism and various usage strategies in general), I do
have a few specific questions about a couple of your statements if you
don't mind following up.

First, inserting a syntax error into my existing code does not hide a
SyntaxError exception as you have stated:

% cat pid.py
#!/usr/bin/python
import os
import sys
import subprocess

def main():

psargs= ["/bin/ps", "-e"]
try:
ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
psout= ps.communicate()[0]
pslines= psout.splitlines()
if pslines not good Python talky-talk
for line in pslines:
print "%s" % line
except KeyboardInterrupt:
print "Keyboard interrupt received -- terminating."
sys.stdout.flush()
sys.exit(-1)
except:
print "%s: unexpected error in generation of system process list" %
prognm
sys.stdout.flush()
sys.exit(-1)

main()

% ./pid.py
File "./pid.py", line 14
if pslines not good Python talky-talk
^
SyntaxError: invalid syntax

It appears that the interpreter is catching the syntax error before
the code is even executed.

Second, python.org's exception hierarchy documentation (Section 6.1 at
http://docs.python.org/library/exceptions.html) shows all exception
types except SystemExit, KeyboardInterrupt and GeneratorExit as being
descendants of Exception. This includes SyntaxError, a child of
StandardError which is itself a child of Exception. So, if I say
'except Exception:' then isn't that clause going to process any child
exception type of Exception (including SyntaxError) that I haven't
already covered in a separate except clause? (Except of course that my
interpreter doesn't seem to treat a syntax error as any kind of
exception at all!)

Finally, and this does not apply to your comments in particular, in
researching around about exception handling I often see the usage

except Exception, e:

suggested, but can't for the life of me figure out what the heck the
', e' part does. Can anybody explain what this means and why it might
be desirable (or not)?

Thanks!
 
R

Roger Davis

Interesting.  It appears that OS X 10.6 takes into account the ...

Thanks very much for your thorough explanation, Ned! I think I've got
what I need now.

Roger
 
C

Chris Rebert

Thanks for the suggestion JM, it is off-topic and, although I will
first just say that the exception mechanism is *not* one of the
reasons I use Python (and stop there with regard to the whole
exception mechanism and various usage strategies in general), I do
have a few specific questions about a couple of your statements if you
don't mind following up.

First, inserting a syntax error into my existing code does not hide a
SyntaxError exception as you have stated:
% ./pid.py
 File "./pid.py", line 14
   if pslines not good Python talky-talk
                     ^
SyntaxError: invalid syntax

It appears that the interpreter is catching the syntax error before
the code is even executed.

Now try:

# junk.py
@#$%^& gibberish @#$%^

# main.py
import junk

You'll get a run-time SyntaxError.

Finally, and this does not apply to your comments in particular, in
researching around about exception handling I often see the usage

  except Exception, e:

suggested, but can't for the life of me figure out what the heck the
', e' part does. Can anybody explain what this means and why it might
be desirable (or not)?

That's how `except Exception as e` used to be spelled. The exception
object will be bound to the variable `e`, allowing you to inspect it
further.
For example, one might use that form of `except` with an I/O-related
exception to be able to check its `errno` attribute in order to handle
the error properly.
If you're just catching a generic Exception, said form of `except` is
less useful.

Cheers,
Chris
 
J

Jean-Michel Pichavant

Roger said:
Completely off topic but I think the try clause could be rewritten that way:
...
Don't use bare except clause, you're masking syntax errors for instance,
which will be flagged as 'unexpected error in generation ...".
In a more general manner, if something unexpected happens it's better to
just let the exception raise uncought. If you want to handle some
errors, meaning you're kindof expecting them then add a explicit clause
(like you did with KeyboardInterrupt).

JM

PS : "except Exception :" will catch most of the exceptions (all
inheriting from that class). It's better than using a bare "except :"
clause. (Exception won't catch SyntaxError)

Thanks for the suggestion JM, it is off-topic and, although I will
first just say that the exception mechanism is *not* one of the
reasons I use Python (and stop there with regard to the whole
exception mechanism and various usage strategies in general), I do
have a few specific questions about a couple of your statements if you
don't mind following up.

First, inserting a syntax error into my existing code does not hide a
SyntaxError exception as you have stated:

% cat pid.py
#!/usr/bin/python
import os
import sys
import subprocess

def main():

psargs= ["/bin/ps", "-e"]
try:
ps= subprocess.Popen(psargs, stdout=subprocess.PIPE, close_fds=True)
psout= ps.communicate()[0]
pslines= psout.splitlines()
if pslines not good Python talky-talk
for line in pslines:
print "%s" % line
except KeyboardInterrupt:
print "Keyboard interrupt received -- terminating."
sys.stdout.flush()
sys.exit(-1)
except:
print "%s: unexpected error in generation of system process list" %
prognm
sys.stdout.flush()
sys.exit(-1)

main()

% ./pid.py
File "./pid.py", line 14
if pslines not good Python talky-talk
^
SyntaxError: invalid syntax

It appears that the interpreter is catching the syntax error before
the code is even executed.
That's right, 'cause my example is approximative. You would need to
import that file.
A better example would be to fire a NameError exception.
In the above code of fix the syntax error, but replace "for line in
pslines" by "for line in psLines". note the typo, very common mistake.
Then see how the error is hidden by your handler.
Second, python.org's exception hierarchy documentation (Section 6.1 at
http://docs.python.org/library/exceptions.html) shows all exception
types except SystemExit, KeyboardInterrupt and GeneratorExit as being
descendants of Exception. This includes SyntaxError, a child of
StandardError which is itself a child of Exception. So, if I say
'except Exception:' then isn't that clause going to process any child
exception type of Exception (including SyntaxError) that I haven't
already covered in a separate except clause?
You're absolutely right. Using Exception is bad, using bare except is
even worse. Exception is slightly better than except because you can at
least get the exception instance that has been raised, print it in your
handler, so you don't loose information (well, with a bare except you
can still inspect some sys objects to get info about the last exception
but it's a bit hackish).
(Except of course that my
interpreter doesn't seem to treat a syntax error as any kind of
exception at all!)

Finally, and this does not apply to your comments in particular, in
researching around about exception handling I often see the usage

except Exception, e:

suggested, but can't for the life of me figure out what the heck the
', e' part does. Can anybody explain what this means and why it might
be desirable (or not)?

Thanks!


Exception is the class, e is an instance of that class that has been
actually fired, it contains informations, most of the time a message.
This syntax has changed in python 3 I think, that would mean that you're
right to be confused, this python 2 syntax is ... strange...

import sys

# what if someone writes sys.hihi ?
try:
raise AttributeError("sys object has no attribute 'hihi'")
except Exception, e:
print e.args
print e.__class__
print e.message
print e.__doc__

output:
("sys object has no attribute 'hihi'",)
<type 'exceptions.AttributeError'>
sys object has no attribute 'hihi'
Attribute not found.


Apart from all these details, just keep in mind this golden rule:
- Don't use bare except clause.
And you'll be fine.

JM
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top