mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 12:28:41 +08:00
8cd7c588de
Patch series "Remove dependency on congestion_wait in mm/", v5. This series that removes all calls to congestion_wait in mm/ and deletes wait_iff_congested. It's not a clever implementation but congestion_wait has been broken for a long time [1]. Even if congestion throttling worked, it was never a great idea. While excessive dirty/writeback pages at the tail of the LRU is one possibility that reclaim may be slow, there is also the problem of too many pages being isolated and reclaim failing for other reasons (elevated references, too many pages isolated, excessive LRU contention etc). This series replaces the "congestion" throttling with 3 different types. - If there are too many dirty/writeback pages, sleep until a timeout or enough pages get cleaned - If too many pages are isolated, sleep until enough isolated pages are either reclaimed or put back on the LRU - If no progress is being made, direct reclaim tasks sleep until another task makes progress with acceptable efficiency. This was initially tested with a mix of workloads that used to trigger corner cases that no longer work. A new test case was created called "stutterp" (pagereclaim-stutterp-noreaders in mmtests) using a freshly created XFS filesystem. Note that it may be necessary to increase the timeout of ssh if executing remotely as ssh itself can get throttled and the connection may timeout. stutterp varies the number of "worker" processes from 4 up to NR_CPUS*4 to check the impact as the number of direct reclaimers increase. It has four types of worker. - One "anon latency" worker creates small mappings with mmap() and times how long it takes to fault the mapping reading it 4K at a time - X file writers which is fio randomly writing X files where the total size of the files add up to the allowed dirty_ratio. fio is allowed to run for a warmup period to allow some file-backed pages to accumulate. The duration of the warmup is based on the best-case linear write speed of the storage. - Y file readers which is fio randomly reading small files - Z anon memory hogs which continually map (100-dirty_ratio)% of memory - Total estimated WSS = (100+dirty_ration) percentage of memory X+Y+Z+1 == NR_WORKERS varying from 4 up to NR_CPUS*4 The intent is to maximise the total WSS with a mix of file and anon memory where some anonymous memory must be swapped and there is a high likelihood of dirty/writeback pages reaching the end of the LRU. The test can be configured to have no background readers to stress dirty/writeback pages. The results below are based on having zero readers. The short summary of the results is that the series works and stalls until some event occurs but the timeouts may need adjustment. The test results are not broken down by patch as the series should be treated as one block that replaces a broken throttling mechanism with a working one. Finally, three machines were tested but I'm reporting the worst set of results. The other two machines had much better latencies for example. First the results of the "anon latency" latency stutterp 5.15.0-rc1 5.15.0-rc1 vanilla mm-reclaimcongest-v5r4 Amean mmap-4 31.4003 ( 0.00%) 2661.0198 (-8374.52%) Amean mmap-7 38.1641 ( 0.00%) 149.2891 (-291.18%) Amean mmap-12 60.0981 ( 0.00%) 187.8105 (-212.51%) Amean mmap-21 161.2699 ( 0.00%) 213.9107 ( -32.64%) Amean mmap-30 174.5589 ( 0.00%) 377.7548 (-116.41%) Amean mmap-48 8106.8160 ( 0.00%) 1070.5616 ( 86.79%) Stddev mmap-4 41.3455 ( 0.00%) 27573.9676 (-66591.66%) Stddev mmap-7 53.5556 ( 0.00%) 4608.5860 (-8505.23%) Stddev mmap-12 171.3897 ( 0.00%) 5559.4542 (-3143.75%) Stddev mmap-21 1506.6752 ( 0.00%) 5746.2507 (-281.39%) Stddev mmap-30 557.5806 ( 0.00%) 7678.1624 (-1277.05%) Stddev mmap-48 61681.5718 ( 0.00%) 14507.2830 ( 76.48%) Max-90 mmap-4 31.4243 ( 0.00%) 83.1457 (-164.59%) Max-90 mmap-7 41.0410 ( 0.00%) 41.0720 ( -0.08%) Max-90 mmap-12 66.5255 ( 0.00%) 53.9073 ( 18.97%) Max-90 mmap-21 146.7479 ( 0.00%) 105.9540 ( 27.80%) Max-90 mmap-30 193.9513 ( 0.00%) 64.3067 ( 66.84%) Max-90 mmap-48 277.9137 ( 0.00%) 591.0594 (-112.68%) Max mmap-4 1913.8009 ( 0.00%) 299623.9695 (-15555.96%) Max mmap-7 2423.9665 ( 0.00%) 204453.1708 (-8334.65%) Max mmap-12 6845.6573 ( 0.00%) 221090.3366 (-3129.64%) Max mmap-21 56278.6508 ( 0.00%) 213877.3496 (-280.03%) Max mmap-30 19716.2990 ( 0.00%) 216287.6229 (-997.00%) Max mmap-48 477923.9400 ( 0.00%) 245414.8238 ( 48.65%) For most thread counts, the time to mmap() is unfortunately increased. In earlier versions of the series, this was lower but a large number of throttling events were reaching their timeout increasing the amount of inefficient scanning of the LRU. There is no prioritisation of reclaim tasks making progress based on each tasks rate of page allocation versus progress of reclaim. The variance is also impacted for high worker counts but in all cases, the differences in latency are not statistically significant due to very large maximum outliers. Max-90 shows that 90% of the stalls are comparable but the Max results show the massive outliers which are increased to to stalling. It is expected that this will be very machine dependant. Due to the test design, reclaim is difficult so allocations stall and there are variances depending on whether THPs can be allocated or not. The amount of memory will affect exactly how bad the corner cases are and how often they trigger. The warmup period calculation is not ideal as it's based on linear writes where as fio is randomly writing multiple files from multiple tasks so the start state of the test is variable. For example, these are the latencies on a single-socket machine that had more memory Amean mmap-4 42.2287 ( 0.00%) 49.6838 * -17.65%* Amean mmap-7 216.4326 ( 0.00%) 47.4451 * 78.08%* Amean mmap-12 2412.0588 ( 0.00%) 51.7497 ( 97.85%) Amean mmap-21 5546.2548 ( 0.00%) 51.8862 ( 99.06%) Amean mmap-30 1085.3121 ( 0.00%) 72.1004 ( 93.36%) The overall system CPU usage and elapsed time is as follows 5.15.0-rc3 5.15.0-rc3 vanilla mm-reclaimcongest-v5r4 Duration User 6989.03 983.42 Duration System 7308.12 799.68 Duration Elapsed 2277.67 2092.98 The patches reduce system CPU usage by 89% as the vanilla kernel is rarely stalling. The high-level /proc/vmstats show 5.15.0-rc1 5.15.0-rc1 vanilla mm-reclaimcongest-v5r2 Ops Direct pages scanned 1056608451.00 503594991.00 Ops Kswapd pages scanned 109795048.00 147289810.00 Ops Kswapd pages reclaimed 63269243.00 31036005.00 Ops Direct pages reclaimed 10803973.00 6328887.00 Ops Kswapd efficiency % 57.62 21.07 Ops Kswapd velocity 48204.98 57572.86 Ops Direct efficiency % 1.02 1.26 Ops Direct velocity 463898.83 196845.97 Kswapd scanned less pages but the detailed pattern is different. The vanilla kernel scans slowly over time where as the patches exhibits burst patterns of scan activity. Direct reclaim scanning is reduced by 52% due to stalling. The pattern for stealing pages is also slightly different. Both kernels exhibit spikes but the vanilla kernel when reclaiming shows pages being reclaimed over a period of time where as the patches tend to reclaim in spikes. The difference is that vanilla is not throttling and instead scanning constantly finding some pages over time where as the patched kernel throttles and reclaims in spikes. Ops Percentage direct scans 90.59 77.37 For direct reclaim, vanilla scanned 90.59% of pages where as with the patches, 77.37% were direct reclaim due to throttling Ops Page writes by reclaim 2613590.00 1687131.00 Page writes from reclaim context are reduced. Ops Page writes anon 2932752.00 1917048.00 And there is less swapping. Ops Page reclaim immediate 996248528.00 107664764.00 The number of pages encountered at the tail of the LRU tagged for immediate reclaim but still dirty/writeback is reduced by 89%. Ops Slabs scanned 164284.00 153608.00 Slab scan activity is similar. ftrace was used to gather stall activity Vanilla ------- 1 writeback_wait_iff_congested: usec_timeout=100000 usec_delayed=16000 2 writeback_wait_iff_congested: usec_timeout=100000 usec_delayed=12000 8 writeback_wait_iff_congested: usec_timeout=100000 usec_delayed=8000 29 writeback_wait_iff_congested: usec_timeout=100000 usec_delayed=4000 82394 writeback_wait_iff_congested: usec_timeout=100000 usec_delayed=0 The fast majority of wait_iff_congested calls do not stall at all. What is likely happening is that cond_resched() reschedules the task for a short period when the BDI is not registering congestion (which it never will in this test setup). 1 writeback_congestion_wait: usec_timeout=100000 usec_delayed=120000 2 writeback_congestion_wait: usec_timeout=100000 usec_delayed=132000 4 writeback_congestion_wait: usec_timeout=100000 usec_delayed=112000 380 writeback_congestion_wait: usec_timeout=100000 usec_delayed=108000 778 writeback_congestion_wait: usec_timeout=100000 usec_delayed=104000 congestion_wait if called always exceeds the timeout as there is no trigger to wake it up. Bottom line: Vanilla will throttle but it's not effective. Patch series ------------ Kswapd throttle activity was always due to scanning pages tagged for immediate reclaim at the tail of the LRU 1 usec_timeout=100000 usect_delayed=72000 reason=VMSCAN_THROTTLE_WRITEBACK 4 usec_timeout=100000 usect_delayed=20000 reason=VMSCAN_THROTTLE_WRITEBACK 5 usec_timeout=100000 usect_delayed=12000 reason=VMSCAN_THROTTLE_WRITEBACK 6 usec_timeout=100000 usect_delayed=16000 reason=VMSCAN_THROTTLE_WRITEBACK 11 usec_timeout=100000 usect_delayed=100000 reason=VMSCAN_THROTTLE_WRITEBACK 11 usec_timeout=100000 usect_delayed=8000 reason=VMSCAN_THROTTLE_WRITEBACK 94 usec_timeout=100000 usect_delayed=0 reason=VMSCAN_THROTTLE_WRITEBACK 112 usec_timeout=100000 usect_delayed=4000 reason=VMSCAN_THROTTLE_WRITEBACK The majority of events did not stall or stalled for a short period. Roughly 16% of stalls reached the timeout before expiry. For direct reclaim, the number of times stalled for each reason were 6624 reason=VMSCAN_THROTTLE_ISOLATED 93246 reason=VMSCAN_THROTTLE_NOPROGRESS 96934 reason=VMSCAN_THROTTLE_WRITEBACK The most common reason to stall was due to excessive pages tagged for immediate reclaim at the tail of the LRU followed by a failure to make forward. A relatively small number were due to too many pages isolated from the LRU by parallel threads For VMSCAN_THROTTLE_ISOLATED, the breakdown of delays was 9 usec_timeout=20000 usect_delayed=4000 reason=VMSCAN_THROTTLE_ISOLATED 12 usec_timeout=20000 usect_delayed=16000 reason=VMSCAN_THROTTLE_ISOLATED 83 usec_timeout=20000 usect_delayed=20000 reason=VMSCAN_THROTTLE_ISOLATED 6520 usec_timeout=20000 usect_delayed=0 reason=VMSCAN_THROTTLE_ISOLATED Most did not stall at all. A small number reached the timeout. For VMSCAN_THROTTLE_NOPROGRESS, the breakdown of stalls were all over the map 1 usec_timeout=500000 usect_delayed=324000 reason=VMSCAN_THROTTLE_NOPROGRESS 1 usec_timeout=500000 usect_delayed=332000 reason=VMSCAN_THROTTLE_NOPROGRESS 1 usec_timeout=500000 usect_delayed=348000 reason=VMSCAN_THROTTLE_NOPROGRESS 1 usec_timeout=500000 usect_delayed=360000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=228000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=260000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=340000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=364000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=372000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=428000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=460000 reason=VMSCAN_THROTTLE_NOPROGRESS 2 usec_timeout=500000 usect_delayed=464000 reason=VMSCAN_THROTTLE_NOPROGRESS 3 usec_timeout=500000 usect_delayed=244000 reason=VMSCAN_THROTTLE_NOPROGRESS 3 usec_timeout=500000 usect_delayed=252000 reason=VMSCAN_THROTTLE_NOPROGRESS 3 usec_timeout=500000 usect_delayed=272000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=188000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=268000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=328000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=380000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=392000 reason=VMSCAN_THROTTLE_NOPROGRESS 4 usec_timeout=500000 usect_delayed=432000 reason=VMSCAN_THROTTLE_NOPROGRESS 5 usec_timeout=500000 usect_delayed=204000 reason=VMSCAN_THROTTLE_NOPROGRESS 5 usec_timeout=500000 usect_delayed=220000 reason=VMSCAN_THROTTLE_NOPROGRESS 5 usec_timeout=500000 usect_delayed=412000 reason=VMSCAN_THROTTLE_NOPROGRESS 5 usec_timeout=500000 usect_delayed=436000 reason=VMSCAN_THROTTLE_NOPROGRESS 6 usec_timeout=500000 usect_delayed=488000 reason=VMSCAN_THROTTLE_NOPROGRESS 7 usec_timeout=500000 usect_delayed=212000 reason=VMSCAN_THROTTLE_NOPROGRESS 7 usec_timeout=500000 usect_delayed=300000 reason=VMSCAN_THROTTLE_NOPROGRESS 7 usec_timeout=500000 usect_delayed=316000 reason=VMSCAN_THROTTLE_NOPROGRESS 7 usec_timeout=500000 usect_delayed=472000 reason=VMSCAN_THROTTLE_NOPROGRESS 8 usec_timeout=500000 usect_delayed=248000 reason=VMSCAN_THROTTLE_NOPROGRESS 8 usec_timeout=500000 usect_delayed=356000 reason=VMSCAN_THROTTLE_NOPROGRESS 8 usec_timeout=500000 usect_delayed=456000 reason=VMSCAN_THROTTLE_NOPROGRESS 9 usec_timeout=500000 usect_delayed=124000 reason=VMSCAN_THROTTLE_NOPROGRESS 9 usec_timeout=500000 usect_delayed=376000 reason=VMSCAN_THROTTLE_NOPROGRESS 9 usec_timeout=500000 usect_delayed=484000 reason=VMSCAN_THROTTLE_NOPROGRESS 10 usec_timeout=500000 usect_delayed=172000 reason=VMSCAN_THROTTLE_NOPROGRESS 10 usec_timeout=500000 usect_delayed=420000 reason=VMSCAN_THROTTLE_NOPROGRESS 10 usec_timeout=500000 usect_delayed=452000 reason=VMSCAN_THROTTLE_NOPROGRESS 11 usec_timeout=500000 usect_delayed=256000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=112000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=116000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=144000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=152000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=264000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=384000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=424000 reason=VMSCAN_THROTTLE_NOPROGRESS 12 usec_timeout=500000 usect_delayed=492000 reason=VMSCAN_THROTTLE_NOPROGRESS 13 usec_timeout=500000 usect_delayed=184000 reason=VMSCAN_THROTTLE_NOPROGRESS 13 usec_timeout=500000 usect_delayed=444000 reason=VMSCAN_THROTTLE_NOPROGRESS 14 usec_timeout=500000 usect_delayed=308000 reason=VMSCAN_THROTTLE_NOPROGRESS 14 usec_timeout=500000 usect_delayed=440000 reason=VMSCAN_THROTTLE_NOPROGRESS 14 usec_timeout=500000 usect_delayed=476000 reason=VMSCAN_THROTTLE_NOPROGRESS 16 usec_timeout=500000 usect_delayed=140000 reason=VMSCAN_THROTTLE_NOPROGRESS 17 usec_timeout=500000 usect_delayed=232000 reason=VMSCAN_THROTTLE_NOPROGRESS 17 usec_timeout=500000 usect_delayed=240000 reason=VMSCAN_THROTTLE_NOPROGRESS 17 usec_timeout=500000 usect_delayed=280000 reason=VMSCAN_THROTTLE_NOPROGRESS 18 usec_timeout=500000 usect_delayed=404000 reason=VMSCAN_THROTTLE_NOPROGRESS 20 usec_timeout=500000 usect_delayed=148000 reason=VMSCAN_THROTTLE_NOPROGRESS 20 usec_timeout=500000 usect_delayed=216000 reason=VMSCAN_THROTTLE_NOPROGRESS 20 usec_timeout=500000 usect_delayed=468000 reason=VMSCAN_THROTTLE_NOPROGRESS 21 usec_timeout=500000 usect_delayed=448000 reason=VMSCAN_THROTTLE_NOPROGRESS 23 usec_timeout=500000 usect_delayed=168000 reason=VMSCAN_THROTTLE_NOPROGRESS 23 usec_timeout=500000 usect_delayed=296000 reason=VMSCAN_THROTTLE_NOPROGRESS 25 usec_timeout=500000 usect_delayed=132000 reason=VMSCAN_THROTTLE_NOPROGRESS 25 usec_timeout=500000 usect_delayed=352000 reason=VMSCAN_THROTTLE_NOPROGRESS 26 usec_timeout=500000 usect_delayed=180000 reason=VMSCAN_THROTTLE_NOPROGRESS 27 usec_timeout=500000 usect_delayed=284000 reason=VMSCAN_THROTTLE_NOPROGRESS 28 usec_timeout=500000 usect_delayed=164000 reason=VMSCAN_THROTTLE_NOPROGRESS 29 usec_timeout=500000 usect_delayed=136000 reason=VMSCAN_THROTTLE_NOPROGRESS 30 usec_timeout=500000 usect_delayed=200000 reason=VMSCAN_THROTTLE_NOPROGRESS 30 usec_timeout=500000 usect_delayed=400000 reason=VMSCAN_THROTTLE_NOPROGRESS 31 usec_timeout=500000 usect_delayed=196000 reason=VMSCAN_THROTTLE_NOPROGRESS 32 usec_timeout=500000 usect_delayed=156000 reason=VMSCAN_THROTTLE_NOPROGRESS 33 usec_timeout=500000 usect_delayed=224000 reason=VMSCAN_THROTTLE_NOPROGRESS 35 usec_timeout=500000 usect_delayed=128000 reason=VMSCAN_THROTTLE_NOPROGRESS 35 usec_timeout=500000 usect_delayed=176000 reason=VMSCAN_THROTTLE_NOPROGRESS 36 usec_timeout=500000 usect_delayed=368000 reason=VMSCAN_THROTTLE_NOPROGRESS 36 usec_timeout=500000 usect_delayed=496000 reason=VMSCAN_THROTTLE_NOPROGRESS 37 usec_timeout=500000 usect_delayed=312000 reason=VMSCAN_THROTTLE_NOPROGRESS 38 usec_timeout=500000 usect_delayed=304000 reason=VMSCAN_THROTTLE_NOPROGRESS 40 usec_timeout=500000 usect_delayed=288000 reason=VMSCAN_THROTTLE_NOPROGRESS 43 usec_timeout=500000 usect_delayed=408000 reason=VMSCAN_THROTTLE_NOPROGRESS 55 usec_timeout=500000 usect_delayed=416000 reason=VMSCAN_THROTTLE_NOPROGRESS 56 usec_timeout=500000 usect_delayed=76000 reason=VMSCAN_THROTTLE_NOPROGRESS 58 usec_timeout=500000 usect_delayed=120000 reason=VMSCAN_THROTTLE_NOPROGRESS 59 usec_timeout=500000 usect_delayed=208000 reason=VMSCAN_THROTTLE_NOPROGRESS 61 usec_timeout=500000 usect_delayed=68000 reason=VMSCAN_THROTTLE_NOPROGRESS 71 usec_timeout=500000 usect_delayed=192000 reason=VMSCAN_THROTTLE_NOPROGRESS 71 usec_timeout=500000 usect_delayed=480000 reason=VMSCAN_THROTTLE_NOPROGRESS 79 usec_timeout=500000 usect_delayed=60000 reason=VMSCAN_THROTTLE_NOPROGRESS 82 usec_timeout=500000 usect_delayed=320000 reason=VMSCAN_THROTTLE_NOPROGRESS 82 usec_timeout=500000 usect_delayed=92000 reason=VMSCAN_THROTTLE_NOPROGRESS 85 usec_timeout=500000 usect_delayed=64000 reason=VMSCAN_THROTTLE_NOPROGRESS 85 usec_timeout=500000 usect_delayed=80000 reason=VMSCAN_THROTTLE_NOPROGRESS 88 usec_timeout=500000 usect_delayed=84000 reason=VMSCAN_THROTTLE_NOPROGRESS 90 usec_timeout=500000 usect_delayed=160000 reason=VMSCAN_THROTTLE_NOPROGRESS 90 usec_timeout=500000 usect_delayed=292000 reason=VMSCAN_THROTTLE_NOPROGRESS 94 usec_timeout=500000 usect_delayed=56000 reason=VMSCAN_THROTTLE_NOPROGRESS 118 usec_timeout=500000 usect_delayed=88000 reason=VMSCAN_THROTTLE_NOPROGRESS 119 usec_timeout=500000 usect_delayed=72000 reason=VMSCAN_THROTTLE_NOPROGRESS 126 usec_timeout=500000 usect_delayed=108000 reason=VMSCAN_THROTTLE_NOPROGRESS 146 usec_timeout=500000 usect_delayed=52000 reason=VMSCAN_THROTTLE_NOPROGRESS 148 usec_timeout=500000 usect_delayed=36000 reason=VMSCAN_THROTTLE_NOPROGRESS 148 usec_timeout=500000 usect_delayed=48000 reason=VMSCAN_THROTTLE_NOPROGRESS 159 usec_timeout=500000 usect_delayed=28000 reason=VMSCAN_THROTTLE_NOPROGRESS 178 usec_timeout=500000 usect_delayed=44000 reason=VMSCAN_THROTTLE_NOPROGRESS 183 usec_timeout=500000 usect_delayed=40000 reason=VMSCAN_THROTTLE_NOPROGRESS 237 usec_timeout=500000 usect_delayed=100000 reason=VMSCAN_THROTTLE_NOPROGRESS 266 usec_timeout=500000 usect_delayed=32000 reason=VMSCAN_THROTTLE_NOPROGRESS 313 usec_timeout=500000 usect_delayed=24000 reason=VMSCAN_THROTTLE_NOPROGRESS 347 usec_timeout=500000 usect_delayed=96000 reason=VMSCAN_THROTTLE_NOPROGRESS 470 usec_timeout=500000 usect_delayed=20000 reason=VMSCAN_THROTTLE_NOPROGRESS 559 usec_timeout=500000 usect_delayed=16000 reason=VMSCAN_THROTTLE_NOPROGRESS 964 usec_timeout=500000 usect_delayed=12000 reason=VMSCAN_THROTTLE_NOPROGRESS 2001 usec_timeout=500000 usect_delayed=104000 reason=VMSCAN_THROTTLE_NOPROGRESS 2447 usec_timeout=500000 usect_delayed=8000 reason=VMSCAN_THROTTLE_NOPROGRESS 7888 usec_timeout=500000 usect_delayed=4000 reason=VMSCAN_THROTTLE_NOPROGRESS 22727 usec_timeout=500000 usect_delayed=0 reason=VMSCAN_THROTTLE_NOPROGRESS 51305 usec_timeout=500000 usect_delayed=500000 reason=VMSCAN_THROTTLE_NOPROGRESS The full timeout is often hit but a large number also do not stall at all. The remainder slept a little allowing other reclaim tasks to make progress. While this timeout could be further increased, it could also negatively impact worst-case behaviour when there is no prioritisation of what task should make progress. For VMSCAN_THROTTLE_WRITEBACK, the breakdown was 1 usec_timeout=100000 usect_delayed=44000 reason=VMSCAN_THROTTLE_WRITEBACK 2 usec_timeout=100000 usect_delayed=76000 reason=VMSCAN_THROTTLE_WRITEBACK 3 usec_timeout=100000 usect_delayed=80000 reason=VMSCAN_THROTTLE_WRITEBACK 5 usec_timeout=100000 usect_delayed=48000 reason=VMSCAN_THROTTLE_WRITEBACK 5 usec_timeout=100000 usect_delayed=84000 reason=VMSCAN_THROTTLE_WRITEBACK 6 usec_timeout=100000 usect_delayed=72000 reason=VMSCAN_THROTTLE_WRITEBACK 7 usec_timeout=100000 usect_delayed=88000 reason=VMSCAN_THROTTLE_WRITEBACK 11 usec_timeout=100000 usect_delayed=56000 reason=VMSCAN_THROTTLE_WRITEBACK 12 usec_timeout=100000 usect_delayed=64000 reason=VMSCAN_THROTTLE_WRITEBACK 16 usec_timeout=100000 usect_delayed=92000 reason=VMSCAN_THROTTLE_WRITEBACK 24 usec_timeout=100000 usect_delayed=68000 reason=VMSCAN_THROTTLE_WRITEBACK 28 usec_timeout=100000 usect_delayed=32000 reason=VMSCAN_THROTTLE_WRITEBACK 30 usec_timeout=100000 usect_delayed=60000 reason=VMSCAN_THROTTLE_WRITEBACK 30 usec_timeout=100000 usect_delayed=96000 reason=VMSCAN_THROTTLE_WRITEBACK 32 usec_timeout=100000 usect_delayed=52000 reason=VMSCAN_THROTTLE_WRITEBACK 42 usec_timeout=100000 usect_delayed=40000 reason=VMSCAN_THROTTLE_WRITEBACK 77 usec_timeout=100000 usect_delayed=28000 reason=VMSCAN_THROTTLE_WRITEBACK 99 usec_timeout=100000 usect_delayed=36000 reason=VMSCAN_THROTTLE_WRITEBACK 137 usec_timeout=100000 usect_delayed=24000 reason=VMSCAN_THROTTLE_WRITEBACK 190 usec_timeout=100000 usect_delayed=20000 reason=VMSCAN_THROTTLE_WRITEBACK 339 usec_timeout=100000 usect_delayed=16000 reason=VMSCAN_THROTTLE_WRITEBACK 518 usec_timeout=100000 usect_delayed=12000 reason=VMSCAN_THROTTLE_WRITEBACK 852 usec_timeout=100000 usect_delayed=8000 reason=VMSCAN_THROTTLE_WRITEBACK 3359 usec_timeout=100000 usect_delayed=4000 reason=VMSCAN_THROTTLE_WRITEBACK 7147 usec_timeout=100000 usect_delayed=0 reason=VMSCAN_THROTTLE_WRITEBACK 83962 usec_timeout=100000 usect_delayed=100000 reason=VMSCAN_THROTTLE_WRITEBACK The majority hit the timeout in direct reclaim context although a sizable number did not stall at all. This is very different to kswapd where only a tiny percentage of stalls due to writeback reached the timeout. Bottom line, the throttling appears to work and the wakeup events may limit worst case stalls. There might be some grounds for adjusting timeouts but it's likely futile as the worst-case scenarios depend on the workload, memory size and the speed of the storage. A better approach to improve the series further would be to prioritise tasks based on their rate of allocation with the caveat that it may be very expensive to track. This patch (of 5): Page reclaim throttles on wait_iff_congested under the following conditions: - kswapd is encountering pages under writeback and marked for immediate reclaim implying that pages are cycling through the LRU faster than pages can be cleaned. - Direct reclaim will stall if all dirty pages are backed by congested inodes. wait_iff_congested is almost completely broken with few exceptions. This patch adds a new node-based workqueue and tracks the number of throttled tasks and pages written back since throttling started. If enough pages belonging to the node are written back then the throttled tasks will wake early. If not, the throttled tasks sleeps until the timeout expires. [neilb@suse.de: Uninterruptible sleep and simpler wakeups] [hdanton@sina.com: Avoid race when reclaim starts] [vbabka@suse.cz: vmstat irq-safe api, clarifications] Link: https://lore.kernel.org/linux-mm/45d8b7a6-8548-65f5-cccf-9f451d4ae3d4@kernel.dk/ [1] Link: https://lkml.kernel.org/r/20211022144651.19914-1-mgorman@techsingularity.net Link: https://lkml.kernel.org/r/20211022144651.19914-2-mgorman@techsingularity.net Signed-off-by: Mel Gorman <mgorman@techsingularity.net> Acked-by: Vlastimil Babka <vbabka@suse.cz> Cc: NeilBrown <neilb@suse.de> Cc: "Theodore Ts'o" <tytso@mit.edu> Cc: Andreas Dilger <adilger.kernel@dilger.ca> Cc: "Darrick J . Wong" <djwong@kernel.org> Cc: Matthew Wilcox <willy@infradead.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Dave Chinner <david@fromorbit.com> Cc: Rik van Riel <riel@surriel.com> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Jonathan Corbet <corbet@lwn.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2229 lines
54 KiB
C
2229 lines
54 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/mm/vmstat.c
|
|
*
|
|
* Manages VM statistics
|
|
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
|
|
*
|
|
* zoned VM statistics
|
|
* Copyright (C) 2006 Silicon Graphics, Inc.,
|
|
* Christoph Lameter <christoph@lameter.com>
|
|
* Copyright (C) 2008-2014 Christoph Lameter
|
|
*/
|
|
#include <linux/fs.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/err.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/vmstat.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/writeback.h>
|
|
#include <linux/compaction.h>
|
|
#include <linux/mm_inline.h>
|
|
#include <linux/page_ext.h>
|
|
#include <linux/page_owner.h>
|
|
|
|
#include "internal.h"
|
|
|
|
#ifdef CONFIG_NUMA
|
|
int sysctl_vm_numa_stat = ENABLE_NUMA_STAT;
|
|
|
|
/* zero numa counters within a zone */
|
|
static void zero_zone_numa_counters(struct zone *zone)
|
|
{
|
|
int item, cpu;
|
|
|
|
for (item = 0; item < NR_VM_NUMA_EVENT_ITEMS; item++) {
|
|
atomic_long_set(&zone->vm_numa_event[item], 0);
|
|
for_each_online_cpu(cpu) {
|
|
per_cpu_ptr(zone->per_cpu_zonestats, cpu)->vm_numa_event[item]
|
|
= 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* zero numa counters of all the populated zones */
|
|
static void zero_zones_numa_counters(void)
|
|
{
|
|
struct zone *zone;
|
|
|
|
for_each_populated_zone(zone)
|
|
zero_zone_numa_counters(zone);
|
|
}
|
|
|
|
/* zero global numa counters */
|
|
static void zero_global_numa_counters(void)
|
|
{
|
|
int item;
|
|
|
|
for (item = 0; item < NR_VM_NUMA_EVENT_ITEMS; item++)
|
|
atomic_long_set(&vm_numa_event[item], 0);
|
|
}
|
|
|
|
static void invalid_numa_statistics(void)
|
|
{
|
|
zero_zones_numa_counters();
|
|
zero_global_numa_counters();
|
|
}
|
|
|
|
static DEFINE_MUTEX(vm_numa_stat_lock);
|
|
|
|
int sysctl_vm_numa_stat_handler(struct ctl_table *table, int write,
|
|
void *buffer, size_t *length, loff_t *ppos)
|
|
{
|
|
int ret, oldval;
|
|
|
|
mutex_lock(&vm_numa_stat_lock);
|
|
if (write)
|
|
oldval = sysctl_vm_numa_stat;
|
|
ret = proc_dointvec_minmax(table, write, buffer, length, ppos);
|
|
if (ret || !write)
|
|
goto out;
|
|
|
|
if (oldval == sysctl_vm_numa_stat)
|
|
goto out;
|
|
else if (sysctl_vm_numa_stat == ENABLE_NUMA_STAT) {
|
|
static_branch_enable(&vm_numa_stat_key);
|
|
pr_info("enable numa statistics\n");
|
|
} else {
|
|
static_branch_disable(&vm_numa_stat_key);
|
|
invalid_numa_statistics();
|
|
pr_info("disable numa statistics, and clear numa counters\n");
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&vm_numa_stat_lock);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_VM_EVENT_COUNTERS
|
|
DEFINE_PER_CPU(struct vm_event_state, vm_event_states) = {{0}};
|
|
EXPORT_PER_CPU_SYMBOL(vm_event_states);
|
|
|
|
static void sum_vm_events(unsigned long *ret)
|
|
{
|
|
int cpu;
|
|
int i;
|
|
|
|
memset(ret, 0, NR_VM_EVENT_ITEMS * sizeof(unsigned long));
|
|
|
|
for_each_online_cpu(cpu) {
|
|
struct vm_event_state *this = &per_cpu(vm_event_states, cpu);
|
|
|
|
for (i = 0; i < NR_VM_EVENT_ITEMS; i++)
|
|
ret[i] += this->event[i];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Accumulate the vm event counters across all CPUs.
|
|
* The result is unavoidably approximate - it can change
|
|
* during and after execution of this function.
|
|
*/
|
|
void all_vm_events(unsigned long *ret)
|
|
{
|
|
cpus_read_lock();
|
|
sum_vm_events(ret);
|
|
cpus_read_unlock();
|
|
}
|
|
EXPORT_SYMBOL_GPL(all_vm_events);
|
|
|
|
/*
|
|
* Fold the foreign cpu events into our own.
|
|
*
|
|
* This is adding to the events on one processor
|
|
* but keeps the global counts constant.
|
|
*/
|
|
void vm_events_fold_cpu(int cpu)
|
|
{
|
|
struct vm_event_state *fold_state = &per_cpu(vm_event_states, cpu);
|
|
int i;
|
|
|
|
for (i = 0; i < NR_VM_EVENT_ITEMS; i++) {
|
|
count_vm_events(i, fold_state->event[i]);
|
|
fold_state->event[i] = 0;
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_VM_EVENT_COUNTERS */
|
|
|
|
/*
|
|
* Manage combined zone based / global counters
|
|
*
|
|
* vm_stat contains the global counters
|
|
*/
|
|
atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS] __cacheline_aligned_in_smp;
|
|
atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS] __cacheline_aligned_in_smp;
|
|
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS] __cacheline_aligned_in_smp;
|
|
EXPORT_SYMBOL(vm_zone_stat);
|
|
EXPORT_SYMBOL(vm_node_stat);
|
|
|
|
#ifdef CONFIG_NUMA
|
|
static void fold_vm_zone_numa_events(struct zone *zone)
|
|
{
|
|
unsigned long zone_numa_events[NR_VM_NUMA_EVENT_ITEMS] = { 0, };
|
|
int cpu;
|
|
enum numa_stat_item item;
|
|
|
|
for_each_online_cpu(cpu) {
|
|
struct per_cpu_zonestat *pzstats;
|
|
|
|
pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu);
|
|
for (item = 0; item < NR_VM_NUMA_EVENT_ITEMS; item++)
|
|
zone_numa_events[item] += xchg(&pzstats->vm_numa_event[item], 0);
|
|
}
|
|
|
|
for (item = 0; item < NR_VM_NUMA_EVENT_ITEMS; item++)
|
|
zone_numa_event_add(zone_numa_events[item], zone, item);
|
|
}
|
|
|
|
void fold_vm_numa_events(void)
|
|
{
|
|
struct zone *zone;
|
|
|
|
for_each_populated_zone(zone)
|
|
fold_vm_zone_numa_events(zone);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
int calculate_pressure_threshold(struct zone *zone)
|
|
{
|
|
int threshold;
|
|
int watermark_distance;
|
|
|
|
/*
|
|
* As vmstats are not up to date, there is drift between the estimated
|
|
* and real values. For high thresholds and a high number of CPUs, it
|
|
* is possible for the min watermark to be breached while the estimated
|
|
* value looks fine. The pressure threshold is a reduced value such
|
|
* that even the maximum amount of drift will not accidentally breach
|
|
* the min watermark
|
|
*/
|
|
watermark_distance = low_wmark_pages(zone) - min_wmark_pages(zone);
|
|
threshold = max(1, (int)(watermark_distance / num_online_cpus()));
|
|
|
|
/*
|
|
* Maximum threshold is 125
|
|
*/
|
|
threshold = min(125, threshold);
|
|
|
|
return threshold;
|
|
}
|
|
|
|
int calculate_normal_threshold(struct zone *zone)
|
|
{
|
|
int threshold;
|
|
int mem; /* memory in 128 MB units */
|
|
|
|
/*
|
|
* The threshold scales with the number of processors and the amount
|
|
* of memory per zone. More memory means that we can defer updates for
|
|
* longer, more processors could lead to more contention.
|
|
* fls() is used to have a cheap way of logarithmic scaling.
|
|
*
|
|
* Some sample thresholds:
|
|
*
|
|
* Threshold Processors (fls) Zonesize fls(mem)+1
|
|
* ------------------------------------------------------------------
|
|
* 8 1 1 0.9-1 GB 4
|
|
* 16 2 2 0.9-1 GB 4
|
|
* 20 2 2 1-2 GB 5
|
|
* 24 2 2 2-4 GB 6
|
|
* 28 2 2 4-8 GB 7
|
|
* 32 2 2 8-16 GB 8
|
|
* 4 2 2 <128M 1
|
|
* 30 4 3 2-4 GB 5
|
|
* 48 4 3 8-16 GB 8
|
|
* 32 8 4 1-2 GB 4
|
|
* 32 8 4 0.9-1GB 4
|
|
* 10 16 5 <128M 1
|
|
* 40 16 5 900M 4
|
|
* 70 64 7 2-4 GB 5
|
|
* 84 64 7 4-8 GB 6
|
|
* 108 512 9 4-8 GB 6
|
|
* 125 1024 10 8-16 GB 8
|
|
* 125 1024 10 16-32 GB 9
|
|
*/
|
|
|
|
mem = zone_managed_pages(zone) >> (27 - PAGE_SHIFT);
|
|
|
|
threshold = 2 * fls(num_online_cpus()) * (1 + fls(mem));
|
|
|
|
/*
|
|
* Maximum threshold is 125
|
|
*/
|
|
threshold = min(125, threshold);
|
|
|
|
return threshold;
|
|
}
|
|
|
|
/*
|
|
* Refresh the thresholds for each zone.
|
|
*/
|
|
void refresh_zone_stat_thresholds(void)
|
|
{
|
|
struct pglist_data *pgdat;
|
|
struct zone *zone;
|
|
int cpu;
|
|
int threshold;
|
|
|
|
/* Zero current pgdat thresholds */
|
|
for_each_online_pgdat(pgdat) {
|
|
for_each_online_cpu(cpu) {
|
|
per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold = 0;
|
|
}
|
|
}
|
|
|
|
for_each_populated_zone(zone) {
|
|
struct pglist_data *pgdat = zone->zone_pgdat;
|
|
unsigned long max_drift, tolerate_drift;
|
|
|
|
threshold = calculate_normal_threshold(zone);
|
|
|
|
for_each_online_cpu(cpu) {
|
|
int pgdat_threshold;
|
|
|
|
per_cpu_ptr(zone->per_cpu_zonestats, cpu)->stat_threshold
|
|
= threshold;
|
|
|
|
/* Base nodestat threshold on the largest populated zone. */
|
|
pgdat_threshold = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold;
|
|
per_cpu_ptr(pgdat->per_cpu_nodestats, cpu)->stat_threshold
|
|
= max(threshold, pgdat_threshold);
|
|
}
|
|
|
|
/*
|
|
* Only set percpu_drift_mark if there is a danger that
|
|
* NR_FREE_PAGES reports the low watermark is ok when in fact
|
|
* the min watermark could be breached by an allocation
|
|
*/
|
|
tolerate_drift = low_wmark_pages(zone) - min_wmark_pages(zone);
|
|
max_drift = num_online_cpus() * threshold;
|
|
if (max_drift > tolerate_drift)
|
|
zone->percpu_drift_mark = high_wmark_pages(zone) +
|
|
max_drift;
|
|
}
|
|
}
|
|
|
|
void set_pgdat_percpu_threshold(pg_data_t *pgdat,
|
|
int (*calculate_pressure)(struct zone *))
|
|
{
|
|
struct zone *zone;
|
|
int cpu;
|
|
int threshold;
|
|
int i;
|
|
|
|
for (i = 0; i < pgdat->nr_zones; i++) {
|
|
zone = &pgdat->node_zones[i];
|
|
if (!zone->percpu_drift_mark)
|
|
continue;
|
|
|
|
threshold = (*calculate_pressure)(zone);
|
|
for_each_online_cpu(cpu)
|
|
per_cpu_ptr(zone->per_cpu_zonestats, cpu)->stat_threshold
|
|
= threshold;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For use when we know that interrupts are disabled,
|
|
* or when we know that preemption is disabled and that
|
|
* particular counter cannot be updated from interrupt context.
|
|
*/
|
|
void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
|
|
long delta)
|
|
{
|
|
struct per_cpu_zonestat __percpu *pcp = zone->per_cpu_zonestats;
|
|
s8 __percpu *p = pcp->vm_stat_diff + item;
|
|
long x;
|
|
long t;
|
|
|
|
/*
|
|
* Accurate vmstat updates require a RMW. On !PREEMPT_RT kernels,
|
|
* atomicity is provided by IRQs being disabled -- either explicitly
|
|
* or via local_lock_irq. On PREEMPT_RT, local_lock_irq only disables
|
|
* CPU migrations and preemption potentially corrupts a counter so
|
|
* disable preemption.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
x = delta + __this_cpu_read(*p);
|
|
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
|
|
if (unlikely(abs(x) > t)) {
|
|
zone_page_state_add(x, zone, item);
|
|
x = 0;
|
|
}
|
|
__this_cpu_write(*p, x);
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
EXPORT_SYMBOL(__mod_zone_page_state);
|
|
|
|
void __mod_node_page_state(struct pglist_data *pgdat, enum node_stat_item item,
|
|
long delta)
|
|
{
|
|
struct per_cpu_nodestat __percpu *pcp = pgdat->per_cpu_nodestats;
|
|
s8 __percpu *p = pcp->vm_node_stat_diff + item;
|
|
long x;
|
|
long t;
|
|
|
|
if (vmstat_item_in_bytes(item)) {
|
|
/*
|
|
* Only cgroups use subpage accounting right now; at
|
|
* the global level, these items still change in
|
|
* multiples of whole pages. Store them as pages
|
|
* internally to keep the per-cpu counters compact.
|
|
*/
|
|
VM_WARN_ON_ONCE(delta & (PAGE_SIZE - 1));
|
|
delta >>= PAGE_SHIFT;
|
|
}
|
|
|
|
/* See __mod_node_page_state */
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
x = delta + __this_cpu_read(*p);
|
|
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
|
|
if (unlikely(abs(x) > t)) {
|
|
node_page_state_add(x, pgdat, item);
|
|
x = 0;
|
|
}
|
|
__this_cpu_write(*p, x);
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
EXPORT_SYMBOL(__mod_node_page_state);
|
|
|
|
/*
|
|
* Optimized increment and decrement functions.
|
|
*
|
|
* These are only for a single page and therefore can take a struct page *
|
|
* argument instead of struct zone *. This allows the inclusion of the code
|
|
* generated for page_zone(page) into the optimized functions.
|
|
*
|
|
* No overflow check is necessary and therefore the differential can be
|
|
* incremented or decremented in place which may allow the compilers to
|
|
* generate better code.
|
|
* The increment or decrement is known and therefore one boundary check can
|
|
* be omitted.
|
|
*
|
|
* NOTE: These functions are very performance sensitive. Change only
|
|
* with care.
|
|
*
|
|
* Some processors have inc/dec instructions that are atomic vs an interrupt.
|
|
* However, the code must first determine the differential location in a zone
|
|
* based on the processor number and then inc/dec the counter. There is no
|
|
* guarantee without disabling preemption that the processor will not change
|
|
* in between and therefore the atomicity vs. interrupt cannot be exploited
|
|
* in a useful way here.
|
|
*/
|
|
void __inc_zone_state(struct zone *zone, enum zone_stat_item item)
|
|
{
|
|
struct per_cpu_zonestat __percpu *pcp = zone->per_cpu_zonestats;
|
|
s8 __percpu *p = pcp->vm_stat_diff + item;
|
|
s8 v, t;
|
|
|
|
/* See __mod_node_page_state */
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
v = __this_cpu_inc_return(*p);
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
if (unlikely(v > t)) {
|
|
s8 overstep = t >> 1;
|
|
|
|
zone_page_state_add(v + overstep, zone, item);
|
|
__this_cpu_write(*p, -overstep);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
|
|
void __inc_node_state(struct pglist_data *pgdat, enum node_stat_item item)
|
|
{
|
|
struct per_cpu_nodestat __percpu *pcp = pgdat->per_cpu_nodestats;
|
|
s8 __percpu *p = pcp->vm_node_stat_diff + item;
|
|
s8 v, t;
|
|
|
|
VM_WARN_ON_ONCE(vmstat_item_in_bytes(item));
|
|
|
|
/* See __mod_node_page_state */
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
v = __this_cpu_inc_return(*p);
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
if (unlikely(v > t)) {
|
|
s8 overstep = t >> 1;
|
|
|
|
node_page_state_add(v + overstep, pgdat, item);
|
|
__this_cpu_write(*p, -overstep);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
|
|
void __inc_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
__inc_zone_state(page_zone(page), item);
|
|
}
|
|
EXPORT_SYMBOL(__inc_zone_page_state);
|
|
|
|
void __inc_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
__inc_node_state(page_pgdat(page), item);
|
|
}
|
|
EXPORT_SYMBOL(__inc_node_page_state);
|
|
|
|
void __dec_zone_state(struct zone *zone, enum zone_stat_item item)
|
|
{
|
|
struct per_cpu_zonestat __percpu *pcp = zone->per_cpu_zonestats;
|
|
s8 __percpu *p = pcp->vm_stat_diff + item;
|
|
s8 v, t;
|
|
|
|
/* See __mod_node_page_state */
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
v = __this_cpu_dec_return(*p);
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
if (unlikely(v < - t)) {
|
|
s8 overstep = t >> 1;
|
|
|
|
zone_page_state_add(v - overstep, zone, item);
|
|
__this_cpu_write(*p, overstep);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
|
|
void __dec_node_state(struct pglist_data *pgdat, enum node_stat_item item)
|
|
{
|
|
struct per_cpu_nodestat __percpu *pcp = pgdat->per_cpu_nodestats;
|
|
s8 __percpu *p = pcp->vm_node_stat_diff + item;
|
|
s8 v, t;
|
|
|
|
VM_WARN_ON_ONCE(vmstat_item_in_bytes(item));
|
|
|
|
/* See __mod_node_page_state */
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_disable();
|
|
|
|
v = __this_cpu_dec_return(*p);
|
|
t = __this_cpu_read(pcp->stat_threshold);
|
|
if (unlikely(v < - t)) {
|
|
s8 overstep = t >> 1;
|
|
|
|
node_page_state_add(v - overstep, pgdat, item);
|
|
__this_cpu_write(*p, overstep);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PREEMPT_RT))
|
|
preempt_enable();
|
|
}
|
|
|
|
void __dec_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
__dec_zone_state(page_zone(page), item);
|
|
}
|
|
EXPORT_SYMBOL(__dec_zone_page_state);
|
|
|
|
void __dec_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
__dec_node_state(page_pgdat(page), item);
|
|
}
|
|
EXPORT_SYMBOL(__dec_node_page_state);
|
|
|
|
#ifdef CONFIG_HAVE_CMPXCHG_LOCAL
|
|
/*
|
|
* If we have cmpxchg_local support then we do not need to incur the overhead
|
|
* that comes with local_irq_save/restore if we use this_cpu_cmpxchg.
|
|
*
|
|
* mod_state() modifies the zone counter state through atomic per cpu
|
|
* operations.
|
|
*
|
|
* Overstep mode specifies how overstep should handled:
|
|
* 0 No overstepping
|
|
* 1 Overstepping half of threshold
|
|
* -1 Overstepping minus half of threshold
|
|
*/
|
|
static inline void mod_zone_state(struct zone *zone,
|
|
enum zone_stat_item item, long delta, int overstep_mode)
|
|
{
|
|
struct per_cpu_zonestat __percpu *pcp = zone->per_cpu_zonestats;
|
|
s8 __percpu *p = pcp->vm_stat_diff + item;
|
|
long o, n, t, z;
|
|
|
|
do {
|
|
z = 0; /* overflow to zone counters */
|
|
|
|
/*
|
|
* The fetching of the stat_threshold is racy. We may apply
|
|
* a counter threshold to the wrong the cpu if we get
|
|
* rescheduled while executing here. However, the next
|
|
* counter update will apply the threshold again and
|
|
* therefore bring the counter under the threshold again.
|
|
*
|
|
* Most of the time the thresholds are the same anyways
|
|
* for all cpus in a zone.
|
|
*/
|
|
t = this_cpu_read(pcp->stat_threshold);
|
|
|
|
o = this_cpu_read(*p);
|
|
n = delta + o;
|
|
|
|
if (abs(n) > t) {
|
|
int os = overstep_mode * (t >> 1) ;
|
|
|
|
/* Overflow must be added to zone counters */
|
|
z = n + os;
|
|
n = -os;
|
|
}
|
|
} while (this_cpu_cmpxchg(*p, o, n) != o);
|
|
|
|
if (z)
|
|
zone_page_state_add(z, zone, item);
|
|
}
|
|
|
|
void mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
|
|
long delta)
|
|
{
|
|
mod_zone_state(zone, item, delta, 0);
|
|
}
|
|
EXPORT_SYMBOL(mod_zone_page_state);
|
|
|
|
void inc_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
mod_zone_state(page_zone(page), item, 1, 1);
|
|
}
|
|
EXPORT_SYMBOL(inc_zone_page_state);
|
|
|
|
void dec_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
mod_zone_state(page_zone(page), item, -1, -1);
|
|
}
|
|
EXPORT_SYMBOL(dec_zone_page_state);
|
|
|
|
static inline void mod_node_state(struct pglist_data *pgdat,
|
|
enum node_stat_item item, int delta, int overstep_mode)
|
|
{
|
|
struct per_cpu_nodestat __percpu *pcp = pgdat->per_cpu_nodestats;
|
|
s8 __percpu *p = pcp->vm_node_stat_diff + item;
|
|
long o, n, t, z;
|
|
|
|
if (vmstat_item_in_bytes(item)) {
|
|
/*
|
|
* Only cgroups use subpage accounting right now; at
|
|
* the global level, these items still change in
|
|
* multiples of whole pages. Store them as pages
|
|
* internally to keep the per-cpu counters compact.
|
|
*/
|
|
VM_WARN_ON_ONCE(delta & (PAGE_SIZE - 1));
|
|
delta >>= PAGE_SHIFT;
|
|
}
|
|
|
|
do {
|
|
z = 0; /* overflow to node counters */
|
|
|
|
/*
|
|
* The fetching of the stat_threshold is racy. We may apply
|
|
* a counter threshold to the wrong the cpu if we get
|
|
* rescheduled while executing here. However, the next
|
|
* counter update will apply the threshold again and
|
|
* therefore bring the counter under the threshold again.
|
|
*
|
|
* Most of the time the thresholds are the same anyways
|
|
* for all cpus in a node.
|
|
*/
|
|
t = this_cpu_read(pcp->stat_threshold);
|
|
|
|
o = this_cpu_read(*p);
|
|
n = delta + o;
|
|
|
|
if (abs(n) > t) {
|
|
int os = overstep_mode * (t >> 1) ;
|
|
|
|
/* Overflow must be added to node counters */
|
|
z = n + os;
|
|
n = -os;
|
|
}
|
|
} while (this_cpu_cmpxchg(*p, o, n) != o);
|
|
|
|
if (z)
|
|
node_page_state_add(z, pgdat, item);
|
|
}
|
|
|
|
void mod_node_page_state(struct pglist_data *pgdat, enum node_stat_item item,
|
|
long delta)
|
|
{
|
|
mod_node_state(pgdat, item, delta, 0);
|
|
}
|
|
EXPORT_SYMBOL(mod_node_page_state);
|
|
|
|
void inc_node_state(struct pglist_data *pgdat, enum node_stat_item item)
|
|
{
|
|
mod_node_state(pgdat, item, 1, 1);
|
|
}
|
|
|
|
void inc_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
mod_node_state(page_pgdat(page), item, 1, 1);
|
|
}
|
|
EXPORT_SYMBOL(inc_node_page_state);
|
|
|
|
void dec_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
mod_node_state(page_pgdat(page), item, -1, -1);
|
|
}
|
|
EXPORT_SYMBOL(dec_node_page_state);
|
|
#else
|
|
/*
|
|
* Use interrupt disable to serialize counter updates
|
|
*/
|
|
void mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
|
|
long delta)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__mod_zone_page_state(zone, item, delta);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(mod_zone_page_state);
|
|
|
|
void inc_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
unsigned long flags;
|
|
struct zone *zone;
|
|
|
|
zone = page_zone(page);
|
|
local_irq_save(flags);
|
|
__inc_zone_state(zone, item);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(inc_zone_page_state);
|
|
|
|
void dec_zone_page_state(struct page *page, enum zone_stat_item item)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__dec_zone_page_state(page, item);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(dec_zone_page_state);
|
|
|
|
void inc_node_state(struct pglist_data *pgdat, enum node_stat_item item)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__inc_node_state(pgdat, item);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(inc_node_state);
|
|
|
|
void mod_node_page_state(struct pglist_data *pgdat, enum node_stat_item item,
|
|
long delta)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__mod_node_page_state(pgdat, item, delta);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(mod_node_page_state);
|
|
|
|
void inc_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
unsigned long flags;
|
|
struct pglist_data *pgdat;
|
|
|
|
pgdat = page_pgdat(page);
|
|
local_irq_save(flags);
|
|
__inc_node_state(pgdat, item);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(inc_node_page_state);
|
|
|
|
void dec_node_page_state(struct page *page, enum node_stat_item item)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__dec_node_page_state(page, item);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(dec_node_page_state);
|
|
#endif
|
|
|
|
/*
|
|
* Fold a differential into the global counters.
|
|
* Returns the number of counters updated.
|
|
*/
|
|
static int fold_diff(int *zone_diff, int *node_diff)
|
|
{
|
|
int i;
|
|
int changes = 0;
|
|
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
|
|
if (zone_diff[i]) {
|
|
atomic_long_add(zone_diff[i], &vm_zone_stat[i]);
|
|
changes++;
|
|
}
|
|
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++)
|
|
if (node_diff[i]) {
|
|
atomic_long_add(node_diff[i], &vm_node_stat[i]);
|
|
changes++;
|
|
}
|
|
return changes;
|
|
}
|
|
|
|
/*
|
|
* Update the zone counters for the current cpu.
|
|
*
|
|
* Note that refresh_cpu_vm_stats strives to only access
|
|
* node local memory. The per cpu pagesets on remote zones are placed
|
|
* in the memory local to the processor using that pageset. So the
|
|
* loop over all zones will access a series of cachelines local to
|
|
* the processor.
|
|
*
|
|
* The call to zone_page_state_add updates the cachelines with the
|
|
* statistics in the remote zone struct as well as the global cachelines
|
|
* with the global counters. These could cause remote node cache line
|
|
* bouncing and will have to be only done when necessary.
|
|
*
|
|
* The function returns the number of global counters updated.
|
|
*/
|
|
static int refresh_cpu_vm_stats(bool do_pagesets)
|
|
{
|
|
struct pglist_data *pgdat;
|
|
struct zone *zone;
|
|
int i;
|
|
int global_zone_diff[NR_VM_ZONE_STAT_ITEMS] = { 0, };
|
|
int global_node_diff[NR_VM_NODE_STAT_ITEMS] = { 0, };
|
|
int changes = 0;
|
|
|
|
for_each_populated_zone(zone) {
|
|
struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats;
|
|
#ifdef CONFIG_NUMA
|
|
struct per_cpu_pages __percpu *pcp = zone->per_cpu_pageset;
|
|
#endif
|
|
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) {
|
|
int v;
|
|
|
|
v = this_cpu_xchg(pzstats->vm_stat_diff[i], 0);
|
|
if (v) {
|
|
|
|
atomic_long_add(v, &zone->vm_stat[i]);
|
|
global_zone_diff[i] += v;
|
|
#ifdef CONFIG_NUMA
|
|
/* 3 seconds idle till flush */
|
|
__this_cpu_write(pcp->expire, 3);
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef CONFIG_NUMA
|
|
|
|
if (do_pagesets) {
|
|
cond_resched();
|
|
/*
|
|
* Deal with draining the remote pageset of this
|
|
* processor
|
|
*
|
|
* Check if there are pages remaining in this pageset
|
|
* if not then there is nothing to expire.
|
|
*/
|
|
if (!__this_cpu_read(pcp->expire) ||
|
|
!__this_cpu_read(pcp->count))
|
|
continue;
|
|
|
|
/*
|
|
* We never drain zones local to this processor.
|
|
*/
|
|
if (zone_to_nid(zone) == numa_node_id()) {
|
|
__this_cpu_write(pcp->expire, 0);
|
|
continue;
|
|
}
|
|
|
|
if (__this_cpu_dec_return(pcp->expire))
|
|
continue;
|
|
|
|
if (__this_cpu_read(pcp->count)) {
|
|
drain_zone_pages(zone, this_cpu_ptr(pcp));
|
|
changes++;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for_each_online_pgdat(pgdat) {
|
|
struct per_cpu_nodestat __percpu *p = pgdat->per_cpu_nodestats;
|
|
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) {
|
|
int v;
|
|
|
|
v = this_cpu_xchg(p->vm_node_stat_diff[i], 0);
|
|
if (v) {
|
|
atomic_long_add(v, &pgdat->vm_stat[i]);
|
|
global_node_diff[i] += v;
|
|
}
|
|
}
|
|
}
|
|
|
|
changes += fold_diff(global_zone_diff, global_node_diff);
|
|
return changes;
|
|
}
|
|
|
|
/*
|
|
* Fold the data for an offline cpu into the global array.
|
|
* There cannot be any access by the offline cpu and therefore
|
|
* synchronization is simplified.
|
|
*/
|
|
void cpu_vm_stats_fold(int cpu)
|
|
{
|
|
struct pglist_data *pgdat;
|
|
struct zone *zone;
|
|
int i;
|
|
int global_zone_diff[NR_VM_ZONE_STAT_ITEMS] = { 0, };
|
|
int global_node_diff[NR_VM_NODE_STAT_ITEMS] = { 0, };
|
|
|
|
for_each_populated_zone(zone) {
|
|
struct per_cpu_zonestat *pzstats;
|
|
|
|
pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu);
|
|
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) {
|
|
if (pzstats->vm_stat_diff[i]) {
|
|
int v;
|
|
|
|
v = pzstats->vm_stat_diff[i];
|
|
pzstats->vm_stat_diff[i] = 0;
|
|
atomic_long_add(v, &zone->vm_stat[i]);
|
|
global_zone_diff[i] += v;
|
|
}
|
|
}
|
|
#ifdef CONFIG_NUMA
|
|
for (i = 0; i < NR_VM_NUMA_EVENT_ITEMS; i++) {
|
|
if (pzstats->vm_numa_event[i]) {
|
|
unsigned long v;
|
|
|
|
v = pzstats->vm_numa_event[i];
|
|
pzstats->vm_numa_event[i] = 0;
|
|
zone_numa_event_add(v, zone, i);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for_each_online_pgdat(pgdat) {
|
|
struct per_cpu_nodestat *p;
|
|
|
|
p = per_cpu_ptr(pgdat->per_cpu_nodestats, cpu);
|
|
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++)
|
|
if (p->vm_node_stat_diff[i]) {
|
|
int v;
|
|
|
|
v = p->vm_node_stat_diff[i];
|
|
p->vm_node_stat_diff[i] = 0;
|
|
atomic_long_add(v, &pgdat->vm_stat[i]);
|
|
global_node_diff[i] += v;
|
|
}
|
|
}
|
|
|
|
fold_diff(global_zone_diff, global_node_diff);
|
|
}
|
|
|
|
/*
|
|
* this is only called if !populated_zone(zone), which implies no other users of
|
|
* pset->vm_stat_diff[] exist.
|
|
*/
|
|
void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats)
|
|
{
|
|
unsigned long v;
|
|
int i;
|
|
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) {
|
|
if (pzstats->vm_stat_diff[i]) {
|
|
v = pzstats->vm_stat_diff[i];
|
|
pzstats->vm_stat_diff[i] = 0;
|
|
zone_page_state_add(v, zone, i);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_NUMA
|
|
for (i = 0; i < NR_VM_NUMA_EVENT_ITEMS; i++) {
|
|
if (pzstats->vm_numa_event[i]) {
|
|
v = pzstats->vm_numa_event[i];
|
|
pzstats->vm_numa_event[i] = 0;
|
|
zone_numa_event_add(v, zone, i);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NUMA
|
|
/*
|
|
* Determine the per node value of a stat item. This function
|
|
* is called frequently in a NUMA machine, so try to be as
|
|
* frugal as possible.
|
|
*/
|
|
unsigned long sum_zone_node_page_state(int node,
|
|
enum zone_stat_item item)
|
|
{
|
|
struct zone *zones = NODE_DATA(node)->node_zones;
|
|
int i;
|
|
unsigned long count = 0;
|
|
|
|
for (i = 0; i < MAX_NR_ZONES; i++)
|
|
count += zone_page_state(zones + i, item);
|
|
|
|
return count;
|
|
}
|
|
|
|
/* Determine the per node value of a numa stat item. */
|
|
unsigned long sum_zone_numa_event_state(int node,
|
|
enum numa_stat_item item)
|
|
{
|
|
struct zone *zones = NODE_DATA(node)->node_zones;
|
|
unsigned long count = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_NR_ZONES; i++)
|
|
count += zone_numa_event_state(zones + i, item);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* Determine the per node value of a stat item.
|
|
*/
|
|
unsigned long node_page_state_pages(struct pglist_data *pgdat,
|
|
enum node_stat_item item)
|
|
{
|
|
long x = atomic_long_read(&pgdat->vm_stat[item]);
|
|
#ifdef CONFIG_SMP
|
|
if (x < 0)
|
|
x = 0;
|
|
#endif
|
|
return x;
|
|
}
|
|
|
|
unsigned long node_page_state(struct pglist_data *pgdat,
|
|
enum node_stat_item item)
|
|
{
|
|
VM_WARN_ON_ONCE(vmstat_item_in_bytes(item));
|
|
|
|
return node_page_state_pages(pgdat, item);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_COMPACTION
|
|
|
|
struct contig_page_info {
|
|
unsigned long free_pages;
|
|
unsigned long free_blocks_total;
|
|
unsigned long free_blocks_suitable;
|
|
};
|
|
|
|
/*
|
|
* Calculate the number of free pages in a zone, how many contiguous
|
|
* pages are free and how many are large enough to satisfy an allocation of
|
|
* the target size. Note that this function makes no attempt to estimate
|
|
* how many suitable free blocks there *might* be if MOVABLE pages were
|
|
* migrated. Calculating that is possible, but expensive and can be
|
|
* figured out from userspace
|
|
*/
|
|
static void fill_contig_page_info(struct zone *zone,
|
|
unsigned int suitable_order,
|
|
struct contig_page_info *info)
|
|
{
|
|
unsigned int order;
|
|
|
|
info->free_pages = 0;
|
|
info->free_blocks_total = 0;
|
|
info->free_blocks_suitable = 0;
|
|
|
|
for (order = 0; order < MAX_ORDER; order++) {
|
|
unsigned long blocks;
|
|
|
|
/* Count number of free blocks */
|
|
blocks = zone->free_area[order].nr_free;
|
|
info->free_blocks_total += blocks;
|
|
|
|
/* Count free base pages */
|
|
info->free_pages += blocks << order;
|
|
|
|
/* Count the suitable free blocks */
|
|
if (order >= suitable_order)
|
|
info->free_blocks_suitable += blocks <<
|
|
(order - suitable_order);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A fragmentation index only makes sense if an allocation of a requested
|
|
* size would fail. If that is true, the fragmentation index indicates
|
|
* whether external fragmentation or a lack of memory was the problem.
|
|
* The value can be used to determine if page reclaim or compaction
|
|
* should be used
|
|
*/
|
|
static int __fragmentation_index(unsigned int order, struct contig_page_info *info)
|
|
{
|
|
unsigned long requested = 1UL << order;
|
|
|
|
if (WARN_ON_ONCE(order >= MAX_ORDER))
|
|
return 0;
|
|
|
|
if (!info->free_blocks_total)
|
|
return 0;
|
|
|
|
/* Fragmentation index only makes sense when a request would fail */
|
|
if (info->free_blocks_suitable)
|
|
return -1000;
|
|
|
|
/*
|
|
* Index is between 0 and 1 so return within 3 decimal places
|
|
*
|
|
* 0 => allocation would fail due to lack of memory
|
|
* 1 => allocation would fail due to fragmentation
|
|
*/
|
|
return 1000 - div_u64( (1000+(div_u64(info->free_pages * 1000ULL, requested))), info->free_blocks_total);
|
|
}
|
|
|
|
/*
|
|
* Calculates external fragmentation within a zone wrt the given order.
|
|
* It is defined as the percentage of pages found in blocks of size
|
|
* less than 1 << order. It returns values in range [0, 100].
|
|
*/
|
|
unsigned int extfrag_for_order(struct zone *zone, unsigned int order)
|
|
{
|
|
struct contig_page_info info;
|
|
|
|
fill_contig_page_info(zone, order, &info);
|
|
if (info.free_pages == 0)
|
|
return 0;
|
|
|
|
return div_u64((info.free_pages -
|
|
(info.free_blocks_suitable << order)) * 100,
|
|
info.free_pages);
|
|
}
|
|
|
|
/* Same as __fragmentation index but allocs contig_page_info on stack */
|
|
int fragmentation_index(struct zone *zone, unsigned int order)
|
|
{
|
|
struct contig_page_info info;
|
|
|
|
fill_contig_page_info(zone, order, &info);
|
|
return __fragmentation_index(order, &info);
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_PROC_FS) || defined(CONFIG_SYSFS) || \
|
|
defined(CONFIG_NUMA) || defined(CONFIG_MEMCG)
|
|
#ifdef CONFIG_ZONE_DMA
|
|
#define TEXT_FOR_DMA(xx) xx "_dma",
|
|
#else
|
|
#define TEXT_FOR_DMA(xx)
|
|
#endif
|
|
|
|
#ifdef CONFIG_ZONE_DMA32
|
|
#define TEXT_FOR_DMA32(xx) xx "_dma32",
|
|
#else
|
|
#define TEXT_FOR_DMA32(xx)
|
|
#endif
|
|
|
|
#ifdef CONFIG_HIGHMEM
|
|
#define TEXT_FOR_HIGHMEM(xx) xx "_high",
|
|
#else
|
|
#define TEXT_FOR_HIGHMEM(xx)
|
|
#endif
|
|
|
|
#define TEXTS_FOR_ZONES(xx) TEXT_FOR_DMA(xx) TEXT_FOR_DMA32(xx) xx "_normal", \
|
|
TEXT_FOR_HIGHMEM(xx) xx "_movable",
|
|
|
|
const char * const vmstat_text[] = {
|
|
/* enum zone_stat_item counters */
|
|
"nr_free_pages",
|
|
"nr_zone_inactive_anon",
|
|
"nr_zone_active_anon",
|
|
"nr_zone_inactive_file",
|
|
"nr_zone_active_file",
|
|
"nr_zone_unevictable",
|
|
"nr_zone_write_pending",
|
|
"nr_mlock",
|
|
"nr_bounce",
|
|
#if IS_ENABLED(CONFIG_ZSMALLOC)
|
|
"nr_zspages",
|
|
#endif
|
|
"nr_free_cma",
|
|
|
|
/* enum numa_stat_item counters */
|
|
#ifdef CONFIG_NUMA
|
|
"numa_hit",
|
|
"numa_miss",
|
|
"numa_foreign",
|
|
"numa_interleave",
|
|
"numa_local",
|
|
"numa_other",
|
|
#endif
|
|
|
|
/* enum node_stat_item counters */
|
|
"nr_inactive_anon",
|
|
"nr_active_anon",
|
|
"nr_inactive_file",
|
|
"nr_active_file",
|
|
"nr_unevictable",
|
|
"nr_slab_reclaimable",
|
|
"nr_slab_unreclaimable",
|
|
"nr_isolated_anon",
|
|
"nr_isolated_file",
|
|
"workingset_nodes",
|
|
"workingset_refault_anon",
|
|
"workingset_refault_file",
|
|
"workingset_activate_anon",
|
|
"workingset_activate_file",
|
|
"workingset_restore_anon",
|
|
"workingset_restore_file",
|
|
"workingset_nodereclaim",
|
|
"nr_anon_pages",
|
|
"nr_mapped",
|
|
"nr_file_pages",
|
|
"nr_dirty",
|
|
"nr_writeback",
|
|
"nr_writeback_temp",
|
|
"nr_shmem",
|
|
"nr_shmem_hugepages",
|
|
"nr_shmem_pmdmapped",
|
|
"nr_file_hugepages",
|
|
"nr_file_pmdmapped",
|
|
"nr_anon_transparent_hugepages",
|
|
"nr_vmscan_write",
|
|
"nr_vmscan_immediate_reclaim",
|
|
"nr_dirtied",
|
|
"nr_written",
|
|
"nr_throttled_written",
|
|
"nr_kernel_misc_reclaimable",
|
|
"nr_foll_pin_acquired",
|
|
"nr_foll_pin_released",
|
|
"nr_kernel_stack",
|
|
#if IS_ENABLED(CONFIG_SHADOW_CALL_STACK)
|
|
"nr_shadow_call_stack",
|
|
#endif
|
|
"nr_page_table_pages",
|
|
#ifdef CONFIG_SWAP
|
|
"nr_swapcached",
|
|
#endif
|
|
|
|
/* enum writeback_stat_item counters */
|
|
"nr_dirty_threshold",
|
|
"nr_dirty_background_threshold",
|
|
|
|
#if defined(CONFIG_VM_EVENT_COUNTERS) || defined(CONFIG_MEMCG)
|
|
/* enum vm_event_item counters */
|
|
"pgpgin",
|
|
"pgpgout",
|
|
"pswpin",
|
|
"pswpout",
|
|
|
|
TEXTS_FOR_ZONES("pgalloc")
|
|
TEXTS_FOR_ZONES("allocstall")
|
|
TEXTS_FOR_ZONES("pgskip")
|
|
|
|
"pgfree",
|
|
"pgactivate",
|
|
"pgdeactivate",
|
|
"pglazyfree",
|
|
|
|
"pgfault",
|
|
"pgmajfault",
|
|
"pglazyfreed",
|
|
|
|
"pgrefill",
|
|
"pgreuse",
|
|
"pgsteal_kswapd",
|
|
"pgsteal_direct",
|
|
"pgdemote_kswapd",
|
|
"pgdemote_direct",
|
|
"pgscan_kswapd",
|
|
"pgscan_direct",
|
|
"pgscan_direct_throttle",
|
|
"pgscan_anon",
|
|
"pgscan_file",
|
|
"pgsteal_anon",
|
|
"pgsteal_file",
|
|
|
|
#ifdef CONFIG_NUMA
|
|
"zone_reclaim_failed",
|
|
#endif
|
|
"pginodesteal",
|
|
"slabs_scanned",
|
|
"kswapd_inodesteal",
|
|
"kswapd_low_wmark_hit_quickly",
|
|
"kswapd_high_wmark_hit_quickly",
|
|
"pageoutrun",
|
|
|
|
"pgrotated",
|
|
|
|
"drop_pagecache",
|
|
"drop_slab",
|
|
"oom_kill",
|
|
|
|
#ifdef CONFIG_NUMA_BALANCING
|
|
"numa_pte_updates",
|
|
"numa_huge_pte_updates",
|
|
"numa_hint_faults",
|
|
"numa_hint_faults_local",
|
|
"numa_pages_migrated",
|
|
#endif
|
|
#ifdef CONFIG_MIGRATION
|
|
"pgmigrate_success",
|
|
"pgmigrate_fail",
|
|
"thp_migration_success",
|
|
"thp_migration_fail",
|
|
"thp_migration_split",
|
|
#endif
|
|
#ifdef CONFIG_COMPACTION
|
|
"compact_migrate_scanned",
|
|
"compact_free_scanned",
|
|
"compact_isolated",
|
|
"compact_stall",
|
|
"compact_fail",
|
|
"compact_success",
|
|
"compact_daemon_wake",
|
|
"compact_daemon_migrate_scanned",
|
|
"compact_daemon_free_scanned",
|
|
#endif
|
|
|
|
#ifdef CONFIG_HUGETLB_PAGE
|
|
"htlb_buddy_alloc_success",
|
|
"htlb_buddy_alloc_fail",
|
|
#endif
|
|
#ifdef CONFIG_CMA
|
|
"cma_alloc_success",
|
|
"cma_alloc_fail",
|
|
#endif
|
|
"unevictable_pgs_culled",
|
|
"unevictable_pgs_scanned",
|
|
"unevictable_pgs_rescued",
|
|
"unevictable_pgs_mlocked",
|
|
"unevictable_pgs_munlocked",
|
|
"unevictable_pgs_cleared",
|
|
"unevictable_pgs_stranded",
|
|
|
|
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
|
"thp_fault_alloc",
|
|
"thp_fault_fallback",
|
|
"thp_fault_fallback_charge",
|
|
"thp_collapse_alloc",
|
|
"thp_collapse_alloc_failed",
|
|
"thp_file_alloc",
|
|
"thp_file_fallback",
|
|
"thp_file_fallback_charge",
|
|
"thp_file_mapped",
|
|
"thp_split_page",
|
|
"thp_split_page_failed",
|
|
"thp_deferred_split_page",
|
|
"thp_split_pmd",
|
|
#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
|
|
"thp_split_pud",
|
|
#endif
|
|
"thp_zero_page_alloc",
|
|
"thp_zero_page_alloc_failed",
|
|
"thp_swpout",
|
|
"thp_swpout_fallback",
|
|
#endif
|
|
#ifdef CONFIG_MEMORY_BALLOON
|
|
"balloon_inflate",
|
|
"balloon_deflate",
|
|
#ifdef CONFIG_BALLOON_COMPACTION
|
|
"balloon_migrate",
|
|
#endif
|
|
#endif /* CONFIG_MEMORY_BALLOON */
|
|
#ifdef CONFIG_DEBUG_TLBFLUSH
|
|
"nr_tlb_remote_flush",
|
|
"nr_tlb_remote_flush_received",
|
|
"nr_tlb_local_flush_all",
|
|
"nr_tlb_local_flush_one",
|
|
#endif /* CONFIG_DEBUG_TLBFLUSH */
|
|
|
|
#ifdef CONFIG_DEBUG_VM_VMACACHE
|
|
"vmacache_find_calls",
|
|
"vmacache_find_hits",
|
|
#endif
|
|
#ifdef CONFIG_SWAP
|
|
"swap_ra",
|
|
"swap_ra_hit",
|
|
#endif
|
|
#ifdef CONFIG_X86
|
|
"direct_map_level2_splits",
|
|
"direct_map_level3_splits",
|
|
#endif
|
|
#endif /* CONFIG_VM_EVENT_COUNTERS || CONFIG_MEMCG */
|
|
};
|
|
#endif /* CONFIG_PROC_FS || CONFIG_SYSFS || CONFIG_NUMA || CONFIG_MEMCG */
|
|
|
|
#if (defined(CONFIG_DEBUG_FS) && defined(CONFIG_COMPACTION)) || \
|
|
defined(CONFIG_PROC_FS)
|
|
static void *frag_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
pg_data_t *pgdat;
|
|
loff_t node = *pos;
|
|
|
|
for (pgdat = first_online_pgdat();
|
|
pgdat && node;
|
|
pgdat = next_online_pgdat(pgdat))
|
|
--node;
|
|
|
|
return pgdat;
|
|
}
|
|
|
|
static void *frag_next(struct seq_file *m, void *arg, loff_t *pos)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
(*pos)++;
|
|
return next_online_pgdat(pgdat);
|
|
}
|
|
|
|
static void frag_stop(struct seq_file *m, void *arg)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Walk zones in a node and print using a callback.
|
|
* If @assert_populated is true, only use callback for zones that are populated.
|
|
*/
|
|
static void walk_zones_in_node(struct seq_file *m, pg_data_t *pgdat,
|
|
bool assert_populated, bool nolock,
|
|
void (*print)(struct seq_file *m, pg_data_t *, struct zone *))
|
|
{
|
|
struct zone *zone;
|
|
struct zone *node_zones = pgdat->node_zones;
|
|
unsigned long flags;
|
|
|
|
for (zone = node_zones; zone - node_zones < MAX_NR_ZONES; ++zone) {
|
|
if (assert_populated && !populated_zone(zone))
|
|
continue;
|
|
|
|
if (!nolock)
|
|
spin_lock_irqsave(&zone->lock, flags);
|
|
print(m, pgdat, zone);
|
|
if (!nolock)
|
|
spin_unlock_irqrestore(&zone->lock, flags);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static void frag_show_print(struct seq_file *m, pg_data_t *pgdat,
|
|
struct zone *zone)
|
|
{
|
|
int order;
|
|
|
|
seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name);
|
|
for (order = 0; order < MAX_ORDER; ++order)
|
|
seq_printf(m, "%6lu ", zone->free_area[order].nr_free);
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
/*
|
|
* This walks the free areas for each zone.
|
|
*/
|
|
static int frag_show(struct seq_file *m, void *arg)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
walk_zones_in_node(m, pgdat, true, false, frag_show_print);
|
|
return 0;
|
|
}
|
|
|
|
static void pagetypeinfo_showfree_print(struct seq_file *m,
|
|
pg_data_t *pgdat, struct zone *zone)
|
|
{
|
|
int order, mtype;
|
|
|
|
for (mtype = 0; mtype < MIGRATE_TYPES; mtype++) {
|
|
seq_printf(m, "Node %4d, zone %8s, type %12s ",
|
|
pgdat->node_id,
|
|
zone->name,
|
|
migratetype_names[mtype]);
|
|
for (order = 0; order < MAX_ORDER; ++order) {
|
|
unsigned long freecount = 0;
|
|
struct free_area *area;
|
|
struct list_head *curr;
|
|
bool overflow = false;
|
|
|
|
area = &(zone->free_area[order]);
|
|
|
|
list_for_each(curr, &area->free_list[mtype]) {
|
|
/*
|
|
* Cap the free_list iteration because it might
|
|
* be really large and we are under a spinlock
|
|
* so a long time spent here could trigger a
|
|
* hard lockup detector. Anyway this is a
|
|
* debugging tool so knowing there is a handful
|
|
* of pages of this order should be more than
|
|
* sufficient.
|
|
*/
|
|
if (++freecount >= 100000) {
|
|
overflow = true;
|
|
break;
|
|
}
|
|
}
|
|
seq_printf(m, "%s%6lu ", overflow ? ">" : "", freecount);
|
|
spin_unlock_irq(&zone->lock);
|
|
cond_resched();
|
|
spin_lock_irq(&zone->lock);
|
|
}
|
|
seq_putc(m, '\n');
|
|
}
|
|
}
|
|
|
|
/* Print out the free pages at each order for each migatetype */
|
|
static void pagetypeinfo_showfree(struct seq_file *m, void *arg)
|
|
{
|
|
int order;
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
/* Print header */
|
|
seq_printf(m, "%-43s ", "Free pages count per migrate type at order");
|
|
for (order = 0; order < MAX_ORDER; ++order)
|
|
seq_printf(m, "%6d ", order);
|
|
seq_putc(m, '\n');
|
|
|
|
walk_zones_in_node(m, pgdat, true, false, pagetypeinfo_showfree_print);
|
|
}
|
|
|
|
static void pagetypeinfo_showblockcount_print(struct seq_file *m,
|
|
pg_data_t *pgdat, struct zone *zone)
|
|
{
|
|
int mtype;
|
|
unsigned long pfn;
|
|
unsigned long start_pfn = zone->zone_start_pfn;
|
|
unsigned long end_pfn = zone_end_pfn(zone);
|
|
unsigned long count[MIGRATE_TYPES] = { 0, };
|
|
|
|
for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) {
|
|
struct page *page;
|
|
|
|
page = pfn_to_online_page(pfn);
|
|
if (!page)
|
|
continue;
|
|
|
|
if (page_zone(page) != zone)
|
|
continue;
|
|
|
|
mtype = get_pageblock_migratetype(page);
|
|
|
|
if (mtype < MIGRATE_TYPES)
|
|
count[mtype]++;
|
|
}
|
|
|
|
/* Print counts */
|
|
seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name);
|
|
for (mtype = 0; mtype < MIGRATE_TYPES; mtype++)
|
|
seq_printf(m, "%12lu ", count[mtype]);
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
/* Print out the number of pageblocks for each migratetype */
|
|
static void pagetypeinfo_showblockcount(struct seq_file *m, void *arg)
|
|
{
|
|
int mtype;
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
seq_printf(m, "\n%-23s", "Number of blocks type ");
|
|
for (mtype = 0; mtype < MIGRATE_TYPES; mtype++)
|
|
seq_printf(m, "%12s ", migratetype_names[mtype]);
|
|
seq_putc(m, '\n');
|
|
walk_zones_in_node(m, pgdat, true, false,
|
|
pagetypeinfo_showblockcount_print);
|
|
}
|
|
|
|
/*
|
|
* Print out the number of pageblocks for each migratetype that contain pages
|
|
* of other types. This gives an indication of how well fallbacks are being
|
|
* contained by rmqueue_fallback(). It requires information from PAGE_OWNER
|
|
* to determine what is going on
|
|
*/
|
|
static void pagetypeinfo_showmixedcount(struct seq_file *m, pg_data_t *pgdat)
|
|
{
|
|
#ifdef CONFIG_PAGE_OWNER
|
|
int mtype;
|
|
|
|
if (!static_branch_unlikely(&page_owner_inited))
|
|
return;
|
|
|
|
drain_all_pages(NULL);
|
|
|
|
seq_printf(m, "\n%-23s", "Number of mixed blocks ");
|
|
for (mtype = 0; mtype < MIGRATE_TYPES; mtype++)
|
|
seq_printf(m, "%12s ", migratetype_names[mtype]);
|
|
seq_putc(m, '\n');
|
|
|
|
walk_zones_in_node(m, pgdat, true, true,
|
|
pagetypeinfo_showmixedcount_print);
|
|
#endif /* CONFIG_PAGE_OWNER */
|
|
}
|
|
|
|
/*
|
|
* This prints out statistics in relation to grouping pages by mobility.
|
|
* It is expensive to collect so do not constantly read the file.
|
|
*/
|
|
static int pagetypeinfo_show(struct seq_file *m, void *arg)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
/* check memoryless node */
|
|
if (!node_state(pgdat->node_id, N_MEMORY))
|
|
return 0;
|
|
|
|
seq_printf(m, "Page block order: %d\n", pageblock_order);
|
|
seq_printf(m, "Pages per block: %lu\n", pageblock_nr_pages);
|
|
seq_putc(m, '\n');
|
|
pagetypeinfo_showfree(m, pgdat);
|
|
pagetypeinfo_showblockcount(m, pgdat);
|
|
pagetypeinfo_showmixedcount(m, pgdat);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations fragmentation_op = {
|
|
.start = frag_start,
|
|
.next = frag_next,
|
|
.stop = frag_stop,
|
|
.show = frag_show,
|
|
};
|
|
|
|
static const struct seq_operations pagetypeinfo_op = {
|
|
.start = frag_start,
|
|
.next = frag_next,
|
|
.stop = frag_stop,
|
|
.show = pagetypeinfo_show,
|
|
};
|
|
|
|
static bool is_zone_first_populated(pg_data_t *pgdat, struct zone *zone)
|
|
{
|
|
int zid;
|
|
|
|
for (zid = 0; zid < MAX_NR_ZONES; zid++) {
|
|
struct zone *compare = &pgdat->node_zones[zid];
|
|
|
|
if (populated_zone(compare))
|
|
return zone == compare;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void zoneinfo_show_print(struct seq_file *m, pg_data_t *pgdat,
|
|
struct zone *zone)
|
|
{
|
|
int i;
|
|
seq_printf(m, "Node %d, zone %8s", pgdat->node_id, zone->name);
|
|
if (is_zone_first_populated(pgdat, zone)) {
|
|
seq_printf(m, "\n per-node stats");
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) {
|
|
unsigned long pages = node_page_state_pages(pgdat, i);
|
|
|
|
if (vmstat_item_print_in_thp(i))
|
|
pages /= HPAGE_PMD_NR;
|
|
seq_printf(m, "\n %-12s %lu", node_stat_name(i),
|
|
pages);
|
|
}
|
|
}
|
|
seq_printf(m,
|
|
"\n pages free %lu"
|
|
"\n boost %lu"
|
|
"\n min %lu"
|
|
"\n low %lu"
|
|
"\n high %lu"
|
|
"\n spanned %lu"
|
|
"\n present %lu"
|
|
"\n managed %lu"
|
|
"\n cma %lu",
|
|
zone_page_state(zone, NR_FREE_PAGES),
|
|
zone->watermark_boost,
|
|
min_wmark_pages(zone),
|
|
low_wmark_pages(zone),
|
|
high_wmark_pages(zone),
|
|
zone->spanned_pages,
|
|
zone->present_pages,
|
|
zone_managed_pages(zone),
|
|
zone_cma_pages(zone));
|
|
|
|
seq_printf(m,
|
|
"\n protection: (%ld",
|
|
zone->lowmem_reserve[0]);
|
|
for (i = 1; i < ARRAY_SIZE(zone->lowmem_reserve); i++)
|
|
seq_printf(m, ", %ld", zone->lowmem_reserve[i]);
|
|
seq_putc(m, ')');
|
|
|
|
/* If unpopulated, no other information is useful */
|
|
if (!populated_zone(zone)) {
|
|
seq_putc(m, '\n');
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
|
|
seq_printf(m, "\n %-12s %lu", zone_stat_name(i),
|
|
zone_page_state(zone, i));
|
|
|
|
#ifdef CONFIG_NUMA
|
|
for (i = 0; i < NR_VM_NUMA_EVENT_ITEMS; i++)
|
|
seq_printf(m, "\n %-12s %lu", numa_stat_name(i),
|
|
zone_numa_event_state(zone, i));
|
|
#endif
|
|
|
|
seq_printf(m, "\n pagesets");
|
|
for_each_online_cpu(i) {
|
|
struct per_cpu_pages *pcp;
|
|
struct per_cpu_zonestat __maybe_unused *pzstats;
|
|
|
|
pcp = per_cpu_ptr(zone->per_cpu_pageset, i);
|
|
seq_printf(m,
|
|
"\n cpu: %i"
|
|
"\n count: %i"
|
|
"\n high: %i"
|
|
"\n batch: %i",
|
|
i,
|
|
pcp->count,
|
|
pcp->high,
|
|
pcp->batch);
|
|
#ifdef CONFIG_SMP
|
|
pzstats = per_cpu_ptr(zone->per_cpu_zonestats, i);
|
|
seq_printf(m, "\n vm stats threshold: %d",
|
|
pzstats->stat_threshold);
|
|
#endif
|
|
}
|
|
seq_printf(m,
|
|
"\n node_unreclaimable: %u"
|
|
"\n start_pfn: %lu",
|
|
pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES,
|
|
zone->zone_start_pfn);
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
/*
|
|
* Output information about zones in @pgdat. All zones are printed regardless
|
|
* of whether they are populated or not: lowmem_reserve_ratio operates on the
|
|
* set of all zones and userspace would not be aware of such zones if they are
|
|
* suppressed here (zoneinfo displays the effect of lowmem_reserve_ratio).
|
|
*/
|
|
static int zoneinfo_show(struct seq_file *m, void *arg)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
walk_zones_in_node(m, pgdat, false, false, zoneinfo_show_print);
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations zoneinfo_op = {
|
|
.start = frag_start, /* iterate over all zones. The same as in
|
|
* fragmentation. */
|
|
.next = frag_next,
|
|
.stop = frag_stop,
|
|
.show = zoneinfo_show,
|
|
};
|
|
|
|
#define NR_VMSTAT_ITEMS (NR_VM_ZONE_STAT_ITEMS + \
|
|
NR_VM_NUMA_EVENT_ITEMS + \
|
|
NR_VM_NODE_STAT_ITEMS + \
|
|
NR_VM_WRITEBACK_STAT_ITEMS + \
|
|
(IS_ENABLED(CONFIG_VM_EVENT_COUNTERS) ? \
|
|
NR_VM_EVENT_ITEMS : 0))
|
|
|
|
static void *vmstat_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
unsigned long *v;
|
|
int i;
|
|
|
|
if (*pos >= NR_VMSTAT_ITEMS)
|
|
return NULL;
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(vmstat_text) < NR_VMSTAT_ITEMS);
|
|
fold_vm_numa_events();
|
|
v = kmalloc_array(NR_VMSTAT_ITEMS, sizeof(unsigned long), GFP_KERNEL);
|
|
m->private = v;
|
|
if (!v)
|
|
return ERR_PTR(-ENOMEM);
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++)
|
|
v[i] = global_zone_page_state(i);
|
|
v += NR_VM_ZONE_STAT_ITEMS;
|
|
|
|
#ifdef CONFIG_NUMA
|
|
for (i = 0; i < NR_VM_NUMA_EVENT_ITEMS; i++)
|
|
v[i] = global_numa_event_state(i);
|
|
v += NR_VM_NUMA_EVENT_ITEMS;
|
|
#endif
|
|
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) {
|
|
v[i] = global_node_page_state_pages(i);
|
|
if (vmstat_item_print_in_thp(i))
|
|
v[i] /= HPAGE_PMD_NR;
|
|
}
|
|
v += NR_VM_NODE_STAT_ITEMS;
|
|
|
|
global_dirty_limits(v + NR_DIRTY_BG_THRESHOLD,
|
|
v + NR_DIRTY_THRESHOLD);
|
|
v += NR_VM_WRITEBACK_STAT_ITEMS;
|
|
|
|
#ifdef CONFIG_VM_EVENT_COUNTERS
|
|
all_vm_events(v);
|
|
v[PGPGIN] /= 2; /* sectors -> kbytes */
|
|
v[PGPGOUT] /= 2;
|
|
#endif
|
|
return (unsigned long *)m->private + *pos;
|
|
}
|
|
|
|
static void *vmstat_next(struct seq_file *m, void *arg, loff_t *pos)
|
|
{
|
|
(*pos)++;
|
|
if (*pos >= NR_VMSTAT_ITEMS)
|
|
return NULL;
|
|
return (unsigned long *)m->private + *pos;
|
|
}
|
|
|
|
static int vmstat_show(struct seq_file *m, void *arg)
|
|
{
|
|
unsigned long *l = arg;
|
|
unsigned long off = l - (unsigned long *)m->private;
|
|
|
|
seq_puts(m, vmstat_text[off]);
|
|
seq_put_decimal_ull(m, " ", *l);
|
|
seq_putc(m, '\n');
|
|
|
|
if (off == NR_VMSTAT_ITEMS - 1) {
|
|
/*
|
|
* We've come to the end - add any deprecated counters to avoid
|
|
* breaking userspace which might depend on them being present.
|
|
*/
|
|
seq_puts(m, "nr_unstable 0\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void vmstat_stop(struct seq_file *m, void *arg)
|
|
{
|
|
kfree(m->private);
|
|
m->private = NULL;
|
|
}
|
|
|
|
static const struct seq_operations vmstat_op = {
|
|
.start = vmstat_start,
|
|
.next = vmstat_next,
|
|
.stop = vmstat_stop,
|
|
.show = vmstat_show,
|
|
};
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
#ifdef CONFIG_SMP
|
|
static DEFINE_PER_CPU(struct delayed_work, vmstat_work);
|
|
int sysctl_stat_interval __read_mostly = HZ;
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static void refresh_vm_stats(struct work_struct *work)
|
|
{
|
|
refresh_cpu_vm_stats(true);
|
|
}
|
|
|
|
int vmstat_refresh(struct ctl_table *table, int write,
|
|
void *buffer, size_t *lenp, loff_t *ppos)
|
|
{
|
|
long val;
|
|
int err;
|
|
int i;
|
|
|
|
/*
|
|
* The regular update, every sysctl_stat_interval, may come later
|
|
* than expected: leaving a significant amount in per_cpu buckets.
|
|
* This is particularly misleading when checking a quantity of HUGE
|
|
* pages, immediately after running a test. /proc/sys/vm/stat_refresh,
|
|
* which can equally be echo'ed to or cat'ted from (by root),
|
|
* can be used to update the stats just before reading them.
|
|
*
|
|
* Oh, and since global_zone_page_state() etc. are so careful to hide
|
|
* transiently negative values, report an error here if any of
|
|
* the stats is negative, so we know to go looking for imbalance.
|
|
*/
|
|
err = schedule_on_each_cpu(refresh_vm_stats);
|
|
if (err)
|
|
return err;
|
|
for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) {
|
|
/*
|
|
* Skip checking stats known to go negative occasionally.
|
|
*/
|
|
switch (i) {
|
|
case NR_ZONE_WRITE_PENDING:
|
|
case NR_FREE_CMA_PAGES:
|
|
continue;
|
|
}
|
|
val = atomic_long_read(&vm_zone_stat[i]);
|
|
if (val < 0) {
|
|
pr_warn("%s: %s %ld\n",
|
|
__func__, zone_stat_name(i), val);
|
|
}
|
|
}
|
|
for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) {
|
|
/*
|
|
* Skip checking stats known to go negative occasionally.
|
|
*/
|
|
switch (i) {
|
|
case NR_WRITEBACK:
|
|
continue;
|
|
}
|
|
val = atomic_long_read(&vm_node_stat[i]);
|
|
if (val < 0) {
|
|
pr_warn("%s: %s %ld\n",
|
|
__func__, node_stat_name(i), val);
|
|
}
|
|
}
|
|
if (write)
|
|
*ppos += *lenp;
|
|
else
|
|
*lenp = 0;
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
static void vmstat_update(struct work_struct *w)
|
|
{
|
|
if (refresh_cpu_vm_stats(true)) {
|
|
/*
|
|
* Counters were updated so we expect more updates
|
|
* to occur in the future. Keep on running the
|
|
* update worker thread.
|
|
*/
|
|
queue_delayed_work_on(smp_processor_id(), mm_percpu_wq,
|
|
this_cpu_ptr(&vmstat_work),
|
|
round_jiffies_relative(sysctl_stat_interval));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if the diffs for a certain cpu indicate that
|
|
* an update is needed.
|
|
*/
|
|
static bool need_update(int cpu)
|
|
{
|
|
pg_data_t *last_pgdat = NULL;
|
|
struct zone *zone;
|
|
|
|
for_each_populated_zone(zone) {
|
|
struct per_cpu_zonestat *pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu);
|
|
struct per_cpu_nodestat *n;
|
|
|
|
/*
|
|
* The fast way of checking if there are any vmstat diffs.
|
|
*/
|
|
if (memchr_inv(pzstats->vm_stat_diff, 0, sizeof(pzstats->vm_stat_diff)))
|
|
return true;
|
|
|
|
if (last_pgdat == zone->zone_pgdat)
|
|
continue;
|
|
last_pgdat = zone->zone_pgdat;
|
|
n = per_cpu_ptr(zone->zone_pgdat->per_cpu_nodestats, cpu);
|
|
if (memchr_inv(n->vm_node_stat_diff, 0, sizeof(n->vm_node_stat_diff)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Switch off vmstat processing and then fold all the remaining differentials
|
|
* until the diffs stay at zero. The function is used by NOHZ and can only be
|
|
* invoked when tick processing is not active.
|
|
*/
|
|
void quiet_vmstat(void)
|
|
{
|
|
if (system_state != SYSTEM_RUNNING)
|
|
return;
|
|
|
|
if (!delayed_work_pending(this_cpu_ptr(&vmstat_work)))
|
|
return;
|
|
|
|
if (!need_update(smp_processor_id()))
|
|
return;
|
|
|
|
/*
|
|
* Just refresh counters and do not care about the pending delayed
|
|
* vmstat_update. It doesn't fire that often to matter and canceling
|
|
* it would be too expensive from this path.
|
|
* vmstat_shepherd will take care about that for us.
|
|
*/
|
|
refresh_cpu_vm_stats(false);
|
|
}
|
|
|
|
/*
|
|
* Shepherd worker thread that checks the
|
|
* differentials of processors that have their worker
|
|
* threads for vm statistics updates disabled because of
|
|
* inactivity.
|
|
*/
|
|
static void vmstat_shepherd(struct work_struct *w);
|
|
|
|
static DECLARE_DEFERRABLE_WORK(shepherd, vmstat_shepherd);
|
|
|
|
static void vmstat_shepherd(struct work_struct *w)
|
|
{
|
|
int cpu;
|
|
|
|
cpus_read_lock();
|
|
/* Check processors whose vmstat worker threads have been disabled */
|
|
for_each_online_cpu(cpu) {
|
|
struct delayed_work *dw = &per_cpu(vmstat_work, cpu);
|
|
|
|
if (!delayed_work_pending(dw) && need_update(cpu))
|
|
queue_delayed_work_on(cpu, mm_percpu_wq, dw, 0);
|
|
|
|
cond_resched();
|
|
}
|
|
cpus_read_unlock();
|
|
|
|
schedule_delayed_work(&shepherd,
|
|
round_jiffies_relative(sysctl_stat_interval));
|
|
}
|
|
|
|
static void __init start_shepherd_timer(void)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu)
|
|
INIT_DEFERRABLE_WORK(per_cpu_ptr(&vmstat_work, cpu),
|
|
vmstat_update);
|
|
|
|
schedule_delayed_work(&shepherd,
|
|
round_jiffies_relative(sysctl_stat_interval));
|
|
}
|
|
|
|
static void __init init_cpu_node_state(void)
|
|
{
|
|
int node;
|
|
|
|
for_each_online_node(node) {
|
|
if (cpumask_weight(cpumask_of_node(node)) > 0)
|
|
node_set_state(node, N_CPU);
|
|
}
|
|
}
|
|
|
|
static int vmstat_cpu_online(unsigned int cpu)
|
|
{
|
|
refresh_zone_stat_thresholds();
|
|
node_set_state(cpu_to_node(cpu), N_CPU);
|
|
return 0;
|
|
}
|
|
|
|
static int vmstat_cpu_down_prep(unsigned int cpu)
|
|
{
|
|
cancel_delayed_work_sync(&per_cpu(vmstat_work, cpu));
|
|
return 0;
|
|
}
|
|
|
|
static int vmstat_cpu_dead(unsigned int cpu)
|
|
{
|
|
const struct cpumask *node_cpus;
|
|
int node;
|
|
|
|
node = cpu_to_node(cpu);
|
|
|
|
refresh_zone_stat_thresholds();
|
|
node_cpus = cpumask_of_node(node);
|
|
if (cpumask_weight(node_cpus) > 0)
|
|
return 0;
|
|
|
|
node_clear_state(node, N_CPU);
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
struct workqueue_struct *mm_percpu_wq;
|
|
|
|
void __init init_mm_internals(void)
|
|
{
|
|
int ret __maybe_unused;
|
|
|
|
mm_percpu_wq = alloc_workqueue("mm_percpu_wq", WQ_MEM_RECLAIM, 0);
|
|
|
|
#ifdef CONFIG_SMP
|
|
ret = cpuhp_setup_state_nocalls(CPUHP_MM_VMSTAT_DEAD, "mm/vmstat:dead",
|
|
NULL, vmstat_cpu_dead);
|
|
if (ret < 0)
|
|
pr_err("vmstat: failed to register 'dead' hotplug state\n");
|
|
|
|
ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "mm/vmstat:online",
|
|
vmstat_cpu_online,
|
|
vmstat_cpu_down_prep);
|
|
if (ret < 0)
|
|
pr_err("vmstat: failed to register 'online' hotplug state\n");
|
|
|
|
cpus_read_lock();
|
|
init_cpu_node_state();
|
|
cpus_read_unlock();
|
|
|
|
start_shepherd_timer();
|
|
#endif
|
|
#ifdef CONFIG_PROC_FS
|
|
proc_create_seq("buddyinfo", 0444, NULL, &fragmentation_op);
|
|
proc_create_seq("pagetypeinfo", 0400, NULL, &pagetypeinfo_op);
|
|
proc_create_seq("vmstat", 0444, NULL, &vmstat_op);
|
|
proc_create_seq("zoneinfo", 0444, NULL, &zoneinfo_op);
|
|
#endif
|
|
}
|
|
|
|
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_COMPACTION)
|
|
|
|
/*
|
|
* Return an index indicating how much of the available free memory is
|
|
* unusable for an allocation of the requested size.
|
|
*/
|
|
static int unusable_free_index(unsigned int order,
|
|
struct contig_page_info *info)
|
|
{
|
|
/* No free memory is interpreted as all free memory is unusable */
|
|
if (info->free_pages == 0)
|
|
return 1000;
|
|
|
|
/*
|
|
* Index should be a value between 0 and 1. Return a value to 3
|
|
* decimal places.
|
|
*
|
|
* 0 => no fragmentation
|
|
* 1 => high fragmentation
|
|
*/
|
|
return div_u64((info->free_pages - (info->free_blocks_suitable << order)) * 1000ULL, info->free_pages);
|
|
|
|
}
|
|
|
|
static void unusable_show_print(struct seq_file *m,
|
|
pg_data_t *pgdat, struct zone *zone)
|
|
{
|
|
unsigned int order;
|
|
int index;
|
|
struct contig_page_info info;
|
|
|
|
seq_printf(m, "Node %d, zone %8s ",
|
|
pgdat->node_id,
|
|
zone->name);
|
|
for (order = 0; order < MAX_ORDER; ++order) {
|
|
fill_contig_page_info(zone, order, &info);
|
|
index = unusable_free_index(order, &info);
|
|
seq_printf(m, "%d.%03d ", index / 1000, index % 1000);
|
|
}
|
|
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
/*
|
|
* Display unusable free space index
|
|
*
|
|
* The unusable free space index measures how much of the available free
|
|
* memory cannot be used to satisfy an allocation of a given size and is a
|
|
* value between 0 and 1. The higher the value, the more of free memory is
|
|
* unusable and by implication, the worse the external fragmentation is. This
|
|
* can be expressed as a percentage by multiplying by 100.
|
|
*/
|
|
static int unusable_show(struct seq_file *m, void *arg)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
/* check memoryless node */
|
|
if (!node_state(pgdat->node_id, N_MEMORY))
|
|
return 0;
|
|
|
|
walk_zones_in_node(m, pgdat, true, false, unusable_show_print);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations unusable_sops = {
|
|
.start = frag_start,
|
|
.next = frag_next,
|
|
.stop = frag_stop,
|
|
.show = unusable_show,
|
|
};
|
|
|
|
DEFINE_SEQ_ATTRIBUTE(unusable);
|
|
|
|
static void extfrag_show_print(struct seq_file *m,
|
|
pg_data_t *pgdat, struct zone *zone)
|
|
{
|
|
unsigned int order;
|
|
int index;
|
|
|
|
/* Alloc on stack as interrupts are disabled for zone walk */
|
|
struct contig_page_info info;
|
|
|
|
seq_printf(m, "Node %d, zone %8s ",
|
|
pgdat->node_id,
|
|
zone->name);
|
|
for (order = 0; order < MAX_ORDER; ++order) {
|
|
fill_contig_page_info(zone, order, &info);
|
|
index = __fragmentation_index(order, &info);
|
|
seq_printf(m, "%d.%03d ", index / 1000, index % 1000);
|
|
}
|
|
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
/*
|
|
* Display fragmentation index for orders that allocations would fail for
|
|
*/
|
|
static int extfrag_show(struct seq_file *m, void *arg)
|
|
{
|
|
pg_data_t *pgdat = (pg_data_t *)arg;
|
|
|
|
walk_zones_in_node(m, pgdat, true, false, extfrag_show_print);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations extfrag_sops = {
|
|
.start = frag_start,
|
|
.next = frag_next,
|
|
.stop = frag_stop,
|
|
.show = extfrag_show,
|
|
};
|
|
|
|
DEFINE_SEQ_ATTRIBUTE(extfrag);
|
|
|
|
static int __init extfrag_debug_init(void)
|
|
{
|
|
struct dentry *extfrag_debug_root;
|
|
|
|
extfrag_debug_root = debugfs_create_dir("extfrag", NULL);
|
|
|
|
debugfs_create_file("unusable_index", 0444, extfrag_debug_root, NULL,
|
|
&unusable_fops);
|
|
|
|
debugfs_create_file("extfrag_index", 0444, extfrag_debug_root, NULL,
|
|
&extfrag_fops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
module_init(extfrag_debug_init);
|
|
#endif
|