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