7

I have a python script that runs on my web server. The main function is called then when it returns it just sleeps for a few seconds and gets called again. It's purpose is to pick up any new uploaded videos that users have added and convert them to webm, pull out the middle frame as an image and a bunch of other funky stuff. I am using an external call to ffmpeg. The code clip below shows how I call it.

    duration = output[durationIndex+10:durationIndex+18]
    durationBits = duration.split(":")
    lengthInSeconds = (int(durationBits[0])*60*60) + (int(durationBits[1])*60) + (int(durationBits[2]))

    child = subprocess.Popen(["ffmpeg","-y","-i",sourceVideo,"-f","mjpeg","-vframes","1","-ss",str(lengthInSeconds/2),destination], shell=True, stderr=subprocess.PIPE)
    output = ""
    while True:
        out = child.stderr.read(1)
        if out == '' and child.poll() != None:
            break
        if out != '':
            output += out

    updateSQL = "update `videos_graduatevideo` set thumbnail = '" + str(destination) + "' where `original_video` = '" + sourceVideo + "'"
    cursor.execute(updateSQL)

This script is running on a Windows machine atm but I will probably deploy it on a Unix system when it is dev complete.

The problem is. I need this python script to keep running. If something goes wrong with ffmpeg and my script hangs, user uploaded videos will just sit in a "pending" status until I go poke the python script. I know a certain mov file I have makes ffmpeg hang indefinately. Is there someway I can check how long a process has been running and then kill it off if it has been running for too long?

DrLazer
  • 2,805
  • 3
  • 41
  • 52
  • 1
    I had the exactly same problem (I was using Pylons instead of Django). I wrote an external program with database access (if you give it Django settings, you can use your models) and an ajax polling system to get the result. – JBernardo Dec 29 '11 at 19:21
  • What about [celery](http://celeryproject.org/). Has [monitoring](http://celery.readthedocs.org/en/latest/userguide/monitoring.html) support. – dani herrera Dec 30 '11 at 22:13

5 Answers5

6

I agree with S. Lott in that it would seem you'd benefit from considering a MQ for your architecture, but for this particular issue I think your use of Popen is OK.

For each process you create, save the creating time (something like datetime.datetime.today() would suffice). Then every minute or so go over the list of open processes and times and reap the ones that shouldn't be there using Popen.send_signal(signal), terminate(), or kill().

Example:

import time
from subprocess import Popen
from datetime import datetime
jobs = []
max_life = 600 # in seconds

def reap_jobs(jobs):
  now = datetime.datetime.today()
  for job in jobs:
    if job[0] < now - datetime.timedelta(seconds=max_life)
      job[1].kill()
      # remove the job from the list if you want. 
      # but remember not to do it while iterating over the list

for video in list_of_videos:
  time = datetime.datetime.today()
  job = Popen(...)
  jobs.append((time,child))

while True:
  reap_jobs(jobs)
  time.sleep(60)
Eduardo Ivanec
  • 11,668
  • 2
  • 39
  • 42
1

Since the controlling script is the one that started it, and since you want it killed based on time, not system resource useage, it should be fairly simple. Below is your example code with some modifications; look for the lines with comments.

import time
timeout = 60 #child is allowed to run for 1 minute.
duration = output[durationIndex+10:durationIndex+18]
durationBits = duration.split(":")
lengthInSeconds = (int(durationBits[0])*60*60) + (int(durationBits[1])*60) + (int(durationBits[2]))

child = subprocess.Popen(["ffmpeg","-y","-i",sourceVideo,"-f","mjpeg","-vframes","1","-ss",str(lengthInSeconds/2),destination], shell=True, stderr=subprocess.PIPE)
killtime = time.time() + timeout #timestamp after which the child process should be killed
output = ""
while True:
    out = child.stderr.read(1)
    if out == '' and child.poll() != None:
        break
    if out != '':
        output += out
    if time.time() > killtime: #check if 60 seconds have passed
        child.kill() #tell the child to exit
        raise RuntimeError("Child process still going %i seconds after launch" %killtime) #raise an exception so that updateSQL doesn't get executed

updateSQL = "update `videos_graduatevideo` set thumbnail = '" + str(destination) + "' where `original_video` = '" + sourceVideo + "'"
cursor.execute(updateSQL)

You could change the RuntimeError to something else, or have it set a flag instead of raising an exception, depending on what else you need it to do. The child.kill() line will cause the child process to die, but it may not be the most graceful way to end it. If you deploy it on a posix system, you could use os.system('kill -s 15 %i' %child.pid) instead, to kill it more gracefully.

Perkins
  • 2,409
  • 25
  • 23
0

There is a python module that provides an interface for retrieving information on all running processes and system utilization (CPU, disk, memory) in a portable way, implementing many functionalities offered by command line tools such as: ps, top, df, kill, free, lsof, free, netstat, ifconfig, nice, ionice, iostato, iotop, uptime, tty: psutil. It should help.

Pavel Shvedov
  • 1,284
  • 11
  • 8
-1

Take a look at God - A Process Monitor,which monitors the process you specified, and perform some actions according to your monitoring condition. For example, it can keep an eye on the cpu usage and restart the process if the cpu usage is above 50%:

# code in Ruby
# copyied from the documentation
w.restart_if do |restart|   
  restart.condition(:cpu_usage) do |c|
    c.above = 50.percent
    c.times = 5
  end
end
Limbo Peng
  • 2,485
  • 19
  • 12
-3

Step 1. Don't use CGI scripts. Use a framework.

Step 2. Don't start the subprocess directly in the function which creates the response. Use celery.

this process is just running on the server all the time. It's independent of any framework and reads from the same db that django populates

Step 2, again. Don't leave this subprocess running all the time. Use Celery so that it is started when a request arrives, handles that request (and only that request) and then stops.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • You've assumed a lot in your answer. - I'm using django, but this process is just running on the server all the time. It's independent of any framework and reads from the same db that django populates. - No response is created, it just fills in some db fields when it is done – DrLazer Dec 22 '11 at 12:27
  • @DrLazer: I'm assuming a lot in my answer because you didn't provide much detail in your question. Do all of us a favor and **update** your question to have all the facts. Concealing facts in a comment to an answer isn't terribly helpful to others. – S.Lott Dec 22 '11 at 12:32
  • 1
    I provide enough detail to cover the question I'm asking. I'm hardly going to write the whole spec for the system in a question. – DrLazer Dec 22 '11 at 12:36
  • Thanks for updating your question. If I use celery i'll have the same problem. If it calls ffmpeg externally and ffmpeg hangs. – DrLazer Dec 22 '11 at 12:42
  • @DrLazer: "provide enough detail to cover the question I'm asking". Here's the point. You added facts in a comment on my answer. Don't do that. Add facts in the question, where they're visible to everyone. It's simple. Don't accuse me of assuming. Correct my assumptions by providing facts in the question. – S.Lott Dec 22 '11 at 12:45
  • If you use celery, you have a hung subprocess, disconnected from Django, and disconnected from Celery. Your web site runs fast. Celery still serves requests from users. You just have a file which didn't get converted. Since the site (and celery) are still spawning subprocesses, hung ffmpegx subprocesses are irrelevant to overall responsiveness. Please read the answer. Celery should be used to start a fresh, new, "convert this file only" subprocess. You don't have a long-running ffmpegx "server" looking in a directory for uploaded files. – S.Lott Dec 22 '11 at 12:47
  • This discussion is silly. An answer could be - Run the code on a PC not a toaster I wouldn't then add "this is not running on a toaster" to the question. Then you could say "im not running it on a toaster" then I could say "dont accuse me of assuming things". It's all irrelevant. All I want to know is if there is a way of killing of a process in python, and if there is a way of telling how long it has been running for. – DrLazer Dec 22 '11 at 12:53
  • sorry we are in dual conversation mode here. Just catching up – DrLazer Dec 22 '11 at 12:55
  • Yeah it's a good idea, but I'm worried about that hung process. If I had 1000 hung ffmpeg processes that never closed i'd run out of memory and system responsiveness would be like a toaster instead of a PC. – DrLazer Dec 22 '11 at 12:56
  • 1
    @DrLazer: "This discussion is silly". No. It's not. If you need to provide additional facts, you need to **update** the question. It's very simple. Don't accuse people of assuming. Correct the assumptions by updating the question. Please don't complain. Please just **update** the question to correct assumptions. That's all. – S.Lott Dec 22 '11 at 13:00
  • @DrLazer: "If I had 1000 hung ffmpeg processes that never closed i'd run out of memory". That's false. What makes you think that would happen? Hung processes are swapped out of memory. You will eventually run of of "process slots" in the scheduler. This is trivially solved with a scheduled "cron" job to remove hung processes. Or have celery start a timeout process when it starts the ffMPEGx process. The timeout sleeps for 1 hr and then attempts to kill the main process: `import time, sys, os; time.sleep(60*60); os.kill(int(argv[1)])` or something similar. – S.Lott Dec 22 '11 at 13:04
  • I just encoded a mov in ffmpeg that hung. its memory useage was stuck on 4,900K. I run another one, same thing happened, and again. I sense a pattern here. The trivial cron job to kill the process sounds like it would have the code that would answer my original question. Just making some toast, sorry for any delay. – DrLazer Dec 22 '11 at 23:47
  • "The trivial cron job to kill the process" won't work in a general way since you can't easily identify "hung". Spawning two tasks -- one to do ffMPEGx and one to idle for the timeout period -- works better. I don't get the "toast" comment. I'm sure it's really profound. Does it have something to do with your absolute refusal to clarify the ambiguities in your question? If so, I don't get why ambiguity is so important and resolving ambiguity so hateful. But. If you hate clarifying, I guess we're stuck assuming. In which case, please don't be negative when we're forced to assume. – S.Lott Dec 23 '11 at 01:55
  • 1
    @DrLazer: "its memory useage was stuck on 4,900K"? Real, physical memory? Or ordinary swap-space memory that's quietly swapped out of real memory and ignored? – S.Lott Dec 23 '11 at 11:51