Whamcloud - gitweb
LU-5560 llite: basic support of SELinux in CLIO
[fs/lustre-release.git] / lustre / obdclass / lprocfs_jobstats.c
1 /* GPL HEADER START
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 only,
7  * as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License version 2 for more details (a copy is included
13  * in the LICENSE file that accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License
16  * version 2 along with this program; If not, see
17  * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
18  *
19  * GPL HEADER END
20  */
21 /*
22  * Copyright (c) 2012, 2014, Intel Corporation.
23  * Use is subject to license terms.
24  *
25  * Author: Niu Yawei <niu@whamcloud.com>
26  */
27 /*
28  * lustre/obdclass/lprocfs_jobstats.c
29  */
30
31 #define DEBUG_SUBSYSTEM S_CLASS
32
33
34 #include <obd_class.h>
35 #include <lprocfs_status.h>
36 #include <lustre/lustre_idl.h>
37
38 #ifdef CONFIG_PROC_FS
39
40 /*
41  * JobID formats & JobID environment variable names for supported
42  * job schedulers:
43  *
44  * SLURM:
45  *   JobID format:  32 bit integer.
46  *   JobID env var: SLURM_JOB_ID.
47  * SGE:
48  *   JobID format:  Decimal integer range to 99999.
49  *   JobID env var: JOB_ID.
50  * LSF:
51  *   JobID format:  6 digit integer by default (up to 999999), can be
52  *                increased to 10 digit (up to 2147483646).
53  *   JobID env var: LSB_JOBID.
54  * Loadleveler:
55  *   JobID format:  String of machine_name.cluster_id.process_id, for
56  *                example: fr2n02.32.0
57  *   JobID env var: LOADL_STEP_ID.
58  * PBS:
59  *   JobID format:  String of sequence_number[.server_name][@server].
60  *   JobID env var: PBS_JOBID.
61  * Maui/MOAB:
62  *   JobID format:  Same as PBS.
63  *   JobID env var: Same as PBS.
64  */
65
66 struct job_stat {
67         struct hlist_node       js_hash;
68         struct list_head        js_list;
69         atomic_t                js_refcount;
70         char                    js_jobid[LUSTRE_JOBID_SIZE];
71         time_t                  js_timestamp; /* seconds */
72         struct lprocfs_stats    *js_stats;
73         struct obd_job_stats    *js_jobstats;
74 };
75
76 static unsigned
77 job_stat_hash(struct cfs_hash *hs, const void *key, unsigned mask)
78 {
79         return cfs_hash_djb2_hash(key, strlen(key), mask);
80 }
81
82 static void *job_stat_key(struct hlist_node *hnode)
83 {
84         struct job_stat *job;
85         job = hlist_entry(hnode, struct job_stat, js_hash);
86         return job->js_jobid;
87 }
88
89 static int job_stat_keycmp(const void *key, struct hlist_node *hnode)
90 {
91         struct job_stat *job;
92         job = hlist_entry(hnode, struct job_stat, js_hash);
93         return (strlen(job->js_jobid) == strlen(key)) &&
94                !strncmp(job->js_jobid, key, strlen(key));
95 }
96
97 static void *job_stat_object(struct hlist_node *hnode)
98 {
99         return hlist_entry(hnode, struct job_stat, js_hash);
100 }
101
102 static void job_stat_get(struct cfs_hash *hs, struct hlist_node *hnode)
103 {
104         struct job_stat *job;
105         job = hlist_entry(hnode, struct job_stat, js_hash);
106         atomic_inc(&job->js_refcount);
107 }
108
109 static void job_free(struct job_stat *job)
110 {
111         LASSERT(atomic_read(&job->js_refcount) == 0);
112         LASSERT(job->js_jobstats);
113
114         write_lock(&job->js_jobstats->ojs_lock);
115         list_del_init(&job->js_list);
116         write_unlock(&job->js_jobstats->ojs_lock);
117
118         lprocfs_free_stats(&job->js_stats);
119         OBD_FREE_PTR(job);
120 }
121
122 static void job_putref(struct job_stat *job)
123 {
124         LASSERT(atomic_read(&job->js_refcount) > 0);
125         if (atomic_dec_and_test(&job->js_refcount))
126                 job_free(job);
127 }
128
129 static void job_stat_put_locked(struct cfs_hash *hs, struct hlist_node *hnode)
130 {
131         struct job_stat *job;
132         job = hlist_entry(hnode, struct job_stat, js_hash);
133         job_putref(job);
134 }
135
136 static void job_stat_exit(struct cfs_hash *hs, struct hlist_node *hnode)
137 {
138         CERROR("should not have any items\n");
139 }
140
141 static struct cfs_hash_ops job_stats_hash_ops = {
142         .hs_hash       = job_stat_hash,
143         .hs_key        = job_stat_key,
144         .hs_keycmp     = job_stat_keycmp,
145         .hs_object     = job_stat_object,
146         .hs_get        = job_stat_get,
147         .hs_put_locked = job_stat_put_locked,
148         .hs_exit       = job_stat_exit,
149 };
150
151 static int job_iter_callback(struct cfs_hash *hs, struct cfs_hash_bd *bd,
152                              struct hlist_node *hnode, void *data)
153 {
154         time_t oldest = *((time_t *)data);
155         struct job_stat *job;
156
157         job = hlist_entry(hnode, struct job_stat, js_hash);
158         if (!oldest || job->js_timestamp < oldest)
159                 cfs_hash_bd_del_locked(hs, bd, hnode);
160
161         return 0;
162 }
163
164 static void lprocfs_job_cleanup(struct obd_job_stats *stats, bool force)
165 {
166         time_t oldest, now;
167
168         if (stats->ojs_cleanup_interval == 0)
169                 return;
170
171         now = cfs_time_current_sec();
172         if (!force && now < stats->ojs_last_cleanup +
173                             stats->ojs_cleanup_interval)
174                 return;
175
176         oldest = now - stats->ojs_cleanup_interval;
177         cfs_hash_for_each_safe(stats->ojs_hash, job_iter_callback,
178                                &oldest);
179         stats->ojs_last_cleanup = cfs_time_current_sec();
180 }
181
182 static struct job_stat *job_alloc(char *jobid, struct obd_job_stats *jobs)
183 {
184         struct job_stat *job;
185
186         LASSERT(jobs->ojs_cntr_num && jobs->ojs_cntr_init_fn);
187
188         OBD_ALLOC_PTR(job);
189         if (job == NULL)
190                 return NULL;
191
192         job->js_stats = lprocfs_alloc_stats(jobs->ojs_cntr_num, 0);
193         if (job->js_stats == NULL) {
194                 OBD_FREE_PTR(job);
195                 return NULL;
196         }
197
198         jobs->ojs_cntr_init_fn(job->js_stats);
199
200         memcpy(job->js_jobid, jobid, LUSTRE_JOBID_SIZE);
201         job->js_timestamp = cfs_time_current_sec();
202         job->js_jobstats = jobs;
203         INIT_HLIST_NODE(&job->js_hash);
204         INIT_LIST_HEAD(&job->js_list);
205         atomic_set(&job->js_refcount, 1);
206
207         return job;
208 }
209
210 int lprocfs_job_stats_log(struct obd_device *obd, char *jobid,
211                           int event, long amount)
212 {
213         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
214         struct job_stat *job, *job2;
215         ENTRY;
216
217         LASSERT(stats && stats->ojs_hash);
218
219         lprocfs_job_cleanup(stats, false);
220
221         if (!jobid || !strlen(jobid))
222                 RETURN(-EINVAL);
223
224         if (strlen(jobid) >= LUSTRE_JOBID_SIZE) {
225                 CERROR("Invalid jobid size (%lu), expect(%d)\n",
226                        (unsigned long)strlen(jobid) + 1, LUSTRE_JOBID_SIZE);
227                 RETURN(-EINVAL);
228         }
229
230         job = cfs_hash_lookup(stats->ojs_hash, jobid);
231         if (job)
232                 goto found;
233
234         job = job_alloc(jobid, stats);
235         if (job == NULL)
236                 RETURN(-ENOMEM);
237
238         job2 = cfs_hash_findadd_unique(stats->ojs_hash, job->js_jobid,
239                                        &job->js_hash);
240         if (job2 != job) {
241                 job_putref(job);
242                 job = job2;
243                 /* We cannot LASSERT(!list_empty(&job->js_list)) here,
244                  * since we just lost the race for inserting "job" into the
245                  * ojs_list, and some other thread is doing it _right_now_.
246                  * Instead, be content the other thread is doing this, since
247                  * "job2" was initialized in job_alloc() already. LU-2163 */
248         } else {
249                 LASSERT(list_empty(&job->js_list));
250                 write_lock(&stats->ojs_lock);
251                 list_add_tail(&job->js_list, &stats->ojs_list);
252                 write_unlock(&stats->ojs_lock);
253         }
254
255 found:
256         LASSERT(stats == job->js_jobstats);
257         LASSERT(stats->ojs_cntr_num > event);
258         job->js_timestamp = cfs_time_current_sec();
259         lprocfs_counter_add(job->js_stats, event, amount);
260
261         job_putref(job);
262         RETURN(0);
263 }
264 EXPORT_SYMBOL(lprocfs_job_stats_log);
265
266 void lprocfs_job_stats_fini(struct obd_device *obd)
267 {
268         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
269         time_t oldest = 0;
270
271         if (stats->ojs_hash == NULL)
272                 return;
273         cfs_hash_for_each_safe(stats->ojs_hash, job_iter_callback, &oldest);
274         cfs_hash_putref(stats->ojs_hash);
275         stats->ojs_hash = NULL;
276         LASSERT(list_empty(&stats->ojs_list));
277 }
278 EXPORT_SYMBOL(lprocfs_job_stats_fini);
279
280 static void *lprocfs_jobstats_seq_start(struct seq_file *p, loff_t *pos)
281 {
282         struct obd_job_stats *stats = p->private;
283         loff_t off = *pos;
284         struct job_stat *job;
285
286         read_lock(&stats->ojs_lock);
287         if (off == 0)
288                 return SEQ_START_TOKEN;
289         off--;
290         list_for_each_entry(job, &stats->ojs_list, js_list) {
291                 if (!off--)
292                         return job;
293         }
294         return NULL;
295 }
296
297 static void lprocfs_jobstats_seq_stop(struct seq_file *p, void *v)
298 {
299         struct obd_job_stats *stats = p->private;
300
301         read_unlock(&stats->ojs_lock);
302 }
303
304 static void *lprocfs_jobstats_seq_next(struct seq_file *p, void *v, loff_t *pos)
305 {
306         struct obd_job_stats *stats = p->private;
307         struct job_stat *job;
308         struct list_head *next;
309
310         ++*pos;
311         if (v == SEQ_START_TOKEN) {
312                 next = stats->ojs_list.next;
313         } else {
314                 job = (struct job_stat *)v;
315                 next = job->js_list.next;
316         }
317
318         return next == &stats->ojs_list ? NULL :
319                 list_entry(next, struct job_stat, js_list);
320 }
321
322 /*
323  * Example of output on MDT:
324  *
325  * job_stats:
326  * - job_id:        test_id.222.25844
327  *   snapshot_time: 1322494486
328  *   open:          { samples:         3, unit: reqs }
329  *   close:         { samples:         3, unit: reqs }
330  *   mknod:         { samples:         0, unit: reqs }
331  *   link:          { samples:         0, unit: reqs }
332  *   unlink:        { samples:         0, unit: reqs }
333  *   mkdir:         { samples:         0, unit: reqs }
334  *   rmdir:         { samples:         0, unit: reqs }
335  *   rename:        { samples:         1, unit: reqs }
336  *   getattr:       { samples:         7, unit: reqs }
337  *   setattr:       { samples:         0, unit: reqs }
338  *   getxattr:      { samples:         0, unit: reqs }
339  *   setxattr:      { samples:         0, unit: reqs }
340  *   statfs:        { samples:         0, unit: reqs }
341  *   sync:          { samples:         0, unit: reqs }
342  *
343  * Example of output on OST:
344  *
345  * job_stats:
346  * - job_id         4854
347  *   snapshot_time: 1322494602
348  *   read:          { samples:  0, unit: bytes, min:  0, max:  0, sum:  0 }
349  *   write:         { samples:  1, unit: bytes, min: 10, max: 10, sum: 10 }
350  *   setattr:       { samples:  0, unit: reqs }
351  *   punch:         { samples:  0, unit: reqs }
352  *   sync:          { samples:  0, unit: reqs }
353  */
354
355 static const char spaces[] = "                    ";
356
357 static int inline width(const char *str, int len)
358 {
359         return len - min((int)strlen(str), 15);
360 }
361
362 static int lprocfs_jobstats_seq_show(struct seq_file *p, void *v)
363 {
364         struct job_stat                 *job = v;
365         struct lprocfs_stats            *s;
366         struct lprocfs_counter          ret;
367         struct lprocfs_counter_header   *cntr_header;
368         int                             i;
369
370         if (v == SEQ_START_TOKEN) {
371                 seq_printf(p, "job_stats:\n");
372                 return 0;
373         }
374
375         seq_printf(p, "- %-16s %s\n", "job_id:", job->js_jobid);
376         seq_printf(p, "  %-16s %ld\n", "snapshot_time:", job->js_timestamp);
377
378         s = job->js_stats;
379         for (i = 0; i < s->ls_num; i++) {
380                 cntr_header = &s->ls_cnt_header[i];
381                 lprocfs_stats_collect(s, i, &ret);
382
383                 seq_printf(p, "  %s:%.*s { samples: %11"LPF64"u",
384                            cntr_header->lc_name,
385                            width(cntr_header->lc_name, 15), spaces,
386                            ret.lc_count);
387                 if (cntr_header->lc_units[0] != '\0')
388                         seq_printf(p, ", unit: %5s", cntr_header->lc_units);
389
390                 if (cntr_header->lc_config & LPROCFS_CNTR_AVGMINMAX) {
391                         seq_printf(p, ", min:%8"LPF64"u, max:%8"LPF64"u,"
392                                    " sum:%16"LPF64"u",
393                                    ret.lc_count ? ret.lc_min : 0,
394                                    ret.lc_count ? ret.lc_max : 0,
395                                    ret.lc_count ? ret.lc_sum : 0);
396                 }
397                 if (cntr_header->lc_config & LPROCFS_CNTR_STDDEV) {
398                         seq_printf(p, ", sumsq: %18"LPF64"u",
399                                    ret.lc_count ? ret.lc_sumsquare : 0);
400                 }
401
402                 seq_printf(p, " }\n");
403
404         }
405         return 0;
406 }
407
408 static const struct seq_operations lprocfs_jobstats_seq_sops = {
409         start: lprocfs_jobstats_seq_start,
410         stop:  lprocfs_jobstats_seq_stop,
411         next:  lprocfs_jobstats_seq_next,
412         show:  lprocfs_jobstats_seq_show,
413 };
414
415 static int lprocfs_jobstats_seq_open(struct inode *inode, struct file *file)
416 {
417         struct seq_file *seq;
418         int rc;
419
420         rc = LPROCFS_ENTRY_CHECK(inode);
421         if (rc < 0)
422                 return rc;
423
424         rc = seq_open(file, &lprocfs_jobstats_seq_sops);
425         if (rc)
426                 return rc;
427         seq = file->private_data;
428         seq->private = PDE_DATA(inode);
429         return 0;
430 }
431
432 static ssize_t lprocfs_jobstats_seq_write(struct file *file,
433                                           const char __user *buf,
434                                           size_t len, loff_t *off)
435 {
436         struct seq_file *seq = file->private_data;
437         struct obd_job_stats *stats = seq->private;
438         char jobid[LUSTRE_JOBID_SIZE];
439         int all = 0;
440         struct job_stat *job;
441
442         if (len == 0 || len >= LUSTRE_JOBID_SIZE)
443                 return -EINVAL;
444
445         if (copy_from_user(jobid, buf, len))
446                 return -EFAULT;
447         jobid[len] = 0;
448
449         /* Trim '\n' if any */
450         if (jobid[len - 1] == '\n')
451                 jobid[len - 1] = 0;
452
453         if (strcmp(jobid, "clear") == 0)
454                 all = 1;
455
456         LASSERT(stats->ojs_hash);
457         if (all) {
458                 time_t oldest = 0;
459                 cfs_hash_for_each_safe(stats->ojs_hash, job_iter_callback,
460                                        &oldest);
461                 return len;
462         }
463
464         if (!strlen(jobid))
465                 return -EINVAL;
466
467         job = cfs_hash_lookup(stats->ojs_hash, jobid);
468         if (!job)
469                 return -EINVAL;
470
471         cfs_hash_del_key(stats->ojs_hash, jobid);
472
473         job_putref(job);
474         return len;
475 }
476
477 static const struct file_operations lprocfs_jobstats_seq_fops = {
478         .owner   = THIS_MODULE,
479         .open    = lprocfs_jobstats_seq_open,
480         .read    = seq_read,
481         .write   = lprocfs_jobstats_seq_write,
482         .llseek  = seq_lseek,
483         .release = lprocfs_seq_release,
484 };
485
486 int lprocfs_job_stats_init(struct obd_device *obd, int cntr_num,
487                            cntr_init_callback init_fn)
488 {
489         struct proc_dir_entry *entry;
490         struct obd_job_stats *stats;
491         ENTRY;
492
493         LASSERT(obd->obd_proc_entry != NULL);
494         LASSERT(obd->obd_type->typ_name);
495
496         if (strcmp(obd->obd_type->typ_name, LUSTRE_MDT_NAME) &&
497             strcmp(obd->obd_type->typ_name, LUSTRE_OST_NAME)) {
498                 CERROR("Invalid obd device type.\n");
499                 RETURN(-EINVAL);
500         }
501         stats = &obd->u.obt.obt_jobstats;
502
503         LASSERT(stats->ojs_hash == NULL);
504         stats->ojs_hash = cfs_hash_create("JOB_STATS",
505                                           HASH_JOB_STATS_CUR_BITS,
506                                           HASH_JOB_STATS_MAX_BITS,
507                                           HASH_JOB_STATS_BKT_BITS, 0,
508                                           CFS_HASH_MIN_THETA,
509                                           CFS_HASH_MAX_THETA,
510                                           &job_stats_hash_ops,
511                                           CFS_HASH_DEFAULT);
512         if (stats->ojs_hash == NULL)
513                 RETURN(-ENOMEM);
514
515         INIT_LIST_HEAD(&stats->ojs_list);
516         rwlock_init(&stats->ojs_lock);
517         stats->ojs_cntr_num = cntr_num;
518         stats->ojs_cntr_init_fn = init_fn;
519         stats->ojs_cleanup_interval = 600; /* 10 mins by default */
520         stats->ojs_last_cleanup = cfs_time_current_sec();
521
522         entry = lprocfs_add_simple(obd->obd_proc_entry, "job_stats", stats,
523                                    &lprocfs_jobstats_seq_fops);
524         if (IS_ERR(entry)) {
525                 lprocfs_job_stats_fini(obd);
526                 RETURN(-ENOMEM);
527         }
528         RETURN(0);
529 }
530 EXPORT_SYMBOL(lprocfs_job_stats_init);
531
532 int lprocfs_job_interval_seq_show(struct seq_file *m, void *data)
533 {
534         struct obd_device *obd = m->private;
535         struct obd_job_stats *stats;
536
537         LASSERT(obd != NULL);
538         stats = &obd->u.obt.obt_jobstats;
539         return seq_printf(m, "%d\n", stats->ojs_cleanup_interval);
540 }
541 EXPORT_SYMBOL(lprocfs_job_interval_seq_show);
542
543 ssize_t
544 lprocfs_job_interval_seq_write(struct file *file, const char *buffer,
545                                 size_t count, loff_t *off)
546 {
547         struct obd_device *obd = ((struct seq_file *)file->private_data)->private;
548         struct obd_job_stats *stats;
549         int val, rc;
550
551         LASSERT(obd != NULL);
552         stats = &obd->u.obt.obt_jobstats;
553
554         rc = lprocfs_write_helper(buffer, count, &val);
555         if (rc)
556                 return rc;
557
558         stats->ojs_cleanup_interval = val;
559         lprocfs_job_cleanup(stats, true);
560         return count;
561 }
562 EXPORT_SYMBOL(lprocfs_job_interval_seq_write);
563 #endif /* CONFIG_PROC_FS*/