summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2023-02-12 13:31:51 +0200
committerPaul Buetow <paul@buetow.org>2023-02-12 13:31:51 +0200
commit7529cdce08fa461e42c7932985bd9448f8722e5f (patch)
treecf478f42b1f74fd9d59775b266de0d0e0e6f0c81
parente2b1981f930873f580d93cb57c278d6d9274055e (diff)
initial reporter
-rw-r--r--src/guprecords.raku113
1 files changed, 66 insertions, 47 deletions
diff --git a/src/guprecords.raku b/src/guprecords.raku
index 629a6e0..1d8e6dc 100644
--- a/src/guprecords.raku
+++ b/src/guprecords.raku
@@ -2,41 +2,46 @@
use v6.d;
+#= The stats major category.
+subset Cat of Str where * eq any <hostname os os-major uname>;
+#= The sub-cateogory.
+subset SubCat of Str where * eq any <boots uptime downtime lifespan>;
+
class Aggregate {
- has Int $.total-uptime;
+ has Str $.name is required;
+ has Int $.uptime;
has Int $.first-boot;
has Int $.last-seen;
- has Int $.elems;
+ has Int $.boots;
- method aggregate(Str :$uptime is readonly, Str :$boot-time is readonly) {
- $!total-uptime += $uptime;
+ method add-record(Str:D :$uptime is readonly, Str:D :$boot-time is readonly) {
+ $!uptime += $uptime;
$!first-boot = +$boot-time if not defined $!first-boot
or $!first-boot > $boot-time;
my Int $last-seen = $uptime + $boot-time;
$!last-seen = $last-seen if not defined $!last-seen
or $!last-seen < $last-seen;
- $!elems++;
+ $!boots++;
}
- method total-downtime { $.last-seen - $.first-boot - $.total-uptime }
- method total-time { self.total-downtime + $.total-uptime }
+ method downtime { $.last-seen - $.first-boot - $.uptime }
+ method lifespan { self.downtime + $.uptime }
method Str returns Str {
- #duration($!total-uptime)
- my Str $active = self.is-active ?? '* ' !! ' ';
- return "$active {duration($!total-uptime)} {date($!last-seen)}";
+ my Str $active = self!is-active ?? '* ' !! ' ';
+ return "$active {$!name}\t{duration($!uptime)} {$!uptime}"
}
- method is-active(Int \limit = 90) returns Bool {
+ method !is-active(Int:D \limit = 90) returns Bool {
(DateTime.now - DateTime.new(Instant.from-posix: $!last-seen)) < limit * 3600 * 24;
}
- sub duration(Int \seconds) returns Str {
+ sub duration(Int:D \seconds) returns Str {
my DateTime \dt .= new(Instant.from-posix: seconds);
return "{dt.year-1970} years, {dt.month} months, {dt.day} days";
}
- sub date(Int \epoch) returns Str {
+ sub date(Int:D \epoch) returns Str {
DateTime.new(Instant.from-posix: epoch).yyyy-mm-dd
}
}
@@ -44,48 +49,62 @@ class Aggregate {
class Aggregator {
has %.aggregates = { hostname => {}, os => {}, uname => {}, os-major => {} }
- method aggregate(IO::Path :$file is readonly) {
+ method add-file(IO::Path:D :$file is readonly) {
my Str $hostname = $file.IO.basename.split('.').first;
- %!aggregates<hostname>{$hostname} //= Aggregate.new;
-
- for $file.IO.lines {
- my Str ($uptime, $boot-time, $os) = .trim.split(':');
- my Str $uname = $os.split(' ').first;
- my Str $os-major = "$uname {$os.split(' ')[1].split('.').first}...";
-
- %!aggregates<os>{$os} //= Aggregate.new;
- %!aggregates<uname>{$uname} //= Aggregate.new;
- %!aggregates<os-major>{$os-major} //= Aggregate.new;
-
- for %!aggregates<hostname>{$hostname},
- %!aggregates<os>{$os},
- %!aggregates<uname>{$uname},
- %!aggregates<os-major>{$os-major} {
- .aggregate(:$uptime, :$boot-time);
- }
+ %!aggregates<hostname>{$hostname} //= Aggregate.new: :name($hostname);
+ for $file.IO.lines -> Str $line { self!add-line(:$line, :$hostname) }
+ }
+
+ method !add-line(Str:D :$line is readonly, Str:D :$hostname is readonly) {
+ my Str ($uptime, $boot-time, $os) = $line.trim.split(':');
+ my Str $uname = $os.split(' ').first;
+ my Str $os-major = "$uname {$os.split(' ')[1].split('.').first}...";
+
+ %!aggregates<os>{$os} //= Aggregate.new: :name($os);
+ %!aggregates<uname>{$uname} //= Aggregate.new: :name($uname);
+ %!aggregates<os-major>{$os-major} //= Aggregate.new: :name($os-major);
+
+ for %!aggregates<hostname>{$hostname},
+ %!aggregates<os>{$os},
+ %!aggregates<uname>{$uname},
+ %!aggregates<os-major>{$os-major} {
+ .add-record(:$uptime, :$boot-time);
}
}
}
-# TODO:
-# --category switch (hostname, os, os-major, uname...)
-# --stats switch (record count, total uptime, total downtime, total time/lifespan,
-# individual uptime, individual downtime)
+class Reporter {
+ has %.aggregates is required;
+ has Cat $.cat is required;
+ has SubCat $.sub-cat is required;
+
+ method report {
+ for self.sort-by($!sub-cat) -> $what {
+ $what.Str.say;
+ }
+ }
+
+ multi method sort-by('uptime') { self.sort-by: *.uptime }
+ multi method sort-by('downtime') { self.sort-by: *.downtime }
+ multi method sort-by('lifespan') { self.sort-by: *.lifespan }
+ multi method sort-by('boots') { self.sort-by: *.boots }
+
+ multi method sort-by(Code:D $sort-by) {
+ %!aggregates{$!cat}.values.sort(&$sort-by).reverse
+ }
+}
+
sub MAIN(
- Str $in-dir = './stats',
- Str $sort-by = 'uptime';
+ Str :$stats-dir is required, #= The uptimed raw record input dir.
+ Cat :$cat = 'hostname'; #= Category, one of hostname, os os-major and uname.
+ SubCat :$sub-cat = 'uptime'; #= Sort by one of boots uptime downtime and lifespan.
) {
- my Aggregator $aggregator .= new;
+ my Aggregator $agg .= new;
- for dir($in-dir, test => { /\.records$/ }) -> $file {
- $aggregator.aggregate(:$file)
+ for dir($stats-dir, test => { /\.records$/ }) -> $file {
+ $agg.add-file(:$file)
}
- for $aggregator.aggregates.kv -> $category, $aggregates {
- say "Category $category";
- for $aggregates.kv -> $name, $agg {
- my Str $plural = $agg.elems > 1 ?? 's' !! '';
- say "\t$name $agg (" ~ $agg.elems ~ " record$plural)";
- }
- }
+ my Reporter $reporter .= new: :aggregates($agg.aggregates), :$cat, :$sub-cat;
+ $reporter.report;
}