jbd2: use non-racy method for proc entries creation
[pandora-kernel.git] / fs / jbd2 / journal.c
index 6ddc553..eb7eb6c 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/poison.h>
 #include <linux/proc_fs.h>
 #include <linux/debugfs.h>
+#include <linux/seq_file.h>
 
 #include <asm/uaccess.h>
 #include <asm/page.h>
@@ -218,7 +219,7 @@ static int jbd2_journal_start_thread(journal_t *journal)
        if (IS_ERR(t))
                return PTR_ERR(t);
 
-       wait_event(journal->j_wait_done_commit, journal->j_task != 0);
+       wait_event(journal->j_wait_done_commit, journal->j_task != NULL);
        return 0;
 }
 
@@ -230,7 +231,7 @@ static void journal_kill_thread(journal_t *journal)
        while (journal->j_task) {
                wake_up(&journal->j_wait_commit);
                spin_unlock(&journal->j_state_lock);
-               wait_event(journal->j_wait_done_commit, journal->j_task == 0);
+               wait_event(journal->j_wait_done_commit, journal->j_task == NULL);
                spin_lock(&journal->j_state_lock);
        }
        spin_unlock(&journal->j_state_lock);
@@ -640,6 +641,303 @@ struct journal_head *jbd2_journal_get_descriptor_buffer(journal_t *journal)
        return jbd2_journal_add_journal_head(bh);
 }
 
+struct jbd2_stats_proc_session {
+       journal_t *journal;
+       struct transaction_stats_s *stats;
+       int start;
+       int max;
+};
+
+static void *jbd2_history_skip_empty(struct jbd2_stats_proc_session *s,
+                                       struct transaction_stats_s *ts,
+                                       int first)
+{
+       if (ts == s->stats + s->max)
+               ts = s->stats;
+       if (!first && ts == s->stats + s->start)
+               return NULL;
+       while (ts->ts_type == 0) {
+               ts++;
+               if (ts == s->stats + s->max)
+                       ts = s->stats;
+               if (ts == s->stats + s->start)
+                       return NULL;
+       }
+       return ts;
+
+}
+
+static void *jbd2_seq_history_start(struct seq_file *seq, loff_t *pos)
+{
+       struct jbd2_stats_proc_session *s = seq->private;
+       struct transaction_stats_s *ts;
+       int l = *pos;
+
+       if (l == 0)
+               return SEQ_START_TOKEN;
+       ts = jbd2_history_skip_empty(s, s->stats + s->start, 1);
+       if (!ts)
+               return NULL;
+       l--;
+       while (l) {
+               ts = jbd2_history_skip_empty(s, ++ts, 0);
+               if (!ts)
+                       break;
+               l--;
+       }
+       return ts;
+}
+
+static void *jbd2_seq_history_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+       struct jbd2_stats_proc_session *s = seq->private;
+       struct transaction_stats_s *ts = v;
+
+       ++*pos;
+       if (v == SEQ_START_TOKEN)
+               return jbd2_history_skip_empty(s, s->stats + s->start, 1);
+       else
+               return jbd2_history_skip_empty(s, ++ts, 0);
+}
+
+static int jbd2_seq_history_show(struct seq_file *seq, void *v)
+{
+       struct transaction_stats_s *ts = v;
+       if (v == SEQ_START_TOKEN) {
+               seq_printf(seq, "%-4s %-5s %-5s %-5s %-5s %-5s %-5s %-6s %-5s "
+                               "%-5s %-5s %-5s %-5s %-5s\n", "R/C", "tid",
+                               "wait", "run", "lock", "flush", "log", "hndls",
+                               "block", "inlog", "ctime", "write", "drop",
+                               "close");
+               return 0;
+       }
+       if (ts->ts_type == JBD2_STATS_RUN)
+               seq_printf(seq, "%-4s %-5lu %-5u %-5u %-5u %-5u %-5u "
+                               "%-6lu %-5lu %-5lu\n", "R", ts->ts_tid,
+                               jiffies_to_msecs(ts->u.run.rs_wait),
+                               jiffies_to_msecs(ts->u.run.rs_running),
+                               jiffies_to_msecs(ts->u.run.rs_locked),
+                               jiffies_to_msecs(ts->u.run.rs_flushing),
+                               jiffies_to_msecs(ts->u.run.rs_logging),
+                               ts->u.run.rs_handle_count,
+                               ts->u.run.rs_blocks,
+                               ts->u.run.rs_blocks_logged);
+       else if (ts->ts_type == JBD2_STATS_CHECKPOINT)
+               seq_printf(seq, "%-4s %-5lu %48s %-5u %-5lu %-5lu %-5lu\n",
+                               "C", ts->ts_tid, " ",
+                               jiffies_to_msecs(ts->u.chp.cs_chp_time),
+                               ts->u.chp.cs_written, ts->u.chp.cs_dropped,
+                               ts->u.chp.cs_forced_to_close);
+       else
+               J_ASSERT(0);
+       return 0;
+}
+
+static void jbd2_seq_history_stop(struct seq_file *seq, void *v)
+{
+}
+
+static struct seq_operations jbd2_seq_history_ops = {
+       .start  = jbd2_seq_history_start,
+       .next   = jbd2_seq_history_next,
+       .stop   = jbd2_seq_history_stop,
+       .show   = jbd2_seq_history_show,
+};
+
+static int jbd2_seq_history_open(struct inode *inode, struct file *file)
+{
+       journal_t *journal = PDE(inode)->data;
+       struct jbd2_stats_proc_session *s;
+       int rc, size;
+
+       s = kmalloc(sizeof(*s), GFP_KERNEL);
+       if (s == NULL)
+               return -ENOMEM;
+       size = sizeof(struct transaction_stats_s) * journal->j_history_max;
+       s->stats = kmalloc(size, GFP_KERNEL);
+       if (s->stats == NULL) {
+               kfree(s);
+               return -ENOMEM;
+       }
+       spin_lock(&journal->j_history_lock);
+       memcpy(s->stats, journal->j_history, size);
+       s->max = journal->j_history_max;
+       s->start = journal->j_history_cur % s->max;
+       spin_unlock(&journal->j_history_lock);
+
+       rc = seq_open(file, &jbd2_seq_history_ops);
+       if (rc == 0) {
+               struct seq_file *m = file->private_data;
+               m->private = s;
+       } else {
+               kfree(s->stats);
+               kfree(s);
+       }
+       return rc;
+
+}
+
+static int jbd2_seq_history_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *seq = file->private_data;
+       struct jbd2_stats_proc_session *s = seq->private;
+
+       kfree(s->stats);
+       kfree(s);
+       return seq_release(inode, file);
+}
+
+static struct file_operations jbd2_seq_history_fops = {
+       .owner          = THIS_MODULE,
+       .open           = jbd2_seq_history_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = jbd2_seq_history_release,
+};
+
+static void *jbd2_seq_info_start(struct seq_file *seq, loff_t *pos)
+{
+       return *pos ? NULL : SEQ_START_TOKEN;
+}
+
+static void *jbd2_seq_info_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+       return NULL;
+}
+
+static int jbd2_seq_info_show(struct seq_file *seq, void *v)
+{
+       struct jbd2_stats_proc_session *s = seq->private;
+
+       if (v != SEQ_START_TOKEN)
+               return 0;
+       seq_printf(seq, "%lu transaction, each upto %u blocks\n",
+                       s->stats->ts_tid,
+                       s->journal->j_max_transaction_buffers);
+       if (s->stats->ts_tid == 0)
+               return 0;
+       seq_printf(seq, "average: \n  %ums waiting for transaction\n",
+           jiffies_to_msecs(s->stats->u.run.rs_wait / s->stats->ts_tid));
+       seq_printf(seq, "  %ums running transaction\n",
+           jiffies_to_msecs(s->stats->u.run.rs_running / s->stats->ts_tid));
+       seq_printf(seq, "  %ums transaction was being locked\n",
+           jiffies_to_msecs(s->stats->u.run.rs_locked / s->stats->ts_tid));
+       seq_printf(seq, "  %ums flushing data (in ordered mode)\n",
+           jiffies_to_msecs(s->stats->u.run.rs_flushing / s->stats->ts_tid));
+       seq_printf(seq, "  %ums logging transaction\n",
+           jiffies_to_msecs(s->stats->u.run.rs_logging / s->stats->ts_tid));
+       seq_printf(seq, "  %lu handles per transaction\n",
+           s->stats->u.run.rs_handle_count / s->stats->ts_tid);
+       seq_printf(seq, "  %lu blocks per transaction\n",
+           s->stats->u.run.rs_blocks / s->stats->ts_tid);
+       seq_printf(seq, "  %lu logged blocks per transaction\n",
+           s->stats->u.run.rs_blocks_logged / s->stats->ts_tid);
+       return 0;
+}
+
+static void jbd2_seq_info_stop(struct seq_file *seq, void *v)
+{
+}
+
+static struct seq_operations jbd2_seq_info_ops = {
+       .start  = jbd2_seq_info_start,
+       .next   = jbd2_seq_info_next,
+       .stop   = jbd2_seq_info_stop,
+       .show   = jbd2_seq_info_show,
+};
+
+static int jbd2_seq_info_open(struct inode *inode, struct file *file)
+{
+       journal_t *journal = PDE(inode)->data;
+       struct jbd2_stats_proc_session *s;
+       int rc, size;
+
+       s = kmalloc(sizeof(*s), GFP_KERNEL);
+       if (s == NULL)
+               return -ENOMEM;
+       size = sizeof(struct transaction_stats_s);
+       s->stats = kmalloc(size, GFP_KERNEL);
+       if (s->stats == NULL) {
+               kfree(s);
+               return -ENOMEM;
+       }
+       spin_lock(&journal->j_history_lock);
+       memcpy(s->stats, &journal->j_stats, size);
+       s->journal = journal;
+       spin_unlock(&journal->j_history_lock);
+
+       rc = seq_open(file, &jbd2_seq_info_ops);
+       if (rc == 0) {
+               struct seq_file *m = file->private_data;
+               m->private = s;
+       } else {
+               kfree(s->stats);
+               kfree(s);
+       }
+       return rc;
+
+}
+
+static int jbd2_seq_info_release(struct inode *inode, struct file *file)
+{
+       struct seq_file *seq = file->private_data;
+       struct jbd2_stats_proc_session *s = seq->private;
+       kfree(s->stats);
+       kfree(s);
+       return seq_release(inode, file);
+}
+
+static struct file_operations jbd2_seq_info_fops = {
+       .owner          = THIS_MODULE,
+       .open           = jbd2_seq_info_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = jbd2_seq_info_release,
+};
+
+static struct proc_dir_entry *proc_jbd2_stats;
+
+static void jbd2_stats_proc_init(journal_t *journal)
+{
+       char name[BDEVNAME_SIZE];
+
+       snprintf(name, sizeof(name) - 1, "%s", bdevname(journal->j_dev, name));
+       journal->j_proc_entry = proc_mkdir(name, proc_jbd2_stats);
+       if (journal->j_proc_entry) {
+               proc_create_data("history", S_IRUGO, journal->j_proc_entry,
+                                &jbd2_seq_history_fops, journal);
+               proc_create_data("info", S_IRUGO, journal->j_proc_entry,
+                                &jbd2_seq_info_fops, journal);
+       }
+}
+
+static void jbd2_stats_proc_exit(journal_t *journal)
+{
+       char name[BDEVNAME_SIZE];
+
+       snprintf(name, sizeof(name) - 1, "%s", bdevname(journal->j_dev, name));
+       remove_proc_entry("info", journal->j_proc_entry);
+       remove_proc_entry("history", journal->j_proc_entry);
+       remove_proc_entry(name, proc_jbd2_stats);
+}
+
+static void journal_init_stats(journal_t *journal)
+{
+       int size;
+
+       if (!proc_jbd2_stats)
+               return;
+
+       journal->j_history_max = 100;
+       size = sizeof(struct transaction_stats_s) * journal->j_history_max;
+       journal->j_history = kzalloc(size, GFP_KERNEL);
+       if (!journal->j_history) {
+               journal->j_history_max = 0;
+               return;
+       }
+       spin_lock_init(&journal->j_history_lock);
+}
+
 /*
  * Management for journal control blocks: functions to create and
  * destroy journal_t structures, and to initialise and read existing
@@ -681,6 +979,9 @@ static journal_t * journal_init_common (void)
                kfree(journal);
                goto fail;
        }
+
+       journal_init_stats(journal);
+
        return journal;
 fail:
        return NULL;
@@ -735,6 +1036,7 @@ journal_t * jbd2_journal_init_dev(struct block_device *bdev,
        journal->j_fs_dev = fs_dev;
        journal->j_blk_offset = start;
        journal->j_maxlen = len;
+       jbd2_stats_proc_init(journal);
 
        bh = __getblk(journal->j_dev, start, journal->j_blocksize);
        J_ASSERT(bh != NULL);
@@ -773,6 +1075,7 @@ journal_t * jbd2_journal_init_inode (struct inode *inode)
 
        journal->j_maxlen = inode->i_size >> inode->i_sb->s_blocksize_bits;
        journal->j_blocksize = inode->i_sb->s_blocksize;
+       jbd2_stats_proc_init(journal);
 
        /* journal descriptor can store up to n blocks -bzzz */
        n = journal->j_blocksize / sizeof(journal_block_tag_t);
@@ -1153,6 +1456,8 @@ void jbd2_journal_destroy(journal_t *journal)
                brelse(journal->j_sb_buffer);
        }
 
+       if (journal->j_proc_entry)
+               jbd2_stats_proc_exit(journal);
        if (journal->j_inode)
                iput(journal->j_inode);
        if (journal->j_revoke)
@@ -1264,6 +1569,32 @@ int jbd2_journal_set_features (journal_t *journal, unsigned long compat,
        return 1;
 }
 
+/*
+ * jbd2_journal_clear_features () - Clear a given journal feature in the
+ *                                 superblock
+ * @journal: Journal to act on.
+ * @compat: bitmask of compatible features
+ * @ro: bitmask of features that force read-only mount
+ * @incompat: bitmask of incompatible features
+ *
+ * Clear a given journal feature as present on the
+ * superblock.
+ */
+void jbd2_journal_clear_features(journal_t *journal, unsigned long compat,
+                               unsigned long ro, unsigned long incompat)
+{
+       journal_superblock_t *sb;
+
+       jbd_debug(1, "Clear features 0x%lx/0x%lx/0x%lx\n",
+                 compat, ro, incompat);
+
+       sb = journal->j_superblock;
+
+       sb->s_feature_compat    &= ~cpu_to_be32(compat);
+       sb->s_feature_ro_compat &= ~cpu_to_be32(ro);
+       sb->s_feature_incompat  &= ~cpu_to_be32(incompat);
+}
+EXPORT_SYMBOL(jbd2_journal_clear_features);
 
 /**
  * int jbd2_journal_update_format () - Update on-disk journal structure.
@@ -1629,14 +1960,14 @@ static int journal_init_jbd2_journal_head_cache(void)
 {
        int retval;
 
-       J_ASSERT(jbd2_journal_head_cache == 0);
+       J_ASSERT(jbd2_journal_head_cache == NULL);
        jbd2_journal_head_cache = kmem_cache_create("jbd2_journal_head",
                                sizeof(struct journal_head),
                                0,              /* offset */
-                               0,              /* flags */
+                               SLAB_TEMPORARY, /* flags */
                                NULL);          /* ctor */
        retval = 0;
-       if (jbd2_journal_head_cache == 0) {
+       if (!jbd2_journal_head_cache) {
                retval = -ENOMEM;
                printk(KERN_EMERG "JBD: no memory for journal_head cache\n");
        }
@@ -1662,14 +1993,14 @@ static struct journal_head *journal_alloc_journal_head(void)
        atomic_inc(&nr_journal_heads);
 #endif
        ret = kmem_cache_alloc(jbd2_journal_head_cache, GFP_NOFS);
-       if (ret == 0) {
+       if (!ret) {
                jbd_debug(1, "out of memory for journal_head\n");
                if (time_after(jiffies, last_warning + 5*HZ)) {
                        printk(KERN_NOTICE "ENOMEM in %s, retrying.\n",
                               __FUNCTION__);
                        last_warning = jiffies;
                }
-               while (ret == 0) {
+               while (!ret) {
                        yield();
                        ret = kmem_cache_alloc(jbd2_journal_head_cache, GFP_NOFS);
                }
@@ -1900,6 +2231,28 @@ static void __exit jbd2_remove_debugfs_entry(void)
 
 #endif
 
+#ifdef CONFIG_PROC_FS
+
+#define JBD2_STATS_PROC_NAME "fs/jbd2"
+
+static void __init jbd2_create_jbd_stats_proc_entry(void)
+{
+       proc_jbd2_stats = proc_mkdir(JBD2_STATS_PROC_NAME, NULL);
+}
+
+static void __exit jbd2_remove_jbd_stats_proc_entry(void)
+{
+       if (proc_jbd2_stats)
+               remove_proc_entry(JBD2_STATS_PROC_NAME, NULL);
+}
+
+#else
+
+#define jbd2_create_jbd_stats_proc_entry() do {} while (0)
+#define jbd2_remove_jbd_stats_proc_entry() do {} while (0)
+
+#endif
+
 struct kmem_cache *jbd2_handle_cache;
 
 static int __init journal_init_handle_cache(void)
@@ -1907,7 +2260,7 @@ static int __init journal_init_handle_cache(void)
        jbd2_handle_cache = kmem_cache_create("jbd2_journal_handle",
                                sizeof(handle_t),
                                0,              /* offset */
-                               0,              /* flags */
+                               SLAB_TEMPORARY, /* flags */
                                NULL);          /* ctor */
        if (jbd2_handle_cache == NULL) {
                printk(KERN_EMERG "JBD: failed to create handle cache\n");
@@ -1955,6 +2308,7 @@ static int __init journal_init(void)
        if (ret != 0)
                jbd2_journal_destroy_caches();
        jbd2_create_debugfs_entry();
+       jbd2_create_jbd_stats_proc_entry();
        return ret;
 }
 
@@ -1966,6 +2320,7 @@ static void __exit journal_exit(void)
                printk(KERN_EMERG "JBD: leaked %d journal_heads!\n", n);
 #endif
        jbd2_remove_debugfs_entry();
+       jbd2_remove_jbd_stats_proc_entry();
        jbd2_journal_destroy_caches();
 }