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