Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
7df4f9a9 WT |
2 | /* |
3 | * Activity LED trigger | |
4 | * | |
5 | * Copyright (C) 2017 Willy Tarreau <w@1wt.eu> | |
6 | * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c. | |
7df4f9a9 | 7 | */ |
033692eb | 8 | |
7df4f9a9 WT |
9 | #include <linux/init.h> |
10 | #include <linux/kernel.h> | |
11 | #include <linux/kernel_stat.h> | |
12 | #include <linux/leds.h> | |
13 | #include <linux/module.h> | |
f39650de | 14 | #include <linux/panic_notifier.h> |
7df4f9a9 WT |
15 | #include <linux/reboot.h> |
16 | #include <linux/sched.h> | |
17 | #include <linux/slab.h> | |
18 | #include <linux/timer.h> | |
19 | #include "../leds.h" | |
20 | ||
21 | static int panic_detected; | |
22 | ||
23 | struct activity_data { | |
24 | struct timer_list timer; | |
49404665 | 25 | struct led_classdev *led_cdev; |
7df4f9a9 WT |
26 | u64 last_used; |
27 | u64 last_boot; | |
28 | int time_left; | |
29 | int state; | |
30 | int invert; | |
31 | }; | |
32 | ||
49404665 | 33 | static void led_activity_function(struct timer_list *t) |
7df4f9a9 | 34 | { |
49404665 KC |
35 | struct activity_data *activity_data = from_timer(activity_data, t, |
36 | timer); | |
37 | struct led_classdev *led_cdev = activity_data->led_cdev; | |
7df4f9a9 WT |
38 | unsigned int target; |
39 | unsigned int usage; | |
40 | int delay; | |
41 | u64 curr_used; | |
42 | u64 curr_boot; | |
43 | s32 diff_used; | |
44 | s32 diff_boot; | |
45 | int cpus; | |
46 | int i; | |
47 | ||
48 | if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) | |
49 | led_cdev->blink_brightness = led_cdev->new_blink_brightness; | |
50 | ||
51 | if (unlikely(panic_detected)) { | |
52 | /* full brightness in case of panic */ | |
53 | led_set_brightness_nosleep(led_cdev, led_cdev->blink_brightness); | |
54 | return; | |
55 | } | |
56 | ||
7df4f9a9 WT |
57 | cpus = 0; |
58 | curr_used = 0; | |
59 | ||
60 | for_each_possible_cpu(i) { | |
8688f2fb FW |
61 | struct kernel_cpustat kcpustat; |
62 | ||
63 | kcpustat_cpu_fetch(&kcpustat, i); | |
64 | ||
65 | curr_used += kcpustat.cpustat[CPUTIME_USER] | |
66 | + kcpustat.cpustat[CPUTIME_NICE] | |
67 | + kcpustat.cpustat[CPUTIME_SYSTEM] | |
68 | + kcpustat.cpustat[CPUTIME_SOFTIRQ] | |
69 | + kcpustat.cpustat[CPUTIME_IRQ]; | |
7df4f9a9 WT |
70 | cpus++; |
71 | } | |
72 | ||
73 | /* We come here every 100ms in the worst case, so that's 100M ns of | |
74 | * cumulated time. By dividing by 2^16, we get the time resolution | |
75 | * down to 16us, ensuring we won't overflow 32-bit computations below | |
76 | * even up to 3k CPUs, while keeping divides cheap on smaller systems. | |
77 | */ | |
9285ec4c | 78 | curr_boot = ktime_get_boottime_ns() * cpus; |
7df4f9a9 WT |
79 | diff_boot = (curr_boot - activity_data->last_boot) >> 16; |
80 | diff_used = (curr_used - activity_data->last_used) >> 16; | |
81 | activity_data->last_boot = curr_boot; | |
82 | activity_data->last_used = curr_used; | |
83 | ||
84 | if (diff_boot <= 0 || diff_used < 0) | |
85 | usage = 0; | |
86 | else if (diff_used >= diff_boot) | |
87 | usage = 100; | |
88 | else | |
89 | usage = 100 * diff_used / diff_boot; | |
90 | ||
91 | /* | |
92 | * Now we know the total boot_time multiplied by the number of CPUs, and | |
93 | * the total idle+wait time for all CPUs. We'll compare how they evolved | |
94 | * since last call. The % of overall CPU usage is : | |
95 | * | |
96 | * 1 - delta_idle / delta_boot | |
97 | * | |
98 | * What we want is that when the CPU usage is zero, the LED must blink | |
99 | * slowly with very faint flashes that are detectable but not disturbing | |
100 | * (typically 10ms every second, or 10ms ON, 990ms OFF). Then we want | |
101 | * blinking frequency to increase up to the point where the load is | |
102 | * enough to saturate one core in multi-core systems or 50% in single | |
103 | * core systems. At this point it should reach 10 Hz with a 10/90 duty | |
104 | * cycle (10ms ON, 90ms OFF). After this point, the blinking frequency | |
105 | * remains stable (10 Hz) and only the duty cycle increases to report | |
106 | * the activity, up to the point where we have 90ms ON, 10ms OFF when | |
107 | * all cores are saturated. It's important that the LED never stays in | |
108 | * a steady state so that it's easy to distinguish an idle or saturated | |
109 | * machine from a hung one. | |
110 | * | |
111 | * This gives us : | |
112 | * - a target CPU usage of min(50%, 100%/#CPU) for a 10% duty cycle | |
113 | * (10ms ON, 90ms OFF) | |
114 | * - below target : | |
115 | * ON_ms = 10 | |
116 | * OFF_ms = 90 + (1 - usage/target) * 900 | |
117 | * - above target : | |
118 | * ON_ms = 10 + (usage-target)/(100%-target) * 80 | |
119 | * OFF_ms = 90 - (usage-target)/(100%-target) * 80 | |
120 | * | |
121 | * In order to keep a good responsiveness, we cap the sleep time to | |
122 | * 100 ms and keep track of the sleep time left. This allows us to | |
123 | * quickly change it if needed. | |
124 | */ | |
125 | ||
126 | activity_data->time_left -= 100; | |
127 | if (activity_data->time_left <= 0) { | |
128 | activity_data->time_left = 0; | |
129 | activity_data->state = !activity_data->state; | |
130 | led_set_brightness_nosleep(led_cdev, | |
131 | (activity_data->state ^ activity_data->invert) ? | |
132 | led_cdev->blink_brightness : LED_OFF); | |
133 | } | |
134 | ||
135 | target = (cpus > 1) ? (100 / cpus) : 50; | |
136 | ||
137 | if (usage < target) | |
138 | delay = activity_data->state ? | |
139 | 10 : /* ON */ | |
140 | 990 - 900 * usage / target; /* OFF */ | |
141 | else | |
142 | delay = activity_data->state ? | |
143 | 10 + 80 * (usage - target) / (100 - target) : /* ON */ | |
144 | 90 - 80 * (usage - target) / (100 - target); /* OFF */ | |
145 | ||
146 | ||
147 | if (!activity_data->time_left || delay <= activity_data->time_left) | |
148 | activity_data->time_left = delay; | |
149 | ||
150 | delay = min_t(int, activity_data->time_left, 100); | |
151 | mod_timer(&activity_data->timer, jiffies + msecs_to_jiffies(delay)); | |
152 | } | |
153 | ||
154 | static ssize_t led_invert_show(struct device *dev, | |
155 | struct device_attribute *attr, char *buf) | |
156 | { | |
13d698cb | 157 | struct activity_data *activity_data = led_trigger_get_drvdata(dev); |
7df4f9a9 WT |
158 | |
159 | return sprintf(buf, "%u\n", activity_data->invert); | |
160 | } | |
161 | ||
162 | static ssize_t led_invert_store(struct device *dev, | |
163 | struct device_attribute *attr, | |
164 | const char *buf, size_t size) | |
165 | { | |
13d698cb | 166 | struct activity_data *activity_data = led_trigger_get_drvdata(dev); |
7df4f9a9 WT |
167 | unsigned long state; |
168 | int ret; | |
169 | ||
170 | ret = kstrtoul(buf, 0, &state); | |
171 | if (ret) | |
172 | return ret; | |
173 | ||
174 | activity_data->invert = !!state; | |
175 | ||
176 | return size; | |
177 | } | |
178 | ||
179 | static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); | |
180 | ||
13d698cb UKK |
181 | static struct attribute *activity_led_attrs[] = { |
182 | &dev_attr_invert.attr, | |
183 | NULL | |
184 | }; | |
185 | ATTRIBUTE_GROUPS(activity_led); | |
186 | ||
2282e125 | 187 | static int activity_activate(struct led_classdev *led_cdev) |
7df4f9a9 WT |
188 | { |
189 | struct activity_data *activity_data; | |
7df4f9a9 WT |
190 | |
191 | activity_data = kzalloc(sizeof(*activity_data), GFP_KERNEL); | |
192 | if (!activity_data) | |
13d698cb | 193 | return -ENOMEM; |
7df4f9a9 | 194 | |
13d698cb | 195 | led_set_trigger_data(led_cdev, activity_data); |
7df4f9a9 | 196 | |
49404665 KC |
197 | activity_data->led_cdev = led_cdev; |
198 | timer_setup(&activity_data->timer, led_activity_function, 0); | |
7df4f9a9 WT |
199 | if (!led_cdev->blink_brightness) |
200 | led_cdev->blink_brightness = led_cdev->max_brightness; | |
49404665 | 201 | led_activity_function(&activity_data->timer); |
7df4f9a9 | 202 | set_bit(LED_BLINK_SW, &led_cdev->work_flags); |
2282e125 UKK |
203 | |
204 | return 0; | |
7df4f9a9 WT |
205 | } |
206 | ||
207 | static void activity_deactivate(struct led_classdev *led_cdev) | |
208 | { | |
13d698cb UKK |
209 | struct activity_data *activity_data = led_get_trigger_data(led_cdev); |
210 | ||
292a089d | 211 | timer_shutdown_sync(&activity_data->timer); |
13d698cb UKK |
212 | kfree(activity_data); |
213 | clear_bit(LED_BLINK_SW, &led_cdev->work_flags); | |
7df4f9a9 WT |
214 | } |
215 | ||
216 | static struct led_trigger activity_led_trigger = { | |
217 | .name = "activity", | |
218 | .activate = activity_activate, | |
219 | .deactivate = activity_deactivate, | |
13d698cb | 220 | .groups = activity_led_groups, |
7df4f9a9 WT |
221 | }; |
222 | ||
223 | static int activity_reboot_notifier(struct notifier_block *nb, | |
224 | unsigned long code, void *unused) | |
225 | { | |
226 | led_trigger_unregister(&activity_led_trigger); | |
227 | return NOTIFY_DONE; | |
228 | } | |
229 | ||
230 | static int activity_panic_notifier(struct notifier_block *nb, | |
231 | unsigned long code, void *unused) | |
232 | { | |
233 | panic_detected = 1; | |
234 | return NOTIFY_DONE; | |
235 | } | |
236 | ||
237 | static struct notifier_block activity_reboot_nb = { | |
238 | .notifier_call = activity_reboot_notifier, | |
239 | }; | |
240 | ||
241 | static struct notifier_block activity_panic_nb = { | |
242 | .notifier_call = activity_panic_notifier, | |
243 | }; | |
244 | ||
245 | static int __init activity_init(void) | |
246 | { | |
247 | int rc = led_trigger_register(&activity_led_trigger); | |
248 | ||
249 | if (!rc) { | |
250 | atomic_notifier_chain_register(&panic_notifier_list, | |
251 | &activity_panic_nb); | |
252 | register_reboot_notifier(&activity_reboot_nb); | |
253 | } | |
254 | return rc; | |
255 | } | |
256 | ||
257 | static void __exit activity_exit(void) | |
258 | { | |
259 | unregister_reboot_notifier(&activity_reboot_nb); | |
260 | atomic_notifier_chain_unregister(&panic_notifier_list, | |
261 | &activity_panic_nb); | |
262 | led_trigger_unregister(&activity_led_trigger); | |
263 | } | |
264 | ||
265 | module_init(activity_init); | |
266 | module_exit(activity_exit); | |
267 | ||
268 | MODULE_AUTHOR("Willy Tarreau <w@1wt.eu>"); | |
269 | MODULE_DESCRIPTION("Activity LED trigger"); | |
033692eb | 270 | MODULE_LICENSE("GPL v2"); |