Whamcloud - gitweb
LU-16639 misc: cleanup concole messages
[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 + NUL*/
69         ktime_t                 js_time_init;   /* time of initial stat*/
70         ktime_t                 js_time_latest; /* time of most recent stat*/
71         struct lprocfs_stats    *js_stats;      /* per-job statistics */
72         struct obd_job_stats    *js_jobstats;   /* for accessing ojs_lock */
73 };
74
75 static unsigned
76 job_stat_hash(struct cfs_hash *hs, const void *key, unsigned mask)
77 {
78         return cfs_hash_djb2_hash(key, strlen(key), mask);
79 }
80
81 static void *job_stat_key(struct hlist_node *hnode)
82 {
83         struct job_stat *job;
84         job = hlist_entry(hnode, struct job_stat, js_hash);
85         return job->js_jobid;
86 }
87
88 static int job_stat_keycmp(const void *key, struct hlist_node *hnode)
89 {
90         struct job_stat *job;
91         job = hlist_entry(hnode, struct job_stat, js_hash);
92         return (strlen(job->js_jobid) == strlen(key)) &&
93                !strncmp(job->js_jobid, key, strlen(key));
94 }
95
96 static void *job_stat_object(struct hlist_node *hnode)
97 {
98         return hlist_entry(hnode, struct job_stat, js_hash);
99 }
100
101 static void job_stat_get(struct cfs_hash *hs, struct hlist_node *hnode)
102 {
103         struct job_stat *job;
104         job = hlist_entry(hnode, struct job_stat, js_hash);
105         atomic_inc(&job->js_refcount);
106 }
107
108 static void job_free(struct job_stat *job)
109 {
110         LASSERT(atomic_read(&job->js_refcount) == 0);
111         LASSERT(job->js_jobstats != NULL);
112
113         write_lock(&job->js_jobstats->ojs_lock);
114         list_del_init(&job->js_list);
115         write_unlock(&job->js_jobstats->ojs_lock);
116
117         lprocfs_stats_free(&job->js_stats);
118         OBD_FREE_PTR(job);
119 }
120
121 static void job_putref(struct job_stat *job)
122 {
123         LASSERT(atomic_read(&job->js_refcount) > 0);
124         if (atomic_dec_and_test(&job->js_refcount))
125                 job_free(job);
126 }
127
128 static void job_stat_put_locked(struct cfs_hash *hs, struct hlist_node *hnode)
129 {
130         struct job_stat *job;
131
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         ktime_t oldest_time = *((ktime_t *)data);
168         struct job_stat *job;
169
170         job = hlist_entry(hnode, struct job_stat, js_hash);
171         if (ktime_before(job->js_time_latest, 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 on average around cleanup_interval / 4 older than the cleanup
183  * interval, but that is not considered harmful.
184  *
185  * The value stored in ojs_cleanup_interval is how often to perform a cleanup
186  * scan, and 1/2 of the maximum age of the individual statistics.  This is
187  * done rather than dividing the interval by two each time, because it is
188  * much easier to do the division when the value is initially set (in seconds)
189  * rather than after it has been converted to ktime_t, and maybe a bit faster.
190  *
191  * If \a clear is true then this will force clean up all jobstats
192  * (e.g. at shutdown).
193  *
194  * If there is already another thread doing jobstats cleanup, don't try to
195  * do this again in the current thread unless this is a force cleanup.
196  *
197  * \param[in] stats     stucture tracking all job stats for this device
198  * \param[in] clear     clear all job stats if true
199  */
200 static void lprocfs_job_cleanup(struct obd_job_stats *stats, bool clear)
201 {
202         ktime_t cleanup_interval = stats->ojs_cleanup_interval;
203         ktime_t now = ktime_get_real();
204         ktime_t oldest;
205
206         if (likely(!clear)) {
207                 /* ojs_cleanup_interval of zero means never clean up stats */
208                 if (ktime_to_ns(cleanup_interval) == 0)
209                         return;
210
211                 if (ktime_before(now, ktime_add(stats->ojs_cleanup_last,
212                                                 cleanup_interval)))
213                         return;
214
215                 if (stats->ojs_cleaning)
216                         return;
217         }
218
219         write_lock(&stats->ojs_lock);
220         if (!clear && stats->ojs_cleaning) {
221                 write_unlock(&stats->ojs_lock);
222                 return;
223         }
224
225         stats->ojs_cleaning = true;
226         write_unlock(&stats->ojs_lock);
227
228         /* Can't hold ojs_lock over hash iteration, since it is grabbed by
229          * job_cleanup_iter_callback()
230          *   ->cfs_hash_bd_del_locked()
231          *     ->job_putref()
232          *       ->job_free()
233          *
234          * Holding ojs_lock isn't necessary for safety of the hash iteration,
235          * since locking of the hash is handled internally, but there isn't
236          * any benefit to having multiple threads doing cleanup at one time.
237          *
238          * Subtract or add twice the cleanup_interval, since it is 1/2 the
239          * maximum age.  When clearing all stats, push oldest into the future.
240          */
241         cleanup_interval = ktime_add(cleanup_interval, cleanup_interval);
242         if (likely(!clear))
243                 oldest = ktime_sub(now, cleanup_interval);
244         else
245                 oldest = ktime_add(now, cleanup_interval);
246         cfs_hash_for_each_safe(stats->ojs_hash, job_cleanup_iter_callback,
247                                &oldest);
248
249         write_lock(&stats->ojs_lock);
250         stats->ojs_cleaning = false;
251         stats->ojs_cleanup_last = ktime_get_real();
252         write_unlock(&stats->ojs_lock);
253 }
254
255 static struct job_stat *job_alloc(char *jobid, struct obd_job_stats *jobs)
256 {
257         struct job_stat *job;
258
259         OBD_ALLOC_PTR(job);
260         if (job == NULL)
261                 return NULL;
262
263         job->js_stats = lprocfs_stats_alloc(jobs->ojs_cntr_num, 0);
264         if (job->js_stats == NULL) {
265                 OBD_FREE_PTR(job);
266                 return NULL;
267         }
268
269         jobs->ojs_cntr_init_fn(job->js_stats, 0, 0);
270
271         memcpy(job->js_jobid, jobid, sizeof(job->js_jobid));
272         job->js_time_latest = job->js_stats->ls_init;
273         job->js_jobstats = jobs;
274         INIT_HLIST_NODE(&job->js_hash);
275         INIT_LIST_HEAD(&job->js_list);
276         atomic_set(&job->js_refcount, 1);
277
278         return job;
279 }
280
281 int lprocfs_job_stats_log(struct obd_device *obd, char *jobid,
282                           int event, long amount)
283 {
284         struct obd_job_stats *stats = &obd2obt(obd)->obt_jobstats;
285         struct job_stat *job, *job2;
286         ENTRY;
287
288         LASSERT(stats != NULL);
289         LASSERT(stats->ojs_hash != NULL);
290
291         if (event >= stats->ojs_cntr_num)
292                 RETURN(-EINVAL);
293
294         if (jobid == NULL || strlen(jobid) == 0)
295                 RETURN(0);
296
297         /* unterminated jobid should be handled in lustre_msg_get_jobid() */
298         if (strlen(jobid) >= LUSTRE_JOBID_SIZE) {
299                 CERROR("%s: invalid jobid size %lu, expect %d\n", obd->obd_name,
300                        (unsigned long)strlen(jobid) + 1, LUSTRE_JOBID_SIZE);
301                 RETURN(-EINVAL);
302         }
303
304         job = cfs_hash_lookup(stats->ojs_hash, jobid);
305         if (job)
306                 goto found;
307
308         lprocfs_job_cleanup(stats, false);
309
310         job = job_alloc(jobid, stats);
311         if (job == NULL)
312                 RETURN(-ENOMEM);
313
314         job2 = cfs_hash_findadd_unique(stats->ojs_hash, job->js_jobid,
315                                        &job->js_hash);
316         if (job2 != job) {
317                 job_putref(job);
318                 job = job2;
319                 /* We cannot LASSERT(!list_empty(&job->js_list)) here,
320                  * since we just lost the race for inserting "job" into the
321                  * ojs_list, and some other thread is doing it _right_now_.
322                  * Instead, be content the other thread is doing this, since
323                  * "job2" was initialized in job_alloc() already. LU-2163 */
324         } else {
325                 LASSERT(list_empty(&job->js_list));
326                 write_lock(&stats->ojs_lock);
327                 list_add_tail(&job->js_list, &stats->ojs_list);
328                 write_unlock(&stats->ojs_lock);
329         }
330
331 found:
332         LASSERT(stats == job->js_jobstats);
333         job->js_time_latest = ktime_get_real();
334         lprocfs_counter_add(job->js_stats, event, amount);
335
336         job_putref(job);
337
338         RETURN(0);
339 }
340 EXPORT_SYMBOL(lprocfs_job_stats_log);
341
342 void lprocfs_job_stats_fini(struct obd_device *obd)
343 {
344         struct obd_job_stats *stats = &obd2obt(obd)->obt_jobstats;
345
346         if (stats->ojs_hash == NULL)
347                 return;
348
349         lprocfs_job_cleanup(stats, true);
350         cfs_hash_putref(stats->ojs_hash);
351         stats->ojs_hash = NULL;
352         LASSERT(list_empty(&stats->ojs_list));
353 }
354 EXPORT_SYMBOL(lprocfs_job_stats_fini);
355
356 static void *lprocfs_jobstats_seq_start(struct seq_file *p, loff_t *pos)
357 {
358         struct obd_job_stats *stats = p->private;
359         loff_t off = *pos;
360         struct job_stat *job;
361
362         read_lock(&stats->ojs_lock);
363         if (off == 0)
364                 return SEQ_START_TOKEN;
365         off--;
366         list_for_each_entry(job, &stats->ojs_list, js_list) {
367                 if (!off--)
368                         return job;
369         }
370         return NULL;
371 }
372
373 static void lprocfs_jobstats_seq_stop(struct seq_file *p, void *v)
374 {
375         struct obd_job_stats *stats = p->private;
376
377         read_unlock(&stats->ojs_lock);
378 }
379
380 static void *lprocfs_jobstats_seq_next(struct seq_file *p, void *v, loff_t *pos)
381 {
382         struct obd_job_stats *stats = p->private;
383         struct job_stat *job;
384         struct list_head *next;
385
386         ++*pos;
387         if (v == SEQ_START_TOKEN) {
388                 next = stats->ojs_list.next;
389         } else {
390                 job = (struct job_stat *)v;
391                 next = job->js_list.next;
392         }
393
394         return next == &stats->ojs_list ? NULL :
395                 list_entry(next, struct job_stat, js_list);
396 }
397
398 /*
399  * Example of output on MDT:
400  *
401  * job_stats:
402  * - job_id:        dd.4854
403  *   snapshot_time: 1322494486.123456789
404  *   start_time:    1322494476.012345678
405  *   elapsed_time:  10.111111111
406  *   open:          { samples:         1, unit: reqs }
407  *   close:         { samples:         1, unit: reqs }
408  *   mknod:         { samples:         0, unit: reqs }
409  *   link:          { samples:         0, unit: reqs }
410  *   unlink:        { samples:         0, unit: reqs }
411  *   mkdir:         { samples:         0, unit: reqs }
412  *   rmdir:         { samples:         0, unit: reqs }
413  *   rename:        { samples:         0, unit: reqs }
414  *   getattr:       { samples:         1, unit: reqs }
415  *   setattr:       { samples:         0, unit: reqs }
416  *   getxattr:      { samples:         0, unit: reqs }
417  *   setxattr:      { samples:         0, unit: reqs }
418  *   statfs:        { samples:         0, unit: reqs }
419  *   sync:          { samples:         0, unit: reqs }
420  *
421  * Example of output on OST:
422  *
423  * job_stats:
424  * - job_id         dd.4854
425  *   snapshot_time: 1322494602.123456789
426  *   start_time:    1322494592.987654321
427  *   elapsed_time:  9.135802468
428  *   read:          { samples: 0, unit: bytes, min:  0, max:  0, sum:  0 }
429  *   write:         { samples: 1, unit: bytes, min: 4096, max: 4096, sum: 4096 }
430  *   setattr:       { samples: 0, unit: reqs }
431  *   punch:         { samples: 0, unit: reqs }
432  *   sync:          { samples: 0, unit: reqs }
433  */
434
435 static const char spaces[] = "                    ";
436
437 static int inline width(const char *str, int len)
438 {
439         return len - min((int)strlen(str), 15);
440 }
441
442 static int lprocfs_jobstats_seq_show(struct seq_file *p, void *v)
443 {
444         struct job_stat *job = v;
445         struct lprocfs_stats *s;
446         struct lprocfs_counter ret;
447         struct lprocfs_counter_header *cntr_header;
448         char escaped[LUSTRE_JOBID_SIZE * 4] = "";
449         char *quote = "", *c, *end;
450         int i, joblen = 0;
451
452         if (v == SEQ_START_TOKEN) {
453                 seq_puts(p, "job_stats:\n");
454                 return 0;
455         }
456
457         /* Quote and escape jobid characters to escape hex codes "\xHH" if
458          * it contains any non-standard characters (space, newline, etc),
459          * so it will be confined to single line and not break parsing.
460          */
461         for (c = job->js_jobid, end = job->js_jobid + sizeof(job->js_jobid);
462              c < end && *c != '\0';
463              c++, joblen++) {
464                 if (!isalnum(*c) && strchr(".@-_:/", *c) == NULL) {
465                         quote = "\"";
466                         snprintf(escaped + joblen, sizeof(escaped), "\\x%02X",
467                                  (unsigned char)*c);
468                         joblen += 3;
469                 } else {
470                         escaped[joblen] = *c;
471                         /* if jobid has ':', it should be quoted too */
472                         if (*c == ':')
473                                 quote = "\"";
474                 }
475         }
476
477         seq_printf(p, "- %-16s %s%*s%s\n",
478                    "job_id:", quote, joblen, escaped, quote);
479         lprocfs_stats_header(p, job->js_time_latest, job->js_stats->ls_init,
480                              16, ":", true, "  ");
481
482         s = job->js_stats;
483         for (i = 0; i < s->ls_num; i++) {
484                 struct obd_histogram *hist;
485
486                 cntr_header = &s->ls_cnt_header[i];
487                 lprocfs_stats_collect(s, i, &ret);
488
489                 seq_printf(p, "  %s:%.*s { samples: %11llu",
490                            cntr_header->lc_name,
491                            width(cntr_header->lc_name, 15), spaces,
492                            ret.lc_count);
493                 if (cntr_header->lc_units[0] != '\0')
494                         seq_printf(p, ", unit: %5s", cntr_header->lc_units);
495
496                 if (cntr_header->lc_config & LPROCFS_CNTR_AVGMINMAX) {
497                         seq_printf(p, ", min: %8llu, max: %8llu, sum: %16llu",
498                                    ret.lc_count ? ret.lc_min : 0,
499                                    ret.lc_count ? ret.lc_max : 0,
500                                    ret.lc_count ? ret.lc_sum : 0);
501                 }
502                 if (cntr_header->lc_config & LPROCFS_CNTR_STDDEV) {
503                         seq_printf(p, ", sumsq: %18llu",
504                                    ret.lc_count ? ret.lc_sumsquare : 0);
505                 }
506
507                 /* show obd_histogram */
508                 hist = s->ls_cnt_header[i].lc_hist;
509                 if (hist != NULL) {
510                         bool first = true;
511                         int j;
512
513                         seq_puts(p, ", hist: { ");
514                         for (j = 0; j < ARRAY_SIZE(hist->oh_buckets); j++) {
515                                 unsigned long val = hist->oh_buckets[j];
516
517                                 if (val == 0)
518                                         continue;
519                                 if (first)
520                                         first = false;
521                                 else
522                                         seq_puts(p, ", ");
523
524                                 if (j < 10)
525                                         seq_printf(p, "%lu: %lu", BIT(j), val);
526                                 else if (j < 20)
527                                         seq_printf(p, "%luK: %lu", BIT(j - 10),
528                                                    val);
529                                 else if (j < 30)
530                                         seq_printf(p, "%luM: %lu", BIT(j - 20),
531                                                    val);
532                                 else
533                                         seq_printf(p, "%luG: %lu", BIT(j - 30),
534                                                    val);
535                         }
536                         seq_puts(p, " }");
537                 }
538                 seq_puts(p, " }\n");
539         }
540
541         return 0;
542 }
543
544 static const struct seq_operations lprocfs_jobstats_seq_sops = {
545         .start  = lprocfs_jobstats_seq_start,
546         .stop   = lprocfs_jobstats_seq_stop,
547         .next   = lprocfs_jobstats_seq_next,
548         .show   = lprocfs_jobstats_seq_show,
549 };
550
551 static int lprocfs_jobstats_seq_open(struct inode *inode, struct file *file)
552 {
553         struct seq_file *seq;
554         int rc;
555
556         rc = seq_open(file, &lprocfs_jobstats_seq_sops);
557         if (rc)
558                 return rc;
559         seq = file->private_data;
560         seq->private = pde_data(inode);
561         return 0;
562 }
563
564 static ssize_t lprocfs_jobstats_seq_write(struct file *file,
565                                           const char __user *buf,
566                                           size_t len, loff_t *off)
567 {
568         struct seq_file *seq = file->private_data;
569         struct obd_job_stats *stats = seq->private;
570         char jobid[4 * LUSTRE_JOBID_SIZE]; /* all escaped chars, plus ""\n\0 */
571         char *p1, *p2, *last;
572         unsigned int c;
573         struct job_stat *job;
574
575         if (len == 0 || len >= 4 * LUSTRE_JOBID_SIZE)
576                 return -EINVAL;
577
578         if (stats->ojs_hash == NULL)
579                 return -ENODEV;
580
581         if (copy_from_user(jobid, buf, len))
582                 return -EFAULT;
583         jobid[len] = 0;
584         last = jobid + len - 1;
585
586         /* Trim '\n' if any */
587         if (*last == '\n')
588                 *(last--) = 0;
589
590         /* decode escaped chars if jobid is a quoted string */
591         if (jobid[0] == '"' && *last == '"') {
592                 last--;
593
594                 for (p1 = jobid, p2 = jobid + 1; p2 <= last; p1++, p2++) {
595                         if (*p2 != '\\') {
596                                 *p1 = *p2;
597                         } else if (p2 + 3 <= last && *(p2 + 1) == 'x' &&
598                                  sscanf(p2 + 2, "%02X", &c) == 1) {
599                                 *p1 = c;
600                                 p2 += 3;
601                         } else {
602                                 return -EINVAL;
603                         }
604                 }
605                 *p1 = 0;
606
607         }
608         jobid[LUSTRE_JOBID_SIZE - 1] = 0;
609
610         if (strcmp(jobid, "clear") == 0) {
611                 lprocfs_job_cleanup(stats, true);
612
613                 return len;
614         }
615
616         if (strlen(jobid) == 0)
617                 return -EINVAL;
618
619         job = cfs_hash_lookup(stats->ojs_hash, jobid);
620         if (!job)
621                 return -EINVAL;
622
623         cfs_hash_del_key(stats->ojs_hash, jobid);
624
625         job_putref(job);
626         return len;
627 }
628
629 /**
630  * Clean up the seq file state when the /proc file is closed.
631  *
632  * This also expires old job stats from the cache after they have been
633  * printed in case the system is idle and not generating new jobstats.
634  *
635  * \param[in] inode     struct inode for seq file being closed
636  * \param[in] file      struct file for seq file being closed
637  *
638  * \retval              0 on success
639  * \retval              negative errno on failure
640  */
641 static int lprocfs_jobstats_seq_release(struct inode *inode, struct file *file)
642 {
643         struct seq_file *seq = file->private_data;
644         struct obd_job_stats *stats = seq->private;
645
646         lprocfs_job_cleanup(stats, false);
647
648         return lprocfs_seq_release(inode, file);
649 }
650
651 static const struct proc_ops lprocfs_jobstats_seq_fops = {
652         PROC_OWNER(THIS_MODULE)
653         .proc_open      = lprocfs_jobstats_seq_open,
654         .proc_read      = seq_read,
655         .proc_write     = lprocfs_jobstats_seq_write,
656         .proc_lseek     = seq_lseek,
657         .proc_release   = lprocfs_jobstats_seq_release,
658 };
659
660 int lprocfs_job_stats_init(struct obd_device *obd, int cntr_num,
661                            cntr_init_callback init_fn)
662 {
663         struct proc_dir_entry *entry;
664         struct obd_job_stats *stats;
665         ENTRY;
666
667         LASSERT(obd->obd_proc_entry != NULL);
668         LASSERT(obd->obd_type->typ_name);
669
670         if (cntr_num <= 0)
671                 RETURN(-EINVAL);
672
673         if (init_fn == NULL)
674                 RETURN(-EINVAL);
675
676         /* Currently needs to be a target due to the use of obt_jobstats. */
677         if (strcmp(obd->obd_type->typ_name, LUSTRE_MDT_NAME) != 0 &&
678             strcmp(obd->obd_type->typ_name, LUSTRE_OST_NAME) != 0) {
679                 CERROR("%s: invalid device type %s for job stats: rc = %d\n",
680                        obd->obd_name, obd->obd_type->typ_name, -EINVAL);
681                 RETURN(-EINVAL);
682         }
683         stats = &obd2obt(obd)->obt_jobstats;
684
685         LASSERT(stats->ojs_hash == NULL);
686         stats->ojs_hash = cfs_hash_create("JOB_STATS",
687                                           HASH_JOB_STATS_CUR_BITS,
688                                           HASH_JOB_STATS_MAX_BITS,
689                                           HASH_JOB_STATS_BKT_BITS, 0,
690                                           CFS_HASH_MIN_THETA,
691                                           CFS_HASH_MAX_THETA,
692                                           &job_stats_hash_ops,
693                                           CFS_HASH_DEFAULT);
694         if (stats->ojs_hash == NULL)
695                 RETURN(-ENOMEM);
696
697         INIT_LIST_HEAD(&stats->ojs_list);
698         rwlock_init(&stats->ojs_lock);
699         stats->ojs_cntr_num = cntr_num;
700         stats->ojs_cntr_init_fn = init_fn;
701         /* Store 1/2 the actual interval, since we use that the most, and
702          * it is easier to work with.
703          */
704         stats->ojs_cleanup_interval = ktime_set(600 / 2, 0); /* default 10 min*/
705         stats->ojs_cleanup_last = ktime_get_real();
706
707         entry = lprocfs_add_simple(obd->obd_proc_entry, "job_stats", stats,
708                                    &lprocfs_jobstats_seq_fops);
709         if (IS_ERR(entry)) {
710                 lprocfs_job_stats_fini(obd);
711                 RETURN(-ENOMEM);
712         }
713         RETURN(0);
714 }
715 EXPORT_SYMBOL(lprocfs_job_stats_init);
716 #endif /* CONFIG_PROC_FS*/
717
718 ssize_t job_cleanup_interval_show(struct kobject *kobj, struct attribute *attr,
719                                   char *buf)
720 {
721         struct obd_device *obd = container_of(kobj, struct obd_device,
722                                               obd_kset.kobj);
723         struct obd_job_stats *stats;
724         struct timespec64 ts;
725
726         stats = &obd2obt(obd)->obt_jobstats;
727         ts = ktime_to_timespec64(stats->ojs_cleanup_interval);
728
729         return scnprintf(buf, PAGE_SIZE, "%lld\n", (long long)ts.tv_sec * 2);
730 }
731 EXPORT_SYMBOL(job_cleanup_interval_show);
732
733 ssize_t job_cleanup_interval_store(struct kobject *kobj,
734                                    struct attribute *attr,
735                                    const char *buffer, size_t count)
736 {
737         struct obd_device *obd = container_of(kobj, struct obd_device,
738                                               obd_kset.kobj);
739         struct obd_job_stats *stats;
740         unsigned int val;
741         int rc;
742
743         stats = &obd2obt(obd)->obt_jobstats;
744
745         rc = kstrtouint(buffer, 0, &val);
746         if (rc)
747                 return rc;
748
749         stats->ojs_cleanup_interval = ktime_set(val / 2, 0);
750         lprocfs_job_cleanup(stats, false);
751
752         return count;
753 }
754 EXPORT_SYMBOL(job_cleanup_interval_store);