| /*****************************************************************************\ |
| * common.c - common functions for generating reports |
| * from accounting infrastructure. |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * Copyright (C) 2008 Lawrence Livermore National Security. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Danny Auble <da@llnl.gov> |
| * CODE-OCEC-09-009. All rights reserved. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include "sreport.h" |
| #include "src/common/proc_args.h" |
| |
| int sort_user_tres_id = TRES_CPU; /* controls sorting users (sort_user_dec) */ |
| |
| extern char *sreport_get_time_str(uint64_t value, uint64_t total_time) |
| { |
| char *output = NULL; |
| |
| if (!total_time) |
| total_time = 1; |
| |
| if (!(value == NO_VAL) || (value == INFINITE)) { |
| double percent = (double)value; |
| double temp_d = (double)value; |
| |
| switch (time_format) { |
| case SLURMDB_REPORT_TIME_SECS: |
| output = xstrdup_printf("%"PRIu64"", value); |
| break; |
| case SLURMDB_REPORT_TIME_MINS: |
| temp_d /= 60; |
| output = xstrdup_printf("%.0lf", temp_d); |
| break; |
| case SLURMDB_REPORT_TIME_HOURS: |
| temp_d /= 3600; |
| output = xstrdup_printf("%.0lf", temp_d); |
| break; |
| case SLURMDB_REPORT_TIME_PERCENT: |
| percent /= total_time; |
| percent *= 100; |
| output = xstrdup_printf("%.2lf%%", percent); |
| break; |
| case SLURMDB_REPORT_TIME_SECS_PER: |
| percent /= total_time; |
| percent *= 100; |
| output = xstrdup_printf("%"PRIu64"(%.2lf%%)", |
| value, percent); |
| break; |
| case SLURMDB_REPORT_TIME_MINS_PER: |
| percent /= total_time; |
| percent *= 100; |
| temp_d /= 60; |
| output = xstrdup_printf("%.0lf(%.2lf%%)", |
| temp_d, percent); |
| break; |
| case SLURMDB_REPORT_TIME_HOURS_PER: |
| percent /= total_time; |
| percent *= 100; |
| temp_d /= 3600; |
| output = xstrdup_printf("%.0lf(%.2lf%%)", |
| temp_d, percent); |
| break; |
| default: |
| temp_d /= 60; |
| output = xstrdup_printf("%.0lf", temp_d); |
| break; |
| } |
| } |
| return output; |
| } |
| |
| extern int parse_option_end(char *option) |
| { |
| int end = 0; |
| |
| if (!option) |
| return 0; |
| |
| while(option[end] && option[end] != '=') |
| end++; |
| if (!option[end]) |
| return 0; |
| end++; |
| return end; |
| } |
| |
| |
| /* Do not allow the endtime request for sreport to exceed 'now'. */ |
| extern time_t sanity_check_endtime(time_t endtime) |
| { |
| time_t now = time(NULL); |
| |
| if (endtime > now) |
| endtime = now; |
| |
| return endtime; |
| } |
| |
| /* you need to xfree whatever is sent from here */ |
| extern char *strip_quotes(char *option, int *increased) |
| { |
| int end = 0; |
| int i=0, start=0; |
| char *meat = NULL; |
| |
| if (!option) |
| return NULL; |
| |
| /* first strip off the ("|')'s */ |
| if (option[i] == '\"' || option[i] == '\'') |
| i++; |
| start = i; |
| |
| while (option[i]) { |
| if (option[i] == '\"' || option[i] == '\'') { |
| end++; |
| break; |
| } |
| i++; |
| } |
| end += i; |
| |
| meat = xmalloc((i-start)+1); |
| memcpy(meat, option+start, (i-start)); |
| |
| if (increased) |
| (*increased) += end; |
| |
| return meat; |
| } |
| |
| extern void addto_char_list(list_t *char_list, char *names) |
| { |
| int i = 0, start = 0; |
| char *name = NULL, *tmp_char = NULL; |
| list_itr_t *itr = NULL; |
| |
| if (!char_list) { |
| error("No list was given to fill in"); |
| return; |
| } |
| |
| itr = list_iterator_create(char_list); |
| if (names) { |
| if (names[i] == '\"' || names[i] == '\'') |
| i++; |
| start = i; |
| while(names[i]) { |
| if (names[i] == '\"' || names[i] == '\'') |
| break; |
| else if (names[i] == ',') { |
| if ((i-start) > 0) { |
| name = xmalloc((i-start+1)); |
| memcpy(name, names+start, (i-start)); |
| |
| while((tmp_char = list_next(itr))) { |
| if (!xstrcasecmp(tmp_char, |
| name)) |
| break; |
| } |
| |
| if (!tmp_char) |
| list_append(char_list, name); |
| else |
| xfree(name); |
| list_iterator_reset(itr); |
| } |
| i++; |
| start = i; |
| } |
| i++; |
| } |
| if ((i-start) > 0) { |
| name = xmalloc((i-start)+1); |
| memcpy(name, names+start, (i-start)); |
| while((tmp_char = list_next(itr))) { |
| if (!xstrcasecmp(tmp_char, name)) |
| break; |
| } |
| |
| if (!tmp_char) |
| list_append(char_list, name); |
| else |
| xfree(name); |
| } |
| } |
| list_iterator_destroy(itr); |
| } |
| |
| /* |
| * Comparator to sort users from higher usage of sort_user_tres_id to smallest |
| * |
| * returns: 1: user_a > user_b 0: user_a == user_b -1: user_a < user_b |
| * |
| */ |
| extern int sort_user_dec(void *v1, void *v2) |
| { |
| slurmdb_report_user_rec_t *user_a; |
| slurmdb_report_user_rec_t *user_b; |
| int diff; |
| |
| user_a = *(slurmdb_report_user_rec_t **)v1; |
| user_b = *(slurmdb_report_user_rec_t **)v2; |
| |
| if (sort_flag == SLURMDB_REPORT_SORT_TIME) { |
| slurmdb_tres_rec_t *tres_a, *tres_b; |
| |
| if (!user_a->tres_list || !user_b->tres_list) |
| return 0; |
| |
| if (!(tres_a = list_find_first(user_a->tres_list, |
| slurmdb_find_tres_in_list, |
| &sort_user_tres_id))) |
| return 1; |
| |
| if (!(tres_b = list_find_first(user_b->tres_list, |
| slurmdb_find_tres_in_list, |
| &sort_user_tres_id))) |
| return -1; |
| |
| |
| if (tres_a->alloc_secs > tres_b->alloc_secs) |
| return -1; |
| else if (tres_a->alloc_secs < tres_b->alloc_secs) |
| return 1; |
| } |
| |
| if (!user_a->name || !user_b->name) |
| return 0; |
| |
| diff = xstrcmp(user_a->name, user_b->name); |
| |
| if (diff > 0) |
| return 1; |
| else if (diff < 0) |
| return -1; |
| |
| return 0; |
| |
| } |
| |
| /* |
| * Comparator used for sorting clusters alphabetically |
| * |
| * returns: 1: cluster_a > cluster_b |
| * 0: cluster_a == cluster_b |
| * -1: cluster_a < cluster_b |
| * |
| */ |
| extern int sort_cluster_dec(void *v1, void *v2) |
| { |
| int diff; |
| slurmdb_report_cluster_rec_t *cluster_a; |
| slurmdb_report_cluster_rec_t *cluster_b; |
| |
| cluster_a = *(slurmdb_report_cluster_rec_t **)v1; |
| cluster_b = *(slurmdb_report_cluster_rec_t **)v2; |
| |
| if (!cluster_a->name || !cluster_b->name) |
| return 0; |
| |
| diff = xstrcmp(cluster_a->name, cluster_b->name); |
| |
| if (diff > 0) |
| return 1; |
| else if (diff < 0) |
| return -1; |
| |
| return 0; |
| } |
| |
| /* |
| * Comparator used for sorting assocs alphabetically by acct and then |
| * by user. The association with a total count of time is at the top |
| * of the accts. |
| * |
| * returns: -1: assoc_a > assoc_b |
| * 0: assoc_a == assoc_b |
| * 1: assoc_a < assoc_b |
| * |
| */ |
| extern int sort_assoc_dec(void *v1, void *v2) |
| { |
| int diff; |
| slurmdb_report_assoc_rec_t *assoc_a; |
| slurmdb_report_assoc_rec_t *assoc_b; |
| |
| assoc_a = *(slurmdb_report_assoc_rec_t **)v1; |
| assoc_b = *(slurmdb_report_assoc_rec_t **)v2; |
| |
| if (!assoc_a->acct || !assoc_b->acct) |
| return 0; |
| |
| diff = xstrcmp(assoc_a->acct, assoc_b->acct); |
| |
| if (diff > 0) |
| return 1; |
| else if (diff < 0) |
| return -1; |
| |
| if (!assoc_a->user && assoc_b->user) |
| return 1; |
| else if (!assoc_b->user) |
| return -1; |
| |
| diff = xstrcmp(assoc_a->user, assoc_b->user); |
| |
| if (diff > 0) |
| return 1; |
| else if (diff < 0) |
| return -1; |
| |
| |
| return 0; |
| } |
| |
| extern int get_uint(char *in_value, uint32_t *out_value, char *type) |
| { |
| char *ptr = NULL, *meat = NULL; |
| long num; |
| |
| if (!(meat = strip_quotes(in_value, NULL))) { |
| error("Problem with strip_quotes"); |
| return SLURM_ERROR; |
| } |
| num = strtol(meat, &ptr, 10); |
| if ((num == 0) && ptr && ptr[0]) { |
| error("Invalid value for %s (%s)", type, meat); |
| xfree(meat); |
| return SLURM_ERROR; |
| } |
| xfree(meat); |
| |
| if (num < 0) |
| *out_value = INFINITE; /* flag to clear */ |
| else |
| *out_value = (uint32_t) num; |
| return SLURM_SUCCESS; |
| } |
| |
| extern void sreport_set_tres_recs(slurmdb_tres_rec_t **cluster_tres_rec, |
| slurmdb_tres_rec_t **tres_rec, |
| list_t *cluster_tres_list, list_t *tres_list, |
| slurmdb_tres_rec_t *tres_rec_in) |
| { |
| if (!(*cluster_tres_rec = list_find_first(cluster_tres_list, |
| slurmdb_find_tres_in_list, |
| &tres_rec_in->id))) { |
| debug2("No cluster TRES %s%s%s(%d)", |
| tres_rec_in->type, |
| tres_rec_in->name ? "/" : "", |
| tres_rec_in->name ? tres_rec_in->name : "", |
| tres_rec_in->id); |
| } |
| |
| if (!(*tres_rec = list_find_first(tres_list, |
| slurmdb_find_tres_in_list, |
| &tres_rec_in->id))) { |
| debug2("No TRES %s%s%s(%d)", |
| tres_rec_in->type, |
| tres_rec_in->name ? "/" : "", |
| tres_rec_in->name ? tres_rec_in->name : "", |
| tres_rec_in->id); |
| } else if (!(*tres_rec)->alloc_secs) { |
| debug2("No TRES %s%s%s(%d) usage", |
| tres_rec_in->type, |
| tres_rec_in->name ? "/" : "", |
| tres_rec_in->name ? tres_rec_in->name : "", |
| tres_rec_in->id); |
| } |
| } |
| |
| extern void sreport_set_usage_col_width(print_field_t *field, uint64_t number) |
| { |
| uint64_t order, max_order; |
| |
| //info("got %p %"PRIu64, field, number); |
| if (!field) |
| return; |
| |
| order = 100000000; |
| max_order = order * 100000000000000000; |
| |
| /* smallest usage we want if this changes change order and |
| * max_order appropriately */ |
| field->len = 8; |
| |
| while (order < max_order) { |
| if (number < order) |
| break; |
| |
| field->len++; |
| order *= 10; |
| } |
| |
| if (time_format == SLURMDB_REPORT_TIME_SECS_PER |
| || time_format == SLURMDB_REPORT_TIME_MINS_PER |
| || time_format == SLURMDB_REPORT_TIME_HOURS_PER) |
| field->len += 9; |
| } |
| |
| extern void sreport_set_usage_column_width(print_field_t *usage_field, |
| print_field_t *energy_field, |
| list_t *slurmdb_report_cluster_list) |
| { |
| uint64_t max_usage = 0, max_energy = 0; |
| list_itr_t *tres_itr, *cluster_itr; |
| slurmdb_report_cluster_rec_t *slurmdb_report_cluster = NULL; |
| |
| xassert(slurmdb_report_cluster_list); |
| |
| tres_itr = list_iterator_create(tres_list); |
| cluster_itr = list_iterator_create(slurmdb_report_cluster_list); |
| while ((slurmdb_report_cluster = list_next(cluster_itr))) { |
| slurmdb_tres_rec_t *tres, *tres_rec; |
| list_t *use_list = slurmdb_report_cluster->tres_list; |
| |
| /* The first association will always be the largest |
| * count of any TRES, so just peek at it. If the |
| * cluster doesn't have associations for some reason |
| * use the cluster main one which has the total time. |
| */ |
| |
| if (slurmdb_report_cluster->assoc_list) { |
| slurmdb_report_assoc_rec_t *slurmdb_report = |
| list_peek(slurmdb_report_cluster->assoc_list); |
| if (slurmdb_report) |
| use_list = slurmdb_report->tres_list; |
| } else if (slurmdb_report_cluster->user_list) { |
| slurmdb_report_user_rec_t *slurmdb_report; |
| list_sort(slurmdb_report_cluster->user_list, |
| (ListCmpF)sort_user_dec); |
| slurmdb_report = |
| list_peek(slurmdb_report_cluster->user_list); |
| if (slurmdb_report) |
| use_list = slurmdb_report->tres_list; |
| } else { |
| error("%s: unknown type of slurmdb_report_cluster " |
| "given for cluster %s", |
| __func__, slurmdb_report_cluster->name); |
| continue; |
| } |
| |
| if (energy_field) { |
| uint32_t tres_id = TRES_CPU; |
| if ((tres_rec = list_find_first( |
| use_list, |
| slurmdb_find_tres_in_list, |
| &tres_id))) { |
| max_usage = MAX(max_usage, |
| tres_rec->alloc_secs); |
| } |
| tres_id = TRES_ENERGY; |
| if ((tres_rec = list_find_first( |
| use_list, |
| slurmdb_find_tres_in_list, |
| &tres_id))) { |
| max_energy = MAX(max_energy, |
| tres_rec->alloc_secs); |
| } |
| } else { |
| list_iterator_reset(tres_itr); |
| while ((tres = list_next(tres_itr))) { |
| if (tres->id == NO_VAL) |
| continue; |
| if (!(tres_rec = list_find_first( |
| use_list, |
| slurmdb_find_tres_in_list, |
| &tres->id))) |
| continue; |
| max_usage = MAX(max_usage, |
| tres_rec->alloc_secs); |
| } |
| } |
| } |
| list_iterator_destroy(tres_itr); |
| list_iterator_destroy(cluster_itr); |
| |
| sreport_set_usage_col_width(usage_field, max_usage); |
| sreport_set_usage_col_width(energy_field, max_energy); |
| } |
| |
| /* Return 1 if UID for two association records match */ |
| static int _match_assoc(void *x, void *key) |
| { |
| slurmdb_report_assoc_rec_t *orig_report_assoc; |
| slurmdb_report_assoc_rec_t *dup_report_assoc; |
| |
| orig_report_assoc = (slurmdb_report_assoc_rec_t *) key; |
| dup_report_assoc = (slurmdb_report_assoc_rec_t *) x; |
| if (!xstrcmp(orig_report_assoc->acct, dup_report_assoc->acct) && |
| !xstrcmp(orig_report_assoc->user, dup_report_assoc->user)) |
| return 1; |
| return 0; |
| } |
| |
| /* Return 1 if a slurmdb association record has NULL tres_list */ |
| static int _find_empty_assoc_tres(void *x, void *key) |
| { |
| slurmdb_report_assoc_rec_t *dup_report_assoc; |
| |
| dup_report_assoc = (slurmdb_report_assoc_rec_t *) x; |
| if (dup_report_assoc->tres_list == NULL) |
| return 1; |
| return 0; |
| } |
| |
| /* For duplicate user/account records, combine TRES records into the original |
| * list and purge the duplicate records */ |
| extern void combine_assoc_tres(list_t *first_assoc_list, list_t *new_assoc_list) |
| { |
| slurmdb_report_assoc_rec_t *orig_report_assoc = NULL; |
| slurmdb_report_assoc_rec_t *dup_report_assoc = NULL; |
| list_itr_t *iter = NULL; |
| |
| if (!first_assoc_list || !new_assoc_list) |
| return; |
| |
| iter = list_iterator_create(first_assoc_list); |
| while ((orig_report_assoc = list_next(iter))) { |
| dup_report_assoc = list_find_first(new_assoc_list, |
| _match_assoc, |
| orig_report_assoc); |
| if (!dup_report_assoc) |
| continue; |
| combine_tres_list(orig_report_assoc->tres_list, |
| dup_report_assoc->tres_list); |
| FREE_NULL_LIST(dup_report_assoc->tres_list); |
| } |
| list_iterator_destroy(iter); |
| |
| (void) list_delete_all(new_assoc_list, _find_empty_assoc_tres, NULL); |
| list_transfer(first_assoc_list, new_assoc_list); |
| } |
| |
| /* Return 1 if two TRES records have the same TRES ID */ |
| static int _match_tres_id(void *x, void *key) |
| { |
| slurmdb_tres_rec_t *orig_tres = (slurmdb_tres_rec_t *) key; |
| slurmdb_tres_rec_t *dup_tres = (slurmdb_tres_rec_t *) x; |
| |
| if (orig_tres->id == dup_tres->id) |
| return 1; |
| return 0; |
| } |
| |
| /* Return 1 if a TRES alloc_secs is zero, the entry has already been merged */ |
| static int _zero_alloc_secs(void *x, void *key) |
| { |
| slurmdb_tres_rec_t *dup_tres; |
| |
| dup_tres = (slurmdb_tres_rec_t *) x; |
| if (dup_tres->alloc_secs == 0) |
| return 1; |
| return 0; |
| } |
| |
| /* Given two TRES lists, combine the content of the second with the first, |
| * adding the counts for duplicate TRES IDs */ |
| extern void combine_tres_list(list_t *orig_tres_list, list_t *dup_tres_list) |
| { |
| slurmdb_tres_rec_t *orig_tres, *dup_tres; |
| list_itr_t *iter = NULL; |
| |
| if (!orig_tres_list || !dup_tres_list) |
| return; |
| |
| /* Merge counts for common TRES */ |
| iter = list_iterator_create(orig_tres_list); |
| while ((orig_tres = list_next(iter))) { |
| dup_tres = list_find_first(dup_tres_list, _match_tres_id, |
| orig_tres); |
| if (!dup_tres) |
| continue; |
| |
| orig_tres->alloc_secs += dup_tres->alloc_secs; |
| orig_tres->rec_count += dup_tres->rec_count; |
| orig_tres->count += dup_tres->count; |
| dup_tres->alloc_secs = 0; |
| } |
| list_iterator_destroy(iter); |
| |
| /* Now transfer TRES records that exists only in the duplicate */ |
| list_delete_all(dup_tres_list, _zero_alloc_secs, NULL); |
| list_transfer(orig_tres_list, dup_tres_list); |
| } |
| |
| |
| /* Return 1 if a slurmdb user record has NULL tres_list */ |
| static int _find_empty_user_tres(void *x, void *key) |
| { |
| slurmdb_report_user_rec_t *dup_report_user; |
| |
| dup_report_user = (slurmdb_report_user_rec_t *) x; |
| if (dup_report_user->tres_list == NULL) |
| return 1; |
| return 0; |
| } |
| |
| /* Return 1 if UID for two user records match */ |
| static int _match_user_acct(void *x, void *key) |
| { |
| slurmdb_report_user_rec_t *orig_report_user; |
| slurmdb_report_user_rec_t *dup_report_user; |
| |
| orig_report_user = (slurmdb_report_user_rec_t *) key; |
| dup_report_user = (slurmdb_report_user_rec_t *) x; |
| if ((orig_report_user->uid == dup_report_user->uid) && |
| !xstrcmp(orig_report_user->acct, dup_report_user->acct)) |
| return 1; |
| return 0; |
| } |
| |
| /* For duplicate user/account records, combine TRES records into the original |
| * list and purge the duplicate records */ |
| extern void combine_user_tres(list_t *first_user_list, list_t *new_user_list) |
| { |
| slurmdb_report_user_rec_t *orig_report_user = NULL; |
| slurmdb_report_user_rec_t *dup_report_user = NULL; |
| list_itr_t *iter = NULL; |
| |
| if (!first_user_list || !new_user_list) |
| return; |
| |
| iter = list_iterator_create(first_user_list); |
| while ((orig_report_user = list_next(iter))) { |
| dup_report_user = list_find_first(new_user_list, |
| _match_user_acct, |
| orig_report_user); |
| if (!dup_report_user) |
| continue; |
| combine_tres_list(orig_report_user->tres_list, |
| dup_report_user->tres_list); |
| FREE_NULL_LIST(dup_report_user->tres_list); |
| } |
| list_iterator_destroy(iter); |
| |
| (void) list_delete_all(new_user_list, _find_empty_user_tres, NULL); |
| list_transfer(first_user_list, new_user_list); |
| } |
| |
| extern void common_get_qos_list(void) |
| { |
| slurmdb_qos_cond_t qos_cond = { |
| .flags = QOS_COND_FLAG_WITH_DELETED, |
| }; |
| if (g_qos_list) |
| return; |
| g_qos_list = slurmdb_qos_get(db_conn, &qos_cond); |
| } |