98_apptime.pm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. ################################################################
  2. # 98_apptime:application timing
  3. # $Id: 98_apptime.pm 16460 2018-03-21 18:08:15Z martinp876 $
  4. # based on $Id: 98_apptime.pm 16460 2018-03-21 18:08:15Z martinp876 $
  5. ################################################################
  6. # for use with fhem.pl 16214+ due to change in timers
  7. #####################################################
  8. #
  9. package main;
  10. use strict;
  11. use warnings;
  12. use B qw(svref_2object);
  13. use vars qw(%defs); # FHEM device/button definitions
  14. use vars qw(%intAt);
  15. use vars qw($nextat);
  16. use vars qw(@intAtA); # Internal timer array (new!)
  17. use vars qw(%prioQueues);
  18. sub apptime_getTiming($$$@);
  19. sub apptime_Initialize($);
  20. use constant DEBUG_OUTPUT_INTATA => 0;
  21. my $apptimeStatus;
  22. sub apptime_Initialize($){
  23. $apptimeStatus = 1;#set active by default
  24. $cmds{"apptime"}{Fn} = "apptime_CommandDispTiming";
  25. $cmds{"apptime"}{Hlp} = "[clear|<field>|timer|nice] [top|all] [<filter>],application function calls and duration";
  26. }
  27. my $intatlen = 0;
  28. my $maxintatlen = 0;
  29. my $maxintatdone = 0;
  30. my $minTmrHandleTm = 1000000;
  31. my $maxTmrHandleTm = 0;
  32. my $totDly = 0;
  33. my $totCnt = 0;
  34. sub HandleTimeout() {
  35. return undef if(!$nextat);
  36. if (DEBUG_OUTPUT_INTATA) {
  37. my $ms = 0;
  38. my $n = int(@intAtA);
  39. my $j;
  40. for ($j=0; $j < ($n-1); $j++) {
  41. if (!defined($intAtA[$j])) {
  42. Log 0, "Error in intAtA, undefined element $j/$n\n";
  43. }
  44. elsif (!defined($intAtA[$j]->{TRIGGERTIME})) {
  45. Log 0, "Error in intAtA, undefined tim $j/$n\n";
  46. }
  47. next if ($intAtA[$j]->{TRIGGERTIME} <= $intAtA[$j+1]->{TRIGGERTIME});
  48. if (!$ms) {
  49. Log 0, "Error in intAtA, sortErr $j/$n\n";
  50. $ms = 1;
  51. }
  52. }
  53. $j = $n-1;
  54. if (!defined($intAtA[$j])) {
  55. Log 0, "Error in intAtA, undefined element $j/$n\n";
  56. }
  57. elsif (!defined($intAtA[$j]->{TRIGGERTIME})) {
  58. Log 0, "Error in intAtA, undefined tim $j/$n\n";
  59. }
  60. }
  61. my $now = gettimeofday();
  62. if($now < $nextat) {
  63. $selectTimestamp = $now;
  64. return ($nextat-$now);
  65. }
  66. my $handleStart = $now;
  67. #############
  68. # Check the internal list.
  69. $intatlen = int(@intAtA);
  70. $maxintatlen = $intatlen if ($maxintatlen < $intatlen);
  71. my $nd = 0;
  72. my ($fn,$arg,$fnname,$shortarg,$cv);
  73. $nextat = 0;
  74. while(@intAtA) { # may be changed by timer execution !
  75. my $at = $intAtA[0];
  76. my $tim = $at->{TRIGGERTIME};
  77. if($tim && $tim > $now) {
  78. $nextat = $tim;
  79. last;
  80. }
  81. delete $intAt{$at->{atNr}} if($at->{atNr}); # "handling" of old %intAt
  82. shift(@intAtA);
  83. $fn = $at->{FN};
  84. $fnname = $fn;
  85. if (ref($fn) ne "") {
  86. $cv = svref_2object($fn);
  87. $fnname = $cv->GV->NAME;
  88. }
  89. $arg = $at->{ARG};
  90. $shortarg = (defined($arg)?$arg:"");
  91. $shortarg = "HASH_unnamed" if ( (ref($shortarg) eq "HASH")
  92. && !defined($shortarg->{NAME}) );
  93. ($shortarg,undef) = split(/:|;/,$shortarg,2); # for special long args with delim ;
  94. apptime_getTiming("global","tmr-".$fnname.";".$shortarg, $fn, $tim, $arg); # this can delete a timer and can add a timer
  95. $nd++;
  96. }
  97. $maxintatdone = $nd if ($maxintatdone < $nd);
  98. $now = gettimeofday();
  99. if(%prioQueues) {
  100. my $nice = minNum(keys %prioQueues);
  101. my $entry = shift(@{$prioQueues{$nice}});
  102. delete $prioQueues{$nice} if(!@{$prioQueues{$nice}});
  103. $cv = svref_2object($entry->{fn});
  104. $fnname = $cv->GV->NAME;
  105. $shortarg = (defined($entry->{arg})?$entry->{arg}:"");
  106. $shortarg = "HASH_unnamed" if ( (ref($shortarg) eq "HASH")
  107. && !defined($shortarg->{NAME}) );
  108. ($shortarg,undef) = split(/:|;/,$shortarg,2);
  109. apptime_getTiming("global","nice-".$fnname.";".$shortarg, $entry->{fn}, $now, $entry->{arg});
  110. $nextat = 1 if(%prioQueues);
  111. }
  112. $now = gettimeofday(); # if some callbacks took longer
  113. $selectTimestamp = $now;
  114. $handleStart = $now - $handleStart;
  115. $minTmrHandleTm = $handleStart if ($minTmrHandleTm > $handleStart);
  116. $maxTmrHandleTm = $handleStart if ($maxTmrHandleTm < $handleStart);
  117. return undef if !$nextat;
  118. return ($now < $nextat) ? ($nextat-$now) : 0;
  119. }
  120. sub CallFn(@) {
  121. my $d = shift;
  122. my $n = shift;
  123. if(!$d || !$defs{$d}) {
  124. $d = "<undefined>" if(!defined($d));
  125. Log 0, "Strange call for nonexistent $d: $n";
  126. return undef;
  127. }
  128. if(!$defs{$d}{TYPE}) {
  129. Log 0, "Strange call for typeless $d: $n";
  130. return undef;
  131. }
  132. my $fn = $modules{$defs{$d}{TYPE}}{$n};
  133. return "" if(!$fn);
  134. my @ret = apptime_getTiming($d,$fn,$fn,0,@_);
  135. if(wantarray){return @ret;}
  136. else {return $ret[0];}
  137. }
  138. sub apptime_getTiming($$$@) {
  139. my ($e,$fnName,$fn,$tim,@arg) = @_;
  140. my $h;
  141. my $ts1;
  142. if ($apptimeStatus){
  143. if (!$defs{$e}{helper} ||
  144. !$defs{$e}{helper}{bm} ||
  145. !$defs{$e}{helper}{bm}{$fnName} ){
  146. %{$defs{$e}{helper}{bm}{$fnName}} =(max => 0, mAr => "",
  147. cnt => 1, tot => 0,
  148. dmx => -1000, dtotcnt => 0, dtot => 0,
  149. mTS => "");
  150. $h = $defs{$e}{helper}{bm}{$fnName};
  151. }
  152. else{
  153. $h = $defs{$e}{helper}{bm}{$fnName};
  154. $h->{cnt}++;
  155. }
  156. $ts1 = gettimeofday();
  157. if ($tim > 1){
  158. my $td = $ts1-$tim;
  159. $totCnt++;
  160. $totDly += $td;
  161. $totDly = 0 if(!$totCnt);
  162. $h->{dtotcnt}++;
  163. $h->{dtot} += $td;
  164. $h->{dtot} = 0 if(!$h->{dtotcnt});
  165. $h->{dmx} = $td if ($h->{dmx} < $td);
  166. }
  167. }
  168. no strict "refs";
  169. my @ret = &{$fn}(@arg);
  170. use strict "refs";
  171. if ($apptimeStatus){
  172. $ts1 = gettimeofday()-$ts1;
  173. if ($ts1 && $h->{max} < $ts1){
  174. $h->{max} = $ts1;
  175. $h->{mAr} = @arg?\@arg:undef;
  176. $h->{mTS}= strftime("%d.%m. %H:%M:%S", localtime());
  177. }
  178. $h->{tot} += $ts1;
  179. $h->{tot} = 0 if(!$h->{cnt});
  180. }
  181. return @ret;
  182. }
  183. #####################################
  184. sub apptime_CommandDispTiming($$@) {
  185. my ($cl,$param) = @_;
  186. my ($sFld,$top,$filter) = split" ",$param;
  187. $sFld = "max" if (!$sFld);
  188. $top = "top" if (!$top);
  189. my %fld = (name=>0,function=>1,max=>2,count=>3,total=>4,average=>5,maxDly=>6,avgDly=>7,cont=>98,pause=>98,clear=>99,timer=>2,nice=>2);
  190. return "$sFld undefined field, use one of ".join(",",sort keys %fld)
  191. if(!defined $fld{$sFld});
  192. my @bmArr;
  193. my @a = map{"$defs{$_}:$_"} keys (%defs); # prepare mapping hash 2 name
  194. $_ =~ s/[HASH\(\)]//g foreach(@a);
  195. if ($sFld eq "pause"){# no further collection of data, clear also
  196. $apptimeStatus = 0;#stop collecting data
  197. }
  198. elsif ($sFld eq "cont") {# further collection of data, clear also
  199. $apptimeStatus = 1;#continue collecting data
  200. }
  201. elsif ($sFld eq "timer"){
  202. $sFld = "max";
  203. $filter = defined($filter)?$filter:"";
  204. $filter = "\^tmr-.*".$filter if ($filter !~ /^\^tmr-/);
  205. }
  206. elsif ($sFld eq "nice") {
  207. $sFld = "max";
  208. $filter = defined($filter)?$filter:"";
  209. $filter = "\^nice-.*".$filter if ($filter !~ /^\^nice-/);
  210. }
  211. foreach my $d (sort keys %defs) {
  212. next if(!$defs{$d}{helper}||!$defs{$d}{helper}{bm});
  213. if ($sFld eq "clear"){
  214. delete $defs{$d}{helper}{bm};
  215. $totDly = 0;
  216. $totCnt = 0;
  217. $maxintatlen = 0;
  218. $maxintatdone = 0;
  219. }
  220. elsif ($sFld =~ m/(pause|cont)/){
  221. }
  222. else{
  223. foreach my $f (sort keys %{$defs{$d}{helper}{bm}}) {
  224. next if(!defined $defs{$d}{helper}{bm}{$f}{cnt} || !$defs{$d}{helper}{bm}{$f}{cnt});
  225. next if($filter && $d !~ m/$filter/ && $f !~ m/$filter/);
  226. my ($n,$t) = ($d,$f);
  227. ($n,$t) = split(";",$f,2) if ($d eq "global");
  228. $t = "" if (!defined $t);
  229. my $h = $defs{$d}{helper}{bm}{$f};
  230. my $arg = "";
  231. if ($h->{mAr} && scalar(@{$h->{mAr}})){
  232. foreach my $i (0..scalar(@{$h->{mAr}})){
  233. if(ref(${$h->{mAr}}[$i]) eq 'HASH' and exists(${$h->{mAr}}[$i]->{NAME})){
  234. ${$h->{mAr}}[$i] = "HASH(".${$h->{mAr}}[$i]->{NAME}.")";
  235. }
  236. }
  237. $arg = join ("; ", map { $_ // "(undef)" } @{$h->{mAr}});
  238. }
  239. push @bmArr,[($n,$t
  240. ,$h->{max}*1000
  241. ,$h->{cnt}
  242. ,$h->{tot}*1000
  243. ,($h->{cnt}?($h->{tot}/$h->{cnt})*1000:0)
  244. ,(($h->{dmx}>-1000)?$h->{dmx}*1000:0)
  245. ,($h->{dtotcnt}?($h->{dtot}/$h->{dtotcnt})*1000:0)
  246. ,$h->{mTS}
  247. ,$arg
  248. )];
  249. }
  250. }
  251. }
  252. return "apptime initialized\n\nUse apptime ".$cmds{"apptime"}{Hlp} if ($maxTmrHandleTm < $minTmrHandleTm);
  253. my $field = $fld{$sFld};
  254. if ($field>1){@bmArr = sort { $b->[$field] <=> $a->[$field] } @bmArr;}
  255. else {@bmArr = sort { $b->[$field] cmp $a->[$field] } @bmArr;}
  256. my $ret = sprintf("active-timers: %d; max-active timers: %d; max-timer-load: %d ",$intatlen,$maxintatlen,$maxintatdone);
  257. $ret .= sprintf("min-tmrHandlingTm: %0.1fms; max-tmrHandlingTm: %0.1fms; totAvgDly: %0.1fms\n",$minTmrHandleTm*1000,$maxTmrHandleTm*1000,($totCnt?$totDly/$totCnt*1000:0));
  258. $ret .= ($apptimeStatus ? "" : "------ apptime PAUSED data collection ----------\n")
  259. .sprintf("\n %-40s %-35s %6s %8s %10s %8s %8s %8s %-15s %s",
  260. "name","function","max","count","total","average","maxDly","avgDly","TS Max call","param Max call");
  261. my $end = ($top && $top eq "top")?40:@bmArr-1;
  262. $end = @bmArr-1 if ($end>@bmArr-1);
  263. $ret .= sprintf("\n %-40s %-35s %6d %8d %10.2f %8.2f %8.2f %8.2f %-15s %s",@{$bmArr[$_]})for (0..$end);
  264. return $ret;
  265. }
  266. 1;
  267. =pod
  268. =item command
  269. =item summary support to analyse function performance
  270. =item summary_DE Unterst&uuml;tzung bei der Performanceanalyse von Funktionen
  271. =begin html
  272. <a name="apptime"></a>
  273. <h3>apptime</h3>
  274. <div style="padding-left: 2ex;">
  275. <h4><code>apptime</code></h4>
  276. <p>
  277. apptime provides information about application procedure execution time.
  278. It is designed to identify long running jobs causing latency as well as
  279. general high <abbr>CPU</abbr> usage jobs.
  280. </p>
  281. <p>
  282. No information about <abbr>FHEM</abbr> kernel times and delays will be provided.
  283. </p>
  284. <p>
  285. Once started, apptime monitors tasks. User may reset counter during operation.
  286. apptime adds about 1% <abbr>CPU</abbr> load in average to <abbr>FHEM</abbr>.
  287. </p>
  288. <p>
  289. In order to remove apptime, <kbd>shutdown restart</kbd> is necessary.
  290. </p>
  291. <p>
  292. <strong>Features</strong>
  293. </P>
  294. <dl>
  295. <dt><code><kbd>apptime</kbd></code></dt>
  296. <dd>
  297. <p>
  298. <kbd>apptime</kbd> is started with the its first call and continously monitor operations.<br>
  299. To unload apptime, <kbd>shutdown restart</kbd> is necessary.<br>
  300. </p>
  301. </dd>
  302. <dt><code><kbd>apptime clear</kbd></code></dt>
  303. <dd>
  304. <p>
  305. Reset all counter and start from zero.
  306. </p>
  307. </dd>
  308. <dt><code><kbd>apptime pause</kbd></code></dt>
  309. <dd>
  310. <p>
  311. Suspend accumulation of data. Data is not cleared.
  312. </p>
  313. </dd>
  314. <dt><code><kbd>apptime cont</kbd></code></dt>
  315. <dd>
  316. <p>
  317. Continue data collection after pause.
  318. </p>
  319. </dd>
  320. <dt><code><kbd>apptime [count|function|average|clear|max|name|total] [all]</kbd></code></dt>
  321. <dd>
  322. <p>
  323. Display a table sorted by the field selected.
  324. </p>
  325. <p>
  326. <strong><kbd>all</kbd></strong> will display the complete table while by default only the top lines are printed.
  327. </p>
  328. </dd>
  329. </dl>
  330. <p>
  331. <strong>Columns:</strong>
  332. </p>
  333. <dl>
  334. <dt><strong>name</strong></dt>
  335. <dd>
  336. <p>
  337. Name of the entity executing the procedure.
  338. </p>
  339. <p>
  340. If it is a function called by InternalTimer the name starts with <var>tmr-</var>.
  341. By then it gives the name of the function to be called.
  342. </p>
  343. </dd>
  344. <dt><strong>function</strong></dt>
  345. <dd>
  346. <p>
  347. Procedure name which was executed.
  348. </p>
  349. <p>
  350. If it is an <var>InternalTimer</var> call it gives its calling parameter.
  351. </p>
  352. </dd>
  353. <dt><strong>max</strong></dt>
  354. <dd>
  355. <p>
  356. Longest duration measured for this procedure in <abbr>ms</abbr>.
  357. </p>
  358. </dd>
  359. <dt><strong>count</strong></dt>
  360. <dd>
  361. <p>
  362. Number of calls for this procedure.
  363. </p>
  364. </dt>
  365. <dt><strong>total</strong></dt>
  366. <dd>
  367. <p>
  368. Accumulated duration of this procedure over all calls monitored.
  369. </p>
  370. </dd>
  371. <dt><strong>average</strong></dt>
  372. <dd>
  373. <p>
  374. Average time a call of this procedure takes.
  375. </p>
  376. </dd>
  377. <dt><strong>maxDly</strong></dt>
  378. <dd>
  379. <p>
  380. Maximum delay of a timer call to its schedules time.
  381. This column is not relevant for non-timer calls.
  382. </p>
  383. </dd>
  384. <dt><strong>param Max call</strong></dt>
  385. <dd>
  386. <p>
  387. Gives the parameter of the call with the longest duration.
  388. </p>
  389. </dd>
  390. </dl>
  391. </div>
  392. =end html
  393. =cut