pacct 0.8.0-universal-linux

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pacct.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Pacct
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'pacct'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install pacct
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => :spec
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.rspec_opts =%w{--color --format progress}
9
+ task.pattern = 'spec/*_spec.rb'
10
+ end
11
+
12
+ task :docs do
13
+ system("rdoc --exclude '(./)?spec/'")
14
+ end
15
+
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
2
+
3
+ =begin
4
+ if ENV['COVERAGE']
5
+ $CFLAGS << ' -fprofile-arcs -ftest-coverage'
6
+ end
7
+ =end
8
+
9
+ create_makefile("pacct/pacct_c");
@@ -0,0 +1,856 @@
1
+ #include <errno.h>
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+
5
+ #include <grp.h>
6
+ #include <pwd.h>
7
+ #include <unistd.h>
8
+ #include <sys/acct.h>
9
+ #include <sys/types.h>
10
+
11
+ #include "ruby.h"
12
+
13
+ static char const* validFileModes[] = {
14
+ "rb",
15
+ "wb",
16
+ "r+b",
17
+ "w+b",
18
+ };
19
+
20
+ static VALUE mPacct;
21
+ static VALUE cLog;
22
+ static VALUE cEntry;
23
+
24
+ //Classes from Ruby
25
+ static VALUE cTime;
26
+ static VALUE cNoMemoryError;
27
+ static VALUE cSystemCallError;
28
+
29
+ //Identifiers
30
+ static ID id_at;
31
+ static ID id_new;
32
+ static ID id_to_i;
33
+
34
+ //To do: where is a better place to put these?
35
+ static VALUE known_users_by_name = Qnil;
36
+ static VALUE known_groups_by_name = Qnil;
37
+ static VALUE known_users_by_id = Qnil;
38
+ static VALUE known_groups_by_id = Qnil;
39
+
40
+ //System parameters
41
+ static int pageSize;
42
+ static long ticksPerSecond;
43
+
44
+ //Converts a comp_t to a long
45
+ static unsigned long comp_t_to_ulong(comp_t c) {
46
+ return (unsigned long)(c & 0x1fff) << (((c >> 13) & 0x7) * 3);
47
+ }
48
+
49
+ //Prints a number in binary (for debugging)
50
+ static void print_bin(unsigned long val) {
51
+ //Cast prevents warning
52
+ unsigned long bits = (unsigned long)1 << (sizeof(unsigned long) * 8 - 1);
53
+ putchar('0' + ((val & bits) > 0));
54
+ while(bits >>= 1) {
55
+ putchar('0' + ((val & bits) > 0));
56
+ }
57
+ putchar('\n');
58
+ }
59
+
60
+ //Converts a long to a comp_t
61
+ //To consider: make sure the value is positive?
62
+ //To consider: more unit testing?
63
+ static comp_t ulong_to_comp_t(unsigned long l) {
64
+ size_t bits = 0;
65
+ unsigned long l2 = l;
66
+ if(l2) {
67
+ bits = 1;
68
+ while(l2 >>= 1) {
69
+ ++bits;
70
+ }
71
+ }
72
+ if(bits <= 13) {
73
+ return (l & 0x1fff);
74
+ } else {
75
+ size_t div_bits, rem_bits;
76
+ bits -= 13;
77
+ div_bits = bits / 3;
78
+ if(div_bits >= 8) {
79
+ rb_raise(rb_eRangeError, "Exponent overflow in ulong_to_comp_t: Value %lu is too large.", l);
80
+ }
81
+ rem_bits = bits - div_bits * 3;
82
+ if(rem_bits) {
83
+ div_bits += 1;
84
+ }
85
+ //To consider: remove '&'?
86
+ return ((l >> bits) & 0x1fff) | ((div_bits & 0x7) << 13);
87
+ }
88
+ }
89
+
90
+ //Checks the result of a call, raising an error if it fails
91
+ #define CHECK_CALL(expr, expected_result) \
92
+ { \
93
+ typeof(expr) expected = (expected_result); \
94
+ typeof(expr) result; \
95
+ errno = 0; \
96
+ result = (expr); \
97
+ if(result != expected) { \
98
+ if(errno) { \
99
+ char buf[512]; \
100
+ VALUE err; \
101
+ snprintf(buf, sizeof(buf), "%s(%u)", __FILE__, __LINE__); \
102
+ err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(errno)); \
103
+ rb_exc_raise(err); \
104
+ } else { \
105
+ char buf[512]; \
106
+ snprintf(buf, sizeof(buf), #expr ": result %i expected, not %i - %s(%u)", expected, result, __FILE__, __LINE__); \
107
+ rb_raise(rb_eRuntimeError, buf); \
108
+ } \
109
+ } \
110
+ } \
111
+
112
+ #define ENSURE_ALLOCATED(ptr) if(!ptr) rb_raise(cNoMemoryError, "Out of memory");
113
+
114
+ typedef struct {
115
+ FILE* file;
116
+ char* filename;
117
+ long num_entries;
118
+ } PacctLog;
119
+
120
+ static void pacct_log_free(void* p) {
121
+ PacctLog* log = (PacctLog*) p;
122
+ if(log->file) {
123
+ fclose(log->file);
124
+ log->file = NULL;
125
+ }
126
+ free(log->filename);
127
+ free(p);
128
+ }
129
+
130
+ /*
131
+ *call-seq:
132
+ * new(filename)
133
+ *
134
+ *Creates a new Pacct::Log using the given accounting file
135
+ */
136
+ static VALUE pacct_log_new(int argc, VALUE* argv, VALUE class) {
137
+ VALUE log;
138
+ VALUE init_args[2];
139
+ PacctLog* ptr;
140
+
141
+ init_args[1] = Qnil;
142
+ rb_scan_args(argc, argv, "11", init_args, init_args + 1);
143
+
144
+ log = Data_Make_Struct(class, PacctLog, 0, pacct_log_free, ptr);
145
+
146
+ ptr->file = NULL;
147
+ ptr->num_entries = 0;
148
+
149
+ rb_obj_call_init(log, 2, init_args);
150
+ return log;
151
+ }
152
+
153
+ static VALUE pacct_log_init(VALUE self, VALUE filename, VALUE mode) {
154
+ PacctLog* log;
155
+ FILE* acct;
156
+ long length;
157
+ char* c_filename = StringValueCStr(filename);
158
+ size_t c_filename_len;
159
+ const char* c_mode = "rb";
160
+
161
+ if(mode != Qnil) {
162
+ int isValidMode = 0;
163
+ size_t i;
164
+ c_mode = StringValueCStr(mode);
165
+ for(i = 0; i < sizeof(validFileModes) / sizeof(char*); ++i) {
166
+ if(strcmp(c_mode, validFileModes[i]) == 0) {
167
+ isValidMode = 1;
168
+ break;
169
+ }
170
+ }
171
+ if(!isValidMode) {
172
+ rb_raise(rb_eArgError, "Invalid mode for Pacct::File: '%s'", c_mode);
173
+ }
174
+ }
175
+
176
+ acct = fopen(c_filename, c_mode);
177
+ if(!acct) {
178
+ rb_raise(rb_eIOError, "Unable to open file '%s'", c_filename);
179
+ }
180
+
181
+ Data_Get_Struct(self, PacctLog, log);
182
+
183
+ log->file = acct;
184
+ c_filename_len = strlen(c_filename);
185
+ log->filename = malloc(c_filename_len + 1);
186
+ ENSURE_ALLOCATED(log->filename);
187
+ strncpy(log->filename, c_filename, c_filename_len);
188
+ log->filename[c_filename_len] = '\0';
189
+
190
+ CHECK_CALL(fseek(acct, 0, SEEK_END), 0);
191
+ length = ftell(acct);
192
+ rewind(acct);
193
+
194
+ if(length % sizeof(struct acct_v3) != 0) {
195
+ rb_raise(rb_eIOError, "Accounting file '%s' appears to be the wrong size.", c_filename);
196
+ }
197
+
198
+ log->num_entries = length / sizeof(struct acct_v3);
199
+
200
+ return self;
201
+ }
202
+
203
+ static void pacct_log_check_closed(PacctLog* log) {
204
+ if(!log->file) {
205
+ rb_raise(rb_eRuntimeError, "The file '%s' has already been closed.", log->filename);
206
+ }
207
+ }
208
+
209
+ /*
210
+ *Closes the log file
211
+ */
212
+ static VALUE pacct_log_close(VALUE self) {
213
+ PacctLog* log;
214
+
215
+ Data_Get_Struct(self, PacctLog, log);
216
+
217
+ if(log->file) {
218
+ fclose(log->file);
219
+ log->file = NULL;
220
+ }
221
+
222
+ return Qnil;
223
+ }
224
+
225
+ static VALUE pacct_entry_new(PacctLog* log) {
226
+ struct acct_v3* ptr;
227
+ VALUE entry = Data_Make_Struct(cEntry, struct acct_v3, 0, free, ptr);
228
+ if(log) {
229
+ size_t entries_read;
230
+ pacct_log_check_closed(log);
231
+ entries_read = fread(ptr, sizeof(struct acct_v3), 1, log->file);
232
+ if(entries_read != 1) {
233
+ rb_raise(rb_eIOError, "Unable to read record from accounting file '%s'", log->filename);
234
+ }
235
+ } else {
236
+ memset(ptr, 0, sizeof(struct acct_v3));
237
+ }
238
+
239
+ return entry;
240
+ }
241
+
242
+ //This is the version of pacct_entry_new that is actually exposed to Ruby.
243
+ static VALUE ruby_pacct_entry_new(VALUE self) {
244
+ return pacct_entry_new(NULL);
245
+ }
246
+
247
+ /*
248
+ *call-seq:
249
+ * each_entry([start]) {|entry, index| ...}
250
+ *
251
+ *Yields each entry in the file to the given block
252
+ *
253
+ *If start is given, iteration starts at the entry with that index.
254
+ */
255
+ static VALUE each_entry(int argc, VALUE* argv, VALUE self) {
256
+ PacctLog* log;
257
+ VALUE start_value;
258
+ long start = 0;
259
+ int i = 0;
260
+
261
+ rb_scan_args(argc, argv, "01", &start_value);
262
+ if(argc && start_value != Qnil) {
263
+ start = NUM2UINT(start_value);
264
+ }
265
+
266
+ Data_Get_Struct(self, PacctLog, log);
267
+
268
+ pacct_log_check_closed(log);
269
+
270
+ if(start > log->num_entries) {
271
+ rb_raise(rb_eRangeError, "Index %li is out of range", start);
272
+ }
273
+
274
+ CHECK_CALL(fseek(log->file, start * sizeof(struct acct_v3), SEEK_SET), 0);
275
+
276
+ for(i = start; i < log->num_entries; ++i) {
277
+ VALUE entry = pacct_entry_new(log);
278
+ rb_yield(entry);
279
+ }
280
+
281
+ return Qnil;
282
+ }
283
+
284
+ /*
285
+ *Returns the last entry in the file
286
+ */
287
+ static VALUE last_entry(VALUE self) {
288
+ PacctLog* log;
289
+ long pos;
290
+ VALUE entry;
291
+
292
+ Data_Get_Struct(self, PacctLog, log);
293
+
294
+ pacct_log_check_closed(log);
295
+
296
+ if(log->num_entries == 0) {
297
+ return Qnil;
298
+ }
299
+
300
+ pos = ftell(log->file);
301
+ CHECK_CALL(fseek(log->file, -sizeof(struct acct_v3), SEEK_END), 0);
302
+
303
+ entry = pacct_entry_new(log);
304
+
305
+ CHECK_CALL(fseek(log->file, pos, SEEK_SET), 0);
306
+
307
+ return entry;
308
+ }
309
+
310
+ /*
311
+ *Returns the number of entries in the file
312
+ */
313
+ static VALUE get_num_entries(VALUE self) {
314
+ PacctLog* log;
315
+
316
+ Data_Get_Struct(self, PacctLog, log);
317
+
318
+ return INT2NUM(log->num_entries);
319
+ }
320
+
321
+ /*
322
+ *call-seq:
323
+ * write_entry(entry)
324
+ *
325
+ * Appends the given entry to the file
326
+ */
327
+ static VALUE write_entry(VALUE self, VALUE entry) {
328
+ //To do consider: verification?
329
+ PacctLog* log;
330
+ long pos;
331
+ struct acct_v3* acct;
332
+
333
+ Data_Get_Struct(self, PacctLog, log);
334
+ pacct_log_check_closed(log);
335
+ Data_Get_Struct(entry, struct acct_v3, acct);
336
+
337
+ pos = ftell(log->file);
338
+ CHECK_CALL(fseek(log->file, 0, SEEK_END), 0);
339
+
340
+ if(fwrite(acct, sizeof(struct acct_v3), 1, log->file) != 1) {
341
+ rb_raise(rb_eIOError, "Unable to write to accounting file '%s'", log->filename);
342
+ }
343
+
344
+ ++(log->num_entries);
345
+
346
+ CHECK_CALL(fseek(log->file, pos, SEEK_SET), 0);
347
+
348
+ return Qnil;
349
+ }
350
+
351
+ //Methods of Pacct::Entry
352
+ /*
353
+ *Returns the process ID
354
+ */
355
+ static VALUE get_process_id(VALUE self) {
356
+ struct acct_v3* data;
357
+ Data_Get_Struct(self, struct acct_v3, data);
358
+
359
+ return INT2NUM(data->ac_pid);
360
+ }
361
+
362
+ /*
363
+ *Sets the process ID
364
+ */
365
+ static VALUE set_process_id(VALUE self, VALUE pid) {
366
+ struct acct_v3* data;
367
+ Data_Get_Struct(self, struct acct_v3, data);
368
+
369
+ data->ac_pid = NUM2UINT(pid);
370
+
371
+ return Qnil;
372
+ }
373
+
374
+ /*
375
+ *Returns the ID of the user who executed the command
376
+ */
377
+ static VALUE get_user_id(VALUE self) {
378
+ struct acct_v3* data;
379
+ Data_Get_Struct(self, struct acct_v3, data);
380
+
381
+ return INT2NUM(data->ac_uid);
382
+ }
383
+
384
+ /*
385
+ *Returns the name of the user who executed the command
386
+ */
387
+ static VALUE get_user_name(VALUE self) {
388
+ struct acct_v3* data;
389
+ struct passwd* pw_data;
390
+ VALUE id, name;
391
+ Data_Get_Struct(self, struct acct_v3, data);
392
+
393
+ //If there's a cached user name, return it.
394
+ id = UINT2NUM(data->ac_uid);
395
+ name = rb_hash_aref(known_users_by_id, id);
396
+ if(name != Qnil) {
397
+ return name;
398
+ }
399
+
400
+ //Otherwise, get the user name from the OS.
401
+ errno = 0;
402
+ pw_data = getpwuid(data->ac_uid);
403
+ if(!pw_data) {
404
+ char buf[512];
405
+ VALUE err;
406
+ int e = errno;
407
+ snprintf(buf, 512, "Unable to obtain user name for ID %u", data->ac_uid);
408
+ if(e == 0) {
409
+ e = ENODATA;
410
+ }
411
+ err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
412
+ rb_exc_raise(err);
413
+ }
414
+
415
+ //Cache the user name.
416
+ name = rb_str_new2(pw_data->pw_name);
417
+ rb_hash_aset(known_users_by_id, id, name);
418
+
419
+ return name;
420
+ }
421
+
422
+ /*
423
+ *Sets the name of the user who executed the command
424
+ */
425
+ static VALUE set_user_name(VALUE self, VALUE name) {
426
+ struct acct_v3* data;
427
+ struct passwd* pw_data;
428
+ char* c_name = StringValueCStr(name);
429
+ VALUE id;
430
+ Data_Get_Struct(self, struct acct_v3, data);
431
+
432
+ id = rb_hash_aref(known_users_by_name, name);
433
+ if(id != Qnil) {
434
+ data->ac_uid = NUM2UINT(id);
435
+ return Qnil;
436
+ }
437
+
438
+ errno = 0;
439
+ pw_data = getpwnam(c_name);
440
+ if(!pw_data) {
441
+ char buf[512];
442
+ VALUE err;
443
+ int e = errno;
444
+ snprintf(buf, 512, "Unable to obtain user ID for name '%s'", c_name);
445
+ if(e == 0) {
446
+ e = ENODATA;
447
+ }
448
+ err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
449
+ rb_exc_raise(err);
450
+ }
451
+
452
+ id = UINT2NUM(pw_data->pw_uid);
453
+ rb_hash_aset(known_users_by_name, name, id);
454
+
455
+ data->ac_uid = pw_data->pw_uid;
456
+
457
+ return Qnil;
458
+ }
459
+
460
+ /*
461
+ *Returns the group ID of the user who executed the command
462
+ */
463
+ static VALUE get_group_id(VALUE self) {
464
+ struct acct_v3* data;
465
+ Data_Get_Struct(self, struct acct_v3, data);
466
+
467
+ return INT2NUM(data->ac_gid);
468
+ }
469
+
470
+ /*
471
+ *Returns the group name of the user who executed the command
472
+ */
473
+ static VALUE get_group_name(VALUE self) {
474
+ struct acct_v3* data;
475
+ struct group* group_data;
476
+ VALUE name, id;
477
+ Data_Get_Struct(self, struct acct_v3, data);
478
+
479
+ id = UINT2NUM(data->ac_gid);
480
+ name = rb_hash_aref(known_groups_by_id, id);
481
+ if(name != Qnil) {
482
+ return name;
483
+ }
484
+
485
+ errno = 0;
486
+ group_data = getgrgid(data->ac_gid);
487
+ if(!group_data) {
488
+ char buf[512];
489
+ VALUE err;
490
+ int e = errno;
491
+ snprintf(buf, 512, "Unable to obtain group name for ID %u", data->ac_gid);
492
+ if(e == 0) {
493
+ e = ENODATA;
494
+ }
495
+ err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
496
+ rb_exc_raise(err);
497
+ }
498
+
499
+ name = rb_str_new2(group_data->gr_name);
500
+ rb_hash_aset(known_groups_by_id, id, name);
501
+
502
+ return name;
503
+ }
504
+
505
+ /*
506
+ *Sets the group name of the user who executed the command
507
+ */
508
+ static VALUE set_group_name(VALUE self, VALUE name) {
509
+ struct acct_v3* data;
510
+ struct group* group_data;
511
+ VALUE id;
512
+ char* c_name = StringValueCStr(name);
513
+ Data_Get_Struct(self, struct acct_v3, data);
514
+
515
+ id = rb_hash_aref(known_groups_by_name, name);
516
+ if(id != Qnil) {
517
+ data->ac_gid = NUM2UINT(id);
518
+ return Qnil;
519
+ }
520
+
521
+ errno = 0;
522
+ group_data = getgrnam(c_name);
523
+ if(!group_data) {
524
+ char buf[512];
525
+ VALUE err;
526
+ int e = errno;
527
+ snprintf(buf, 512, "Unable to obtain group ID for name '%s'", c_name);
528
+ if(e == 0) {
529
+ e = ENODATA;
530
+ }
531
+ err = rb_funcall(cSystemCallError, id_new, 2, rb_str_new2(buf), INT2NUM(e));
532
+ rb_exc_raise(err);
533
+ }
534
+
535
+ id = UINT2NUM(group_data->gr_gid);
536
+ rb_hash_aset(known_groups_by_name, name, id);
537
+
538
+ data->ac_gid = group_data->gr_gid;
539
+
540
+ return Qnil;
541
+ }
542
+
543
+ /*
544
+ *Returns the task's total user CPU time in seconds
545
+ */
546
+ static VALUE get_user_time(VALUE self) {
547
+ struct acct_v3* data;
548
+ Data_Get_Struct(self, struct acct_v3, data);
549
+
550
+ return INT2NUM(comp_t_to_ulong(data->ac_utime) / ticksPerSecond);
551
+ }
552
+
553
+ /*
554
+ *Sets the task's total user CPU time
555
+ */
556
+ static VALUE set_user_time(VALUE self, VALUE value) {
557
+ struct acct_v3* data;
558
+ Data_Get_Struct(self, struct acct_v3, data);
559
+
560
+ data->ac_utime = ulong_to_comp_t(NUM2ULONG(value) * ticksPerSecond);
561
+
562
+ return Qnil;
563
+ }
564
+
565
+ /*
566
+ *Returns the task's total system CPU time in seconds
567
+ */
568
+ static VALUE get_system_time(VALUE self) {
569
+ struct acct_v3* data;
570
+ Data_Get_Struct(self, struct acct_v3, data);
571
+
572
+ return INT2NUM(comp_t_to_ulong(data->ac_stime) / ticksPerSecond);
573
+ }
574
+
575
+ /*
576
+ *Sets the task's total system CPU time
577
+ */
578
+ static VALUE set_system_time(VALUE self, VALUE value) {
579
+ struct acct_v3* data;
580
+ Data_Get_Struct(self, struct acct_v3, data);
581
+
582
+ data->ac_stime = ulong_to_comp_t(NUM2ULONG(value) * ticksPerSecond);
583
+
584
+ return Qnil;
585
+ }
586
+
587
+ /*
588
+ *Returns the task's total CPU time in seconds
589
+ */
590
+ static VALUE get_cpu_time(VALUE self) {
591
+ struct acct_v3* data;
592
+ Data_Get_Struct(self, struct acct_v3, data);
593
+
594
+ return INT2NUM((comp_t_to_ulong(data->ac_utime) + comp_t_to_ulong(data->ac_stime)) / ticksPerSecond);
595
+ }
596
+
597
+ /*
598
+ *Returns the task's total wall time in seconds
599
+ */
600
+ static VALUE get_wall_time(VALUE self) {
601
+ struct acct_v3* data;
602
+ Data_Get_Struct(self, struct acct_v3, data);
603
+
604
+ return rb_float_new(data->ac_etime);
605
+ }
606
+
607
+ /*
608
+ *Sets the task's total wall time
609
+ */
610
+ static VALUE set_wall_time(VALUE self, VALUE value) {
611
+ struct acct_v3* data;
612
+ Data_Get_Struct(self, struct acct_v3, data);
613
+
614
+ data->ac_etime = NUM2DBL(value);
615
+
616
+ return Qnil;
617
+ }
618
+
619
+ /*
620
+ *Returns the task's start time
621
+ */
622
+ static VALUE get_start_time(VALUE self) {
623
+ struct acct_v3* data;
624
+ Data_Get_Struct(self, struct acct_v3, data);
625
+
626
+ return rb_funcall(cTime, id_at, 1, INT2NUM(data->ac_btime));
627
+ }
628
+
629
+ /*
630
+ *Sets the task's start time
631
+ */
632
+ static VALUE set_start_time(VALUE self, VALUE value) {
633
+ struct acct_v3* data;
634
+ Data_Get_Struct(self, struct acct_v3, data);
635
+
636
+ data->ac_btime = NUM2UINT(rb_funcall(value, id_to_i, 0));
637
+
638
+ return Qnil;
639
+ }
640
+
641
+ /*
642
+ *Returns the task's average memory usage in kilobytes
643
+ */
644
+ static VALUE get_average_mem_usage(VALUE self) {
645
+ struct acct_v3* data;
646
+ Data_Get_Struct(self, struct acct_v3, data);
647
+
648
+ //Why divided by page size?
649
+ return INT2NUM(comp_t_to_ulong(data->ac_mem) * 1024 / pageSize);
650
+ }
651
+
652
+ /*
653
+ *Sets the task's average memory usage in kilobytes
654
+ */
655
+ static VALUE set_average_mem_usage(VALUE self, VALUE value) {
656
+ struct acct_v3* data;
657
+ Data_Get_Struct(self, struct acct_v3, data);
658
+
659
+ data->ac_mem = ulong_to_comp_t(NUM2ULONG(value) * pageSize / 1024);
660
+
661
+ return Qnil;
662
+ }
663
+
664
+ /*
665
+ *Returns the first 15 characters of the command name
666
+ */
667
+ static VALUE get_command_name(VALUE self) {
668
+ struct acct_v3* data;
669
+ Data_Get_Struct(self, struct acct_v3, data);
670
+
671
+ return rb_str_new2(data->ac_comm);
672
+ }
673
+
674
+ /*
675
+ *Sets the first 15 characters of the command name
676
+ */
677
+ static VALUE set_command_name(VALUE self, VALUE name) {
678
+ struct acct_v3* data;
679
+ Data_Get_Struct(self, struct acct_v3, data);
680
+
681
+ strncpy(data->ac_comm, StringValueCStr(name), ACCT_COMM - 1);
682
+ data->ac_comm[ACCT_COMM - 1] = '\0';
683
+
684
+ return Qnil;
685
+ }
686
+
687
+ /*
688
+ *Returns the command's exit code
689
+ */
690
+ static VALUE get_exit_code(VALUE self) {
691
+ struct acct_v3* data;
692
+ Data_Get_Struct(self, struct acct_v3, data);
693
+
694
+ return INT2NUM(data->ac_exitcode);
695
+ }
696
+
697
+ /*
698
+ *Sets the command's exit code
699
+ */
700
+ static VALUE set_exit_code(VALUE self, VALUE value) {
701
+ struct acct_v3* data;
702
+ Data_Get_Struct(self, struct acct_v3, data);
703
+
704
+ data->ac_exitcode = NUM2UINT(value);
705
+
706
+ return Qnil;
707
+ }
708
+
709
+ //Unit testing code
710
+
711
+ static VALUE test_check_call_macro(VALUE self, VALUE test) {
712
+ int i = NUM2INT(test);
713
+ switch(i) {
714
+ case 0:
715
+ CHECK_CALL(0, 0);
716
+ break;
717
+ case 1:
718
+ CHECK_CALL(1, 0);
719
+ break;
720
+ case 2:
721
+ CHECK_CALL(errno = 0, 1);
722
+ case 3:
723
+ CHECK_CALL(errno = ERANGE, 0);
724
+ default:
725
+ rb_raise(rb_eRangeError, "Unknown test code %i", i);
726
+ }
727
+ return Qnil;
728
+ }
729
+
730
+ static VALUE test_read_failure(VALUE self) {
731
+ PacctLog log;
732
+ //VALUE entry = pacct_entry_new(NULL);
733
+ const char* filename = "/dev/null";
734
+ log.num_entries = 0;
735
+ log.filename = malloc(strlen(filename) + 1);
736
+ ENSURE_ALLOCATED(log.filename);
737
+ strcpy(log.filename, filename);
738
+ log.file = fopen(log.filename, "r");
739
+
740
+ pacct_entry_new(&log);
741
+ return Qnil;
742
+ }
743
+
744
+ static VALUE test_write_failure(VALUE self) {
745
+ PacctLog* ptr;
746
+ VALUE log = Data_Make_Struct(cLog, PacctLog, 0, pacct_log_free, ptr);
747
+ VALUE entry = pacct_entry_new(NULL);
748
+ const char* filename = "spec/pacct_spec.rb";
749
+ ptr->num_entries = 0;
750
+ ptr->filename = malloc(strlen(filename) + 1);
751
+ ENSURE_ALLOCATED(ptr->filename);
752
+ strcpy(ptr->filename, filename);
753
+ ptr->file = fopen(ptr->filename, "r");
754
+
755
+ write_entry(log, entry);
756
+ return Qnil;
757
+ }
758
+
759
+ static VALUE test_ulong_to_comp_t(VALUE self, VALUE val) {
760
+ unsigned long l = (unsigned long)NUM2ULONG(val);
761
+ comp_t result = ulong_to_comp_t(l);
762
+ return INT2NUM(result);
763
+ }
764
+
765
+ static VALUE test_comp_t_to_ulong(VALUE self, VALUE val) {
766
+ comp_t c = (comp_t)NUM2UINT(val);
767
+ unsigned long result = comp_t_to_ulong(c);
768
+ return ULONG2NUM(result);
769
+ }
770
+
771
+ void Init_pacct_c() {
772
+ VALUE mRSpec;
773
+
774
+ //Get system parameters
775
+ pageSize = getpagesize();
776
+ ticksPerSecond = sysconf(_SC_CLK_TCK);
777
+
778
+ //Get Ruby objects.
779
+ cTime = rb_const_get(rb_cObject, rb_intern("Time"));
780
+ cSystemCallError = rb_const_get(rb_cObject, rb_intern("SystemCallError"));
781
+ cNoMemoryError = rb_const_get(rb_cObject, rb_intern("NoMemoryError"));
782
+
783
+ id_at = rb_intern("at");
784
+ id_new = rb_intern("new");
785
+ id_to_i = rb_intern("to_i");
786
+
787
+ //To consider: is there any place that these can be unregistered?
788
+ rb_gc_register_address(&known_users_by_name);
789
+ rb_gc_register_address(&known_groups_by_name);
790
+ rb_gc_register_address(&known_users_by_id);
791
+ rb_gc_register_address(&known_groups_by_id);
792
+
793
+ known_users_by_name = rb_hash_new();
794
+ known_groups_by_name = rb_hash_new();
795
+ known_users_by_id = rb_hash_new();
796
+ known_groups_by_id = rb_hash_new();
797
+
798
+ //Define Ruby modules/objects/methods.
799
+ mPacct = rb_define_module("Pacct");
800
+ /*
801
+ *Represents an accounting file in acct(5) format
802
+ */
803
+ cLog = rb_define_class_under(mPacct, "Log", rb_cObject);
804
+ /*
805
+ *Document-class: Pacct::Entry
806
+ *
807
+ *Represents an entry in a Pacct::File
808
+ */
809
+ cEntry = rb_define_class_under(mPacct, "Entry", rb_cObject);
810
+ rb_define_singleton_method(cLog, "new", pacct_log_new, -1);
811
+ rb_define_method(cLog, "initialize", pacct_log_init, 2);
812
+ rb_define_method(cLog, "each_entry", each_entry, -1);
813
+ rb_define_method(cLog, "last_entry", last_entry, 0);
814
+ rb_define_method(cLog, "num_entries", get_num_entries, 0);
815
+ rb_define_method(cLog, "write_entry", write_entry, 1);
816
+ rb_define_method(cLog, "close", pacct_log_close, 0);
817
+
818
+ rb_define_singleton_method(cEntry, "new", ruby_pacct_entry_new, 0);
819
+ rb_define_method(cEntry, "process_id", get_process_id, 0);
820
+ rb_define_method(cEntry, "process_id=", set_process_id, 1);
821
+ rb_define_method(cEntry, "user_id", get_user_id, 0);
822
+ rb_define_method(cEntry, "user_name", get_user_name, 0);
823
+ rb_define_method(cEntry, "user_name=", set_user_name, 1);
824
+ rb_define_method(cEntry, "group_id", get_group_id, 0);
825
+ rb_define_method(cEntry, "group_name", get_group_name, 0);
826
+ rb_define_method(cEntry, "group_name=", set_group_name, 1);
827
+ rb_define_method(cEntry, "user_time", get_user_time, 0);
828
+ rb_define_method(cEntry, "user_time=", set_user_time, 1);
829
+ rb_define_method(cEntry, "system_time", get_system_time, 0);
830
+ rb_define_method(cEntry, "system_time=", set_system_time, 1);
831
+ rb_define_method(cEntry, "cpu_time", get_cpu_time, 0);
832
+ rb_define_method(cEntry, "wall_time", get_wall_time, 0);
833
+ rb_define_method(cEntry, "wall_time=", set_wall_time, 1);
834
+ rb_define_method(cEntry, "start_time", get_start_time, 0);
835
+ rb_define_method(cEntry, "start_time=", set_start_time, 1);
836
+ rb_define_method(cEntry, "memory", get_average_mem_usage, 0);
837
+ rb_define_method(cEntry, "memory=", set_average_mem_usage, 1);
838
+ rb_define_method(cEntry, "exit_code", get_exit_code, 0);
839
+ rb_define_method(cEntry, "exit_code=", set_exit_code, 1);
840
+ rb_define_method(cEntry, "command_name", get_command_name, 0);
841
+ rb_define_method(cEntry, "command_name=", set_command_name, 1);
842
+
843
+ //To consider: support other testing frameworks?
844
+
845
+ mRSpec = rb_const_defined(rb_cObject, rb_intern("RSpec"));
846
+ if(mRSpec == Qtrue) {
847
+ /*
848
+ *Document-module: Pacct::Test
849
+ */
850
+ VALUE mTest = rb_define_module_under(mPacct, "Test");
851
+ rb_define_module_function(mTest, "check_call", test_check_call_macro, 1);
852
+ rb_define_module_function(mTest, "write_failure", test_write_failure, 0);
853
+ rb_define_module_function(mTest, "read_failure", test_read_failure, 0);
854
+ rb_define_module_function(mTest, "comp_t_to_ulong", test_comp_t_to_ulong, 1);
855
+ }
856
+ }
data/lib/pacct.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "pacct/version"
2
+
3
+ require "pacct/pacct_c"
4
+
5
+ ##
6
+ #Contains classes for working with process accounting files in acct(5) format
7
+ module Pacct
8
+
9
+ end
10
+
@@ -0,0 +1,4 @@
1
+ module Pacct
2
+ #The library version
3
+ VERSION = "0.8.0"
4
+ end
data/pacct.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pacct/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pacct"
8
+ gem.version = Pacct::VERSION
9
+ gem.authors = ["Ben Merritt"]
10
+ gem.email = ["blm768@gmail.com"]
11
+ gem.description = %q{A C extension library for parsing accounting files in acct(5) format}
12
+ gem.summary = %q{A C extension library for parsing accounting files in acct(5) format}
13
+ gem.homepage = "https://github.com/blm768/bookie"
14
+ gem.platform = Gem::Platform.new('universal-linux')
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.extensions = ["ext/pacct/extconf.rb"]
19
+ gem.test_files = gem.files.grep(%r{^(spec)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_development_dependency('rspec')
23
+ end
data/snapshot/pacct ADDED
Binary file
Binary file
@@ -0,0 +1 @@
1
+ Wrong length!
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacct::Entry do
4
+ it "correctly converts \"comp_t\"s to integers" do
5
+ comp_t_to_ulong = Pacct::Test.method(:comp_t_to_ulong)
6
+ comp_t_to_ulong.call(1).should eql 1
7
+ comp_t_to_ulong.call((1 << 13) | 1).should eql(1 << 3)
8
+ comp_t_to_ulong.call((3 << 13) | 20).should eql(20 << 9)
9
+ end
10
+
11
+ #To do: unit test this (eventually).
12
+ =begin
13
+ it "correctly converts integers to \"comp_t\"s" do
14
+
15
+ end
16
+ =end
17
+
18
+ it "raises an error when a comp_t overflows" do
19
+ e = Pacct::Entry.new
20
+ expect {
21
+ e.memory = 1000000000000
22
+ }.to raise_error(RangeError, /Exponent overflow in ulong_to_comp_t: Value [\d]+ is too large./)
23
+ end
24
+
25
+ it "truncates command names if they become too long" do
26
+ e = Pacct::Entry.new
27
+ e.command_name = 'some_very_long_command_name'
28
+ e.command_name.should eql 'some_very_long_'
29
+ end
30
+
31
+ it "raises an error when encountering unknown user/group IDs" do
32
+ log = Pacct::Log.new('snapshot/pacct_invalid_ids')
33
+ log.each_entry do |entry|
34
+ #This assumes that these users and groups don't actually exist.
35
+ #If, for some odd reason, they _do_ exist, this test will fail.
36
+ expect { entry.user_name }.to raise_error(
37
+ Errno::ENODATA.new('Unable to obtain user name for ID 4294967295').to_s)
38
+ expect { entry.user_name = '_____ _' }.to raise_error(
39
+ Errno::ENODATA.new("Unable to obtain user ID for name '_____ _'").to_s)
40
+ expect { entry.group_name }.to raise_error(
41
+ Errno::ENODATA.new('Unable to obtain group name for ID 4294967295').to_s)
42
+ expect { entry.group_name = '_____ _' }.to raise_error(
43
+ Errno::ENODATA.new("Unable to obtain group ID for name '_____ _'").to_s)
44
+ end
45
+ end
46
+ end
data/spec/log_spec.rb ADDED
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacct::Log do
4
+ before(:each) do
5
+ @log = Pacct::Log.new('snapshot/pacct')
6
+ end
7
+
8
+ it "correctly loads data" do
9
+ n = 0
10
+ @log.each_entry do |entry|
11
+ entry.process_id.should eql 1742
12
+ entry.user_id.should eql 0
13
+ entry.user_name.should eql "root"
14
+ entry.group_id.should eql 0
15
+ entry.group_name.should eql "root"
16
+ entry.command_name.should eql "accton"
17
+ entry.start_time.should eql Time.at(1349741116)
18
+ entry.wall_time.should eql 2.0
19
+ entry.user_time.should eql 0
20
+ entry.system_time.should eql 0
21
+ entry.cpu_time.should eql 0
22
+ entry.memory.should eql 979
23
+ entry.exit_code.should eql 0
24
+ n += 1
25
+ end
26
+ n.should eql 1
27
+ end
28
+
29
+ it "correctly handles the seek parameter" do
30
+ n = 0
31
+ @log.each_entry(1) do |e|
32
+ n += 1
33
+ end
34
+ n.should eql 0
35
+ expect { @log.each_entry(2) { |e| } }.to raise_error(RangeError)
36
+ end
37
+
38
+ it "can read data more than once" do
39
+ 2.times do
40
+ @log.each_entry do |e|
41
+ e.user_name.should eql 'root'
42
+ end
43
+ end
44
+ end
45
+
46
+ it "raises an error if reading fails" do
47
+ expect { Pacct::Test::read_failure }.to raise_error(IOError, "Unable to read record from accounting file '/dev/null'")
48
+ end
49
+
50
+ it "correctly finds the last entry" do
51
+ Helpers::double_log('snapshot/pacct_write') do |log|
52
+ entry = log.last_entry
53
+ entry.should_not eql nil
54
+ entry.exit_code.should eql 1
55
+ end
56
+ Pacct::Log.new('/dev/null').last_entry.should eql nil
57
+ end
58
+
59
+ it "raises an error if the file is not found" do
60
+ expect { Pacct::Log.new('snapshot/abc') }.to raise_error
61
+ end
62
+
63
+ it "raises an error when the file is the wrong size" do
64
+ expect { Pacct::Log.new('snapshot/pacct_invalid_length') }.to raise_error
65
+ end
66
+
67
+ ENTRY_DATA = {
68
+ :process_id => 3,
69
+ :user_name => 'root',
70
+ :group_name => 'root',
71
+ :command_name => 'ls',
72
+ :start_time => Time.local(2012, 1, 1),
73
+ :wall_time => 10.0,
74
+ :user_time => 1,
75
+ :system_time => 1,
76
+ :memory => 100000,
77
+ :exit_code => 2}
78
+
79
+ it "correctly writes entries at the end of the file" do
80
+ e = Pacct::Entry.new
81
+ ENTRY_DATA.each_pair do |key, value|
82
+ e.method((key.to_s + '=').intern).call(value)
83
+ end
84
+ FileUtils.cp('snapshot/pacct', 'snapshot/pacct_write')
85
+ log = Pacct::Log.new('snapshot/pacct_write', 'r+b')
86
+ log.write_entry(e)
87
+ e = log.last_entry
88
+ e.should_not eql nil
89
+ ENTRY_DATA.each_pair do |key, value|
90
+ e.send(key).should eql value
91
+ end
92
+ FileUtils.rm('snapshot/pacct_write')
93
+ end
94
+
95
+ it "raises an error if writing fails" do
96
+ expect { Pacct::Test::write_failure }.to raise_error(IOError, "Unable to write to accounting file 'spec/pacct_spec.rb'")
97
+ end
98
+
99
+ it "creates files when opened in write mode" do
100
+ FileUtils.rm('snapshot/abc') if File.exists?('snapshot/abc')
101
+ log = Pacct::Log.new('snapshot/abc', 'wb')
102
+ File.exists?('snapshot/abc').should eql true
103
+ FileUtils.rm('snapshot/abc')
104
+ end
105
+
106
+ it "raises an error if an attempt is made to access the file after it has been closed" do
107
+ log = Pacct::Log.new('/dev/null')
108
+ log.close
109
+ str = "The file '/dev/null' has already been closed."
110
+ expect { log.each_entry }.to raise_error(str)
111
+ expect { log.write_entry }.to raise_error(str)
112
+ expect { log.last_entry }.to raise_error(str)
113
+ end
114
+ end
115
+
116
+ module Helpers
117
+ def self.double_log(filename)
118
+ FileUtils.cp('snapshot/pacct', filename)
119
+ log = Pacct::Log.new(filename, 'r+b')
120
+ entry = nil
121
+ log.each_entry do |e|
122
+ entry = e
123
+ break
124
+ end
125
+ entry.exit_code = 1
126
+ log.write_entry(entry)
127
+ yield log
128
+ FileUtils.rm(filename)
129
+ end
130
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pacct do
4
+ describe :CHECK_CALL do
5
+ it "only raises an error if the expected result is not given" do
6
+ Pacct::Test::check_call(0)
7
+ expect { Pacct::Test::check_call(1) }.to raise_error(/1: result 0 expected, not 1 - pacct_c\.c\([\d]+\)/)
8
+ end
9
+
10
+ it "raises an error if errno is zero" do
11
+ expect { Pacct::Test::check_call(2) }.to raise_error(/errno = 0: result 1 expected, not 0 - pacct_c\.c\([\d]+\)/)
12
+ end
13
+
14
+ #To consider: test for negative values in setters?
15
+
16
+ it "raises an error if errno is nonzero" do
17
+ expect { Pacct::Test::check_call(3) }.to raise_error(Errno::ERANGE, /Numerical result out of range - pacct_c\.c\([\d]+\)/)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ require 'pacct'
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pacct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ prerelease:
6
+ platform: universal-linux
7
+ authors:
8
+ - Ben Merritt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: A C extension library for parsing accounting files in acct(5) format
31
+ email:
32
+ - blm768@gmail.com
33
+ executables: []
34
+ extensions:
35
+ - ext/pacct/extconf.rb
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - ext/pacct/extconf.rb
44
+ - ext/pacct/pacct_c.c
45
+ - lib/pacct.rb
46
+ - lib/pacct/version.rb
47
+ - pacct.gemspec
48
+ - snapshot/pacct
49
+ - snapshot/pacct_invalid_ids
50
+ - snapshot/pacct_invalid_length
51
+ - spec/entry_spec.rb
52
+ - spec/log_spec.rb
53
+ - spec/pacct_spec.rb
54
+ - spec/spec_helper.rb
55
+ homepage: https://github.com/blm768/bookie
56
+ licenses: []
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 1.8.24
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: A C extension library for parsing accounting files in acct(5) format
79
+ test_files:
80
+ - spec/entry_spec.rb
81
+ - spec/log_spec.rb
82
+ - spec/pacct_spec.rb
83
+ - spec/spec_helper.rb