Commit 0e175a1835ffc979e55787774e58ec79e41957d7
Committed by
Wu Fengguang
1 parent
ad4e38dd6a
Exists in
master
and in
6 other branches
writeback: Add a 'reason' to wb_writeback_work
This creates a new 'reason' field in a wb_writeback_work structure, which unambiguously identifies who initiates writeback activity. A 'wb_reason' enumeration has been added to writeback.h, to enumerate the possible reasons. The 'writeback_work_class' and tracepoint event class and 'writeback_queue_io' tracepoints are updated to include the symbolic 'reason' in all trace events. And the 'writeback_inodes_sbXXX' family of routines has had a wb_stats parameter added to them, so callers can specify why writeback is being started. Acked-by: Jan Kara <jack@suse.cz> Signed-off-by: Curt Wohlgemuth <curtw@google.com> Signed-off-by: Wu Fengguang <fengguang.wu@intel.com>
Showing 13 changed files with 88 additions and 34 deletions Side-by-side Diff
fs/btrfs/extent-tree.c
... | ... | @@ -3340,7 +3340,8 @@ |
3340 | 3340 | smp_mb(); |
3341 | 3341 | nr_pages = min_t(unsigned long, nr_pages, |
3342 | 3342 | root->fs_info->delalloc_bytes >> PAGE_CACHE_SHIFT); |
3343 | - writeback_inodes_sb_nr_if_idle(root->fs_info->sb, nr_pages); | |
3343 | + writeback_inodes_sb_nr_if_idle(root->fs_info->sb, nr_pages, | |
3344 | + WB_REASON_FS_FREE_SPACE); | |
3344 | 3345 | |
3345 | 3346 | spin_lock(&space_info->lock); |
3346 | 3347 | if (reserved > space_info->bytes_reserved) |
fs/buffer.c
fs/ext4/inode.c
... | ... | @@ -2241,7 +2241,7 @@ |
2241 | 2241 | * start pushing delalloc when 1/2 of free blocks are dirty. |
2242 | 2242 | */ |
2243 | 2243 | if (free_blocks < 2 * dirty_blocks) |
2244 | - writeback_inodes_sb_if_idle(sb); | |
2244 | + writeback_inodes_sb_if_idle(sb, WB_REASON_FS_FREE_SPACE); | |
2245 | 2245 | |
2246 | 2246 | return 0; |
2247 | 2247 | } |
fs/fs-writeback.c
... | ... | @@ -41,11 +41,23 @@ |
41 | 41 | unsigned int for_kupdate:1; |
42 | 42 | unsigned int range_cyclic:1; |
43 | 43 | unsigned int for_background:1; |
44 | + enum wb_reason reason; /* why was writeback initiated? */ | |
44 | 45 | |
45 | 46 | struct list_head list; /* pending work list */ |
46 | 47 | struct completion *done; /* set if the caller waits */ |
47 | 48 | }; |
48 | 49 | |
50 | +const char *wb_reason_name[] = { | |
51 | + [WB_REASON_BACKGROUND] = "background", | |
52 | + [WB_REASON_TRY_TO_FREE_PAGES] = "try_to_free_pages", | |
53 | + [WB_REASON_SYNC] = "sync", | |
54 | + [WB_REASON_PERIODIC] = "periodic", | |
55 | + [WB_REASON_LAPTOP_TIMER] = "laptop_timer", | |
56 | + [WB_REASON_FREE_MORE_MEM] = "free_more_memory", | |
57 | + [WB_REASON_FS_FREE_SPACE] = "fs_free_space", | |
58 | + [WB_REASON_FORKER_THREAD] = "forker_thread" | |
59 | +}; | |
60 | + | |
49 | 61 | /* |
50 | 62 | * Include the creation of the trace points after defining the |
51 | 63 | * wb_writeback_work structure so that the definition remains local to this |
... | ... | @@ -115,7 +127,7 @@ |
115 | 127 | |
116 | 128 | static void |
117 | 129 | __bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages, |
118 | - bool range_cyclic) | |
130 | + bool range_cyclic, enum wb_reason reason) | |
119 | 131 | { |
120 | 132 | struct wb_writeback_work *work; |
121 | 133 | |
... | ... | @@ -135,6 +147,7 @@ |
135 | 147 | work->sync_mode = WB_SYNC_NONE; |
136 | 148 | work->nr_pages = nr_pages; |
137 | 149 | work->range_cyclic = range_cyclic; |
150 | + work->reason = reason; | |
138 | 151 | |
139 | 152 | bdi_queue_work(bdi, work); |
140 | 153 | } |
141 | 154 | |
... | ... | @@ -150,9 +163,10 @@ |
150 | 163 | * completion. Caller need not hold sb s_umount semaphore. |
151 | 164 | * |
152 | 165 | */ |
153 | -void bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages) | |
166 | +void bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages, | |
167 | + enum wb_reason reason) | |
154 | 168 | { |
155 | - __bdi_start_writeback(bdi, nr_pages, true); | |
169 | + __bdi_start_writeback(bdi, nr_pages, true, reason); | |
156 | 170 | } |
157 | 171 | |
158 | 172 | /** |
159 | 173 | |
... | ... | @@ -641,12 +655,14 @@ |
641 | 655 | return wrote; |
642 | 656 | } |
643 | 657 | |
644 | -long writeback_inodes_wb(struct bdi_writeback *wb, long nr_pages) | |
658 | +long writeback_inodes_wb(struct bdi_writeback *wb, long nr_pages, | |
659 | + enum wb_reason reason) | |
645 | 660 | { |
646 | 661 | struct wb_writeback_work work = { |
647 | 662 | .nr_pages = nr_pages, |
648 | 663 | .sync_mode = WB_SYNC_NONE, |
649 | 664 | .range_cyclic = 1, |
665 | + .reason = reason, | |
650 | 666 | }; |
651 | 667 | |
652 | 668 | spin_lock(&wb->list_lock); |
... | ... | @@ -825,6 +841,7 @@ |
825 | 841 | .sync_mode = WB_SYNC_NONE, |
826 | 842 | .for_background = 1, |
827 | 843 | .range_cyclic = 1, |
844 | + .reason = WB_REASON_BACKGROUND, | |
828 | 845 | }; |
829 | 846 | |
830 | 847 | return wb_writeback(wb, &work); |
... | ... | @@ -858,6 +875,7 @@ |
858 | 875 | .sync_mode = WB_SYNC_NONE, |
859 | 876 | .for_kupdate = 1, |
860 | 877 | .range_cyclic = 1, |
878 | + .reason = WB_REASON_PERIODIC, | |
861 | 879 | }; |
862 | 880 | |
863 | 881 | return wb_writeback(wb, &work); |
... | ... | @@ -976,7 +994,7 @@ |
976 | 994 | * Start writeback of `nr_pages' pages. If `nr_pages' is zero, write back |
977 | 995 | * the whole world. |
978 | 996 | */ |
979 | -void wakeup_flusher_threads(long nr_pages) | |
997 | +void wakeup_flusher_threads(long nr_pages, enum wb_reason reason) | |
980 | 998 | { |
981 | 999 | struct backing_dev_info *bdi; |
982 | 1000 | |
... | ... | @@ -989,7 +1007,7 @@ |
989 | 1007 | list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) { |
990 | 1008 | if (!bdi_has_dirty_io(bdi)) |
991 | 1009 | continue; |
992 | - __bdi_start_writeback(bdi, nr_pages, false); | |
1010 | + __bdi_start_writeback(bdi, nr_pages, false, reason); | |
993 | 1011 | } |
994 | 1012 | rcu_read_unlock(); |
995 | 1013 | } |
... | ... | @@ -1210,7 +1228,9 @@ |
1210 | 1228 | * on how many (if any) will be written, and this function does not wait |
1211 | 1229 | * for IO completion of submitted IO. |
1212 | 1230 | */ |
1213 | -void writeback_inodes_sb_nr(struct super_block *sb, unsigned long nr) | |
1231 | +void writeback_inodes_sb_nr(struct super_block *sb, | |
1232 | + unsigned long nr, | |
1233 | + enum wb_reason reason) | |
1214 | 1234 | { |
1215 | 1235 | DECLARE_COMPLETION_ONSTACK(done); |
1216 | 1236 | struct wb_writeback_work work = { |
... | ... | @@ -1219,6 +1239,7 @@ |
1219 | 1239 | .tagged_writepages = 1, |
1220 | 1240 | .done = &done, |
1221 | 1241 | .nr_pages = nr, |
1242 | + .reason = reason, | |
1222 | 1243 | }; |
1223 | 1244 | |
1224 | 1245 | WARN_ON(!rwsem_is_locked(&sb->s_umount)); |
1225 | 1246 | |
... | ... | @@ -1235,9 +1256,9 @@ |
1235 | 1256 | * on how many (if any) will be written, and this function does not wait |
1236 | 1257 | * for IO completion of submitted IO. |
1237 | 1258 | */ |
1238 | -void writeback_inodes_sb(struct super_block *sb) | |
1259 | +void writeback_inodes_sb(struct super_block *sb, enum wb_reason reason) | |
1239 | 1260 | { |
1240 | - return writeback_inodes_sb_nr(sb, get_nr_dirty_pages()); | |
1261 | + return writeback_inodes_sb_nr(sb, get_nr_dirty_pages(), reason); | |
1241 | 1262 | } |
1242 | 1263 | EXPORT_SYMBOL(writeback_inodes_sb); |
1243 | 1264 | |
1244 | 1265 | |
... | ... | @@ -1248,11 +1269,11 @@ |
1248 | 1269 | * Invoke writeback_inodes_sb if no writeback is currently underway. |
1249 | 1270 | * Returns 1 if writeback was started, 0 if not. |
1250 | 1271 | */ |
1251 | -int writeback_inodes_sb_if_idle(struct super_block *sb) | |
1272 | +int writeback_inodes_sb_if_idle(struct super_block *sb, enum wb_reason reason) | |
1252 | 1273 | { |
1253 | 1274 | if (!writeback_in_progress(sb->s_bdi)) { |
1254 | 1275 | down_read(&sb->s_umount); |
1255 | - writeback_inodes_sb(sb); | |
1276 | + writeback_inodes_sb(sb, reason); | |
1256 | 1277 | up_read(&sb->s_umount); |
1257 | 1278 | return 1; |
1258 | 1279 | } else |
1259 | 1280 | |
... | ... | @@ -1269,11 +1290,12 @@ |
1269 | 1290 | * Returns 1 if writeback was started, 0 if not. |
1270 | 1291 | */ |
1271 | 1292 | int writeback_inodes_sb_nr_if_idle(struct super_block *sb, |
1272 | - unsigned long nr) | |
1293 | + unsigned long nr, | |
1294 | + enum wb_reason reason) | |
1273 | 1295 | { |
1274 | 1296 | if (!writeback_in_progress(sb->s_bdi)) { |
1275 | 1297 | down_read(&sb->s_umount); |
1276 | - writeback_inodes_sb_nr(sb, nr); | |
1298 | + writeback_inodes_sb_nr(sb, nr, reason); | |
1277 | 1299 | up_read(&sb->s_umount); |
1278 | 1300 | return 1; |
1279 | 1301 | } else |
... | ... | @@ -1297,6 +1319,7 @@ |
1297 | 1319 | .nr_pages = LONG_MAX, |
1298 | 1320 | .range_cyclic = 0, |
1299 | 1321 | .done = &done, |
1322 | + .reason = WB_REASON_SYNC, | |
1300 | 1323 | }; |
1301 | 1324 | |
1302 | 1325 | WARN_ON(!rwsem_is_locked(&sb->s_umount)); |
fs/quota/quota.c
fs/sync.c
... | ... | @@ -43,7 +43,7 @@ |
43 | 43 | if (wait) |
44 | 44 | sync_inodes_sb(sb); |
45 | 45 | else |
46 | - writeback_inodes_sb(sb); | |
46 | + writeback_inodes_sb(sb, WB_REASON_SYNC); | |
47 | 47 | |
48 | 48 | if (sb->s_op->sync_fs) |
49 | 49 | sb->s_op->sync_fs(sb, wait); |
... | ... | @@ -98,7 +98,7 @@ |
98 | 98 | */ |
99 | 99 | SYSCALL_DEFINE0(sync) |
100 | 100 | { |
101 | - wakeup_flusher_threads(0); | |
101 | + wakeup_flusher_threads(0, WB_REASON_SYNC); | |
102 | 102 | sync_filesystems(0); |
103 | 103 | sync_filesystems(1); |
104 | 104 | if (unlikely(laptop_mode)) |
fs/ubifs/budget.c
include/linux/backing-dev.h
... | ... | @@ -118,7 +118,8 @@ |
118 | 118 | int bdi_register_dev(struct backing_dev_info *bdi, dev_t dev); |
119 | 119 | void bdi_unregister(struct backing_dev_info *bdi); |
120 | 120 | int bdi_setup_and_register(struct backing_dev_info *, char *, unsigned int); |
121 | -void bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages); | |
121 | +void bdi_start_writeback(struct backing_dev_info *bdi, long nr_pages, | |
122 | + enum wb_reason reason); | |
122 | 123 | void bdi_start_background_writeback(struct backing_dev_info *bdi); |
123 | 124 | int bdi_writeback_thread(void *data); |
124 | 125 | int bdi_has_dirty_io(struct backing_dev_info *bdi); |
include/linux/writeback.h
... | ... | @@ -39,6 +39,23 @@ |
39 | 39 | }; |
40 | 40 | |
41 | 41 | /* |
42 | + * why some writeback work was initiated | |
43 | + */ | |
44 | +enum wb_reason { | |
45 | + WB_REASON_BACKGROUND, | |
46 | + WB_REASON_TRY_TO_FREE_PAGES, | |
47 | + WB_REASON_SYNC, | |
48 | + WB_REASON_PERIODIC, | |
49 | + WB_REASON_LAPTOP_TIMER, | |
50 | + WB_REASON_FREE_MORE_MEM, | |
51 | + WB_REASON_FS_FREE_SPACE, | |
52 | + WB_REASON_FORKER_THREAD, | |
53 | + | |
54 | + WB_REASON_MAX, | |
55 | +}; | |
56 | +extern const char *wb_reason_name[]; | |
57 | + | |
58 | +/* | |
42 | 59 | * A control structure which tells the writeback code what to do. These are |
43 | 60 | * always on the stack, and hence need no locking. They are always initialised |
44 | 61 | * in a manner such that unspecified fields are set to zero. |
45 | 62 | |
46 | 63 | |
... | ... | @@ -69,14 +86,17 @@ |
69 | 86 | */ |
70 | 87 | struct bdi_writeback; |
71 | 88 | int inode_wait(void *); |
72 | -void writeback_inodes_sb(struct super_block *); | |
73 | -void writeback_inodes_sb_nr(struct super_block *, unsigned long nr); | |
74 | -int writeback_inodes_sb_if_idle(struct super_block *); | |
75 | -int writeback_inodes_sb_nr_if_idle(struct super_block *, unsigned long nr); | |
89 | +void writeback_inodes_sb(struct super_block *, enum wb_reason reason); | |
90 | +void writeback_inodes_sb_nr(struct super_block *, unsigned long nr, | |
91 | + enum wb_reason reason); | |
92 | +int writeback_inodes_sb_if_idle(struct super_block *, enum wb_reason reason); | |
93 | +int writeback_inodes_sb_nr_if_idle(struct super_block *, unsigned long nr, | |
94 | + enum wb_reason reason); | |
76 | 95 | void sync_inodes_sb(struct super_block *); |
77 | -long writeback_inodes_wb(struct bdi_writeback *wb, long nr_pages); | |
96 | +long writeback_inodes_wb(struct bdi_writeback *wb, long nr_pages, | |
97 | + enum wb_reason reason); | |
78 | 98 | long wb_do_writeback(struct bdi_writeback *wb, int force_wait); |
79 | -void wakeup_flusher_threads(long nr_pages); | |
99 | +void wakeup_flusher_threads(long nr_pages, enum wb_reason reason); | |
80 | 100 | |
81 | 101 | /* writeback.h requires fs.h; it, too, is not included from here. */ |
82 | 102 | static inline void wait_on_inode(struct inode *inode) |
include/trace/events/writeback.h
... | ... | @@ -34,6 +34,7 @@ |
34 | 34 | __field(int, for_kupdate) |
35 | 35 | __field(int, range_cyclic) |
36 | 36 | __field(int, for_background) |
37 | + __field(int, reason) | |
37 | 38 | ), |
38 | 39 | TP_fast_assign( |
39 | 40 | strncpy(__entry->name, dev_name(bdi->dev), 32); |
40 | 41 | |
41 | 42 | |
... | ... | @@ -43,16 +44,18 @@ |
43 | 44 | __entry->for_kupdate = work->for_kupdate; |
44 | 45 | __entry->range_cyclic = work->range_cyclic; |
45 | 46 | __entry->for_background = work->for_background; |
47 | + __entry->reason = work->reason; | |
46 | 48 | ), |
47 | 49 | TP_printk("bdi %s: sb_dev %d:%d nr_pages=%ld sync_mode=%d " |
48 | - "kupdate=%d range_cyclic=%d background=%d", | |
50 | + "kupdate=%d range_cyclic=%d background=%d reason=%s", | |
49 | 51 | __entry->name, |
50 | 52 | MAJOR(__entry->sb_dev), MINOR(__entry->sb_dev), |
51 | 53 | __entry->nr_pages, |
52 | 54 | __entry->sync_mode, |
53 | 55 | __entry->for_kupdate, |
54 | 56 | __entry->range_cyclic, |
55 | - __entry->for_background | |
57 | + __entry->for_background, | |
58 | + wb_reason_name[__entry->reason] | |
56 | 59 | ) |
57 | 60 | ); |
58 | 61 | #define DEFINE_WRITEBACK_WORK_EVENT(name) \ |
... | ... | @@ -165,6 +168,7 @@ |
165 | 168 | __field(unsigned long, older) |
166 | 169 | __field(long, age) |
167 | 170 | __field(int, moved) |
171 | + __field(int, reason) | |
168 | 172 | ), |
169 | 173 | TP_fast_assign( |
170 | 174 | unsigned long *older_than_this = work->older_than_this; |
171 | 175 | |
172 | 176 | |
... | ... | @@ -173,12 +177,14 @@ |
173 | 177 | __entry->age = older_than_this ? |
174 | 178 | (jiffies - *older_than_this) * 1000 / HZ : -1; |
175 | 179 | __entry->moved = moved; |
180 | + __entry->reason = work->reason; | |
176 | 181 | ), |
177 | - TP_printk("bdi %s: older=%lu age=%ld enqueue=%d", | |
182 | + TP_printk("bdi %s: older=%lu age=%ld enqueue=%d reason=%s", | |
178 | 183 | __entry->name, |
179 | 184 | __entry->older, /* older_than_this in jiffies */ |
180 | 185 | __entry->age, /* older_than_this in relative milliseconds */ |
181 | - __entry->moved) | |
186 | + __entry->moved, | |
187 | + wb_reason_name[__entry->reason]) | |
182 | 188 | ); |
183 | 189 | |
184 | 190 | TRACE_EVENT(global_dirty_state, |
mm/backing-dev.c
... | ... | @@ -476,7 +476,8 @@ |
476 | 476 | * the bdi from the thread. Hopefully 1024 is |
477 | 477 | * large enough for efficient IO. |
478 | 478 | */ |
479 | - writeback_inodes_wb(&bdi->wb, 1024); | |
479 | + writeback_inodes_wb(&bdi->wb, 1024, | |
480 | + WB_REASON_FORKER_THREAD); | |
480 | 481 | } else { |
481 | 482 | /* |
482 | 483 | * The spinlock makes sure we do not lose |
mm/page-writeback.c
... | ... | @@ -1301,7 +1301,8 @@ |
1301 | 1301 | * threshold |
1302 | 1302 | */ |
1303 | 1303 | if (bdi_has_dirty_io(&q->backing_dev_info)) |
1304 | - bdi_start_writeback(&q->backing_dev_info, nr_pages); | |
1304 | + bdi_start_writeback(&q->backing_dev_info, nr_pages, | |
1305 | + WB_REASON_LAPTOP_TIMER); | |
1305 | 1306 | } |
1306 | 1307 | |
1307 | 1308 | /* |
mm/vmscan.c
... | ... | @@ -2181,7 +2181,8 @@ |
2181 | 2181 | */ |
2182 | 2182 | writeback_threshold = sc->nr_to_reclaim + sc->nr_to_reclaim / 2; |
2183 | 2183 | if (total_scanned > writeback_threshold) { |
2184 | - wakeup_flusher_threads(laptop_mode ? 0 : total_scanned); | |
2184 | + wakeup_flusher_threads(laptop_mode ? 0 : total_scanned, | |
2185 | + WB_REASON_TRY_TO_FREE_PAGES); | |
2185 | 2186 | sc->may_writepage = 1; |
2186 | 2187 | } |
2187 | 2188 |