radicale 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. # This file is related to Radicale - CalDAV and CardDAV server
  2. # for logwatch (script)
  3. # Copyright © 2024-2025 Peter Bieringer <pb@bieringer.de>
  4. #
  5. # Detail levels
  6. # >= 5: Logins
  7. # >= 10: ResponseTimes
  8. $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
  9. my %ResponseTimes;
  10. my %ResponseSizes;
  11. my %Responses;
  12. my %Requests;
  13. my %Logins;
  14. my %Loglevel;
  15. my %OtherEvents;
  16. my $sum;
  17. my $length;
  18. sub ResponseTimesMinMaxSum($$) {
  19. my $req = $_[0];
  20. my $time = $_[1];
  21. $ResponseTimes{$req}->{'cnt'}++;
  22. if (! defined $ResponseTimes{$req}->{'min'}) {
  23. $ResponseTimes{$req}->{'min'} = $time;
  24. } elsif ($ResponseTimes{$req}->{'min'} > $time) {
  25. $ResponseTimes{$req}->{'min'} = $time;
  26. }
  27. if (! defined $ResponseTimes{$req}->{'max'}) {
  28. $ResponseTimes{$req}{'max'} = $time;
  29. } elsif ($ResponseTimes{$req}->{'max'} < $time) {
  30. $ResponseTimes{$req}{'max'} = $time;
  31. }
  32. $ResponseTimes{$req}->{'sum'} += $time;
  33. }
  34. sub ResponseSizesMinMaxSum($$$) {
  35. my $req = $_[0];
  36. my $type = $_[1];
  37. my $size = $_[2];
  38. $ResponseSizes{$type}->{$req}->{'cnt'}++;
  39. if (! defined $ResponseSizes{$type}->{$req}->{'min'}) {
  40. $ResponseSizes{$type}->{$req}->{'min'} = $size;
  41. } elsif ($ResponseSizes{$type}->{$req}->{'min'} > $size) {
  42. $ResponseSizes{$type}->{$req}->{'min'} = $size;
  43. }
  44. if (! defined $ResponseSizes{$type}->{$req}->{'max'}) {
  45. $ResponseSizes{$type}->{$req}{'max'} = $size;
  46. } elsif ($ResponseSizes{$type}->{$req}->{'max'} < $size) {
  47. $ResponseSizes{$type}->{$req}{'max'} = $size;
  48. }
  49. $ResponseSizes{$type}->{$req}->{'sum'} += $size;
  50. }
  51. sub Sum($) {
  52. my $phash = $_[0];
  53. my $sum = 0;
  54. foreach my $entry (keys %$phash) {
  55. $sum += $phash->{$entry};
  56. }
  57. return $sum;
  58. }
  59. sub MaxLength($) {
  60. my $phash = $_[0];
  61. my $length = 0;
  62. foreach my $entry (keys %$phash) {
  63. $length = length($entry) if (length($entry) > $length);
  64. }
  65. return $length;
  66. }
  67. sub ConvertTokens($) {
  68. my %tokens_h;
  69. # unique
  70. foreach my $token (split(" ", $_[0])) {
  71. $tokens_h{$token} = 1;
  72. }
  73. # map tokens
  74. my @result_a;
  75. if (defined $tokens_h{"sync-token"}) {
  76. push @result_a, "ST";
  77. }
  78. if (defined $tokens_h{"sync-collection"}) {
  79. push @result_a, "SC";
  80. }
  81. if (defined $tokens_h{"getctag"}) {
  82. push @result_a, "GCT";
  83. }
  84. if (defined $tokens_h{"getetag"}) {
  85. push @result_a, "GET";
  86. }
  87. # TODO: add potential others which causing long duration
  88. $result = "";
  89. if (scalar(@result_a) > 0) {
  90. $result = ":F=" . join(",", @result_a);
  91. }
  92. return $result;
  93. }
  94. while (defined($ThisLine = <STDIN>)) {
  95. # count loglevel
  96. if ( $ThisLine =~ /\[(DEBUG|INFO|WARNING|ERROR|CRITICAL)\] /o ) {
  97. $Loglevel{$1}++
  98. }
  99. # parse log for events
  100. if ( $ThisLine =~ /Radicale server ready/o ) {
  101. $OtherEvents{"Radicale server started"}++;
  102. }
  103. elsif ( $ThisLine =~ /Stopping Radicale/o ) {
  104. $OtherEvents{"Radicale server stopped"}++;
  105. }
  106. elsif ( $ThisLine =~ / (\S+) response status/o ) {
  107. my $req = $1;
  108. if ( $ThisLine =~ / \S+ response status for .* with depth '(\d)' in ([0-9.]+) seconds: (\d+)/o ) {
  109. $req .= ":D=" . $1 . ":R=" . $3;
  110. ResponseTimesMinMaxSum($req, $2) if ($Detail >= 10);
  111. } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) seconds: (\d+)/o ) {
  112. $req .= ":R=" . $2;
  113. ResponseTimesMinMaxSum($req, $1) if ($Detail >= 10);
  114. } elsif ( $ThisLine =~ / \S+ response status for .* with depth '(\d)' in ([0-9.]+) seconds (\S+) (\d+) bytes: (\d+)/o ) {
  115. $req .= ":D=" . $1 . ":R=" . $5;
  116. ResponseTimesMinMaxSum($req, $2) if ($Detail >= 10);
  117. ResponseSizesMinMaxSum($req, $3, $4) if ($Detail >= 10);
  118. } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) seconds (\S+) (\d+) bytes: (\d+)/o ) {
  119. $req .= ":R=" . $4;
  120. ResponseTimesMinMaxSum($req, $1) if ($Detail >= 10);
  121. ResponseSizesMinMaxSum($req, $2, $3) if ($Detail >= 10);
  122. } elsif ( $ThisLine =~ / \S+ response status for .* with depth '(\d)' in ([0-9.]+) seconds (\S+) (\d+) bytes \((.*)\): (\d+)/o ) {
  123. $req .= ":D=" . $1 . ":R=" . $6;
  124. $reqWithFlags = $req . ConvertTokens($5);
  125. ResponseTimesMinMaxSum($reqWithFlags, $2) if ($Detail >= 10);
  126. ResponseSizesMinMaxSum($req, $3, $4) if ($Detail >= 10);
  127. } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) seconds (\S+) (\d+) bytes \((.*)\): (\d+)/o ) {
  128. $req .= ":R=" . $5;
  129. $reqWithFlags = $req . ConvertTokens($4);
  130. ResponseTimesMinMaxSum($reqWithFlags, $1) if ($Detail >= 10);
  131. ResponseSizesMinMaxSum($req, $2, $3) if ($Detail >= 10);
  132. }
  133. $Responses{$req}++;
  134. }
  135. elsif ( $ThisLine =~ / (\S+) request for/o ) {
  136. my $req = $1;
  137. if ( $ThisLine =~ / \S+ request for .* with depth '(\d)' received/o ) {
  138. $req .= ":D=" . $1;
  139. }
  140. $Requests{$req}++;
  141. }
  142. elsif ( $ThisLine =~ / (Successful login): '([^']+)'/o ) {
  143. $Logins{$2}++ if ($Detail >= 5);
  144. $OtherEvents{$1}++;
  145. }
  146. elsif ( $ThisLine =~ / (Failed login attempt) /o ) {
  147. $OtherEvents{$1}++;
  148. }
  149. elsif ( $ThisLine =~ / (Profiling data per request method \S+) /o ) {
  150. my $info = $1;
  151. if ( $ThisLine =~ /(no request seen so far)/o ) {
  152. $OtherEvents{$info . " - " . $1}++;
  153. } else {
  154. $OtherEvents{$info}++;
  155. };
  156. }
  157. elsif ( $ThisLine =~ / (Profiling data per request \S+) /o ) {
  158. my $info = $1;
  159. if ( $ThisLine =~ /(suppressed because duration below minimum|suppressed because of no data)/o ) {
  160. $OtherEvents{$info . " - " . $1}++;
  161. } else {
  162. $OtherEvents{$info}++;
  163. };
  164. }
  165. elsif ( $ThisLine =~ /\[(DEBUG|INFO)\] /o ) {
  166. # skip if DEBUG+INFO
  167. }
  168. else {
  169. # Report any unmatched entries...
  170. if ($ThisLine =~ /^({\'| )/o) {
  171. # skip profiling or raw header data
  172. next;
  173. };
  174. if ($ThisLine =~ /^$/o) {
  175. # skip empty line
  176. next;
  177. };
  178. $ThisLine =~ s/^\[\d+(\/Thread-\d+)?\] //; # remove process/Thread ID
  179. chomp($ThisLine);
  180. $OtherList{$ThisLine}++;
  181. }
  182. }
  183. if ($Started) {
  184. print "\nStatistics:\n";
  185. print " Radicale started: $Started Time(s)\n";
  186. }
  187. if (keys %Loglevel) {
  188. $sum = Sum(\%Loglevel);
  189. print "\n**Loglevel counters**\n";
  190. printf "%-18s | %7s | %5s |\n", "Loglevel", "cnt", "ratio";
  191. print "-" x38 . "\n";
  192. foreach my $level (sort keys %Loglevel) {
  193. printf "%-18s | %7d | %3d%% |\n", $level, $Loglevel{$level}, int(($Loglevel{$level} * 100) / $sum);
  194. }
  195. print "-" x38 . "\n";
  196. printf "%-18s | %7d | %3d%% |\n", "", $sum, 100;
  197. }
  198. if (keys %Requests) {
  199. $sum = Sum(\%Requests);
  200. print "\n**Request counters (D=<depth>)**\n";
  201. printf "%-18s | %7s | %5s |\n", "Request", "cnt", "ratio";
  202. print "-" x38 . "\n";
  203. foreach my $req (sort keys %Requests) {
  204. printf "%-18s | %7d | %3d%% |\n", $req, $Requests{$req}, int(($Requests{$req} * 100) / $sum);
  205. }
  206. print "-" x38 . "\n";
  207. printf "%-18s | %7d | %3d%% |\n", "", $sum, 100;
  208. }
  209. if (keys %Responses) {
  210. $sum = Sum(\%Responses);
  211. $length = MaxLength(\%Responses);
  212. print "\n**Response result counters ((D=<depth> R=<result>)**\n";
  213. printf "%-" . $length . "s | %7s | %5s |\n", "Response", "cnt", "ratio";
  214. print "-" x($length + 20) . "\n";
  215. foreach my $req (sort keys %Responses) {
  216. printf "%-" . $length . "s | %7d | %3d%% |\n", $req, $Responses{$req}, int(($Responses{$req} * 100) / $sum);
  217. }
  218. print "-" x($length + 20) . "\n";
  219. printf "%-" . $length . "s | %7d | %3d%% |\n", "", $sum, 100;
  220. }
  221. if (keys %Logins) {
  222. $sum = Sum(\%Logins);
  223. $length = MaxLength(\%Logins);
  224. print "\n**Successful login counters**\n";
  225. printf "%-" . $length . "s | %7s | %5s |\n", "Login", "cnt", "ratio";
  226. print "-" x($length + 20) . "\n";
  227. foreach my $login (sort keys %Logins) {
  228. printf "%-" . $length . "s | %7d | %3d%% |\n", $login, $Logins{$login}, int(($Logins{$login} * 100) / $sum);
  229. }
  230. print "-" x($length + 20) . "\n";
  231. printf "%-" . $length . "s | %7d | %3d%% |\n", "", $sum, 100;
  232. }
  233. if (keys %ResponseTimes) {
  234. $length = MaxLength(\%ResponseTimes);
  235. print "\n**Response timings (counts, seconds) (D=<depth> R=<result> F=<flags>)**\n";
  236. print "* Flags: ST:sync-token SC:sync-collection GCT:getctag GET:getetag\n";
  237. printf "%-" . $length . "s | %7s | %7s | %7s | %7s |\n", "Response", "cnt", "min", "max", "avg";
  238. print "-" x($length + 42) . "\n";
  239. foreach my $req (sort keys %ResponseTimes) {
  240. printf "%-" . $length . "s | %7d | %7.3f | %7.3f | %7.3f |\n", $req
  241. , $ResponseTimes{$req}->{'cnt'}
  242. , $ResponseTimes{$req}->{'min'}
  243. , $ResponseTimes{$req}->{'max'}
  244. , $ResponseTimes{$req}->{'sum'} / $ResponseTimes{$req}->{'cnt'};
  245. }
  246. print "-" x($length + 42) . "\n";
  247. }
  248. if (keys %ResponseSizes) {
  249. for my $type (sort keys %ResponseSizes) {
  250. $length = MaxLength($ResponseSizes{$type});
  251. print "\n**Response sizes (counts, bytes: $type) (D=<depth> R=<result>)**\n";
  252. printf "%-" . $length . "s | %7s | %9s | %9s | %9s |\n", "Response", "cnt", "min", "max", "avg";
  253. print "-" x($length + 48) . "\n";
  254. foreach my $req (sort keys %{$ResponseSizes{$type}}) {
  255. printf "%-" . $length . "s | %7d | %9d | %9d | %9d |\n", $req
  256. , $ResponseSizes{$type}->{$req}->{'cnt'}
  257. , $ResponseSizes{$type}->{$req}->{'min'}
  258. , $ResponseSizes{$type}->{$req}->{'max'}
  259. , $ResponseSizes{$type}->{$req}->{'sum'} / $ResponseSizes{$type}->{$req}->{'cnt'};
  260. }
  261. print "-" x($length + 48) . "\n";
  262. }
  263. }
  264. if (keys %OtherEvents) {
  265. print "\n**Other Events**\n";
  266. foreach $ThisOne (sort keys %OtherEvents) {
  267. print "$ThisOne: $OtherEvents{$ThisOne} Time(s)\n";
  268. }
  269. }
  270. if (keys %OtherList) {
  271. print "\n**Unmatched Entries**\n";
  272. foreach $ThisOne (sort keys %OtherList) {
  273. print "$ThisOne: $OtherList{$ThisOne} Time(s)\n";
  274. }
  275. }
  276. exit(0);
  277. # vim: shiftwidth=3 tabstop=3 syntax=perl et smartindent