App resource consumption

Has anyone found a nice way to track the server resource usage of an app?
I mean the 3 pillars: CPU, RAM, DISK.
Even though I understand that the amazing Anvil team is working on exposing this information to us in some way, I was wondering if in the meantime, I could do it from within the app itself.
What I found is the psutil module, but I can’t find a ‘generic’ way to use it that could easily adapt to all my apps, without coding it differently each and every time.
Something ‘magic’ like cProfile usual usage schema that is like:

import cProfile

def main():
    # your code

cProfile.run('main()')

But Anvil apps start on the client.

I apologize for asking such a broad question, but that’s really how it is: I have not a specific problem to solve here, but I’m raising a more general question to the Anvil developers community.

psutil can be used to track memory usage between points in the code. Create a function decorator and you could have it automatically track memory usage at the beginning and end of server functions.

The profiler can also be used on the server, but I’ve found it to not really give me everything I’d expected. It’s been a few years since I played with it, though. There was a little extra code needed to store profiling data in the user’s session so it could aggregate information between server calls.

2 Likes

I didn’t think about using a decorator, that’s a good idea, I’ll try it out.
Thanks!

Here’s my (very old) code for a couple decorators related to profiling. No idea if it still works or not:

import anvil.server
import functools
import cProfile
import pickle
import pstats

# Use for server calls, accumulates profiling stats over many server calls
def profile(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        if 'stats' not in anvil.server.session:
            pickled_bytes = pickle.dumps(pstats.Stats(), 0)
            anvil.server.session['stats'] = pickled_bytes.decode('utf-8')

        prof = cProfile.Profile()
        prof.enable()
        retval = func(*args, **kw)
        prof.disable()
          
        profiler_stats = pickle.loads(bytes(anvil.server.session['stats'], 'utf-8'))
        profiler_stats.add(prof)
        pickled_bytes = pickle.dumps(profiler_stats, 0)
        anvil.server.session['stats'] = pickled_bytes.decode('utf-8')        
        return retval
        
    return wrapper

# Use for background tasks since they don't share the same session as server calls
def profile_now(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        prof = cProfile.Profile()
        prof.enable()
        retval = func(*args, **kw)
        prof.disable()
        prof.print_stats(sort='cumtime')
        return retval
        
    return wrapper

@anvil.server.callable
def print_stats():
    if 'stats' in anvil.server.session:
        profiler_stats = pickle.loads(bytes(anvil.server.session['stats'], 'utf-8'))
        profiler_stats.sort_stats('cumtime', 'calls')
        # Filter to just our functions, not built-in Python functions
        #profiler_stats.print_stats('InfiniteWorlds')
        profiler_stats.print_stats()
        return
        
    print("No stats found")

@anvil.server.callable
def clear_stats():
    if 'stats' in anvil.server.session:
        del anvil.server.session['stats']