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