Whamcloud - gitweb
LU-6158 mdt: always shrink_capsule in getxattr_all
[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;        /* hash struct for this jobid */
68         struct list_head        js_list;        /* on ojs_list, with ojs_lock */
69         atomic_t                js_refcount;    /* num users of this struct */
70         char                    js_jobid[LUSTRE_JOBID_SIZE]; /* job name */
71         time_t                  js_timestamp;   /* seconds of most recent stat*/
72         struct lprocfs_stats    *js_stats;      /* per-job statistics */
73         struct obd_job_stats    *js_jobstats;   /* for accessing ojs_lock */
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 != NULL);
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 /**
152  * Jobstats expiry iterator to clean up old jobids
153  *
154  * Called for each job_stat structure on this device, it should delete stats
155  * older than the specified \a oldest_time in seconds.  If \a oldest_time is
156  * in the future then this will delete all statistics (e.g. during shutdown).
157  *
158  * \param[in] hs        hash of all jobids on this device
159  * \param[in] bd        hash bucket containing this jobid
160  * \param[in] hnode     hash structure for this jobid
161  * \param[in] data      pointer to stats expiry time in seconds
162  */
163 static int job_cleanup_iter_callback(struct cfs_hash *hs,
164                                      struct cfs_hash_bd *bd,
165                                      struct hlist_node *hnode, void *data)
166 {
167         time_t oldest_time = *((time_t *)data);
168         struct job_stat *job;
169
170         job = hlist_entry(hnode, struct job_stat, js_hash);
171         if (job->js_timestamp < oldest_time)
172                 cfs_hash_bd_del_locked(hs, bd, hnode);
173
174         return 0;
175 }
176
177 /**
178  * Clean up jobstats that were updated more than \a before seconds ago.
179  *
180  * Since this function may be called frequently, do not scan all of the
181  * jobstats on each call, only twice per cleanup interval.  That means stats
182  * may be around on average cleanup_interval / 4 longer than necessary,
183  * but that is not considered harmful.
184  *
185  * If \a before is negative then this will force clean up all jobstats due
186  * to the expiry time being in the future (e.g. at shutdown).
187  *
188  * If there is already another thread doing jobstats cleanup, don't try to
189  * do this again in the current thread unless this is a force cleanup.
190  *
191  * \param[in] stats     stucture tracking all job stats for this device
192  * \param[in] before    expire jobstats updated more than this many seconds ago
193  */
194 static void lprocfs_job_cleanup(struct obd_job_stats *stats, int before)
195 {
196         time_t now = cfs_time_current_sec();
197         time_t oldest;
198
199         if (likely(before >= 0)) {
200                 unsigned int cleanup_interval = stats->ojs_cleanup_interval;
201
202                 if (cleanup_interval == 0 || before == 0)
203                         return;
204
205                 if (now < stats->ojs_last_cleanup + cleanup_interval / 2)
206                         return;
207
208                 if (stats->ojs_cleaning)
209                         return;
210         }
211
212         write_lock(&stats->ojs_lock);
213         if (before >= 0 && stats->ojs_cleaning) {
214                 write_unlock(&stats->ojs_lock);
215                 return;
216         }
217
218         stats->ojs_cleaning = true;
219         write_unlock(&stats->ojs_lock);
220
221         /* Can't hold ojs_lock over hash iteration, since it is grabbed by
222          * job_cleanup_iter_callback()
223          *   ->cfs_hash_bd_del_locked()
224          *     ->job_putref()
225          *       ->job_free()
226          *
227          * Holding ojs_lock isn't necessary for safety of the hash iteration,
228          * since locking of the hash is handled internally, but there isn't
229          * any benefit to having multiple threads doing cleanup at one time.
230          */
231         oldest = now - before;
232         cfs_hash_for_each_safe(stats->ojs_hash, job_cleanup_iter_callback,
233                                &oldest);
234
235         write_lock(&stats->ojs_lock);
236         stats->ojs_cleaning = false;
237         stats->ojs_last_cleanup = cfs_time_current_sec();
238         write_unlock(&stats->ojs_lock);
239 }
240
241 static struct job_stat *job_alloc(char *jobid, struct obd_job_stats *jobs)
242 {
243         struct job_stat *job;
244
245         OBD_ALLOC_PTR(job);
246         if (job == NULL)
247                 return NULL;
248
249         job->js_stats = lprocfs_alloc_stats(jobs->ojs_cntr_num, 0);
250         if (job->js_stats == NULL) {
251                 OBD_FREE_PTR(job);
252                 return NULL;
253         }
254
255         jobs->ojs_cntr_init_fn(job->js_stats);
256
257         memcpy(job->js_jobid, jobid, LUSTRE_JOBID_SIZE);
258         job->js_timestamp = cfs_time_current_sec();
259         job->js_jobstats = jobs;
260         INIT_HLIST_NODE(&job->js_hash);
261         INIT_LIST_HEAD(&job->js_list);
262         atomic_set(&job->js_refcount, 1);
263
264         return job;
265 }
266
267 int lprocfs_job_stats_log(struct obd_device *obd, char *jobid,
268                           int event, long amount)
269 {
270         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
271         struct job_stat *job, *job2;
272         ENTRY;
273
274         LASSERT(stats != NULL);
275         LASSERT(stats->ojs_hash != NULL);
276
277         if (event >= stats->ojs_cntr_num)
278                 RETURN(-EINVAL);
279
280         if (jobid == NULL || strlen(jobid) == 0)
281                 RETURN(-EINVAL);
282
283         if (strlen(jobid) >= LUSTRE_JOBID_SIZE) {
284                 CERROR("Invalid jobid size (%lu), expect(%d)\n",
285                        (unsigned long)strlen(jobid) + 1, LUSTRE_JOBID_SIZE);
286                 RETURN(-EINVAL);
287         }
288
289         job = cfs_hash_lookup(stats->ojs_hash, jobid);
290         if (job)
291                 goto found;
292
293         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
294
295         job = job_alloc(jobid, stats);
296         if (job == NULL)
297                 RETURN(-ENOMEM);
298
299         job2 = cfs_hash_findadd_unique(stats->ojs_hash, job->js_jobid,
300                                        &job->js_hash);
301         if (job2 != job) {
302                 job_putref(job);
303                 job = job2;
304                 /* We cannot LASSERT(!list_empty(&job->js_list)) here,
305                  * since we just lost the race for inserting "job" into the
306                  * ojs_list, and some other thread is doing it _right_now_.
307                  * Instead, be content the other thread is doing this, since
308                  * "job2" was initialized in job_alloc() already. LU-2163 */
309         } else {
310                 LASSERT(list_empty(&job->js_list));
311                 write_lock(&stats->ojs_lock);
312                 list_add_tail(&job->js_list, &stats->ojs_list);
313                 write_unlock(&stats->ojs_lock);
314         }
315
316 found:
317         LASSERT(stats == job->js_jobstats);
318         job->js_timestamp = cfs_time_current_sec();
319         lprocfs_counter_add(job->js_stats, event, amount);
320
321         job_putref(job);
322
323         RETURN(0);
324 }
325 EXPORT_SYMBOL(lprocfs_job_stats_log);
326
327 void lprocfs_job_stats_fini(struct obd_device *obd)
328 {
329         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
330
331         if (stats->ojs_hash == NULL)
332                 return;
333
334         lprocfs_job_cleanup(stats, -99);
335         cfs_hash_putref(stats->ojs_hash);
336         stats->ojs_hash = NULL;
337         LASSERT(list_empty(&stats->ojs_list));
338 }
339 EXPORT_SYMBOL(lprocfs_job_stats_fini);
340
341 static void *lprocfs_jobstats_seq_start(struct seq_file *p, loff_t *pos)
342 {
343         struct obd_job_stats *stats = p->private;
344         loff_t off = *pos;
345         struct job_stat *job;
346
347         read_lock(&stats->ojs_lock);
348         if (off == 0)
349                 return SEQ_START_TOKEN;
350         off--;
351         list_for_each_entry(job, &stats->ojs_list, js_list) {
352                 if (!off--)
353                         return job;
354         }
355         return NULL;
356 }
357
358 static void lprocfs_jobstats_seq_stop(struct seq_file *p, void *v)
359 {
360         struct obd_job_stats *stats = p->private;
361
362         read_unlock(&stats->ojs_lock);
363 }
364
365 static void *lprocfs_jobstats_seq_next(struct seq_file *p, void *v, loff_t *pos)
366 {
367         struct obd_job_stats *stats = p->private;
368         struct job_stat *job;
369         struct list_head *next;
370
371         ++*pos;
372         if (v == SEQ_START_TOKEN) {
373                 next = stats->ojs_list.next;
374         } else {
375                 job = (struct job_stat *)v;
376                 next = job->js_list.next;
377         }
378
379         return next == &stats->ojs_list ? NULL :
380                 list_entry(next, struct job_stat, js_list);
381 }
382
383 /*
384  * Example of output on MDT:
385  *
386  * job_stats:
387  * - job_id:        dd.4854
388  *   snapshot_time: 1322494486
389  *   open:          { samples:         1, unit: reqs }
390  *   close:         { samples:         1, unit: reqs }
391  *   mknod:         { samples:         0, unit: reqs }
392  *   link:          { samples:         0, unit: reqs }
393  *   unlink:        { samples:         0, unit: reqs }
394  *   mkdir:         { samples:         0, unit: reqs }
395  *   rmdir:         { samples:         0, unit: reqs }
396  *   rename:        { samples:         0, unit: reqs }
397  *   getattr:       { samples:         1, unit: reqs }
398  *   setattr:       { samples:         0, unit: reqs }
399  *   getxattr:      { samples:         0, unit: reqs }
400  *   setxattr:      { samples:         0, unit: reqs }
401  *   statfs:        { samples:         0, unit: reqs }
402  *   sync:          { samples:         0, unit: reqs }
403  *
404  * Example of output on OST:
405  *
406  * job_stats:
407  * - job_id         dd.4854
408  *   snapshot_time: 1322494602
409  *   read:          { samples: 0, unit: bytes, min:  0, max:  0, sum:  0 }
410  *   write:         { samples: 1, unit: bytes, min: 4096, max: 4096, sum: 4096 }
411  *   setattr:       { samples: 0, unit: reqs }
412  *   punch:         { samples: 0, unit: reqs }
413  *   sync:          { samples: 0, unit: reqs }
414  */
415
416 static const char spaces[] = "                    ";
417
418 static int inline width(const char *str, int len)
419 {
420         return len - min((int)strlen(str), 15);
421 }
422
423 static int lprocfs_jobstats_seq_show(struct seq_file *p, void *v)
424 {
425         struct job_stat                 *job = v;
426         struct lprocfs_stats            *s;
427         struct lprocfs_counter          ret;
428         struct lprocfs_counter_header   *cntr_header;
429         int                             i;
430
431         if (v == SEQ_START_TOKEN) {
432                 seq_printf(p, "job_stats:\n");
433                 return 0;
434         }
435
436         seq_printf(p, "- %-16s %s\n", "job_id:", job->js_jobid);
437         seq_printf(p, "  %-16s %ld\n", "snapshot_time:", job->js_timestamp);
438
439         s = job->js_stats;
440         for (i = 0; i < s->ls_num; i++) {
441                 cntr_header = &s->ls_cnt_header[i];
442                 lprocfs_stats_collect(s, i, &ret);
443
444                 seq_printf(p, "  %s:%.*s { samples: %11"LPF64"u",
445                            cntr_header->lc_name,
446                            width(cntr_header->lc_name, 15), spaces,
447                            ret.lc_count);
448                 if (cntr_header->lc_units[0] != '\0')
449                         seq_printf(p, ", unit: %5s", cntr_header->lc_units);
450
451                 if (cntr_header->lc_config & LPROCFS_CNTR_AVGMINMAX) {
452                         seq_printf(p, ", min:%8"LPF64"u, max:%8"LPF64"u,"
453                                    " sum:%16"LPF64"u",
454                                    ret.lc_count ? ret.lc_min : 0,
455                                    ret.lc_count ? ret.lc_max : 0,
456                                    ret.lc_count ? ret.lc_sum : 0);
457                 }
458                 if (cntr_header->lc_config & LPROCFS_CNTR_STDDEV) {
459                         seq_printf(p, ", sumsq: %18"LPF64"u",
460                                    ret.lc_count ? ret.lc_sumsquare : 0);
461                 }
462
463                 seq_printf(p, " }\n");
464
465         }
466         return 0;
467 }
468
469 static const struct seq_operations lprocfs_jobstats_seq_sops = {
470         start: lprocfs_jobstats_seq_start,
471         stop:  lprocfs_jobstats_seq_stop,
472         next:  lprocfs_jobstats_seq_next,
473         show:  lprocfs_jobstats_seq_show,
474 };
475
476 static int lprocfs_jobstats_seq_open(struct inode *inode, struct file *file)
477 {
478         struct seq_file *seq;
479         int rc;
480
481         rc = LPROCFS_ENTRY_CHECK(inode);
482         if (rc < 0)
483                 return rc;
484
485         rc = seq_open(file, &lprocfs_jobstats_seq_sops);
486         if (rc)
487                 return rc;
488         seq = file->private_data;
489         seq->private = PDE_DATA(inode);
490         return 0;
491 }
492
493 static ssize_t lprocfs_jobstats_seq_write(struct file *file,
494                                           const char __user *buf,
495                                           size_t len, loff_t *off)
496 {
497         struct seq_file *seq = file->private_data;
498         struct obd_job_stats *stats = seq->private;
499         char jobid[LUSTRE_JOBID_SIZE];
500         struct job_stat *job;
501
502         if (len == 0 || len >= LUSTRE_JOBID_SIZE)
503                 return -EINVAL;
504
505         if (stats->ojs_hash == NULL)
506                 return -ENODEV;
507
508         if (copy_from_user(jobid, buf, len))
509                 return -EFAULT;
510         jobid[len] = 0;
511
512         /* Trim '\n' if any */
513         if (jobid[len - 1] == '\n')
514                 jobid[len - 1] = 0;
515
516         if (strcmp(jobid, "clear") == 0) {
517                 lprocfs_job_cleanup(stats, -99);
518
519                 return len;
520         }
521
522         if (strlen(jobid) == 0)
523                 return -EINVAL;
524
525         job = cfs_hash_lookup(stats->ojs_hash, jobid);
526         if (!job)
527                 return -EINVAL;
528
529         cfs_hash_del_key(stats->ojs_hash, jobid);
530
531         job_putref(job);
532         return len;
533 }
534
535 /**
536  * Clean up the seq file state when the /proc file is closed.
537  *
538  * This also expires old job stats from the cache after they have been
539  * printed in case the system is idle and not generating new jobstats.
540  *
541  * \param[in] inode     struct inode for seq file being closed
542  * \param[in] file      struct file for seq file being closed
543  *
544  * \retval              0 on success
545  * \retval              negative errno on failure
546  */
547 static int lprocfs_jobstats_seq_release(struct inode *inode, struct file *file)
548 {
549         struct seq_file *seq = file->private_data;
550         struct obd_job_stats *stats = seq->private;
551
552         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
553
554         return lprocfs_seq_release(inode, file);
555 }
556
557 static const struct file_operations lprocfs_jobstats_seq_fops = {
558         .owner   = THIS_MODULE,
559         .open    = lprocfs_jobstats_seq_open,
560         .read    = seq_read,
561         .write   = lprocfs_jobstats_seq_write,
562         .llseek  = seq_lseek,
563         .release = lprocfs_jobstats_seq_release,
564 };
565
566 int lprocfs_job_stats_init(struct obd_device *obd, int cntr_num,
567                            cntr_init_callback init_fn)
568 {
569         struct proc_dir_entry *entry;
570         struct obd_job_stats *stats;
571         ENTRY;
572
573         LASSERT(obd->obd_proc_entry != NULL);
574         LASSERT(obd->obd_type->typ_name);
575
576         if (cntr_num <= 0)
577                 RETURN(-EINVAL);
578
579         if (init_fn == NULL)
580                 RETURN(-EINVAL);
581
582         /* Currently needs to be a target due to the use of obt_jobstats. */
583         if (strcmp(obd->obd_type->typ_name, LUSTRE_MDT_NAME) != 0 &&
584             strcmp(obd->obd_type->typ_name, LUSTRE_OST_NAME) != 0) {
585                 CERROR("%s: invalid device type %s for job stats: rc = %d\n",
586                        obd->obd_name, obd->obd_type->typ_name, -EINVAL);
587                 RETURN(-EINVAL);
588         }
589         stats = &obd->u.obt.obt_jobstats;
590
591         LASSERT(stats->ojs_hash == NULL);
592         stats->ojs_hash = cfs_hash_create("JOB_STATS",
593                                           HASH_JOB_STATS_CUR_BITS,
594                                           HASH_JOB_STATS_MAX_BITS,
595                                           HASH_JOB_STATS_BKT_BITS, 0,
596                                           CFS_HASH_MIN_THETA,
597                                           CFS_HASH_MAX_THETA,
598                                           &job_stats_hash_ops,
599                                           CFS_HASH_DEFAULT);
600         if (stats->ojs_hash == NULL)
601                 RETURN(-ENOMEM);
602
603         INIT_LIST_HEAD(&stats->ojs_list);
604         rwlock_init(&stats->ojs_lock);
605         stats->ojs_cntr_num = cntr_num;
606         stats->ojs_cntr_init_fn = init_fn;
607         stats->ojs_cleanup_interval = 600; /* 10 mins by default */
608         stats->ojs_last_cleanup = cfs_time_current_sec();
609
610         entry = lprocfs_add_simple(obd->obd_proc_entry, "job_stats", stats,
611                                    &lprocfs_jobstats_seq_fops);
612         if (IS_ERR(entry)) {
613                 lprocfs_job_stats_fini(obd);
614                 RETURN(-ENOMEM);
615         }
616         RETURN(0);
617 }
618 EXPORT_SYMBOL(lprocfs_job_stats_init);
619
620 int lprocfs_job_interval_seq_show(struct seq_file *m, void *data)
621 {
622         struct obd_device *obd = m->private;
623         struct obd_job_stats *stats;
624
625         if (obd == NULL)
626                 return -ENODEV;
627
628         stats = &obd->u.obt.obt_jobstats;
629         return seq_printf(m, "%d\n", stats->ojs_cleanup_interval);
630 }
631 EXPORT_SYMBOL(lprocfs_job_interval_seq_show);
632
633 ssize_t
634 lprocfs_job_interval_seq_write(struct file *file, const char *buffer,
635                                 size_t count, loff_t *off)
636 {
637         struct obd_device *obd;
638         struct obd_job_stats *stats;
639         int val, rc;
640
641         obd = ((struct seq_file *)file->private_data)->private;
642         if (obd == NULL)
643                 return -ENODEV;
644
645         stats = &obd->u.obt.obt_jobstats;
646
647         rc = lprocfs_write_helper(buffer, count, &val);
648         if (rc)
649                 return rc;
650
651         stats->ojs_cleanup_interval = val;
652         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
653         return count;
654 }
655 EXPORT_SYMBOL(lprocfs_job_interval_seq_write);
656 #endif /* CONFIG_PROC_FS*/