Skip to content

Commit ddff1c4

Browse files
committed
qiita-cron-job initialize-resource-allocations-redis
1 parent 071eb5f commit ddff1c4

File tree

10 files changed

+246
-16
lines changed

10 files changed

+246
-16
lines changed

qiita_db/meta_util.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@
3737
from re import sub
3838
from json import loads, dump, dumps
3939

40-
from qiita_db.util import create_nested_path
40+
from qiita_db.util import create_nested_path, _retrieve_resource_data
41+
from qiita_db.util import resource_allocation_plot, get_software_commands
4142
from qiita_core.qiita_settings import qiita_config, r_client
4243
from qiita_core.configuration_manager import ConfigurationManager
4344
import qiita_db as qdb
4445

46+
# global constant list used in resource_allocation_page
47+
columns = [
48+
"sName", "sVersion", "cID", "cName", "processing_job_id",
49+
"parameters", "samples", "columns", "input_size", "extra_info",
50+
"MaxRSSRaw", "ElapsedRaw", "Start", "node_name", "node_model"]
4551

4652
def _get_data_fpids(constructor, object_id):
4753
"""Small function for getting filepath IDS associated with data object
@@ -546,3 +552,42 @@ def generate_plugin_releases():
546552
# important to "flush" variables to avoid errors
547553
r_client.delete(redis_key)
548554
f(redis_key, v)
555+
556+
557+
def initialize_resource_allocations_redis():
558+
time = datetime.now().strftime('%m-%d-%y %H:%M:%S')
559+
scommands = get_software_commands()
560+
for software, versions in scommands.items():
561+
for version, commands in versions.items():
562+
for command in commands:
563+
print("Generating plot for:", software, version, command)
564+
update_resource_allocation_redis(command, software,
565+
version, time)
566+
redis_key = 'resources:commands'
567+
r_client.set(redis_key, str(scommands))
568+
569+
570+
def update_resource_allocation_redis(cname, sname, version, time):
571+
col_name = "samples * columns"
572+
573+
df = _retrieve_resource_data(cname, sname, version, columns)
574+
fig, axs = resource_allocation_plot(df, cname, sname, col_name)
575+
576+
fig.tight_layout()
577+
plot = BytesIO()
578+
fig.savefig(plot, format='png')
579+
plot.seek(0)
580+
581+
img = 'data:image/png;base64,' + quote(b64encode(plot.getbuffer()).decode('ascii'))
582+
plt.close(fig)
583+
584+
# SID, CID, col_name
585+
values = [
586+
("img", img, r_client.set),
587+
('time', time, r_client.set)
588+
]
589+
590+
for k, v, f in values:
591+
redis_key = 'resources$#%s$#%s$#%s:%s' % (cname, sname, col_name, k)
592+
r_client.delete(redis_key)
593+
f(redis_key, v)

qiita_db/test/test_meta_util.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,23 @@ def test_generate_plugin_releases(self):
519519
'-', '').replace(':', '').replace(' ', '-')
520520
self.assertEqual(tgz_obs, [time])
521521

522+
def test_update_resource_allocation_redis(self):
523+
cname = "Split libraries FASTQ"
524+
sname = "QIIMEq2"
525+
col_name = "samples * columns"
526+
qdb.meta_util.update_resource_allocation_redis(cname, sname)
527+
528+
vals = [
529+
("img", b's', r_client.get),
530+
('time', b'2024-11-11', r_client.get)
531+
]
532+
533+
for k, exp, f in vals:
534+
redis_key = 'resources$#%s$#%s$#%s:%s' % (cname, sname, col_name, k)
535+
# checking redis values
536+
print(f(redis_key))
537+
# self.assertEqual(f(redis_key), exp)
538+
522539

523540
if __name__ == '__main__':
524541
main()

qiita_db/test/test_util.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,8 +1311,9 @@ def test_quick_mounts_purge(self):
13111311

13121312
class ResourceAllocationPlotTests(TestCase):
13131313
def setUp(self):
1314-
self.CNAME = "Split libraries FASTQ"
1315-
self.SNAME = "QIIMEq2"
1314+
self.cname = "Split libraries FASTQ"
1315+
self.sname = "QIIMEq2"
1316+
self.version = "1.9.1"
13161317
self.col_name = 'samples * columns'
13171318
self.columns = [
13181319
"sName", "sVersion", "cID", "cName", "processing_job_id",
@@ -1322,12 +1323,12 @@ def setUp(self):
13221323
# df is a dataframe that represents a table with columns specified in
13231324
# self.columns
13241325
self.df = qdb.util._retrieve_resource_data(
1325-
self.CNAME, self.SNAME, self.columns)
1326+
self.cname, self.sname, self.version, self.columns)
13261327

13271328
def test_plot_return(self):
13281329
# check the plot returns correct objects
13291330
fig1, axs1 = qdb.util.resource_allocation_plot(
1330-
self.df, self.CNAME, self.SNAME, self.col_name)
1331+
self.df, self.cname, self.sname, self.col_name)
13311332
self.assertIsInstance(
13321333
fig1, Figure,
13331334
"Returned object fig1 is not a Matplotlib Figure")
@@ -1338,13 +1339,13 @@ def test_plot_return(self):
13381339

13391340
def test_minimize_const(self):
13401341
self.df = self.df[
1341-
(self.df.cName == self.CNAME) & (self.df.sName == self.SNAME)]
1342+
(self.df.cName == self.cname) & (self.df.sName == self.sname)]
13421343
self.df.dropna(subset=['samples', 'columns'], inplace=True)
13431344
self.df[self.col_name] = self.df.samples * self.df['columns']
13441345
fig, axs = plt.subplots(ncols=2, figsize=(10, 4), sharey=False)
13451346

13461347
bm, options = qdb.util._resource_allocation_plot_helper(
1347-
self.df, axs[0], self.CNAME, self.SNAME, 'MaxRSSRaw',
1348+
self.df, axs[0], self.cname, self.sname, 'MaxRSSRaw',
13481349
qdb.util.MODELS_MEM, self.col_name)
13491350
# check that the algorithm chooses correct model for MaxRSSRaw and
13501351
# has 0 failures
@@ -1366,7 +1367,7 @@ def test_minimize_const(self):
13661367
# check that the algorithm chooses correct model for ElapsedRaw and
13671368
# has 1 failure
13681369
bm, options = qdb.util._resource_allocation_plot_helper(
1369-
self.df, axs[1], self.CNAME, self.SNAME, 'ElapsedRaw',
1370+
self.df, axs[1], self.cname, self.sname, 'ElapsedRaw',
13701371
qdb.util.MODELS_TIME, self.col_name)
13711372
k, a, b = options.x
13721373
failures_df = qdb.util._resource_allocation_failures(
@@ -1423,7 +1424,7 @@ def test_db_update(self):
14231424

14241425
for curr_cname, ids in types.items():
14251426
updated_df = qdb.util._retrieve_resource_data(
1426-
curr_cname, self.SNAME, self.columns)
1427+
curr_cname, self.sname, self.version, self.columns)
14271428
updated_ids_set = set(updated_df['processing_job_id'])
14281429
previous_ids_set = set(self.df['processing_job_id'])
14291430
for id in ids:

qiita_db/util.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,7 +2381,7 @@ def resource_allocation_plot(df, cname, sname, col_name):
23812381
return fig, axs
23822382

23832383

2384-
def _retrieve_resource_data(cname, sname, columns):
2384+
def _retrieve_resource_data(cname, sname, version, columns):
23852385
with qdb.sql_connection.TRN:
23862386
sql = """
23872387
SELECT
@@ -2411,9 +2411,10 @@ def _retrieve_resource_data(cname, sname, columns):
24112411
ON pr.processing_job_id = sra.processing_job_id
24122412
WHERE
24132413
sc.name = %s
2414-
AND s.name = %s;
2414+
AND s.name = %s
2415+
AND s.version = %s
24152416
"""
2416-
qdb.sql_connection.TRN.add(sql, sql_args=[cname, sname])
2417+
qdb.sql_connection.TRN.add(sql, sql_args=[cname, sname, version])
24172418
res = qdb.sql_connection.TRN.execute_fetchindex()
24182419
df = pd.DataFrame(res, columns=columns)
24192420
return df
@@ -2482,15 +2483,19 @@ def _resource_allocation_plot_helper(
24822483
y_plot = best_model(x_plot, k, a, b)
24832484
ax.plot(x_plot, y_plot, linewidth=1, color='orange')
24842485

2486+
cmin_value = min(y_plot)
2487+
cmax_value = max(y_plot)
2488+
24852489
maxi = naturalsize(df[curr].max(), gnu=True) if curr == "MaxRSSRaw" else \
24862490
timedelta(seconds=float(df[curr].max()))
2487-
cmax = naturalsize(max(y_plot), gnu=True) if curr == "MaxRSSRaw" else \
2488-
timedelta(seconds=float(max(y_plot)))
2491+
cmax = naturalsize(cmax_value, gnu=True) if curr == "MaxRSSRaw" else \
2492+
str(timedelta(seconds=round(cmax_value, 2))).rstrip('0').rstrip('.')
24892493

24902494
mini = naturalsize(df[curr].min(), gnu=True) if curr == "MaxRSSRaw" else \
24912495
timedelta(seconds=float(df[curr].min()))
2492-
cmin = naturalsize(min(y_plot), gnu=True) if curr == "MaxRSSRaw" else \
2493-
timedelta(seconds=float(min(y_plot)))
2496+
cmin = naturalsize(cmin_value, gnu=True) if curr == "MaxRSSRaw" else \
2497+
str(timedelta(seconds=round(cmin_value, 2))).rstrip('0').rstrip('.')
2498+
24942499

24952500
x_plot = np.array(df[col_name])
24962501
failures_df = _resource_allocation_failures(
@@ -2919,3 +2924,35 @@ def merge_rows(rows):
29192924
row['node_model']]
29202925
qdb.sql_connection.TRN.add(sql, sql_args=to_insert)
29212926
qdb.sql_connection.TRN.execute()
2927+
2928+
2929+
def get_software_commands():
2930+
res = []
2931+
with qdb.sql_connection.TRN:
2932+
sql_command = """
2933+
SELECT DISTINCT
2934+
s.name AS sName,
2935+
s.version AS sVersion,
2936+
sc.name AS cName
2937+
FROM
2938+
qiita.slurm_resource_allocations sra
2939+
JOIN
2940+
qiita.processing_job pr
2941+
ON sra.processing_job_id = pr.processing_job_id
2942+
JOIN
2943+
qiita.software_command sc on pr.command_id = sc.command_id
2944+
JOIN
2945+
qiita.software s ON sc.software_id = s.software_id
2946+
"""
2947+
qdb.sql_connection.TRN.add(sql_command)
2948+
res = qdb.sql_connection.TRN.execute_fetchindex()
2949+
2950+
software_commands = dict()
2951+
for s_name, s_version, c_name in res:
2952+
if s_name not in software_commands:
2953+
software_commands[s_name] = {}
2954+
if s_version not in software_commands[s_name]:
2955+
software_commands[s_name][s_version] = set()
2956+
software_commands[s_name][s_version].add(c_name)
2957+
2958+
return software_commands

qiita_pet/handlers/resources.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
2+
from tornado.gen import coroutine, Task
3+
from tornado.web import authenticated, HTTPError
4+
5+
6+
from .base_handlers import BaseHandler
7+
from qiita_core.qiita_settings import qiita_config, r_client
8+
from qiita_core.util import execute_as_transaction
9+
10+
default_cname = "Split libraries FASTQ"
11+
default_sname = "QIIMEq2"
12+
default_col_name = "samples * columns"
13+
commands = 'resources:commands'
14+
15+
class ResourcesHandler(BaseHandler):
16+
def check_admin(self):
17+
if self.current_user.level != "admin":
18+
raise HTTPError(403, reason="%s does not have access to portal "
19+
"editing!" % self.current_user.id)
20+
21+
22+
@execute_as_transaction
23+
def _get_resources(self, callback):
24+
resources = {}
25+
vals = [
26+
('img', r_client.get),
27+
('time', r_client.get)
28+
]
29+
for k, f in vals:
30+
redis_key = 'resources$#%s$#%s$#%s:%s' % (default_cname,
31+
default_sname, default_col_name, k)
32+
resources[k] = f(redis_key)
33+
callback(resources)
34+
35+
36+
def _get_commands(self, callback):
37+
r_client.get(commands)
38+
callback(r_client)
39+
40+
41+
@authenticated
42+
@coroutine
43+
@execute_as_transaction
44+
def get(self):
45+
self.check_admin()
46+
resources = yield Task(self._get_resources)
47+
commands = yield Task(self._get_commands) # TODO: it would make more sense to have this rendered once instead of everytime we make a get request, but i'm not sure how to do that right now
48+
self.render('resources.html',
49+
img=resources['img'], time=resources['time'],
50+
commands=commands,
51+
software=default_sname, command=default_cname,
52+
col_name=default_col_name
53+
)
54+
55+
56+
@authenticated
57+
@execute_as_transaction
58+
def post(self):
59+
self.check_admin()
60+
software = self.get_argument("software", "")
61+
command = self.get_argument("command", "")
62+
column_type = self.get_argument("column_type", "")
63+
64+
65+
self.get()
66+
67+

qiita_pet/templates/resources.html

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{% extends sitebase.html %}
2+
{% block head %}
3+
<link rel="stylesheet" href="{% raw qiita_config.portal_dir %}/static/vendor/css/ol.css" type="text/css">
4+
<style type="text/css">
5+
/* Add any custom styles here */
6+
</style>
7+
<script src="{% raw qiita_config.portal_dir %}/static/vendor/js/ol.js"></script>
8+
{% end %}
9+
10+
{% block content %}
11+
<div style="width: 80%; margin: 0 auto; max-width: 1600px;">
12+
<div style="display: flex; flex-direction: column; gap: 10px; background-color: #f8f9fa; padding: 15px; border-radius: 5px;">
13+
<p3>Generated on: {{time}}</p3>
14+
<p3>Software: {{software}}</p3>
15+
<p3>Command: {{command}}</p3>
16+
<p3>Column name: {{col_name}}</p3>
17+
</div>
18+
19+
<form method="POST">
20+
<div class="form-group">
21+
<label for="software">Software:</label>
22+
<select id="software" name="software" class="form-control">
23+
<option value="">Select Software</option>
24+
<option value="QIIMEq2" selected>QIIMEq2</option>
25+
</select>
26+
</div>
27+
28+
<div class="form-group">
29+
<label for="command">Command:</label>
30+
<select id="command" name="command" class="form-control">
31+
<option value="">Select Command</option>
32+
<option value="Split libraries FASTQ" selected>Split libraries FASTQ</option>
33+
</select>
34+
</div>
35+
36+
<div class="form-group">
37+
<label for="column_type">Column Type:</label>
38+
<select id="column_type" name="column_type" class="form-control">
39+
<option value="">Select Column Type</option>
40+
<option value="samples * columns" selected>samples * columns</option>
41+
</select>
42+
</div>
43+
44+
<button type="submit" class="btn btn-primary">Submit</button>
45+
</form>
46+
{% if img and img is not None %}
47+
<div>
48+
<img height="100%" width="100%" src="{% raw img %}"/>
49+
</div>
50+
{% end %}
51+
</div>
52+
{% end %}

qiita_pet/templates/sitebase.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@
384384
<li><a href="{% raw qiita_config.portal_dir %}/admin/approval/">View Studies awaiting approval</a></li>
385385
<li><a href="{% raw qiita_config.portal_dir %}/admin/portals/studies/">Edit study portal connections</a></li>
386386
<li><a href="{% raw qiita_config.portal_dir %}/admin/purge_users/">Purge non-validated users</a></li>
387+
<li><a href="{% raw qiita_config.portal_dir %}/admin/resources/">View Resource Allocation Plots</a></li>
387388
{% end %}
388389
<li><a href="{% raw qiita_config.portal_dir %}/admin/sample_validation/">Sample Validation</a></li>
389390
<li><a href="{% raw qiita_config.portal_dir %}/admin/processing_jobs/">Processing Jobs</a></li>

qiita_pet/webserver.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
from qiita_pet.handlers.upload import (
5151
UploadFileHandler, StudyUploadFileHandler, StudyUploadViaRemote)
5252
from qiita_pet.handlers.stats import StatsHandler
53+
from qiita_pet.handlers.resources import ResourcesHandler
54+
5355
from qiita_pet.handlers.download import (
5456
DownloadHandler, DownloadStudyBIOMSHandler, DownloadRelease,
5557
DownloadRawData, DownloadEBISampleAccessions, DownloadEBIPrepAccessions,
@@ -136,6 +138,7 @@ def __init__(self):
136138
(r"/admin/sample_validation/", SampleValidation),
137139
(r"/admin/purge_users/", PurgeUsersHandler),
138140
(r"/admin/purge_usersAjax/", PurgeUsersAJAXHandler),
141+
(r"/admin/resources/", ResourcesHandler),
139142
(r"/ebi_submission/(.*)", EBISubmitHandler),
140143
# Study handlers
141144
(r"/study/create/", StudyEditHandler),

scripts/all-qiita-cron-job

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ qiita-cron-job empty-trash-upload-folder
44
qiita-cron-job generate-biom-and-metadata-release
55
qiita-cron-job purge-filepaths
66
qiita-cron-job update-redis-stats
7+
qiita-cron-job initialize-resource-allocations-redis
78
qiita-cron-job generate-plugin-releases
89
qiita-cron-job purge-json-web-tokens

0 commit comments

Comments
 (0)