softwareaccounting 1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. sams/__init__.py +19 -0
  2. sams/aggregator/SoftwareAccounting.py +421 -0
  3. sams/aggregator/SoftwareAccountingPW.py +37 -0
  4. sams/aggregator/__init__.py +0 -0
  5. sams/backend/SoftwareAccounting.py +266 -0
  6. sams/backend/SoftwareAccountingPW.py +465 -0
  7. sams/backend/__init__.py +1 -0
  8. sams/base.py +320 -0
  9. sams/core.py +267 -0
  10. sams/listener/Prometheus.py +133 -0
  11. sams/listener/__init__.py +0 -0
  12. sams/loader/File.py +111 -0
  13. sams/loader/FileSlurmInfoFallback.py +118 -0
  14. sams/loader/__init__.py +0 -0
  15. sams/output/Carbon.py +137 -0
  16. sams/output/Collectd.py +132 -0
  17. sams/output/File.py +91 -0
  18. sams/output/Http.py +105 -0
  19. sams/output/Prometheus.py +165 -0
  20. sams/output/__init__.py +0 -0
  21. sams/pidfinder/Slurm.py +90 -0
  22. sams/pidfinder/__init__.py +0 -0
  23. sams/sampler/Core.py +61 -0
  24. sams/sampler/FSStats.py +103 -0
  25. sams/sampler/IOStats.py +159 -0
  26. sams/sampler/NvidiaSMI.py +225 -0
  27. sams/sampler/Pressure.py +129 -0
  28. sams/sampler/SlurmCGroup.py +161 -0
  29. sams/sampler/SlurmCGroup2.py +62 -0
  30. sams/sampler/SlurmInfo.py +134 -0
  31. sams/sampler/Software.py +289 -0
  32. sams/sampler/ZFSStats.py +136 -0
  33. sams/sampler/__init__.py +0 -0
  34. sams/software/Regexp.py +137 -0
  35. sams/software/__init__.py +0 -0
  36. sams/xmlwriter/File.py +149 -0
  37. sams/xmlwriter/__init__.py +1 -0
  38. softwareaccounting-1.9.data/scripts/sams-aggregator.py +148 -0
  39. softwareaccounting-1.9.data/scripts/sams-collector.py +279 -0
  40. softwareaccounting-1.9.data/scripts/sams-post-receiver.py +146 -0
  41. softwareaccounting-1.9.data/scripts/sams-software-extractor.py +132 -0
  42. softwareaccounting-1.9.data/scripts/sams-software-updater.py +206 -0
  43. softwareaccounting-1.9.data/scripts/sgas-sa-registrant +632 -0
  44. softwareaccounting-1.9.dist-info/METADATA +21 -0
  45. softwareaccounting-1.9.dist-info/RECORD +48 -0
  46. softwareaccounting-1.9.dist-info/WHEEL +5 -0
  47. softwareaccounting-1.9.dist-info/licenses/LICENSE +339 -0
  48. softwareaccounting-1.9.dist-info/top_level.txt +1 -0
sams/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ SAMS Software accounting
3
+ Copyright (C) 2018-2021 Swedish National Infrastructure for Computing (SNIC)
4
+
5
+ This program is free software; you can redistribute it and/or
6
+ modify it under the terms of the GNU General Public License
7
+ as published by the Free Software Foundation; either version 2
8
+ of the License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; If not, see <http://www.gnu.org/licenses/>.
17
+ """
18
+
19
+ __version__ = "1.9"
@@ -0,0 +1,421 @@
1
+ """
2
+ Software accounting storage "backend" for updating the list of softwares
3
+
4
+ SAMS Software accounting
5
+ Copyright (C) 2018-2021 Swedish National Infrastructure for Computing (SNIC)
6
+
7
+ This program is free software; you can redistribute it and/or
8
+ modify it under the terms of the GNU General Public License
9
+ as published by the Free Software Foundation; either version 2
10
+ of the License, or (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; If not, see <http://www.gnu.org/licenses/>.
19
+
20
+
21
+ Config options:
22
+
23
+ sams.backend.SoftwareAccounting:
24
+ # Number of jobs in
25
+ jobid_hash_size: 0 # All in one.
26
+
27
+ # sqlite file name(s)
28
+ file_pattern: 'sa-%(jobid_hash)d.db'
29
+
30
+ # Path to sqlite db files
31
+ db_path: /data/softwareaccounting/CLUSTER/db
32
+
33
+ # cluster (used for calculating SGAS recordid)
34
+ cluster: CLUSTER
35
+
36
+ # sqlite temp_store pragma (DEFAULT, FILE or MEMORY)
37
+ # DEFAULT is normally FILE but is dependent on compile time
38
+ # options of the sqlite library.
39
+ sqlite_temp_store: DEFAULT
40
+ """
41
+
42
+ import logging
43
+ import os
44
+ import sqlite3
45
+
46
+ import sams.base
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+ """ Create tables unless exists """
51
+ TABLES = [
52
+ """
53
+ CREATE TABLE IF NOT EXISTS projects (
54
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55
+ project TEXT NOT NULL
56
+ );
57
+ """,
58
+ """
59
+ CREATE TABLE IF NOT EXISTS users (
60
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
61
+ user TEXT NOT NULL
62
+ );
63
+ """,
64
+ """
65
+ CREATE TABLE IF NOT EXISTS jobs (
66
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ jobid TEXT NOT NULL,
68
+ recordid TEXT,
69
+ user INTEGER,
70
+ project INTEGER,
71
+ ncpus INTEGER,
72
+ start_time INTEGER,
73
+ end_time INTEGER,
74
+ user_time REAL,
75
+ system_time REAL
76
+ );
77
+ """,
78
+ """
79
+ CREATE TABLE IF NOT EXISTS software (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ path TEXT NOT NULL,
82
+ software TEXT,
83
+ version TEXT,
84
+ versionstr TEXT,
85
+ user_provided BOOLEAN,
86
+ ignore BOOLEAN,
87
+ last_updated INTEGER
88
+ );
89
+ """,
90
+ """
91
+ CREATE INDEX IF NOT EXISTS software_path_idx on software(path);
92
+ """,
93
+ """
94
+ CREATE TABLE IF NOT EXISTS node (
95
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
96
+ node TEXT NOT NULL
97
+ );
98
+ """,
99
+ """
100
+ CREATE INDEX IF NOT EXISTS node_node_idx on node(node);
101
+ """,
102
+ """
103
+ CREATE TABLE IF NOT EXISTS command (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ jobid INTEGER NOT NULL,
106
+ node INTEGER,
107
+ software INTEGER,
108
+ start_time INTEGER,
109
+ end_time INTEGER,
110
+ user REAL,
111
+ sys REAL,
112
+ updated INTEGER,
113
+ FOREIGN KEY(node) REFERENCES node(node),
114
+ FOREIGN KEY(software) REFERENCES software(software)
115
+ );
116
+ """,
117
+ """
118
+ CREATE TABLE IF NOT EXISTS last_sent (
119
+ timestamp INTEGER
120
+ );
121
+ """,
122
+ """
123
+ INSERT INTO last_sent(timestamp) SELECT 0 WHERE NOT EXISTS(SELECT 1 FROM last_sent);
124
+ """,
125
+ """
126
+ CREATE INDEX IF NOT EXISTS command_jobid_node_software_idx on command(jobid,node,software);
127
+ """,
128
+ """
129
+ CREATE INDEX IF NOT EXISTS command_updated_idx on command(updated);
130
+ """,
131
+ """
132
+ CREATE INDEX IF NOT EXISTS software_last_updated_idx on software(last_updated);
133
+ """,
134
+ """
135
+ CREATE INDEX IF NOT EXISTS jobs_jobid_idx on jobs(jobid);
136
+ """,
137
+ """
138
+ CREATE INDEX IF NOT EXISTS jobs_start_time_idx on jobs(start_time);
139
+ """,
140
+ """
141
+ CREATE INDEX IF NOT EXISTS jobs_end_time_idx on jobs(end_time);
142
+ """,
143
+ """
144
+ CREATE INDEX IF NOT EXISTS jobs_user_time_idx on jobs(user_time);
145
+ """,
146
+ """
147
+ CREATE INDEX IF NOT EXISTS jobs_system_time_idx on jobs(system_time);
148
+ """,
149
+ ]
150
+
151
+ # Update/Insert SQL
152
+ INSERT_USER = """ insert or replace into users (id,user) values ((select ID from users where user = :user), :user); """
153
+ INSERT_PROJECT = """ insert or replace into projects (id,project) values ((select ID from projects where project = :project), :project); """
154
+ INSERT_JOBS = """
155
+ insert or replace into jobs (id,jobid,user,project,ncpus,recordid)
156
+ values ((select ID from jobs where jobid = :jobid), :jobid, :user ,:project, :ncpus, :recordid);
157
+ """
158
+ INSERT_NODE = """ insert or replace into node (id,node) values ((select ID from node where node = :node), :node); """
159
+ FETCH_SOFTWARE_ID = """ select ID from software where path = :software; """
160
+ INSERT_SOFTWARE = """ insert or ignore into software (id,path) values ((select ID from software where path = :software), :software); """
161
+ INSERT_COMMAND = """
162
+ insert or ignore into command (id,jobid,node,software,start_time,end_time,user,sys,updated) values
163
+ (
164
+ (
165
+ select ID from command
166
+ where
167
+ jobid = :id
168
+ and
169
+ node = :node_id
170
+ and
171
+ software = :sw_id
172
+ )
173
+ ,
174
+ :id,
175
+ :node_id,
176
+ :sw_id,
177
+ :start_time,:end_time,
178
+ :user,:sys,
179
+ strftime('%s','now')
180
+ );
181
+ """
182
+
183
+ UPDATE_MINMAX = """
184
+ update jobs set
185
+ start_time = :start_time,
186
+ end_time = :end_time,
187
+ user_time = :user_time,
188
+ system_time = :system_time
189
+ where id = :id
190
+ """
191
+
192
+ FIND_MINMAX_JOBS = """
193
+ select jobid,min(start_time),max(end_time),sum(user),sum(sys)
194
+ from command
195
+ where jobid in (select id from jobs where start_time is null or end_time is null or user_time is null or system_time is null)
196
+ group by jobid
197
+ """
198
+
199
+
200
+ class Aggregator(sams.base.Aggregator):
201
+ """SAMS Software accounting aggregator"""
202
+
203
+ def __init__(self, id, config):
204
+ super(Aggregator, self).__init__(id, config)
205
+ self.db = {}
206
+ self.db_path = self.config.get([self.id, "db_path"])
207
+ self.cluster = self.config.get([self.id, "cluster"])
208
+ self.file_pattern = self.config.get([self.id, "file_pattern"], "sa-%(jobid_hash)d.db")
209
+ self.jobid_hash_size = self.config.get([self.id, "jobid_hash_size"], 0)
210
+ self.inserted = {}
211
+
212
+ self.sqlite_temp_store = self.config.get([self.id, "sqlite_temp_store"], "DEFAULT")
213
+
214
+ if self.sqlite_temp_store not in ["DEFAULT", "FILE", "MEMORY"]:
215
+ sams.base.AggregatorException("sqlite_temp_store must be one of DEFAULT, FILE or MEMORY")
216
+
217
+ def _open_db(self, jobid_hash):
218
+ """Open database object"""
219
+ db = os.path.join(self.db_path, self.file_pattern % {"jobid_hash": int(jobid_hash)})
220
+ self.db[jobid_hash] = sqlite3.connect(db)
221
+ self.db[jobid_hash].isolation_level = None
222
+ c = self.db[jobid_hash].cursor()
223
+ c.execute("PRAGMA temp_store = %s" % self.sqlite_temp_store)
224
+ for sql in TABLES:
225
+ logger.debug(sql)
226
+ c.execute(sql)
227
+ self.db[jobid_hash].commit()
228
+ return self.db[jobid_hash]
229
+
230
+ def get_db(self, jobid):
231
+ """get db connection based on jobid / jobid_hash_size"""
232
+ jobid_hash = 0
233
+ if self.jobid_hash_size > 0:
234
+ jobid_hash = int(jobid / self.jobid_hash_size)
235
+ if jobid_hash in self.db:
236
+ return self.db[jobid_hash]
237
+ return self._open_db(jobid_hash)
238
+
239
+ def save_id(self, jobid, table, value, id):
240
+ """Only try to insert once / session"""
241
+ jobid_hash = 0
242
+ if self.jobid_hash_size > 0:
243
+ jobid_hash = int(jobid / self.jobid_hash_size)
244
+ if value not in self.inserted[jobid_hash][table]:
245
+ self.inserted[jobid_hash][table][value] = id
246
+ logger.debug("save_id(%d (%d),%s,%s,%d)", jobid, jobid_hash, table, value, id)
247
+ return id
248
+
249
+ def get_id(self, jobid, table, value):
250
+ """Only try to insert once / session"""
251
+ jobid_hash = 0
252
+ if self.jobid_hash_size > 0:
253
+ jobid_hash = int(jobid / self.jobid_hash_size)
254
+ logger.debug(
255
+ "get_id(%d (%d),%s,%s,%d)",
256
+ jobid,
257
+ jobid_hash,
258
+ table,
259
+ value,
260
+ self.inserted[jobid_hash][table][value],
261
+ )
262
+ return self.inserted[jobid_hash][table][value]
263
+
264
+ def do_insert(self, jobid, table, value):
265
+ """Only try to insert once / session"""
266
+ jobid_hash = 0
267
+ if self.jobid_hash_size > 0:
268
+ jobid_hash = int(jobid / self.jobid_hash_size)
269
+ if jobid_hash not in self.inserted:
270
+ self.inserted[jobid_hash] = {}
271
+ if table not in self.inserted[jobid_hash]:
272
+ self.inserted[jobid_hash][table] = {}
273
+ if value not in self.inserted[jobid_hash][table]:
274
+ return True
275
+ return False
276
+
277
+ def aggregate(self, data):
278
+ """Information aggregate method"""
279
+
280
+ jobid = int(data["sams.sampler.Core"]["jobid"])
281
+ node = data["sams.sampler.Core"]["node"]
282
+
283
+ # Get database for jobid
284
+ db = self.get_db(jobid)
285
+ c = db.cursor()
286
+
287
+ for module in ["sams.sampler.Software", "sams.sampler.SlurmInfo"]:
288
+ if module not in data:
289
+ logger.info("Jobid: %d on node %s has no %s", jobid, node, module)
290
+ raise sams.base.AggregatorException("Jobid: %d on node %s has no %s" % (jobid, node, module))
291
+
292
+ if len(data["sams.sampler.Software"]["execs"]) == 0:
293
+ raise sams.base.AggregatorException("Jobid: %d on node %s has no execs" % (jobid, node))
294
+
295
+ # Begin transaction
296
+ c.execute("BEGIN TRANSACTION")
297
+ c.execute("PRAGMA temp_store = %s" % self.sqlite_temp_store)
298
+
299
+ # If project (account) is defined in data insert into table
300
+ project = None
301
+ project_id = None
302
+ if "account" in data["sams.sampler.SlurmInfo"]:
303
+ project = data["sams.sampler.SlurmInfo"]["account"]
304
+ if self.do_insert(jobid, "projects", project):
305
+ c.execute(INSERT_PROJECT, {"project": project})
306
+ project_id = self.save_id(jobid, "projects", project, c.lastrowid)
307
+ logger.debug("Inserted project: %s as %d (%d)", project, c.lastrowid, project_id)
308
+ else:
309
+ project_id = self.get_id(jobid, "projects", project)
310
+ logger.debug("Fetched project: %s as %d", project, project_id)
311
+
312
+ # If username is defined in data insert into table
313
+ user = None
314
+ user_id = None
315
+ if "username" in data["sams.sampler.SlurmInfo"]:
316
+ user = data["sams.sampler.SlurmInfo"]["username"]
317
+ if self.do_insert(jobid, "users", user):
318
+ c.execute(INSERT_USER, {"user": user})
319
+ user_id = self.save_id(jobid, "users", user, c.lastrowid)
320
+ logger.debug("Inserted user: %s as %d (%d)", user, c.lastrowid, user_id)
321
+ else:
322
+ user_id = self.get_id(jobid, "users", user)
323
+ logger.debug("Fetched user: %s as %d", user, user_id)
324
+
325
+ recordid = "%s:%s" % (self.cluster, jobid)
326
+ if "starttime" in data["sams.sampler.SlurmInfo"]:
327
+ starttime = data["sams.sampler.SlurmInfo"]["starttime"]
328
+ starttime = starttime.replace("-", "").replace("T", "").replace(":", "")
329
+ recordid = "%s:%s:%s" % (self.cluster, jobid, starttime)
330
+
331
+ # If username is defined in data insert into table
332
+ ncpus = None
333
+ if "cpus" in data["sams.sampler.SlurmInfo"]:
334
+ ncpus = data["sams.sampler.SlurmInfo"]["cpus"]
335
+
336
+ # Insert information about job
337
+ c.execute(
338
+ INSERT_JOBS,
339
+ {
340
+ "jobid": jobid,
341
+ "user": user_id,
342
+ "project": project_id,
343
+ "ncpus": ncpus,
344
+ "recordid": recordid,
345
+ },
346
+ )
347
+ id = c.lastrowid
348
+
349
+ # Insert node
350
+ node_id = None
351
+ if self.do_insert(jobid, "nodes", node):
352
+ c.execute(INSERT_NODE, {"node": node})
353
+ node_id = self.save_id(jobid, "nodes", node, c.lastrowid)
354
+ logger.debug("Inserted node: %s as %d (%d)", node, c.lastrowid, node_id)
355
+ else:
356
+ node_id = self.get_id(jobid, "nodes", node)
357
+
358
+ # Insert information about running commands
359
+ for sw, info in data["sams.sampler.Software"]["execs"].items():
360
+ # Insert software
361
+ sw_id = None
362
+ if self.do_insert(jobid, "softwares", sw):
363
+ sw_id_row = [ts for ts in c.execute(FETCH_SOFTWARE_ID, {"software": sw})]
364
+
365
+ if sw_id_row:
366
+ sw_id = self.save_id(jobid, "softwares", sw, sw_id_row[0][0])
367
+ logger.debug("Fetched sw: %s as %d (%d)", sw, sw_id_row[0][0], sw_id)
368
+ else:
369
+ c.execute(INSERT_SOFTWARE, {"software": sw})
370
+ sw_id = self.save_id(jobid, "softwares", sw, c.lastrowid)
371
+ logger.debug("Inserted sw: %s as %d (%d)", sw, c.lastrowid, sw_id)
372
+ else:
373
+ sw_id = self.get_id(jobid, "softwares", sw)
374
+ logger.debug("Cached sw: %s as %d", sw, sw_id)
375
+
376
+ c.execute(
377
+ INSERT_COMMAND,
378
+ {
379
+ "id": id,
380
+ "node_id": node_id,
381
+ "sw_id": sw_id,
382
+ "start_time": int(data["sams.sampler.Software"]["start_time"]),
383
+ "end_time": int(data["sams.sampler.Software"]["end_time"]),
384
+ "user": info["user"],
385
+ "sys": info["system"],
386
+ },
387
+ )
388
+
389
+ # Commit data to disk
390
+ c.execute("COMMIT")
391
+ db.commit()
392
+
393
+ def cleanup(self):
394
+ for db in self.db.values():
395
+ db.rollback()
396
+
397
+ def close(self):
398
+ for db in self.db.values():
399
+ # Update jobs table.
400
+ try:
401
+ c = db.cursor()
402
+ c.execute("BEGIN TRANSACTION")
403
+ c.execute("PRAGMA temp_store = %s" % self.sqlite_temp_store)
404
+ rows = [row for row in c.execute(FIND_MINMAX_JOBS)]
405
+ for row in rows:
406
+ c.execute(
407
+ UPDATE_MINMAX,
408
+ {
409
+ "id": row[0],
410
+ "start_time": row[1],
411
+ "end_time": row[2],
412
+ "user_time": row[3],
413
+ "system_time": row[4],
414
+ },
415
+ )
416
+ c.execute("COMMIT")
417
+ db.commit()
418
+ except Exception as e:
419
+ logger.exception(e)
420
+
421
+ db.close()
@@ -0,0 +1,37 @@
1
+ """
2
+ Software accounting storage "aggregator" for merging data into database
3
+
4
+ SAMS Software accounting
5
+ Copyright (C) 2018-2021 Swedish National Infrastructure for Computing (SNIC)
6
+
7
+ This program is free software; you can redistribute it and/or
8
+ modify it under the terms of the GNU General Public License
9
+ as published by the Free Software Foundation; either version 2
10
+ of the License, or (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ Config options:
21
+
22
+ sams.aggregator.SoftwareAccountingPW:
23
+ # Configuration is the same as
24
+ # sams.backend.SoftwareAccountingPW
25
+
26
+ """
27
+
28
+ import logging
29
+
30
+ import sams.base
31
+ from sams.backend.SoftwareAccountingPW import Backend
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class Aggregator(Backend, sams.base.Aggregator):
37
+ """SAMS Software accounting aggregator"""
File without changes