Whamcloud - gitweb
LU-9273 tests: disable random I/O in replay-ost-single/5
[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.gnu.org/licenses/gpl-2.0.html
18  *
19  * GPL HEADER END
20  */
21 /*
22  * Copyright (c) 2012, 2016, 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 #include <obd_class.h>
34 #include <lprocfs_status.h>
35
36 #ifdef CONFIG_PROC_FS
37
38 /*
39  * JobID formats & JobID environment variable names for supported
40  * job schedulers:
41  *
42  * SLURM:
43  *   JobID format:  32 bit integer.
44  *   JobID env var: SLURM_JOB_ID.
45  * SGE:
46  *   JobID format:  Decimal integer range to 99999.
47  *   JobID env var: JOB_ID.
48  * LSF:
49  *   JobID format:  6 digit integer by default (up to 999999), can be
50  *                increased to 10 digit (up to 2147483646).
51  *   JobID env var: LSB_JOBID.
52  * Loadleveler:
53  *   JobID format:  String of machine_name.cluster_id.process_id, for
54  *                example: fr2n02.32.0
55  *   JobID env var: LOADL_STEP_ID.
56  * PBS:
57  *   JobID format:  String of sequence_number[.server_name][@server].
58  *   JobID env var: PBS_JOBID.
59  * Maui/MOAB:
60  *   JobID format:  Same as PBS.
61  *   JobID env var: Same as PBS.
62  */
63
64 struct job_stat {
65         struct hlist_node       js_hash;        /* hash struct for this jobid */
66         struct list_head        js_list;        /* on ojs_list, with ojs_lock */
67         atomic_t                js_refcount;    /* num users of this struct */
68         char                    js_jobid[LUSTRE_JOBID_SIZE]; /* job name */
69         time64_t                js_timestamp;   /* seconds of most recent stat*/
70         struct lprocfs_stats    *js_stats;      /* per-job statistics */
71         struct obd_job_stats    *js_jobstats;   /* for accessing ojs_lock */
72 };
73
74 static unsigned
75 job_stat_hash(struct cfs_hash *hs, const void *key, unsigned mask)
76 {
77         return cfs_hash_djb2_hash(key, strlen(key), mask);
78 }
79
80 static void *job_stat_key(struct hlist_node *hnode)
81 {
82         struct job_stat *job;
83         job = hlist_entry(hnode, struct job_stat, js_hash);
84         return job->js_jobid;
85 }
86
87 static int job_stat_keycmp(const void *key, struct hlist_node *hnode)
88 {
89         struct job_stat *job;
90         job = hlist_entry(hnode, struct job_stat, js_hash);
91         return (strlen(job->js_jobid) == strlen(key)) &&
92                !strncmp(job->js_jobid, key, strlen(key));
93 }
94
95 static void *job_stat_object(struct hlist_node *hnode)
96 {
97         return hlist_entry(hnode, struct job_stat, js_hash);
98 }
99
100 static void job_stat_get(struct cfs_hash *hs, struct hlist_node *hnode)
101 {
102         struct job_stat *job;
103         job = hlist_entry(hnode, struct job_stat, js_hash);
104         atomic_inc(&job->js_refcount);
105 }
106
107 static void job_free(struct job_stat *job)
108 {
109         LASSERT(atomic_read(&job->js_refcount) == 0);
110         LASSERT(job->js_jobstats != NULL);
111
112         write_lock(&job->js_jobstats->ojs_lock);
113         list_del_init(&job->js_list);
114         write_unlock(&job->js_jobstats->ojs_lock);
115
116         lprocfs_free_stats(&job->js_stats);
117         OBD_FREE_PTR(job);
118 }
119
120 static void job_putref(struct job_stat *job)
121 {
122         LASSERT(atomic_read(&job->js_refcount) > 0);
123         if (atomic_dec_and_test(&job->js_refcount))
124                 job_free(job);
125 }
126
127 static void job_stat_put_locked(struct cfs_hash *hs, struct hlist_node *hnode)
128 {
129         struct job_stat *job;
130         job = hlist_entry(hnode, struct job_stat, js_hash);
131         job_putref(job);
132 }
133
134 static void job_stat_exit(struct cfs_hash *hs, struct hlist_node *hnode)
135 {
136         CERROR("should not have any items\n");
137 }
138
139 static struct cfs_hash_ops job_stats_hash_ops = {
140         .hs_hash       = job_stat_hash,
141         .hs_key        = job_stat_key,
142         .hs_keycmp     = job_stat_keycmp,
143         .hs_object     = job_stat_object,
144         .hs_get        = job_stat_get,
145         .hs_put_locked = job_stat_put_locked,
146         .hs_exit       = job_stat_exit,
147 };
148
149 /**
150  * Jobstats expiry iterator to clean up old jobids
151  *
152  * Called for each job_stat structure on this device, it should delete stats
153  * older than the specified \a oldest_time in seconds.  If \a oldest_time is
154  * in the future then this will delete all statistics (e.g. during shutdown).
155  *
156  * \param[in] hs        hash of all jobids on this device
157  * \param[in] bd        hash bucket containing this jobid
158  * \param[in] hnode     hash structure for this jobid
159  * \param[in] data      pointer to stats expiry time in seconds
160  */
161 static int job_cleanup_iter_callback(struct cfs_hash *hs,
162                                      struct cfs_hash_bd *bd,
163                                      struct hlist_node *hnode, void *data)
164 {
165         time64_t oldest_time = *((time64_t *)data);
166         struct job_stat *job;
167
168         job = hlist_entry(hnode, struct job_stat, js_hash);
169         if (job->js_timestamp < oldest_time)
170                 cfs_hash_bd_del_locked(hs, bd, hnode);
171
172         return 0;
173 }
174
175 /**
176  * Clean up jobstats that were updated more than \a before seconds ago.
177  *
178  * Since this function may be called frequently, do not scan all of the
179  * jobstats on each call, only twice per cleanup interval.  That means stats
180  * may be around on average cleanup_interval / 4 longer than necessary,
181  * but that is not considered harmful.
182  *
183  * If \a before is negative then this will force clean up all jobstats due
184  * to the expiry time being in the future (e.g. at shutdown).
185  *
186  * If there is already another thread doing jobstats cleanup, don't try to
187  * do this again in the current thread unless this is a force cleanup.
188  *
189  * \param[in] stats     stucture tracking all job stats for this device
190  * \param[in] before    expire jobstats updated more than this many seconds ago
191  */
192 static void lprocfs_job_cleanup(struct obd_job_stats *stats, int before)
193 {
194         time64_t now = ktime_get_real_seconds();
195         time64_t oldest;
196
197         if (likely(before >= 0)) {
198                 unsigned int cleanup_interval = stats->ojs_cleanup_interval;
199
200                 if (cleanup_interval == 0 || before == 0)
201                         return;
202
203                 if (now < stats->ojs_last_cleanup + cleanup_interval / 2)
204                         return;
205
206                 if (stats->ojs_cleaning)
207                         return;
208         }
209
210         write_lock(&stats->ojs_lock);
211         if (before >= 0 && stats->ojs_cleaning) {
212                 write_unlock(&stats->ojs_lock);
213                 return;
214         }
215
216         stats->ojs_cleaning = true;
217         write_unlock(&stats->ojs_lock);
218
219         /* Can't hold ojs_lock over hash iteration, since it is grabbed by
220          * job_cleanup_iter_callback()
221          *   ->cfs_hash_bd_del_locked()
222          *     ->job_putref()
223          *       ->job_free()
224          *
225          * Holding ojs_lock isn't necessary for safety of the hash iteration,
226          * since locking of the hash is handled internally, but there isn't
227          * any benefit to having multiple threads doing cleanup at one time.
228          */
229         oldest = now - before;
230         cfs_hash_for_each_safe(stats->ojs_hash, job_cleanup_iter_callback,
231                                &oldest);
232
233         write_lock(&stats->ojs_lock);
234         stats->ojs_cleaning = false;
235         stats->ojs_last_cleanup = ktime_get_real_seconds();
236         write_unlock(&stats->ojs_lock);
237 }
238
239 static struct job_stat *job_alloc(char *jobid, struct obd_job_stats *jobs)
240 {
241         struct job_stat *job;
242
243         OBD_ALLOC_PTR(job);
244         if (job == NULL)
245                 return NULL;
246
247         job->js_stats = lprocfs_alloc_stats(jobs->ojs_cntr_num, 0);
248         if (job->js_stats == NULL) {
249                 OBD_FREE_PTR(job);
250                 return NULL;
251         }
252
253         jobs->ojs_cntr_init_fn(job->js_stats);
254
255         memcpy(job->js_jobid, jobid, LUSTRE_JOBID_SIZE);
256         job->js_timestamp = ktime_get_real_seconds();
257         job->js_jobstats = jobs;
258         INIT_HLIST_NODE(&job->js_hash);
259         INIT_LIST_HEAD(&job->js_list);
260         atomic_set(&job->js_refcount, 1);
261
262         return job;
263 }
264
265 int lprocfs_job_stats_log(struct obd_device *obd, char *jobid,
266                           int event, long amount)
267 {
268         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
269         struct job_stat *job, *job2;
270         ENTRY;
271
272         LASSERT(stats != NULL);
273         LASSERT(stats->ojs_hash != NULL);
274
275         if (event >= stats->ojs_cntr_num)
276                 RETURN(-EINVAL);
277
278         if (jobid == NULL || strlen(jobid) == 0)
279                 RETURN(-EINVAL);
280
281         if (strlen(jobid) >= LUSTRE_JOBID_SIZE) {
282                 CERROR("Invalid jobid size (%lu), expect(%d)\n",
283                        (unsigned long)strlen(jobid) + 1, LUSTRE_JOBID_SIZE);
284                 RETURN(-EINVAL);
285         }
286
287         job = cfs_hash_lookup(stats->ojs_hash, jobid);
288         if (job)
289                 goto found;
290
291         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
292
293         job = job_alloc(jobid, stats);
294         if (job == NULL)
295                 RETURN(-ENOMEM);
296
297         job2 = cfs_hash_findadd_unique(stats->ojs_hash, job->js_jobid,
298                                        &job->js_hash);
299         if (job2 != job) {
300                 job_putref(job);
301                 job = job2;
302                 /* We cannot LASSERT(!list_empty(&job->js_list)) here,
303                  * since we just lost the race for inserting "job" into the
304                  * ojs_list, and some other thread is doing it _right_now_.
305                  * Instead, be content the other thread is doing this, since
306                  * "job2" was initialized in job_alloc() already. LU-2163 */
307         } else {
308                 LASSERT(list_empty(&job->js_list));
309                 write_lock(&stats->ojs_lock);
310                 list_add_tail(&job->js_list, &stats->ojs_list);
311                 write_unlock(&stats->ojs_lock);
312         }
313
314 found:
315         LASSERT(stats == job->js_jobstats);
316         job->js_timestamp = ktime_get_real_seconds();
317         lprocfs_counter_add(job->js_stats, event, amount);
318
319         job_putref(job);
320
321         RETURN(0);
322 }
323 EXPORT_SYMBOL(lprocfs_job_stats_log);
324
325 void lprocfs_job_stats_fini(struct obd_device *obd)
326 {
327         struct obd_job_stats *stats = &obd->u.obt.obt_jobstats;
328
329         if (stats->ojs_hash == NULL)
330                 return;
331
332         lprocfs_job_cleanup(stats, -99);
333         cfs_hash_putref(stats->ojs_hash);
334         stats->ojs_hash = NULL;
335         LASSERT(list_empty(&stats->ojs_list));
336 }
337 EXPORT_SYMBOL(lprocfs_job_stats_fini);
338
339 static void *lprocfs_jobstats_seq_start(struct seq_file *p, loff_t *pos)
340 {
341         struct obd_job_stats *stats = p->private;
342         loff_t off = *pos;
343         struct job_stat *job;
344
345         read_lock(&stats->ojs_lock);
346         if (off == 0)
347                 return SEQ_START_TOKEN;
348         off--;
349         list_for_each_entry(job, &stats->ojs_list, js_list) {
350                 if (!off--)
351                         return job;
352         }
353         return NULL;
354 }
355
356 static void lprocfs_jobstats_seq_stop(struct seq_file *p, void *v)
357 {
358         struct obd_job_stats *stats = p->private;
359
360         read_unlock(&stats->ojs_lock);
361 }
362
363 static void *lprocfs_jobstats_seq_next(struct seq_file *p, void *v, loff_t *pos)
364 {
365         struct obd_job_stats *stats = p->private;
366         struct job_stat *job;
367         struct list_head *next;
368
369         ++*pos;
370         if (v == SEQ_START_TOKEN) {
371                 next = stats->ojs_list.next;
372         } else {
373                 job = (struct job_stat *)v;
374                 next = job->js_list.next;
375         }
376
377         return next == &stats->ojs_list ? NULL :
378                 list_entry(next, struct job_stat, js_list);
379 }
380
381 /*
382  * Example of output on MDT:
383  *
384  * job_stats:
385  * - job_id:        dd.4854
386  *   snapshot_time: 1322494486
387  *   open:          { samples:         1, unit: reqs }
388  *   close:         { samples:         1, unit: reqs }
389  *   mknod:         { samples:         0, unit: reqs }
390  *   link:          { samples:         0, unit: reqs }
391  *   unlink:        { samples:         0, unit: reqs }
392  *   mkdir:         { samples:         0, unit: reqs }
393  *   rmdir:         { samples:         0, unit: reqs }
394  *   rename:        { samples:         0, unit: reqs }
395  *   getattr:       { samples:         1, unit: reqs }
396  *   setattr:       { samples:         0, unit: reqs }
397  *   getxattr:      { samples:         0, unit: reqs }
398  *   setxattr:      { samples:         0, unit: reqs }
399  *   statfs:        { samples:         0, unit: reqs }
400  *   sync:          { samples:         0, unit: reqs }
401  *
402  * Example of output on OST:
403  *
404  * job_stats:
405  * - job_id         dd.4854
406  *   snapshot_time: 1322494602
407  *   read:          { samples: 0, unit: bytes, min:  0, max:  0, sum:  0 }
408  *   write:         { samples: 1, unit: bytes, min: 4096, max: 4096, sum: 4096 }
409  *   setattr:       { samples: 0, unit: reqs }
410  *   punch:         { samples: 0, unit: reqs }
411  *   sync:          { samples: 0, unit: reqs }
412  */
413
414 static const char spaces[] = "                    ";
415
416 static int inline width(const char *str, int len)
417 {
418         return len - min((int)strlen(str), 15);
419 }
420
421 static int lprocfs_jobstats_seq_show(struct seq_file *p, void *v)
422 {
423         struct job_stat                 *job = v;
424         struct lprocfs_stats            *s;
425         struct lprocfs_counter          ret;
426         struct lprocfs_counter_header   *cntr_header;
427         int                             i;
428
429         if (v == SEQ_START_TOKEN) {
430                 seq_printf(p, "job_stats:\n");
431                 return 0;
432         }
433
434         /* Replace the non-printable character in jobid with '?', so
435          * that the output of jobid will be confined in single line. */
436         seq_printf(p, "- %-16s ", "job_id:");
437         for (i = 0; i < strlen(job->js_jobid); i++) {
438                 if (isprint(job->js_jobid[i]) != 0)
439                         seq_putc(p, job->js_jobid[i]);
440                 else
441                         seq_putc(p, '?');
442         }
443         seq_putc(p, '\n');
444
445         seq_printf(p, "  %-16s %lld\n", "snapshot_time:", job->js_timestamp);
446
447         s = job->js_stats;
448         for (i = 0; i < s->ls_num; i++) {
449                 cntr_header = &s->ls_cnt_header[i];
450                 lprocfs_stats_collect(s, i, &ret);
451
452                 seq_printf(p, "  %s:%.*s { samples: %11llu",
453                            cntr_header->lc_name,
454                            width(cntr_header->lc_name, 15), spaces,
455                            ret.lc_count);
456                 if (cntr_header->lc_units[0] != '\0')
457                         seq_printf(p, ", unit: %5s", cntr_header->lc_units);
458
459                 if (cntr_header->lc_config & LPROCFS_CNTR_AVGMINMAX) {
460                         seq_printf(p, ", min:%8llu, max:%8llu,"
461                                    " sum:%16llu",
462                                    ret.lc_count ? ret.lc_min : 0,
463                                    ret.lc_count ? ret.lc_max : 0,
464                                    ret.lc_count ? ret.lc_sum : 0);
465                 }
466                 if (cntr_header->lc_config & LPROCFS_CNTR_STDDEV) {
467                         seq_printf(p, ", sumsq: %18llu",
468                                    ret.lc_count ? ret.lc_sumsquare : 0);
469                 }
470
471                 seq_printf(p, " }\n");
472
473         }
474         return 0;
475 }
476
477 static const struct seq_operations lprocfs_jobstats_seq_sops = {
478         .start  = lprocfs_jobstats_seq_start,
479         .stop   = lprocfs_jobstats_seq_stop,
480         .next   = lprocfs_jobstats_seq_next,
481         .show   = lprocfs_jobstats_seq_show,
482 };
483
484 static int lprocfs_jobstats_seq_open(struct inode *inode, struct file *file)
485 {
486         struct seq_file *seq;
487         int rc;
488
489         rc = LPROCFS_ENTRY_CHECK(inode);
490         if (rc < 0)
491                 return rc;
492
493         rc = seq_open(file, &lprocfs_jobstats_seq_sops);
494         if (rc)
495                 return rc;
496         seq = file->private_data;
497         seq->private = PDE_DATA(inode);
498         return 0;
499 }
500
501 static ssize_t lprocfs_jobstats_seq_write(struct file *file,
502                                           const char __user *buf,
503                                           size_t len, loff_t *off)
504 {
505         struct seq_file *seq = file->private_data;
506         struct obd_job_stats *stats = seq->private;
507         char jobid[LUSTRE_JOBID_SIZE];
508         struct job_stat *job;
509
510         if (len == 0 || len >= LUSTRE_JOBID_SIZE)
511                 return -EINVAL;
512
513         if (stats->ojs_hash == NULL)
514                 return -ENODEV;
515
516         if (copy_from_user(jobid, buf, len))
517                 return -EFAULT;
518         jobid[len] = 0;
519
520         /* Trim '\n' if any */
521         if (jobid[len - 1] == '\n')
522                 jobid[len - 1] = 0;
523
524         if (strcmp(jobid, "clear") == 0) {
525                 lprocfs_job_cleanup(stats, -99);
526
527                 return len;
528         }
529
530         if (strlen(jobid) == 0)
531                 return -EINVAL;
532
533         job = cfs_hash_lookup(stats->ojs_hash, jobid);
534         if (!job)
535                 return -EINVAL;
536
537         cfs_hash_del_key(stats->ojs_hash, jobid);
538
539         job_putref(job);
540         return len;
541 }
542
543 /**
544  * Clean up the seq file state when the /proc file is closed.
545  *
546  * This also expires old job stats from the cache after they have been
547  * printed in case the system is idle and not generating new jobstats.
548  *
549  * \param[in] inode     struct inode for seq file being closed
550  * \param[in] file      struct file for seq file being closed
551  *
552  * \retval              0 on success
553  * \retval              negative errno on failure
554  */
555 static int lprocfs_jobstats_seq_release(struct inode *inode, struct file *file)
556 {
557         struct seq_file *seq = file->private_data;
558         struct obd_job_stats *stats = seq->private;
559
560         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
561
562         return lprocfs_seq_release(inode, file);
563 }
564
565 static const struct file_operations lprocfs_jobstats_seq_fops = {
566         .owner   = THIS_MODULE,
567         .open    = lprocfs_jobstats_seq_open,
568         .read    = seq_read,
569         .write   = lprocfs_jobstats_seq_write,
570         .llseek  = seq_lseek,
571         .release = lprocfs_jobstats_seq_release,
572 };
573
574 int lprocfs_job_stats_init(struct obd_device *obd, int cntr_num,
575                            cntr_init_callback init_fn)
576 {
577         struct proc_dir_entry *entry;
578         struct obd_job_stats *stats;
579         ENTRY;
580
581         LASSERT(obd->obd_proc_entry != NULL);
582         LASSERT(obd->obd_type->typ_name);
583
584         if (cntr_num <= 0)
585                 RETURN(-EINVAL);
586
587         if (init_fn == NULL)
588                 RETURN(-EINVAL);
589
590         /* Currently needs to be a target due to the use of obt_jobstats. */
591         if (strcmp(obd->obd_type->typ_name, LUSTRE_MDT_NAME) != 0 &&
592             strcmp(obd->obd_type->typ_name, LUSTRE_OST_NAME) != 0) {
593                 CERROR("%s: invalid device type %s for job stats: rc = %d\n",
594                        obd->obd_name, obd->obd_type->typ_name, -EINVAL);
595                 RETURN(-EINVAL);
596         }
597         stats = &obd->u.obt.obt_jobstats;
598
599         LASSERT(stats->ojs_hash == NULL);
600         stats->ojs_hash = cfs_hash_create("JOB_STATS",
601                                           HASH_JOB_STATS_CUR_BITS,
602                                           HASH_JOB_STATS_MAX_BITS,
603                                           HASH_JOB_STATS_BKT_BITS, 0,
604                                           CFS_HASH_MIN_THETA,
605                                           CFS_HASH_MAX_THETA,
606                                           &job_stats_hash_ops,
607                                           CFS_HASH_DEFAULT);
608         if (stats->ojs_hash == NULL)
609                 RETURN(-ENOMEM);
610
611         INIT_LIST_HEAD(&stats->ojs_list);
612         rwlock_init(&stats->ojs_lock);
613         stats->ojs_cntr_num = cntr_num;
614         stats->ojs_cntr_init_fn = init_fn;
615         stats->ojs_cleanup_interval = 600; /* 10 mins by default */
616         stats->ojs_last_cleanup = ktime_get_real_seconds();
617
618         entry = lprocfs_add_simple(obd->obd_proc_entry, "job_stats", stats,
619                                    &lprocfs_jobstats_seq_fops);
620         if (IS_ERR(entry)) {
621                 lprocfs_job_stats_fini(obd);
622                 RETURN(-ENOMEM);
623         }
624         RETURN(0);
625 }
626 EXPORT_SYMBOL(lprocfs_job_stats_init);
627
628 int lprocfs_job_interval_seq_show(struct seq_file *m, void *data)
629 {
630         struct obd_device *obd = m->private;
631         struct obd_job_stats *stats;
632
633         if (obd == NULL)
634                 return -ENODEV;
635
636         stats = &obd->u.obt.obt_jobstats;
637         seq_printf(m, "%d\n", stats->ojs_cleanup_interval);
638         return 0;
639 }
640 EXPORT_SYMBOL(lprocfs_job_interval_seq_show);
641
642 ssize_t
643 lprocfs_job_interval_seq_write(struct file *file, const char __user *buffer,
644                                 size_t count, loff_t *off)
645 {
646         struct obd_device *obd;
647         struct obd_job_stats *stats;
648         unsigned int val;
649         int rc;
650
651         obd = ((struct seq_file *)file->private_data)->private;
652         if (obd == NULL)
653                 return -ENODEV;
654
655         stats = &obd->u.obt.obt_jobstats;
656
657         rc = kstrtouint_from_user(buffer, count, 0, &val);
658         if (rc)
659                 return rc;
660
661         stats->ojs_cleanup_interval = val;
662         lprocfs_job_cleanup(stats, stats->ojs_cleanup_interval);
663         return count;
664 }
665 EXPORT_SYMBOL(lprocfs_job_interval_seq_write);
666 #endif /* CONFIG_PROC_FS*/