Enabling multithreaded compilation.
[debian/lxpanel.git] / src / plugins / cpu / cpu.c
CommitLineData
1ea75322 1/**
6cc5e1a6
DB
2 * CPU usage plugin to lxpanel
3 *
10862fa6 4 * Copyright (c) 2008 LxDE Developers, see the file AUTHORS for details.
6cc5e1a6
DB
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
6cc5e1a6
DB
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#include "panel.h"
33#include "misc.h"
34
10862fa6
DB
35#define BORDER_SIZE 2
36
6cc5e1a6 37#include "dbg.h"
1ea75322
DB
38
39typedef unsigned long CPUTick; /* Value from /proc/stat */
40typedef float CPUSample; /* Saved CPU utilization value as 0.0..1.0 */
6cc5e1a6
DB
41
42struct cpu_stat {
1ea75322 43 CPUTick u, n, s, i; /* User, nice, system, idle */
6cc5e1a6
DB
44};
45
1ea75322 46/* Private context for CPU plugin. */
6cc5e1a6 47typedef struct {
1ea75322
DB
48 GdkColor foreground_color; /* Foreground color for drawing area */
49 GtkWidget * da; /* Drawing area */
0f7f2ef3 50 cairo_surface_t * pixmap; /* Pixmap to be drawn on drawing area */
1ea75322
DB
51
52 guint timer; /* Timer for periodic update */
53 CPUSample * stats_cpu; /* Ring buffer of CPU utilization values */
54 unsigned int ring_cursor; /* Cursor for ring buffer */
55 int pixmap_width; /* Width of drawing area pixmap; also size of ring buffer; does not include border size */
56 int pixmap_height; /* Height of drawing area pixmap; does not include border size */
57 struct cpu_stat previous_cpu_stat; /* Previous value of cpu_stat */
58} CPUPlugin;
59
60static void redraw_pixmap(CPUPlugin * c);
61static gboolean cpu_update(CPUPlugin * c);
62static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c);
63static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c);
64static int cpu_constructor(Plugin * p, char ** fp);
65static void cpu_destructor(Plugin * p);
66
67/* Redraw after timer callback or resize. */
68static void redraw_pixmap(CPUPlugin * c)
6cc5e1a6 69{
0f7f2ef3
AL
70 cairo_t * cr = cairo_create(c->pixmap);
71 cairo_set_line_width (cr, 1.0);
1ea75322 72 /* Erase pixmap. */
0f7f2ef3
AL
73 cairo_rectangle(cr, 0, 0, c->pixmap_width, c->pixmap_height);
74 gdk_cairo_set_source_color(cr, &c->da->style->black);
75 cairo_fill(cr);
1ea75322
DB
76
77 /* Recompute pixmap. */
6cc5e1a6 78 unsigned int i;
1ea75322 79 unsigned int drawing_cursor = c->ring_cursor;
0f7f2ef3 80 gdk_cairo_set_source_color(cr, &c->foreground_color);
1ea75322
DB
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)
0f7f2ef3
AL
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 }
1ea75322
DB
90
91 /* Increment and wrap drawing cursor. */
92 drawing_cursor += 1;
93 if (drawing_cursor >= c->pixmap_width)
94 drawing_cursor = 0;
6cc5e1a6 95 }
1ea75322 96
0f7f2ef3
AL
97 check_cairo_status(cr);
98 cairo_destroy(cr);
99
1ea75322 100 /* Redraw pixmap. */
6cc5e1a6 101 gtk_widget_queue_draw(c->da);
6cc5e1a6
DB
102}
103
1ea75322
DB
104/* Periodic timer callback. */
105static gboolean cpu_update(CPUPlugin * c)
6cc5e1a6 106{
1ea75322
DB
107 if ((c->stats_cpu != NULL) && (c->pixmap != NULL))
108 {
109 /* Open statistics file and scan out CPU usage. */
110 struct cpu_stat cpu;
111 FILE * stat = fopen("/proc/stat", "r");
112 if (stat == NULL)
113 return TRUE;
114 int fscanf_result = fscanf(stat, "cpu %lu %lu %lu %lu", &cpu.u, &cpu.n, &cpu.s, &cpu.i);
115 fclose(stat);
116
117 /* Ensure that fscanf succeeded. */
118 if (fscanf_result == 4)
119 {
120 /* Compute delta from previous statistics. */
121 struct cpu_stat cpu_delta;
122 cpu_delta.u = cpu.u - c->previous_cpu_stat.u;
123 cpu_delta.n = cpu.n - c->previous_cpu_stat.n;
124 cpu_delta.s = cpu.s - c->previous_cpu_stat.s;
125 cpu_delta.i = cpu.i - c->previous_cpu_stat.i;
126
127 /* Copy current to previous. */
128 memcpy(&c->previous_cpu_stat, &cpu, sizeof(struct cpu_stat));
129
130 /* Compute user+nice+system as a fraction of total.
131 * Introduce this sample to ring buffer, increment and wrap ring buffer cursor. */
132 float cpu_uns = cpu_delta.u + cpu_delta.n + cpu_delta.s;
133 c->stats_cpu[c->ring_cursor] = cpu_uns / (cpu_uns + cpu_delta.i);
134 c->ring_cursor += 1;
135 if (c->ring_cursor >= c->pixmap_width)
136 c->ring_cursor = 0;
137
138 /* Redraw with the new sample. */
139 redraw_pixmap(c);
140 }
141 }
142 return TRUE;
6cc5e1a6
DB
143}
144
1ea75322
DB
145/* Handler for configure_event on drawing area. */
146static gboolean configure_event(GtkWidget * widget, GdkEventConfigure * event, CPUPlugin * c)
6cc5e1a6 147{
1ea75322
DB
148 /* Allocate pixmap and statistics buffer without border pixels. */
149 int new_pixmap_width = widget->allocation.width - BORDER_SIZE * 2;
150 int new_pixmap_height = widget->allocation.height - BORDER_SIZE * 2;
151 if ((new_pixmap_width > 0) && (new_pixmap_height > 0))
152 {
153 /* If statistics buffer does not exist or it changed size, reallocate and preserve existing data. */
154 if ((c->stats_cpu == NULL) || (new_pixmap_width != c->pixmap_width))
155 {
156 CPUSample * new_stats_cpu = g_new0(typeof(*c->stats_cpu), new_pixmap_width);
157 if (c->stats_cpu != NULL)
158 {
159 if (new_pixmap_width > c->pixmap_width)
160 {
161 /* New allocation is larger.
162 * Introduce new "oldest" samples of zero following the cursor. */
163 memcpy(&new_stats_cpu[0],
164 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
165 memcpy(&new_stats_cpu[new_pixmap_width - c->pixmap_width + c->ring_cursor],
166 &c->stats_cpu[c->ring_cursor], (c->pixmap_width - c->ring_cursor) * sizeof(CPUSample));
167 }
168 else if (c->ring_cursor <= new_pixmap_width)
169 {
170 /* New allocation is smaller, but still larger than the ring buffer cursor.
171 * Discard the oldest samples following the cursor. */
172 memcpy(&new_stats_cpu[0],
173 &c->stats_cpu[0], c->ring_cursor * sizeof(CPUSample));
174 memcpy(&new_stats_cpu[c->ring_cursor],
175 &c->stats_cpu[c->pixmap_width - new_pixmap_width + c->ring_cursor], (new_pixmap_width - c->ring_cursor) * sizeof(CPUSample));
176 }
177 else
178 {
179 /* New allocation is smaller, and also smaller than the ring buffer cursor.
180 * Discard all oldest samples following the ring buffer cursor and additional samples at the beginning of the buffer. */
181 memcpy(&new_stats_cpu[0],
182 &c->stats_cpu[c->ring_cursor - new_pixmap_width], new_pixmap_width * sizeof(CPUSample));
183 c->ring_cursor = 0;
184 }
185 g_free(c->stats_cpu);
186 }
187 c->stats_cpu = new_stats_cpu;
188 }
189
190 /* Allocate or reallocate pixmap. */
191 c->pixmap_width = new_pixmap_width;
192 c->pixmap_height = new_pixmap_height;
193 if (c->pixmap)
0f7f2ef3
AL
194 cairo_surface_destroy(c->pixmap);
195 c->pixmap = cairo_image_surface_create(CAIRO_FORMAT_RGB24, c->pixmap_width, c->pixmap_height);
196 check_cairo_surface_status(&c->pixmap);
1ea75322
DB
197
198 /* Redraw pixmap at the new size. */
199 redraw_pixmap(c);
200 }
201 return TRUE;
6cc5e1a6
DB
202}
203
1ea75322
DB
204/* Handler for expose_event on drawing area. */
205static gboolean expose_event(GtkWidget * widget, GdkEventExpose * event, CPUPlugin * c)
6cc5e1a6 206{
1ea75322
DB
207 /* Draw the requested part of the pixmap onto the drawing area.
208 * Translate it in both x and y by the border size. */
209 if (c->pixmap != NULL)
6cc5e1a6 210 {
0f7f2ef3
AL
211 cairo_t * cr = gdk_cairo_create(widget->window);
212 gdk_cairo_region(cr, event->region);
213 cairo_clip(cr);
214 gdk_cairo_set_source_color(cr, &c->da->style->black);
215 cairo_set_source_surface(cr, c->pixmap,
216 BORDER_SIZE, BORDER_SIZE);
217 cairo_paint(cr);
218 check_cairo_status(cr);
219 cairo_destroy(cr);
6cc5e1a6
DB
220 }
221 return FALSE;
222}
223
1ea75322
DB
224/* Plugin constructor. */
225static int cpu_constructor(Plugin * p, char ** fp)
6cc5e1a6 226{
1ea75322
DB
227 /* Allocate plugin context and set into Plugin private data pointer. */
228 CPUPlugin * c = g_new0(CPUPlugin, 1);
6cc5e1a6
DB
229 p->priv = c;
230
1ea75322 231 /* Allocate top level widget and set into Plugin widget pointer. */
6cc5e1a6 232 p->pwid = gtk_event_box_new();
1ea75322
DB
233 gtk_container_set_border_width(GTK_CONTAINER(p->pwid), 1);
234 GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
6cc5e1a6 235
1ea75322 236 /* Allocate drawing area as a child of top level widget. Enable button press events. */
6cc5e1a6 237 c->da = gtk_drawing_area_new();
1ea75322
DB
238 gtk_widget_set_size_request(c->da, 40, PANEL_HEIGHT_DEFAULT);
239 gtk_widget_add_events(c->da, GDK_BUTTON_PRESS_MASK);
6cc5e1a6 240 gtk_container_add(GTK_CONTAINER(p->pwid), c->da);
6cc5e1a6 241
1ea75322
DB
242 /* Clone a graphics context and set "green" as its foreground color.
243 * We will use this to draw the graph. */
1ea75322 244 gdk_color_parse("green", &c->foreground_color);
1ea75322
DB
245
246 /* Connect signals. */
247 g_signal_connect(G_OBJECT(c->da), "configure_event", G_CALLBACK(configure_event), (gpointer) c);
248 g_signal_connect(G_OBJECT(c->da), "expose_event", G_CALLBACK(expose_event), (gpointer) c);
249 g_signal_connect(c->da, "button-press-event", G_CALLBACK(plugin_button_press_event), p);
6cc5e1a6 250
1ea75322
DB
251 /* Show the widget. Connect a timer to refresh the statistics. */
252 gtk_widget_show(c->da);
10862fa6 253 c->timer = g_timeout_add(1500, (GSourceFunc) cpu_update, (gpointer) c);
1ea75322 254 return 1;
6cc5e1a6
DB
255}
256
1ea75322
DB
257/* Plugin destructor. */
258static void cpu_destructor(Plugin * p)
6cc5e1a6 259{
1ea75322
DB
260 CPUPlugin * c = (CPUPlugin *) p->priv;
261
262 /* Disconnect the timer. */
263 g_source_remove(c->timer);
6cc5e1a6 264
1ea75322 265 /* Deallocate memory. */
0f7f2ef3 266 cairo_surface_destroy(c->pixmap);
6cc5e1a6 267 g_free(c->stats_cpu);
1ea75322 268 g_free(c);
6cc5e1a6
DB
269}
270
1ea75322 271/* Plugin descriptor. */
6cc5e1a6 272PluginClass cpu_plugin_class = {
1ea75322
DB
273
274 PLUGINCLASS_VERSIONING,
6cc5e1a6
DB
275
276 type : "cpu",
277 name : N_("CPU Usage Monitor"),
278 version: "1.0",
279 description : N_("Display CPU usage"),
280
281 constructor : cpu_constructor,
282 destructor : cpu_destructor,
283 config : NULL,
284 save : NULL
285};