Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jobs results can be very slow / unable to display due to thousands of ensure_git_repository calls #1515

Closed
u1735067 opened this issue Mar 17, 2022 · 21 comments · Fixed by #1585 or #1699
Assignees
Labels
type: bug Something isn't working as expected

Comments

@u1735067
Copy link
Contributor

Environment

  • Python version: 3.10
  • Nautobot version: 1.2.8

Steps to Reproduce

  1. Configure Git backed Jobs
  2. Call them thousand times
  3. (probably not related, but to be complete on the history: disable the Jobs provider for that Git source, add again the same Git source with another slug and enable Jobs provider)
  4. Navigate to the Extra > Job results page

Expected Behavior

Job results are displayed in less than a few seconds max

Observed Behavior

Navigation fails, reverse proxy displays 502 Bad Gateway after 60s or more time, or if there's too much trafic, the container itself is considered unhealthy and a reverse proxy like Traefik can remove it as backend, resulting in 404.

Additional informations

(maybe this title could be added by default to the bug template ?)

In the container logs (of the nautobot container, not the celery_worker one), I've noticed thousands of thoses lines:

Attaching to nautobot_nautobot_1
nautobot_1       |   Repository successfully refreshed
nautobot_1       | 10:56:00.816 INFO    nautobot.jobs :
nautobot_1       |   Repository successfully refreshed
nautobot_1       | 10:56:00.889 INFO    nautobot.jobs :
nautobot_1       |   Repository successfully refreshed
[..]

I've noticed this in the past (#744) but it seemed harmless at the time; today, with way more JobResults, it's impossible for the page to display.

The ensure_git_repository call seems to be made for each JobResult related to a Git job, maybe limited to the MAX_PAGE_SIZE count or the user page count, but I'm not sure since there's really a lot of logs.

This can be critical, because when called multiple times, it can cause the healthcheck to fail / timeout, and it may be possible to DoS the instance this way.

In the Docker (itself) logs, it's also possible to notice:

# journalctl -fu docker
Mar 17 10:47:13 my_hostname dockerd[12283]: time="2022-03-17T10:47:13.700931230+01:00" level=warning msg="Health check for container <my_container_id> error: context deadline exceeded
[..]

I tried to find where this call originate from and noted:

ensure_git_repository
>pull_git_repository_and_refresh_data
>>enqueue_pull_git_repository_and_refresh_data
>Jobs._get_job_source_paths
>>Jobs.get_jobs
>>>get_job_classpaths
>>>get_job
>>>>JobResult.related_object()
>>>JobListView.get()
>>>JobView.get()

The path I think is problematic is JobResult.related_object() > Jobs.get_job > Jobs.get_jobs > Jobs._get_job_source_paths -> ensure_git_repository.

Maybe Jobs.get_jobs could be cached (per request?) (to deduplicate ensure_git_repository calls)? Or maybe ensure_git_repository should not be called at all (updating list on git creation/sync only? And a beat/watcher could monitor changes for local jobs). And shouldn't ensure_git_repository run only on the celery worker?

@u1735067
Copy link
Contributor Author

I just saw

#### Job Database Model ([#1001](https://github.com/nautobot/nautobot/issues/1001))
Installed Jobs are now represented by a data model in the Nautobot database. This allows for new functionality including:
- The Jobs listing UI view can now be filtered and searched like most other Nautobot table/list views.
- Job attributes (name, description, approval requirements, etc.) can now be managed via the Nautobot UI by an administrator or user with appropriate permissions to customize or override the attributes defined in the Job source code.
- Jobs can now be identified by a `slug` as well as by their `class_path`.
- A new set of REST API endpoints have been added at `api/extras/job-models`. The existing `api/extras/jobs` REST API continues to work but should be considered as deprecated.
!!! warning
The new Jobs REST API endpoint URL is likely to change before the final release of Nautobot 1.3.
- As a minor security measure, newly installed Jobs default to `enabled = False`, preventing them from being run until an administrator or user with appropriate permissions updates them to be enabled for running.
!!! note
As a convenience measure, when initially upgrading to Nautobot 1.3.x, any existing Jobs that have been run or scheduled previously (i.e., have at least one associated JobResult and/or ScheduledJob record) will instead default to `enabled = True` so that they may continue to be run without requiring changes.
For more details please refer to the [Jobs feature documentation](../additional-features/jobs.md) as well as the [Job data model documentation](../models/extras/job.md).
, maybe that's covering this?

@u1735067
Copy link
Contributor Author

I was able to access the page using this temporary workaround (caching for 5 secs the Jobs.get_jobs() call):

docker-compose exec --user root nautobot sed -ie '/@cached(timeout=5)  # TMP/d;s/def get_jobs/@cached(timeout=5)  # TMP\ndef get_jobs/' /usr/local/lib/python3.10/site-packages/nautobot/extras/jobs.py
docker-compose restart nautobot

(For some reason, my user page count was set to 1000, that's why there were so many things to refresh; still that shouldn't happen)

@glennmatthews
Copy link
Contributor

Adding caching here would definitely be an appropriate improvement to make. The introduction of the concrete Job model may help somewhat but I think this is likely to still be an issue in some views, which caching could improve immensely.

@glennmatthews glennmatthews added type: bug Something isn't working as expected status: accepted labels Mar 17, 2022
@u1735067
Copy link
Contributor Author

I was able to access the page using this temporary workaround (caching for 5 secs the Jobs.get_jobs() call): [..]

Note that this workaround seems to cause issues when a job form is refreshed rapidly (and maybe at other places), some custom job fields, like selection, can appear unpopulated, while the job code populate it correctly ..

@jathanism
Copy link
Contributor

@u1735067 If you wouldn't mind downloading and trying the fix in #1585 that would be super!

@u1735067
Copy link
Contributor Author

@jathanism thank you, I was able to test but result was the same as in #1515 (comment), still some errors.

First I though it was a concurrency issue with cacheops, but by digging I found it was because threads don't share loaded modules, so the thread running get_jobs (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/jobs.py#L826) is fine, but the others, reading cache, are unable to access the referenced classes.

To workaround this, I tried to use a custom per-thread caching key:

# https://github.com/Suor/django-cacheops/blob/5.1/cacheops/simple.py#L53
# https://github.com/Suor/django-cacheops/blob/5.1/cacheops/utils.py#L89
# https://docs.python.org/3/library/threading.html
# https://github.com/python/cpython/blob/3.10/Lib/threading.py
def _cacheops_func_cache_key_per_thread(func, args, kwargs, extra=None):
    """
    Calculate cache key based on func, arguments and current thread
    """
    import threading
    from cacheops.utils import json, md5hex, obj_key

    current_thread = threading.current_thread()
    thread_factors = [current_thread.name, current_thread.ident]
    if hasattr(current_thread, 'native_id'):
        thread_factors.append(current_thread.native_id)
    
    factors = [func, args, kwargs, extra, thread_factors]
    key = md5hex(json.dumps(factors, sort_keys=True, default=obj_key))

    # Debug
    #import logging
    #logging.getLogger("nautobot.jobs").warning('>>>>>>>>>>>>>>>>>>>>>>>>> {} {} {}'.format(threading.current_thread(), thread_factors, key))
    
    return key


@cached(timeout=60, key_func=_cacheops_func_cache_key_per_thread)
def get_jobs():
    [...]

This forces each thread to run get_jobs at least once. Also, with get_jobs in cache, there's no need to cache ensure_git_repository nor get_job_classpaths. This seems to work correctly for now.

Note that with uWSGI, Thread.ident and Thread.native_id values seems to be the same across threads, so to distinguish threads the name have to be used (https://github.com/unbit/uwsgi/blob/2.0.20/plugins/python/python_plugin.c#L1323):

16:51:55.781 WARNING nautobot.jobs :
  >>>>>>>>>>>>>>>>>>>>>>>>> <_MainThread(uWSGIWorker1Core0, started 140496645598024)> ['uWSGIWorker1Core0', 140496645598024, 1] db4624064d96aa320366075470e4e29f
16:51:56.621 WARNING nautobot.jobs :
  >>>>>>>>>>>>>>>>>>>>>>>>> <_MainThread(uWSGIWorker3Core0, started 140496645598024)> ['uWSGIWorker3Core0', 140496645598024, 1] e252e4d784901cdd1a90584dba336abe
16:51:57.225 WARNING nautobot.jobs :
  >>>>>>>>>>>>>>>>>>>>>>>>> <_MainThread(uWSGIWorker3Core0, started 140496645598024)> ['uWSGIWorker3Core0', 140496645598024, 1] e252e4d784901cdd1a90584dba336abe
16:51:57.799 WARNING nautobot.jobs :
  >>>>>>>>>>>>>>>>>>>>>>>>> <_MainThread(uWSGIWorker2Core0, started 140496645598024)> ['uWSGIWorker2Core0', 140496645598024, 1] 963f6d84537803f7b1b7ac6d318eb132
16:51:58.278 WARNING nautobot.jobs :
  >>>>>>>>>>>>>>>>>>>>>>>>> <_MainThread(uWSGIWorker2Core0, started 140496645598024)> ['uWSGIWorker2Core0', 140496645598024, 1] 963f6d84537803f7b1b7ac6d318eb132

I'm not sure this is enough to cover all uWSGI configurations however.

And about

Note that this workaround seems to cause issues when a job form is refreshed rapidly (and maybe at other places), some custom job fields, like selection, can appear unpopulated, while the job code populate it correctly ..

The empty selection error was because I used a generator to populate a ChoiceVar field, this wasn't correct as only the first read would return choices, adding caching made this appear.

Also, Nautobot seems to import jobs as top-level modules (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/jobs.py#L826), couldn't this be an issue with a job file named sys.py for example? Maybe the jobs could be imported as package named nautobot.jobs.{slug.replace('-', '_')} (automatically generating the __init__.py if not provided)?


Some notes to play with pickle (https://docs.python.org/3/library/pickle.html, https://docs.python.org/3/library/pickletools.html):

import cacheops, pickle, pickletools
pickle.loads(cacheops.redis.redis_client.get('c:963f6d84537803f7b1b7ac6d318eb132'))
pickletools.dis(cacheops.redis.redis_client.get('c:963f6d84537803f7b1b7ac6d318eb132'))

@jathanism
Copy link
Contributor

@u1735067 Great feedback, thanks! I very much appreciate your in-depth analysis here.

@jathanism
Copy link
Contributor

lso, Nautobot seems to import jobs as top-level modules (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/jobs.py#L826), couldn't this be an issue with a job file named sys.py for example? Maybe the jobs could be imported as package named nautobot.jobs.{slug.replace('-', '_')} (automatically generating the __init__.py if not provided)?

Would you mind filing this as a new issue? You're spot on here and it would be good to get this addressed by changing the way job modules are imported.

@u1735067
Copy link
Contributor Author

u1735067 commented Apr 1, 2022

@u1735067 Great feedback, thanks! I very much appreciate your in-depth analysis here.

Glad to help :)

lso, Nautobot seems to import jobs as top-level modules (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/jobs.py#L826), couldn't this be an issue with a job file named sys.py for example? Maybe the jobs could be imported as package named nautobot.jobs.{slug.replace('-', '_')} (automatically generating the __init__.py if not provided)?

Would you mind filing this as a new issue? You're spot on here and it would be good to get this addressed by changing the way job modules are imported.

Sure, done in #1598 (FE)

To workaround this, I tried to use a custom per-thread caching key:

This may be better written like this, because of uwsgi specificities:

def _cacheops_func_cache_key_per_thread(func, args, kwargs, extra=None):
    """
    Calculate cache key based on func, arguments and current thread
    """
    import threading
    from cacheops.utils import json, md5hex, obj_key

    current_thread = threading.current_thread()
    thread_factors = [
        current_thread.name,
        current_thread.ident,
        current_thread.native_id if hasattr(current_thread, 'native_id') else 0
    ]
    # https://uwsgi-docs.readthedocs.io/en/latest/PythonModule.html
    # https://github.com/unbit/uwsgi/blob/2.0.20/plugins/python/uwsgi_pymodule.c#L2157
    # https://github.com/unbit/uwsgi/blob/2.0.20/core/master_utils.c#L702
    try:
        import uwsgi
        uwsgi_workers = uwsgi.workers()
        uwsgi_worker_id = uwsgi.worker_id()
        uwsgi_worker_index = uwsgi_worker_id - 1
        thread_factors += (
            uwsgi_worker_id,
            # Because workers might respawn, worker_id is not enough
            uwsgi_workers[uwsgi_worker_index]['pid'],
            uwsgi_workers[uwsgi_worker_index]['last_spawn']
        )
    except ImportError:
        pass
    
    factors = [func, args, kwargs, extra, thread_factors]
    key = md5hex(json.dumps(factors, sort_keys=True, default=obj_key))

    # Debug
    #import logging
    #logging.getLogger("nautobot.jobs").warning('>>>>>>>>>>>>>>>>>>>>>>>>> {} {} {}'.format(threading.current_thread(), thread_factors, key))
    
    return key

@jathanism
Copy link
Contributor

@u1735067 Great feedback, thanks! I very much appreciate your in-depth analysis here.

Glad to help :)

lso, Nautobot seems to import jobs as top-level modules (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/jobs.py#L826), couldn't this be an issue with a job file named sys.py for example? Maybe the jobs could be imported as package named nautobot.jobs.{slug.replace('-', '_')} (automatically generating the __init__.py if not provided)?

Would you mind filing this as a new issue? You're spot on here and it would be good to get this addressed by changing the way job modules are imported.

Sure, done in #1598 (FE)

To workaround this, I tried to use a custom per-thread caching key:

This may be better written like this, because of uwsgi specificities:

def _cacheops_func_cache_key_per_thread(func, args, kwargs, extra=None):
    """
    Calculate cache key based on func, arguments and current thread
    """
    import threading
    from cacheops.utils import json, md5hex, obj_key

    current_thread = threading.current_thread()
    thread_factors = [
        current_thread.name,
        current_thread.ident,
        current_thread.native_id if hasattr(current_thread, 'native_id') else 0
    ]
    # https://uwsgi-docs.readthedocs.io/en/latest/PythonModule.html
    # https://github.com/unbit/uwsgi/blob/2.0.20/plugins/python/uwsgi_pymodule.c#L2157
    # https://github.com/unbit/uwsgi/blob/2.0.20/core/master_utils.c#L702
    try:
        import uwsgi
        uwsgi_workers = uwsgi.workers()
        uwsgi_worker_id = uwsgi.worker_id()
        uwsgi_worker_index = uwsgi_worker_id - 1
        thread_factors += (
            uwsgi_worker_id,
            # Because workers might respawn, worker_id is not enough
            uwsgi_workers[uwsgi_worker_index]['pid'],
            uwsgi_workers[uwsgi_worker_index]['last_spawn']
        )
    except ImportError:
        pass
    
    factors = [func, args, kwargs, extra, thread_factors]
    key = md5hex(json.dumps(factors, sort_keys=True, default=obj_key))

    # Debug
    #import logging
    #logging.getLogger("nautobot.jobs").warning('>>>>>>>>>>>>>>>>>>>>>>>>> {} {} {}'.format(threading.current_thread(), thread_factors, key))
    
    return key

Thanks for this deep analysis. I for one very much appreciate you taking the time and effort to help us arrive at a solution.

After reviewing it, your solution is specific to uWSGI workers and so may be incomplete for installations not using uWSGI. So after doing some debugging we tried to arrive at an alternate solution that uses file-based caching, so that thread safety is obviated and all processes on the same system can access the cache directly from the file system.

If you wouldn't mind trying the latest HEAD from the PR, that would be fabbbuulouuuuus. Thanks!

@u1735067
Copy link
Contributor Author

u1735067 commented Apr 2, 2022

After reviewing it, your solution is specific to uWSGI workers and so may be incomplete for installations not using uWSGI. So after doing some debugging we tried to arrive at an alternate solution that uses file-based caching, so that thread safety is obviated and all processes on the same system can access the cache directly from the file system.

If you wouldn't mind trying the latest HEAD from the PR, that would be fabbbuulouuuuus. Thanks!

Yes thank you, I'll test in the next days, I'm not sure it'll work however, as the issue when using cache (redis or file backed) is that the get_jobs is doing more than returning a dict with the classes, it is also responsible of loading the jobs modules in the application (and when used with threaded uwsgi, this applies to each thread), and that isn't part of the cache : as far as I could see, pickle (https://github.com/Suor/django-cacheops/blob/5.1/cacheops/simple.py#L152, pickletools) is only saving references to the classes, so when another thread than the one which generated the cache entry tries to load the content, the references that the cache mentions are unknown to pickle and it fails. That's why I went with the per-thread caching (trying to handle both general and uwsgi specific case), to ensure that, at least once, each thread actually load the job modules that can later be reused from the cache.

@bryanculver
Copy link
Member

bryanculver commented Apr 4, 2022

Yes thank you, I'll test in the next days, I'm not sure it'll work however, as the issue when using cache (redis or file backed) is that the get_jobs is doing more than returning a dict with the classes, it is also responsible of loading the jobs modules in the application (and when used with threaded uwsgi, this applies to each thread), and that isn't part of the cache : as far as I could see, pickle (https://github.com/Suor/django-cacheops/blob/5.1/cacheops/simple.py#L152, pickletools) is only saving references to the classes, so when another thread than the one which generated the cache entry tries to load the content, the references that the cache mentions are unknown to pickle and it fails. That's why I went with the per-thread caching (trying to handle both general and uwsgi specific case), to ensure that, at least once, each thread actually load the job modules that can later be reused from the cache.

We moved the cached call down to _get_job_source_paths instead of get_jobs. Your point is correct that the modules are being found and loaded in get_jobs so at minimum that needs to be ran once per thread.

_get_job_source_paths is what was being called in get_jobs which ultimately calls ensure_git_repository and several other things which should be safe per-worker to cache. Modules are still loaded every time get_jobs is called but this should be a fraction of the call time in comparison to the git repository validation.

@u1735067
Copy link
Contributor Author

u1735067 commented Apr 4, 2022

We moved the cached call down to _get_job_source_paths instead of get_jobs.

Oh right, I looked quickly and missed that, sorry! I just tested and there's no errors yes 🙂

_get_job_source_paths is what was being called in get_jobs which ultimately calls ensure_git_repository and several other things which should be safe per-worker to cache.

And the case when repo is removed but _get_job_source_paths still returns it's path (for max 60s) seems to be handled silently by pkgutil.iter_modules, ignoring non-existent paths.

Modules are still loaded every time get_jobs is called but this should be a fraction of the call time in comparison to the git repository validation.

This is still a large part of the time sadly, I tested the TTBF (time to first byte) of 1063 git jobresults

  • 50/page
    • file_cache on _get_job_source_paths & get_job_classpaths
      • 2.42s / 2.30s / 2.56s / 2.46s
      • -> On a common paging size, the UI still feels slow ☹
    • file_cache.cached (thread key) on get_jobs
      • 0.509s / 0.501s / 0.475s / 0.477s
  • 500/page
    • file_cache on _get_job_source_paths & get_job_classpaths
      • 25.79s / 23.88s / 23.7s / 23.41s
    • file_cache.cached (thread key) on get_jobs
      • 4.91s / 4.08s / 4.07s / 4.05s
  • 1000/page
    • file_cache on _get_job_source_paths & get_job_classpaths
      • 46.63s / 46.11s / 46.82s / 46.52s
      • -> Launch multiple refresh of the job results pages and the instance can become unavailable to the healthcheck
    • file_cache.cached (thread key) on get_jobs
      • 10.05s / 8.20s / 8.14s / 9.11s / 8.79s

@glennmatthews
Copy link
Contributor

Thinking about this from a different angle - as you pointed out, the main critical path here is JobResultTable > JobResult.related_object() > Jobs.get_job > Jobs.get_jobs > Jobs._get_job_source_paths -> ensure_git_repository, which is a lot of unnecessary work when all we're using to actually render the table column, in the case where related_object is a Job, is its class_path (actually loading the Jobs into memory, etc is a completely unnecessary bit of processing).

I wonder therefore if we should revisit how this table column is calculated/rendered so that it only calls record.related_object in the case where we can determine in advance that related_object is not a Job, something like:

def job_creator_link(value, record):
    """
    Get a link to the related object, if any, associated with the given JobResult record.
    """
    # record.related_object is potentially slow if the related object is a Job class,
    # as it needs to actually (re)load the Job class into memory. That's unnecessary
    # computation as we don't actually need the class itself, just its class_path which is already
    # available as record.name on the JobResult itself. So save some trouble:
    if record.obj_type == ContentType.objects.get(app_label="extras", model="job"):
        return reverse("extras:job", kwargs={"class_path": record.name})

    # If it's not a Job class, maybe it's something like a GitRepository, which we can look up cheaply:
    related_object = record.related_object
    if related_object:
        return related_object.get_absolute_url()
    return None

@jathanism
Copy link
Contributor

def job_creator_link(value, record):
    """
    Get a link to the related object, if any, associated with the given JobResult record.
    """
    # record.related_object is potentially slow if the related object is a Job class,
    # as it needs to actually (re)load the Job class into memory. That's unnecessary
    # computation as we don't actually need the class itself, just its class_path which is already
    # available as record.name on the JobResult itself. So save some trouble:
    if record.obj_type == ContentType.objects.get(app_label="extras", model="job"):
        return reverse("extras:job", kwargs={"class_path": record.name})

    # If it's not a Job class, maybe it's something like a GitRepository, which we can look up cheaply:
    related_object = record.related_object
    if related_object:
        return related_object.get_absolute_url()
    return None

Giving this a try and will report back.

@glennmatthews
Copy link
Contributor

That won't be exactly right, on reflection, as you will want to change the Column definition so that it doesn't automatically try to load record.related_object before even calling job_creator_link.

@jathanism
Copy link
Contributor

jathanism commented Apr 8, 2022

@u1735067 We revised this again to hybridize the approach here to assert that the caching is in place for Git-based jobs, and that the related_object is only retrieved once per row if it's a not a Job class. If you could please give the latest for this a chance?

One thing however, is that we updated the base for this fix from develop (v1.2.x) to next (v1.3.0-beta.x) as we are about to release v1.3.0. So you will need to run database migrations as well, OR just hot fix your v1.2.x install w/ the updated code. Thanks in advance!

bryanculver added a commit that referenced this issue Apr 14, 2022
@bryanculver
Copy link
Member

While we believe this issues will be closed with #1585, we will leave this open for a bit of time to confirm resolution with reporter.

@u1735067
Copy link
Contributor Author

On 1.2.10 it's not enough as accessing the name (by django_tables2) also result in get_job(s)() being called, see stack:

nautobot_1       | 15:28:49.286 WARNING nautobot.jobs :
nautobot_1       |   >>>>>>>>>>>>>> get_jobs()
nautobot_1       |   File "/usr/local/bin/nautobot-server", line 8, in <module>
nautobot_1       |     sys.exit(main())
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/cli.py", line 54, in main
nautobot_1       |     run_app(
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/runner/runner.py", line 266, in run_app
nautobot_1       |     management.execute_from_command_line([runner_name, command] + command_args)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
nautobot_1       |     utility.execute()
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 395, in execute
nautobot_1       |     self.fetch_command(subcommand).run_from_argv(self.argv)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_webserver/base_command.py", line 25, in run_from_argv
nautobot_1       |     self.start_server(*self.prep_server_args(argv))
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_webserver/management/commands/pyuwsgi.py", line 41, in start_server
nautobot_1       |     pyuwsgi.run(args)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/wsgi.py", line 133, in __call__
nautobot_1       |     response = self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 130, in get_response
nautobot_1       |     response = self._middleware_chain(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/middleware.py", line 125, in __call__
nautobot_1       |     return self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/middleware.py", line 109, in __call__
nautobot_1       |     response = self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/middleware.py", line 95, in __call__
nautobot_1       |     response = self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 114, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 181, in _get_response
nautobot_1       |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 70, in view
nautobot_1       |     return self.dispatch(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/utilities/views.py", line 94, in dispatch
nautobot_1       |     return super().dispatch(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 98, in dispatch
nautobot_1       |     return handler(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/views/generic.py", line 256, in get
nautobot_1       |     return render(request, self.template_name, context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/shortcuts.py", line 19, in render
nautobot_1       |     content = loader.render_to_string(template_name, context, request, using=using)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader.py", line 62, in render_to_string
nautobot_1       |     return template.render(context, request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 61, in render
nautobot_1       |     return self.template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 170, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 150, in render
nautobot_1       |     return compiled_parent._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 62, in render
nautobot_1       |     result = block.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 516, in render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 312, in render
nautobot_1       |     return nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 192, in render
nautobot_1       |     return template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 172, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/templatetags/django_tables2.py", line 167, in render
nautobot_1       |     return template.render(context={"table": table}, request=request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 61, in render
nautobot_1       |     return self.template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 170, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 211, in render
nautobot_1       |     nodelist.append(node.render_annotated(context))
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 167, in render
nautobot_1       |     values = list(values)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 244, in items
nautobot_1       |     column.current_value = self.get_cell(column.name)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 197, in get_cell
nautobot_1       |     return self._get_and_render_with(
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 163, in _get_and_render_with
nautobot_1       |     value = accessor.resolve(self.record)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/utils.py", line 374, in resolve
nautobot_1       |     current = getattr(current, bit)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/models/models.py", line 809, in related_name
nautobot_1       |     related_object = self.related_object
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/models/models.py", line 785, in related_object
nautobot_1       |     return get_job(self.name)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/jobs.py", line 953, in get_job
nautobot_1       |     jobs = get_jobs()
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/jobs.py", line 819, in get_jobs
nautobot_1       |     logging.getLogger("nautobot.jobs").warning('>>>>>>>>>>>>>> get_jobs()\n{}'.format('\n'.join(traceback.format_stack())))

The same happens on 1.3.0:

nautobot_1       | 17:03:03.031 WARNING nautobot.jobs :
nautobot_1       |   >>>>>>>>>>>>>> get_jobs()
nautobot_1       |   File "/usr/local/bin/nautobot-server", line 8, in <module>
nautobot_1       |     sys.exit(main())
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/cli.py", line 54, in main
nautobot_1       |     run_app(
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/runner/runner.py", line 266, in run_app
nautobot_1       |     management.execute_from_command_line([runner_name, command] + command_args)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
nautobot_1       |     utility.execute()
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 413, in execute
nautobot_1       |     self.fetch_command(subcommand).run_from_argv(self.argv)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_webserver/base_command.py", line 25, in run_from_argv
nautobot_1       |     self.start_server(*self.prep_server_args(argv))
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_webserver/management/commands/pyuwsgi.py", line 41, in start_server
nautobot_1       |     pyuwsgi.run(args)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/wsgi.py", line 133, in __call__
nautobot_1       |     response = self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 130, in get_response
nautobot_1       |     response = self._middleware_chain(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/middleware.py", line 110, in __call__
nautobot_1       |     return self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/middleware.py", line 95, in __call__
nautobot_1       |     response = self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/utils/deprecation.py", line 117, in __call__
nautobot_1       |     response = response or self.get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 47, in inner
nautobot_1       |     response = get_response(request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 181, in _get_response
nautobot_1       |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 70, in view
nautobot_1       |     return self.dispatch(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/utilities/views.py", line 94, in dispatch
nautobot_1       |     return super().dispatch(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/views/generic/base.py", line 98, in dispatch
nautobot_1       |     return handler(request, *args, **kwargs)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/core/views/generic.py", line 265, in get
nautobot_1       |     return render(request, self.template_name, context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/shortcuts.py", line 19, in render
nautobot_1       |     content = loader.render_to_string(template_name, context, request, using=using)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader.py", line 62, in render_to_string
nautobot_1       |     return template.render(context, request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 61, in render
nautobot_1       |     return self.template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 170, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 150, in render
nautobot_1       |     return compiled_parent._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 62, in render
nautobot_1       |     result = block.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 519, in render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 315, in render
nautobot_1       |     return nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/loader_tags.py", line 195, in render
nautobot_1       |     return template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 172, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/templatetags/django_tables2.py", line 167, in render
nautobot_1       |     return template.render(context={"table": table}, request=request)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 61, in render
nautobot_1       |     return self.template.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 170, in render
nautobot_1       |     return self._render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 162, in _render
nautobot_1       |     return self.nodelist.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 938, in render
nautobot_1       |     bit = node.render_annotated(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 214, in render
nautobot_1       |     nodelist.append(node.render_annotated(context))
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 905, in render_annotated
nautobot_1       |     return self.render(context)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django/template/defaulttags.py", line 170, in render
nautobot_1       |     values = list(values)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 244, in items
nautobot_1       |     column.current_value = self.get_cell(column.name)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 197, in get_cell
nautobot_1       |     return self._get_and_render_with(
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/rows.py", line 163, in _get_and_render_with
nautobot_1       |     value = accessor.resolve(self.record)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/django_tables2/utils.py", line 374, in resolve
nautobot_1       |     current = getattr(current, bit)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/models/jobs.py", line 471, in related_name
nautobot_1       |     related_object = self.related_object
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/models/jobs.py", line 446, in related_object
nautobot_1       |     return get_job(self.name)
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/jobs.py", line 993, in get_job
nautobot_1       |     jobs = get_jobs()
nautobot_1       | 
nautobot_1       |   File "/usr/local/lib/python3.10/site-packages/nautobot/extras/jobs.py", line 880, in get_jobs
nautobot_1       |     logging.getLogger("nautobot.jobs").warning('>>>>>>>>>>>>>> get_jobs()\n{}'.format('\n'.join(traceback.format_stack())))

In 1.2.10, patching JobResult.related_name (https://github.com/nautobot/nautobot/blob/v1.2.10/nautobot/extras/models/models.py#L805) fixes this (from ~30s to ~8s for 1000 records), but the classpath is displayed instead of the name:

    @property
    def related_name(self):
        if self.obj_type == ContentType.objects.get(app_label="extras", model="job"):
            return self.name

        related_object = self.related_object
        if not related_object:
            return self.name
        if hasattr(related_object, "name"):
            return related_object.name
        return str(related_object)

Not caching get_jobs, it's the only solution I can see in 1.2.x, but in 1.3.x, why display both Job and Related object in Job results?

@glennmatthews
Copy link
Contributor

Not caching get_jobs, it's the only solution I can see in 1.2.x, but in 1.3.x, why display both Job and Related object in Job results?

It's messy - Git repository syncs are not presently considered to be Jobs, but they do create a JobResult where the GitRepository is the Related object. We want to clean this up soon (see discussion on #275) but for the time being both columns are relevant in different circumstances.

Thanks for the update!

@glennmatthews
Copy link
Contributor

In the short term I think we could collapse the Job and Related Object columns into a single consolidated column that displays either the directly linked JobModel or else the indirectly related GitRepository.

bryanculver added a commit that referenced this issue Apr 28, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: bug Something isn't working as expected
Projects
No open projects
Archived in project
4 participants