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

Cw 440 dict job utilisateur #183

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7d9a380
Add labels to fake data.
notoraptor Oct 6, 2023
50d8f45
(WIP)(not working) try to join job and label collections to get label…
notoraptor Oct 6, 2023
c93cd52
Finally match jobs to labels with two separate mongodb calls.
notoraptor Oct 6, 2023
fcc3f6b
Fix unit tests.
notoraptor Oct 6, 2023
0221910
Make sure to set job label to None when deselected
notoraptor Nov 12, 2023
391f004
Allow to display job-user dicts
notoraptor Feb 16, 2024
eb751d5
Format code.
notoraptor Feb 16, 2024
c4d94ea
Improve get_jobs(): filter labels using current user in get_filtered_…
notoraptor Feb 20, 2024
105ecbd
Add a specific improved function _jobs_are_old(cluster_name) to tell …
notoraptor Feb 20, 2024
d3b574e
Add a specific script to populate database with huge fake data, addin…
notoraptor Feb 20, 2024
4f6459d
Make job-user dicts more specific to each job.
notoraptor Feb 20, 2024
ebd4ddf
Script store_huge_fake_data_in_db: add command-line arguments --nb-jo…
notoraptor Feb 26, 2024
b90da41
Allow to pass raw number of jobs or dicts to benchmark scripts.
notoraptor Feb 27, 2024
bb0e6c1
Update
notoraptor Feb 29, 2024
679d1e5
Rename collection and related texts from "labels" to "job user props".
notoraptor Feb 29, 2024
85c6017
Update.
notoraptor Mar 4, 2024
bad73f5
Make sure to clean collections and indexes before inserting new fake …
notoraptor Mar 4, 2024
6bad887
Check if current_user is available before getting job user props.
notoraptor Mar 5, 2024
a7b2f4f
server_benchmark_locust: add a commented code. Code allows to use EMA…
notoraptor Mar 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clockwork_web/browser_routes/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ def route_search():
- "sort_asc" is an optional integer and used to specify if sorting is
ascending (1) or descending (-1). Default is 1.
- "job_array" is optional and used to specify the job array in which we are looking for jobs
- "user_prop_name" is optional and used to specify the user prop name associated to jobs we are looking for
- "user_prop_content" is optional and used to specify the user prop value associated to jobs we are looking for

.. :quickref: list all Slurm job as formatted html
"""
Expand Down Expand Up @@ -164,6 +166,8 @@ def route_search():
"sort_by": query.sort_by,
"sort_asc": query.sort_asc,
"job_array": query.job_array,
"user_prop_name": query.user_prop_name,
"user_prop_content": query.user_prop_content,
},
)

Expand Down
63 changes: 63 additions & 0 deletions clockwork_web/core/jobs_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time

from flask.globals import current_app
from flask_login import current_user
from ..db import get_db


Expand Down Expand Up @@ -157,6 +158,45 @@ def get_filtered_and_paginated_jobs(
# on the server because not enough memory was allocated to perform the sorting.
LD_jobs = list(mc["jobs"].find(mongodb_filter))

# Get job user props
if LD_jobs and current_user:
user_props_map = {}
# Collect all job user props related to found jobs,
# and store them in a dict with keys (mila email username, job ID, cluster_name)
for user_props in list(
mc["job_user_props"].find(
combine_all_mongodb_filters(
{
"job_id": {
"$in": [int(job["slurm"]["job_id"]) for job in LD_jobs]
},
"mila_email_username": current_user.mila_email_username,
}
)
)
):
key = (
user_props["mila_email_username"],
user_props["job_id"],
user_props["cluster_name"],
)
assert key not in user_props_map
user_props_map[key] = user_props["props"]

if user_props_map:
# Populate jobs with user props using
# current user email, job ID and job cluster name
# to find related user props in props map.
for job in LD_jobs:
key = (
# job["cw"]["mila_email_username"],
current_user.mila_email_username,
int(job["slurm"]["job_id"]),
job["slurm"]["cluster_name"],
)
if key in user_props_map:
job["job_user_props"] = user_props_map[key]

# Set nbr_total_jobs
if want_count:
# Get the number of filtered jobs (not paginated)
Expand Down Expand Up @@ -235,6 +275,8 @@ def get_jobs(
sort_by="submit_time",
sort_asc=-1,
job_array=None,
user_prop_name=None,
user_prop_content=None,
):
"""
Set up the filters according to the parameters and retrieve the requested jobs from the database.
Expand All @@ -252,13 +294,33 @@ def get_jobs(
sort_asc Whether or not to sort in ascending order (1)
or descending order (-1).
job_array ID of job array in which we look for jobs.
user_prop_name name of user prop (string) we must find in jobs to look for.
user_prop_content content of user prop (string) we must find in jobs to look for.

Returns:
A tuple containing:
- the list of jobs as first entity
- the total number of jobs corresponding of the filters in the databse, if want_count has been set to
True, None otherwise, as second element
"""
# If job user prop is specified,
# get job indices from jobs associated to this prop.
if user_prop_name is not None and user_prop_content is not None:
mc = get_db()
props_job_ids = [
str(user_props["job_id"])
for user_props in mc["job_user_props"].find(
combine_all_mongodb_filters(
{f"props.{user_prop_name}": user_prop_content}
)
)
]
if job_ids:
# If job ids where provided, make intersection between given job ids and props job ids.
job_ids = list(set(props_job_ids) & set(job_ids))
else:
# Otherwise, just use props job ids.
job_ids = props_job_ids

# Set up and combine filters
filter = get_global_filter(
Expand Down Expand Up @@ -405,6 +467,7 @@ def get_jobs_properties_list_per_page():
"user",
"job_id",
"job_array",
"job_user_props",
"job_name",
"job_state",
"start_time",
Expand Down
6 changes: 6 additions & 0 deletions clockwork_web/core/search_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def parse_search_request(user, args, force_pagination=True):
want_count = to_boolean(want_count)

job_array = args.get("job_array", type=int, default=None)
user_prop_name = args.get("user_prop_name", type=str, default=None) or None
user_prop_content = args.get("user_prop_content", type=str, default=None) or None

default_page_number = "1" if force_pagination else None

Expand Down Expand Up @@ -71,6 +73,8 @@ def parse_search_request(user, args, force_pagination=True):
sort_asc=sort_asc,
want_count=want_count,
job_array=job_array,
user_prop_name=user_prop_name,
user_prop_content=user_prop_content,
)

#########################
Expand Down Expand Up @@ -115,5 +119,7 @@ def search_request(user, args, force_pagination=True):
sort_by=query.sort_by,
sort_asc=query.sort_asc,
job_array=query.job_array,
user_prop_name=query.user_prop_name,
user_prop_content=query.user_prop_content,
)
return (query, jobs, nbr_total_jobs)
43 changes: 23 additions & 20 deletions clockwork_web/core/users_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,19 +592,30 @@ def render_template_with_user_settings(template_name_or_list, **context):

# Get cluster status (if jobs are old and cluster has error).
for cluster_name in context["clusters"]:
# Default status values.
jobs_are_old = False
# Cluster error cannot yet be checked, so
# cluster_has_error is always False for now.
cluster_has_error = False
context["clusters"][cluster_name]["status"] = {
"jobs_are_old": _jobs_are_old(cluster_name),
"cluster_has_error": cluster_has_error,
}

# Check if jobs are old.
jobs, _ = get_jobs(cluster_names=[cluster_name])
job_dates = [
job["cw"]["last_slurm_update"]
for job in jobs
if "last_slurm_update" in job["cw"]
]
if job_dates:
most_recent_job_edition = max(job_dates)
return render_template(template_name_or_list, **context)


def _jobs_are_old(cluster_name):
jobs_are_old = False

mongodb_filter = {"slurm.cluster_name": cluster_name}
mc = get_db()
job_with_max_cw_last_slurm_update = list(
mc["jobs"].find(mongodb_filter).sort([("cw.last_slurm_update", -1)]).limit(1)
)

if job_with_max_cw_last_slurm_update:
(job,) = job_with_max_cw_last_slurm_update
if "last_slurm_update" in job["cw"]:
most_recent_job_edition = job["cw"]["last_slurm_update"]
current_timestamp = datetime.now().timestamp()
elapsed_time = timedelta(
seconds=current_timestamp - most_recent_job_edition
Expand All @@ -613,12 +624,4 @@ def render_template_with_user_settings(template_name_or_list, **context):
max_delay = timedelta(days=30)
jobs_are_old = elapsed_time > max_delay

# Cluster error cannot yet be checked, so
# cluster_has_error is always False for now.

context["clusters"][cluster_name]["status"] = {
"jobs_are_old": jobs_are_old,
"cluster_has_error": cluster_has_error,
}

return render_template(template_name_or_list, **context)
return jobs_are_old
15 changes: 14 additions & 1 deletion clockwork_web/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js" integrity="sha512-57oZ/vW8ANMjR/KQ6Be9v/+/h6bq9/l3f0Oc7vn6qMqyhvPd1cvKBRWWpzu0QoneImqr2SkmO4MSqU+RpHom3Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script type="text/javascript" src="{{ url_for('static', filename='js/sortable.min.js') }}"></script>

<script type="text/javascript" src="{{ url_for('static', filename='js/moment-with-locales.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/livestamp.min.js') }}"></script>

Expand Down Expand Up @@ -323,6 +323,12 @@ <h1><a data-bs-toggle="collapse" data-bs-target=".formCollapse" aria-expanded="f
<input type="hidden" name="sort_asc" value="{{ previous_request_args['sort_asc'] }}"/>
{% if previous_request_args['job_array'] is not none %}
<input type="hidden" name="job_array" value="{{ previous_request_args['job_array'] }}"/>
{% endif %}
{% if previous_request_args['user_prop_name'] is not none %}
<input type="hidden" name="user_prop_name" value="{{ previous_request_args['user_prop_name'] }}"/>
{% endif %}
{% if previous_request_args['user_prop_content'] is not none %}
<input type="hidden" name="user_prop_content" value="{{ previous_request_args['user_prop_content'] }}"/>
{% endif %}

<div class="row align-items-center">
Expand All @@ -334,6 +340,13 @@ <h1><a data-bs-toggle="collapse" data-bs-target=".formCollapse" aria-expanded="f
<i class="fa-solid fa-circle-xmark" style="color: #888a85;"></i>
</a>
{% endif %}

{% if previous_request_args['user_prop_name'] is not none and previous_request_args['user_prop_content'] is not none %}
<a href="{{ modify_query(user_prop_name='', user_prop_content='') }}" title="Reset filter by job user prop" class="px-3 py-2">
User prop <strong>{{ previous_request_args['user_prop_name'] }}</strong>: "{{ previous_request_args['user_prop_content'] }}"&nbsp;&nbsp;&nbsp;&nbsp;
<i class="fa-solid fa-circle-xmark" style="color: #888a85;"></i>
</a>
{% endif %}
</div>
<!-- button -->
<div class="col-sm-12 col-md-4 offset-md-8">
Expand Down
18 changes: 18 additions & 0 deletions clockwork_web/templates/jobs_search.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ <h1>JOBS</h1>
{% if (web_settings | check_web_settings_column_display(page_name, "job_array")) %}
<th>Job array</th>
{% endif %}
<!-- Job user props header -->
{% if (web_settings | check_web_settings_column_display(page_name, "job_user_props")) %}
<th>Job-user props</th>
{% endif %}
<!-- Job name header -->
{% if (web_settings | check_web_settings_column_display(page_name, "job_name")) %}
{% set sort_by = "name" %}
Expand Down Expand Up @@ -193,6 +197,20 @@ <h1>JOBS</h1>
</td>
{% endif %}

<!-- Job user props -->
{% if (web_settings | check_web_settings_column_display(page_name, "job_user_props")) %}
<td>
{% for D_user_prop_name, D_user_prop_content in D_job.get('job_user_props', {}).items() %}
<p>
<a href="{{ modify_query(user_prop_name=D_user_prop_name, user_prop_content=D_user_prop_content, page_num=1) }}" title="Filter by job-user prop &quot;{{D_user_prop_name}}&quot;: &quot;{{D_user_prop_content}}&quot;">
<strong>{{ D_user_prop_name }}</strong><br/>
{{ D_user_prop_content }}
</a>
</p>
{% endfor %}
</td>
{% endif %}

<!-- Job name -->
{% if (web_settings | check_web_settings_column_display(page_name, "job_name")) %}
<td>{{D_job['slurm'].get("name", "")[0:20]}}</td>
Expand Down
3 changes: 2 additions & 1 deletion clockwork_web/templates/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ <h1>{{ gettext("User settings %(mila_email_username)s", mila_email_username=curr
<th>{{ gettext("User (@mila.quebec)") }}</th>
<th>{{ gettext("Job ID") }}</th>
<th>{{ gettext("Job array") }}</th>
<th>{{ gettext("Job-user props") }}</th>
<th>{{ gettext("Job name [:20]") }}</th>
<th>{{ gettext("Job state") }}</th>
<th>{{ gettext("Submit time") }}</th>
Expand All @@ -291,7 +292,7 @@ <h1>{{ gettext("User settings %(mila_email_username)s", mila_email_username=curr
<tbody>
<tr>
{% set page_name = "jobs_list" %}
{% for column_name in ["clusters", "user","job_id", "job_array", "job_name", "job_state", "submit_time", "start_time", "end_time", "links"] %}
{% for column_name in ["clusters", "user","job_id", "job_array", "job_user_props", "job_name", "job_state", "submit_time", "start_time", "end_time", "links"] %}
<td><div class="form-check form-switch">
{% if (web_settings | check_web_settings_column_display(page_name, column_name)) %}
<input name="{{page_name}}_{{column_name}}_toggle" id="{{page_name}}_{{column_name}}_toggle" type="checkbox" class="form-check-input" onclick="switch_column_setting('{{page_name}}', '{{column_name}}')" checked />
Expand Down
99 changes: 99 additions & 0 deletions scripts/gen_benchmark_script_students.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import sys
import os
from datetime import datetime
import argparse

SIZES_STUDENT00 = [0, 10_000, 100_000, 1_000_000, 2_000_000]
SIZES_STUDENT01 = list(range(0, 101, 20))
NB_PROPS_PER_DICT = 4

NB_REQUESTS = 10


def main(argv):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
"--disable-index",
action="store_true",
help="If specified, will not create MongoDB index when storing fake data.",
)
args = parser.parse_args(argv[1:])
print("Generating benchmark script with args:", args, file=sys.stderr)

bench_date = datetime.now()
bench_basename = "bench_students"
if args.disable_index:
bench_basename += "_noindex"
bench_name = f"{bench_basename}_{bench_date}".replace(" ", "_").replace(":", "-")
assert not os.path.exists(bench_name)
os.mkdir(bench_name)

script_name = f"{bench_name}.sh"
with open(script_name, "w") as file:
print("set -eu", file=file)
print("export CLOCKWORK_API_KEY='000aaa01'", file=file)
print("export CLOCKWORK_EMAIL='[email protected]'", file=file)
print(file=file)

for std_00 in SIZES_STUDENT00:
for std_01 in SIZES_STUDENT01:
gen_commands(std_00, std_01, bench_name, args, file)

print(file=file)
print(f"python3 scripts/plot_benchmark_students.py {bench_name}", file=file)
print(f"tar -cf {bench_name}.tar {bench_name}/", file=file)
print(f"echo Benchmark compressed in: {bench_name}.tar", file=file)

print("Benchmark script saved in:", script_name, file=sys.stderr)


def gen_commands(nb_jobs_student00, nb_jobs_student01, working_directory, args, file):
nb_dicts = nb_jobs_student00 + nb_jobs_student01
task_name = (
f"std00-{nb_jobs_student00:06}_"
f"std01-{nb_jobs_student01:06}_"
f"dicts-{nb_dicts}_"
f"props-{NB_PROPS_PER_DICT}_"
f"index-{0 if args.disable_index else 1}"
)

print(
(
f"python3 scripts/store_huge_fake_data_in_db.py "
f"-j student00={nb_jobs_student00} "
f"-j student01={nb_jobs_student01} "
f"--nb-dicts {nb_dicts} "
f"--nb-props-per-dict {NB_PROPS_PER_DICT} "
f"--props-username [email protected] "
f"{'--disable-index' if args.disable_index else ''}"
),
file=file,
)
print('python3 -m flask run --host="0.0.0.0" &', file=file)
print("export SERVER_PID=$!", file=file)
print("sleep 1", file=file)
print(
'''python3 -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:5000/').getcode())"''',
file=file,
)
print(
(
f"python3 scripts/job_request_benchmark.py "
f"-w {working_directory} "
f'--address "0.0.0.0" '
f"--port 5000 "
f'--username "[email protected]" '
f"--nb-requests {NB_REQUESTS} "
f"--output {task_name}"
),
file=file,
)
print("kill $SERVER_PID", file=file)
print("export SERVER_PID=", file=file)
print(file=file)


if __name__ == "__main__":
main(sys.argv)
Loading
Loading