Merging upstream version 0.8.2 (Closes: #786485, #784725, #801319).
[debian/lxpanel.git] / plugins / cpu / cpu.c
CommitLineData
38c4c1ba 1/*
6cc5e1a6
DB
2 * CPU usage plugin to lxpanel
3 *
38c4c1ba
AG
4 * Copyright (C) 2006-2008 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 * 2006-2008 Jim Huang <jserv.tw@gmail.com>
6 * 2009 Marty Jack <martyj19@comcast.net>
7 * 2009 Jürgen Hötzel <juergen@archlinux.org>
8 * 2012 Rafał Mużyło <galtgendo@gmail.com>
9 * 2012-2013 Henry Gebhardt <hsggebhardt@gmail.com>
10 * 2013 Marko Rauhamaa <marko@pacujo.net>
11 * 2014 Andriy Grytsenko <andrej@rep.kiev.ua>
12 *
13 * This file is a part of LXPanel project.
14 *
6cc5e1a6
DB
15 * Copyright (C) 2004 by Alexandre Pereira da Silva <alexandre.pereira@poli.usp.br>
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 *
31 */
32/*A little bug fixed by Mykola <mykola@2ka.mipt.ru>:) */
33
6cc5e1a6
DB
34#include <string.h>
35#include <sys/time.h>
36#include <time.h>
37#include <sys/sysinfo.h>
38#include <stdlib.h>
39#include <glib/gi18n.h>
40
41#include "plugin.h"
6cc5e1a6 42
10862fa6 43#define BORDER_SIZE 2
00916e98 44#define PANEL_HEIGHT_DEFAULT 26 /* from panel defaults */
10862fa6 45
00916e98 46/* #include "../../dbg.h" */
1ea75322 47
00916e98 48typedef unsigned long long CPUTick; /* Value from /proc/stat */
1ea75322 49typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
6cc5e1a6
DB
50
51struct cpu_stat {
1ea75322 52 CPUTick u, n, s, i; /* User, nice, system, idle */
6cc5e1a6
DB
53};
54
1ea75322 55/* Private context for CPU plugin. */
6cc5e1a6 56typedef struct {
1ea75322
DB
57 GdkColor foreground_color; /* Foreground color for drawing area */
58 GtkWidget * da; /* Drawing area */
0f7f2ef3 59 cairo_surface_t * pixmap; /* Pixmap to be drawn on drawing area */
1ea75322
DB
60
61 guint timer; /* Timer for periodic update */
62 CPUSample * stats_cpu; /* Ring buffer of CPU utilization values */
63 unsigned int ring_cursor; /* Cursor for ring buffer */
00916e98
AG
64 guint pixmap_width; /* Width of drawing area pixmap; also size of ring buffer; does not include border size */
65 guint pixmap_height; /* Height of drawing area pixmap; does not include border size */
1ea75322
DB
66 struct cpu_stat previous_cpu_stat; /* Previous value of cpu_stat */
67} CPUPlugin;
68
69static void redraw_pixmap(CPUPlugin * c);
70static gboolean cpu_update(CPUPlugin * c);
71static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c);
72static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c);
00916e98
AG
73
74static void cpu_destructor(gpointer user_data);
1ea75322
DB
75
76/* Redraw after timer callback or resize. */
77static void redraw_pixmap(CPUPlugin * c)
6cc5e1a6 78{
0f7f2ef3 79 cairo_t * cr = cairo_create(c->pixmap);
00916e98 80 GtkStyle * style = gtk_widget_get_style(c->da);
0f7f2ef3 81 cairo_set_line_width (cr, 1.0);
1ea75322 82 /* Erase pixmap. */
0f7f2ef3 83 cairo_rectangle(cr, 0, 0, c->pixmap_width, c->pixmap_height);
00916e98 84 gdk_cairo_set_source_color(cr, &style->black);
0f7f2ef3 85 cairo_fill(cr);
1ea75322
DB
86
87 /* Recompute pixmap. */
6cc5e1a6 88 unsigned int i;
1ea75322 89 unsigned int drawing_cursor = c->ring_cursor;
0f7f2ef3 90 gdk_cairo_set_source_color(cr, &c->foreground_color);
1ea75322
DB
91 for (i = 0; i < c->pixmap_width; i++)
92 {
93 /* Draw one bar of the CPU usage graph. */
94 if (c->stats_cpu[drawing_cursor] != 0.0)
0f7f2ef3
AL
95 {
96 cairo_move_to(cr, i + 0.5, c->pixmap_height);
97 cairo_line_to(cr, i + 0.5, c->pixmap_height - c->stats_cpu[drawing_cursor] * c->pixmap_height);
98 cairo_stroke(cr);
99 }
1ea75322
DB
100
101 /* Increment and wrap drawing cursor. */
102 drawing_cursor += 1;
00916e98 103 if (drawing_cursor >= c->pixmap_width)
1ea75322 104 drawing_cursor = 0;
6cc5e1a6 105 }
1ea75322 106
00916e98 107 /* check_cairo_status(cr); */
0f7f2ef3
AL
108 cairo_destroy(cr);
109
1ea75322 110 /* Redraw pixmap. */
6cc5e1a6 111 gtk_widget_queue_draw(c->da);
6cc5e1a6
DB
112}
113
1ea75322
DB
114/* Periodic timer callback. */
115static gboolean cpu_update(CPUPlugin * c)
6cc5e1a6 116{
00916e98
AG
117 if (g_source_is_destroyed(g_main_current_source()))
118 return FALSE;
1ea75322
DB
119 if ((c->stats_cpu != NULL) && (c->pixmap != NULL))
120 {
121 /* Open statistics file and scan out CPU usage. */
122 struct cpu_stat cpu;
123 FILE * stat = fopen("/proc/stat", "r");
124 if (stat == NULL)
125 return TRUE;
00916e98 126 int fscanf_result = fscanf(stat, "cpu %llu %llu %llu %llu", &cpu.u, &cpu.n, &cpu.s, &cpu.i);
1ea75322
DB
127 fclose(stat);
128
129 /* Ensure that fscanf succeeded. */
130 if (fscanf_result == 4)
131 {
132 /* Compute delta from previous statistics. */
133 struct cpu_stat cpu_delta;
134 cpu_delta.u = cpu.u - c->previous_cpu_stat.u;
135 cpu_delta.n = cpu.n - c->previous_cpu_stat.n;
136 cpu_delta.s = cpu.s - c->previous_cpu_stat.s;
137 cpu_delta.i = cpu.i - c->previous_cpu_stat.i;
138
139 /* Copy current to previous. */
140 memcpy(&c->previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
141
142 /* Compute user+nice+system as a fraction of total.
143 * Introduce this sample to ring buffer, increment and wrap ring buffer cursor. */
144 float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
145 c->stats_cpu[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
146 c->ring_cursor += 1;
147 if (c->ring_cursor >= c->pixmap_width)
148 c->ring_cursor = 0;
149
150 /* Redraw with the new sample. */
151 redraw_pixmap(c);
152 }
153 }
154 return TRUE;
6cc5e1a6
DB
155}
156
1ea75322
DB
157/* Handler for configure_event on drawing area. */
158static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c)
6cc5e1a6 159{
00916e98
AG
160 GtkAllocation allocation;
161
162 gtk_widget_get_allocation(widget, &allocation);
1ea75322 163 /* Allocate pixmap and statistics buffer without border pixels. */
00916e98
AG
164 guint new_pixmap_width = MAX(allocation.width - BORDER_SIZE * 2, 0);
165 guint new_pixmap_height = MAX(allocation.height - BORDER_SIZE * 2, 0);
1ea75322
DB
166 if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
167 {
168 /* If statistics buffer does not exist or it changed size, reallocate and preserve existing data. */
169 if ((c->stats_cpu == NULL) || (new_pixmap_width != c->pixmap_width))
170 {
171 CPUSample * new_stats_cpu = g_new0(typeof(*c->stats_cpu), new_pixmap_width);
172 if (c->stats_cpu != NULL)
173 {
174 if (new_pixmap_width > c->pixmap_width)
175 {
176 /* New allocation is larger.
177 * Introduce new "oldest" samples of zero following the cursor. */
178 memcpy(&new_stats_cpu[0],
179 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
180 memcpy(&new_stats_cpu[new_pixmap_width - c->pixmap_width + c->ring_cursor],
181 &c->stats_cpu[c->ring_cursor], (c->pixmap_width - c->ring_cursor) * sizeof(CPUSample));
182 }
183 else if (c->ring_cursor <= new_pixmap_width)
184 {
185 /* New allocation is smaller, but still larger than the ring buffer cursor.
186 * Discard the oldest samples following the cursor. */
187 memcpy(&new_stats_cpu[0],
188 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
189 memcpy(&new_stats_cpu[c->ring_cursor],
190 &c->stats_cpu[c->pixmap_width - new_pixmap_width + c->ring_cursor], (new_pixmap_width - c->ring_cursor) * sizeof(CPUSample));
191 }
192 else
193 {
194 /* New allocation is smaller, and also smaller than the ring buffer cursor.
195 * Discard all oldest samples following the ring buffer cursor and additional samples at the beginning of the buffer. */
196 memcpy(&new_stats_cpu[0],
197 &c->stats_cpu[c->ring_cursor - new_pixmap_width], new_pixmap_width * sizeof(CPUSample));
198 c->ring_cursor = 0;
199 }
200 g_free(c->stats_cpu);
201 }
202 c->stats_cpu = new_stats_cpu;
203 }
204
205 /* Allocate or reallocate pixmap. */
206 c->pixmap_width = new_pixmap_width;
207 c->pixmap_height = new_pixmap_height;
208 if (c->pixmap)
0f7f2ef3
AL
209 cairo_surface_destroy(c->pixmap);
210 c->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24, c->pixmap_width, c->pixmap_height);
00916e98 211 /* check_cairo_surface_status(&c->pixmap); */
1ea75322
DB
212
213 /* Redraw pixmap at the new size. */
214 redraw_pixmap(c);
215 }
216 return TRUE;
6cc5e1a6
DB
217}
218
1ea75322
DB
219/* Handler for expose_event on drawing area. */
220static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c)
6cc5e1a6 221{
1ea75322
DB
222 /* Draw the requested part of the pixmap onto the drawing area.
223 * Translate it in both x and y by the border size. */
224 if (c->pixmap != NULL)
6cc5e1a6 225 {
00916e98
AG
226 cairo_t * cr = gdk_cairo_create(gtk_widget_get_window(widget));
227 GtkStyle * style = gtk_widget_get_style(c->da);
0f7f2ef3
AL
228 gdk_cairo_region(cr, event->region);
229 cairo_clip(cr);
00916e98 230 gdk_cairo_set_source_color(cr, &style->black);
0f7f2ef3
AL
231 cairo_set_source_surface(cr, c->pixmap,
232 BORDER_SIZE, BORDER_SIZE);
233 cairo_paint(cr);
00916e98 234 /* check_cairo_status(cr); */
0f7f2ef3 235 cairo_destroy(cr);
6cc5e1a6
DB
236 }
237 return FALSE;
238}
239
1ea75322 240/* Plugin constructor. */
00916e98 241static GtkWidget *cpu_constructor(LXPanel *panel, config_setting_t *settings)
6cc5e1a6 242{
1ea75322
DB
243 /* Allocate plugin context and set into Plugin private data pointer. */
244 CPUPlugin * c = g_new0(CPUPlugin, 1);
00916e98 245 GtkWidget * p;
6cc5e1a6 246
1ea75322 247 /* Allocate top level widget and set into Plugin widget pointer. */
00916e98
AG
248 p = gtk_event_box_new();
249 gtk_widget_set_has_window(p, FALSE);
250 lxpanel_plugin_set_data(p, c, cpu_destructor);
6cc5e1a6 251
1ea75322 252 /* Allocate drawing area as a child of top level widget. Enable button press events. */
6cc5e1a6 253 c->da = gtk_drawing_area_new();
1ea75322
DB
254 gtk_widget_set_size_request(c->da, 40, PANEL_HEIGHT_DEFAULT);
255 gtk_widget_add_events(c->da, GDK_BUTTON_PRESS_MASK);
00916e98 256 gtk_container_add(GTK_CONTAINER(p), c->da);
6cc5e1a6 257
1ea75322
DB
258 /* Clone a graphics context and set "green" as its foreground color.
259 * We will use this to draw the graph. */
1ea75322 260 gdk_color_parse("green", &c->foreground_color);
1ea75322
DB
261
262 /* Connect signals. */
00916e98
AG
263 g_signal_connect(G_OBJECT(c->da), "configure-event", G_CALLBACK(configure_event), (gpointer) c);
264 g_signal_connect(G_OBJECT(c->da), "expose-event", G_CALLBACK(expose_event), (gpointer) c);
6cc5e1a6 265
1ea75322
DB
266 /* Show the widget. Connect a timer to refresh the statistics. */
267 gtk_widget_show(c->da);
10862fa6 268 c->timer = g_timeout_add(1500, (GSourceFunc) cpu_update, (gpointer) c);
00916e98 269 return p;
6cc5e1a6
DB
270}
271
1ea75322 272/* Plugin destructor. */
00916e98 273static void cpu_destructor(gpointer user_data)
6cc5e1a6 274{
00916e98 275 CPUPlugin * c = (CPUPlugin *)user_data;
1ea75322
DB
276
277 /* Disconnect the timer. */
278 g_source_remove(c->timer);
6cc5e1a6 279
1ea75322 280 /* Deallocate memory. */
0f7f2ef3 281 cairo_surface_destroy(c->pixmap);
6cc5e1a6 282 g_free(c->stats_cpu);
1ea75322 283 g_free(c);
6cc5e1a6
DB
284}
285
00916e98 286FM_DEFINE_MODULE(lxpanel_gtk, cpu)
6cc5e1a6 287
00916e98
AG
288/* Plugin descriptor. */
289LXPanelPluginInit fm_module_init_lxpanel_gtk = {
290 .name = N_("CPU Usage Monitor"),
291 .description = N_("Display CPU usage"),
292 .new_instance = cpu_constructor,
6cc5e1a6 293};