Engine.pm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. # ex:ts=8 sw=4:
  2. # $OpenBSD: Engine.pm,v 1.124 2017/05/04 23:40:29 espie Exp $
  3. #
  4. # Copyright (c) 2010-2013 Marc Espie <espie@openbsd.org>
  5. #
  6. # Permission to use, copy, modify, and distribute this software for any
  7. # purpose with or without fee is hereby granted, provided that the above
  8. # copyright notice and this permission notice appear in all copies.
  9. #
  10. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  11. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  12. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  13. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  14. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  15. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  16. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  17. use strict;
  18. use warnings;
  19. use DPB::Limiter;
  20. use DPB::SubEngine;
  21. use DPB::ErrorList;
  22. package DPB::Engine;
  23. our @ISA = qw(DPB::Limiter);
  24. use DPB::Heuristics;
  25. use DPB::Util;
  26. sub subengine_class
  27. {
  28. my ($class, $state) = @_;
  29. if ($state->{fetch_only}) {
  30. return "DPB::SubEngine::NoBuild";
  31. } else {
  32. require DPB::SubEngine::Build;
  33. return "DPB::SubEngine::Build";
  34. }
  35. }
  36. sub new
  37. {
  38. my ($class, $state) = @_;
  39. my $o = bless {built => {},
  40. tobuild => {},
  41. state => $state,
  42. installable => {},
  43. heuristics => $state->heuristics,
  44. sizer => $state->sizer,
  45. locker => $state->locker,
  46. logger => $state->logger,
  47. affinity => $state->{affinity},
  48. errors => DPB::ErrorList->new,
  49. locks => DPB::LockList->new,
  50. nfslist => DPB::NFSList->new,
  51. ts => time(),
  52. requeued => [],
  53. ignored => []}, $class;
  54. $o->{buildable} = $class->subengine_class($state)->new($o, $state->builder);
  55. if ($state->{want_fetchinfo}) {
  56. require DPB::SubEngine::Fetch;
  57. $o->{tofetch} = DPB::SubEngine::Fetch->new($o);
  58. }
  59. $o->{log} = $state->logger->append("engine");
  60. $o->{stats} = DPB::Stats->new($state);
  61. return $o;
  62. }
  63. sub recheck_errors
  64. {
  65. my $self = shift;
  66. $self->{errors}->recheck($self);
  67. $self->{locks}->recheck($self);
  68. $self->{nfslist}->recheck($self);
  69. }
  70. sub log_no_ts
  71. {
  72. my ($self, $kind, $v, $extra) = @_;
  73. $extra //= '';
  74. my $fh = $self->{log};
  75. my $ts = int($self->{ts});
  76. print $fh "$$\@$ts: $kind";
  77. if (defined $v) {
  78. print $fh ": ", $v->logname, $extra;
  79. }
  80. print $fh "\n";
  81. }
  82. sub log
  83. {
  84. my $self = shift;
  85. $self->{ts} = time();
  86. $self->log_no_ts(@_);
  87. }
  88. sub flush
  89. {
  90. my $self = shift;
  91. $self->{log}->flush;
  92. }
  93. sub count
  94. {
  95. my ($self, $field) = @_;
  96. my $r = $self->{$field};
  97. if (ref($r) eq 'HASH') {
  98. return scalar keys %$r;
  99. } elsif (ref($r) eq 'ARRAY') {
  100. return scalar @$r;
  101. } else {
  102. return "?";
  103. }
  104. }
  105. sub fetchcount
  106. {
  107. my ($self, $q, $t)= @_;
  108. return () unless defined $self->{tofetch};
  109. if ($self->{state}{fetch_only}) {
  110. $self->{tofetch}{queue}->set_fetchonly;
  111. } elsif ($q < 30) {
  112. $self->{tofetch}{queue}->set_h1;
  113. } else {
  114. $self->{tofetch}{queue}->set_h2;
  115. }
  116. return ("F=".$self->{tofetch}->count);
  117. }
  118. sub statline
  119. {
  120. my $self = shift;
  121. my $q = $self->{buildable}->count;
  122. my $t = $self->count("tobuild");
  123. return join(" ",
  124. "I=".$self->count("installable"),
  125. "B=".$self->count("built"),
  126. "Q=$q",
  127. "T=$t",
  128. $self->fetchcount($q, $t));
  129. }
  130. sub may_add
  131. {
  132. my ($self, $prefix, $s) = @_;
  133. if ($s eq '') {
  134. return '';
  135. } else {
  136. return "$prefix$s\n";
  137. }
  138. }
  139. sub report
  140. {
  141. my $self = shift;
  142. my $q = $self->{buildable}->count;
  143. my $t = $self->count("tobuild");
  144. return join(" ",
  145. $self->statline,
  146. "!=".$self->count("ignored"))."\n".
  147. $self->may_add("L=", $self->{locks}->stringize).
  148. $self->may_add("E=", $self->{errors}->stringize).
  149. $self->may_add("H=", $self->{nfslist}->stringize);
  150. }
  151. sub stats
  152. {
  153. my $self = shift;
  154. $self->{stats}->log($self->{ts}, $self->statline);
  155. }
  156. sub important
  157. {
  158. my $self = shift;
  159. $self->{lasterrors} //= 0;
  160. if (@{$self->{errors}} != $self->{lasterrors}) {
  161. $self->{lasterrors} = @{$self->{errors}};
  162. return "Error in ".join(' ', map {$_->fullpkgpath} @{$self->{errors}})."\n";
  163. }
  164. }
  165. sub adjust
  166. {
  167. my ($self, $v, $kind, $kind2) = @_;
  168. return 0 if !exists $v->{info}{$kind};
  169. my $not_yet = 0;
  170. # XXX don't use `values` in this loop, it may trigger perlbug 77706
  171. my @values = values %{$v->{info}{$kind}};
  172. for my $d (@values) {
  173. $self->{heuristics}->mark_depend($d, $v);
  174. if ($self->{installable}{$d} ||
  175. (defined $d->{info} &&
  176. $d->fullpkgname eq $v->fullpkgname)) {
  177. delete $v->{info}{$kind}{$d};
  178. $v->{info}{$kind2}{$d} = $d if defined $kind2;
  179. } else {
  180. $not_yet++;
  181. }
  182. }
  183. return $not_yet if $not_yet;
  184. delete $v->{info}{$kind};
  185. return 0;
  186. }
  187. sub missing_dep
  188. {
  189. my ($self, $v, $kind) = @_;
  190. return undef if !exists $v->{info}{$kind};
  191. for my $d (values %{$v->{info}{$kind}}) {
  192. return $d if (defined $d->{info}) && $d->{info}{IGNORE};
  193. }
  194. return undef;
  195. }
  196. sub stub_out
  197. {
  198. my ($self, $v) = @_;
  199. push(@{$self->{ignored}}, $v);
  200. # keep the info if it exists, make sure it's stubbed out otherwise
  201. my $i = $v->{info};
  202. $v->{info} = DPB::PortInfo->stub;
  203. return if !defined $i;
  204. for my $w ($v->build_path_list) {
  205. # don't fill in equiv lists if they don't matter.
  206. next if !defined $w->{info};
  207. if ($w->{info} eq $i) {
  208. $w->{info} = DPB::PortInfo->stub;
  209. }
  210. }
  211. }
  212. # need to ignore $v because of some missing $kind dependency:
  213. # wipe out its info and put it in the right list
  214. sub should_ignore
  215. {
  216. my ($self, $v, $kind) = @_;
  217. if (my $d = $self->missing_dep($v, $kind)) {
  218. $self->log_no_ts('!', $v, " because of ".$d->fullpkgpath);
  219. $self->stub_out($v);
  220. return 1;
  221. } else {
  222. return 0;
  223. }
  224. }
  225. sub has_known_depends
  226. {
  227. my ($self, $v) = @_;
  228. for my $kind (qw(DEPENDS BDEPENDS)) {
  229. next unless defined $v->{info}{$kind};
  230. for my $d (values %{$v->{info}{$kind}}) {
  231. return 0 unless $d->has_fullpkgname;
  232. }
  233. }
  234. return 1;
  235. }
  236. sub adjust_extra
  237. {
  238. my ($self, $v, $kind, $kind2) = @_;
  239. return 0 if !exists $v->{info}{$kind};
  240. my $not_yet = 0;
  241. for my $d (values %{$v->{info}{$kind}}) {
  242. $self->{heuristics}->mark_depend($d, $v);
  243. if ((defined $d->{info} && !$self->{tobuild}{$d} &&
  244. $self->has_known_depends($d)) ||
  245. ($d->has_fullpkgname &&
  246. $d->fullpkgname eq $v->fullpkgname)) {
  247. delete $v->{info}{$kind}{$d};
  248. $v->{info}{$kind2}{$d} = $d if defined $kind2;
  249. } else {
  250. $not_yet++;
  251. }
  252. }
  253. return $not_yet if $not_yet;
  254. delete $v->{info}{$kind};
  255. return 0;
  256. }
  257. sub adjust_distfiles
  258. {
  259. my ($self, $v) = @_;
  260. return 0 if !exists $v->{info}{FDEPENDS};
  261. my $not_yet = 0;
  262. for my $f (values %{$v->{info}{FDEPENDS}}) {
  263. if ($self->{tofetch}->is_done($f)) {
  264. $v->{info}{distsize} //= 0;
  265. $v->{info}{distsize} += $f->{sz};
  266. delete $v->{info}{FDEPENDS}{$f};
  267. next;
  268. }
  269. $not_yet++;
  270. }
  271. return $not_yet if $not_yet;
  272. delete $v->{info}{FDEPENDS};
  273. return 0;
  274. }
  275. my $output = {};
  276. sub adjust_built
  277. {
  278. my $self = shift;
  279. my $changes = 0;
  280. for my $v (values %{$self->{built}}) {
  281. if ($self->adjust($v, 'RDEPENDS') == 0) {
  282. delete $self->{built}{$v};
  283. # okay, thanks to equiv, some other path was marked
  284. # as stub, and obviously we lost our deps
  285. if ($v->{info}->is_stub) {
  286. $self->log_no_ts('!', $v,
  287. " equivalent to an ignored path");
  288. # just drop it, it's already ignored as
  289. # an equivalent path
  290. next;
  291. }
  292. $self->{installable}{$v} = $v;
  293. if ($v->{wantinstall}) {
  294. $self->{buildable}->will_install($v);
  295. }
  296. $self->log_no_ts('I', $v,' # '.$v->fullpkgname);
  297. $changes++;
  298. } elsif ($self->should_ignore($v, 'RDEPENDS')) {
  299. delete $self->{built}{$v};
  300. $changes++;
  301. }
  302. }
  303. return $changes;
  304. }
  305. sub adjust_depends1
  306. {
  307. my ($self, $v, $has) = @_;
  308. $has->{$v} = $self->adjust($v, 'DEPENDS', 'BDEPENDS');
  309. }
  310. sub adjust_depends2
  311. {
  312. my ($self, $v, $has) = @_;
  313. if ($has->{$v} != 0) {
  314. if (my $d = $self->should_ignore($v, 'DEPENDS')) {
  315. delete $self->{tobuild}{$v};
  316. } else {
  317. $v->{has} = 2;
  318. }
  319. } else {
  320. # okay, thanks to equiv, some other path was marked
  321. # as stub, and obviously we lost our deps
  322. if ($v->{info}->is_stub) {
  323. $self->log_no_ts('!', $v,
  324. " equivalent to an ignored path");
  325. # just drop it, it's already ignored as
  326. # an equivalent path
  327. delete $self->{tobuild}{$v};
  328. return;
  329. }
  330. my $has = $has->{$v} +
  331. $self->adjust_extra($v, 'EXTRA', 'BEXTRA');
  332. my $has2 = $self->adjust_distfiles($v);
  333. # being buildable directly is a priority,
  334. # but put the patch/dist/small stuff down the
  335. # line as otherwise we will tend to grab
  336. # patch files first
  337. $v->{has} = 2 * ($has != 0) + ($has2 > 1);
  338. if ($has + $has2 == 0) {
  339. delete $self->{tobuild}{$v};
  340. # XXX because of this, not all build_path_list
  341. # are considered equal...
  342. if ($self->should_ignore($v, 'RDEPENDS')) {
  343. $self->{buildable}->remove($v);
  344. } else {
  345. $self->{buildable}->add($v);
  346. $self->log_no_ts('Q', $v);
  347. }
  348. }
  349. }
  350. }
  351. sub adjust_tobuild
  352. {
  353. my $self = shift;
  354. my $has = {};
  355. for my $v (values %{$self->{tobuild}}) {
  356. # XXX we don't have enough there !
  357. next if $self->{buildable}->detained($v);
  358. # due to pkgname aliases, we may have been built through
  359. # another pkgpath.
  360. next if $self->{buildable}->is_done_quick($v);
  361. $self->adjust_depends1($v, $has);
  362. }
  363. for my $v (values %{$self->{tobuild}}) {
  364. # XXX we don't have enough there !
  365. next if $self->{buildable}->detained($v);
  366. $self->adjust_depends2($v, $has);
  367. }
  368. }
  369. sub check_buildable
  370. {
  371. my ($self, $forced) = @_;
  372. my $r = $self->limit($forced, 50, "ENG", 1,
  373. # $self->{buildable}->count > 0,
  374. sub {
  375. $self->log('+');
  376. 1 while $self->adjust_built;
  377. $self->adjust_tobuild;
  378. $self->flush;
  379. });
  380. $self->stats;
  381. return $r;
  382. }
  383. sub new_path
  384. {
  385. my ($self, $v) = @_;
  386. if (defined $v->{info}{IGNORE} &&
  387. !$self->{state}->{fetch_only}) {
  388. $self->log('!', $v, " ".$v->{info}{IGNORE}->string);
  389. $self->stub_out($v);
  390. return;
  391. }
  392. if (defined $v->{info}{MISSING_FILES}) {
  393. $self->add_fatal($v, "fetch manually",
  394. "Missing distfiles: ".
  395. $v->{info}{MISSING_FILES}->string,
  396. $v->{info}{FETCH_MANUALLY}->string);
  397. return;
  398. }
  399. # $self->{heuristics}->todo($v);
  400. if (!$self->{buildable}->is_done_quick($v)) {
  401. $self->{tobuild}{$v} = $v;
  402. $self->log('T', $v);
  403. }
  404. my $notyet = 0;
  405. if (defined $v->{info}{FDEPENDS}) {
  406. for my $f (values %{$v->{info}{FDEPENDS}}) {
  407. if ($self->{tofetch}->contains($f) ||
  408. $self->{tofetch}{doing}{$f}) {
  409. next;
  410. }
  411. if ($self->{tofetch}->is_done($f)) {
  412. $v->{info}{distsize} //= 0;
  413. $v->{info}{distsize} += $f->{sz};
  414. delete $v->{info}{FDEPENDS}{$f};
  415. next;
  416. }
  417. $self->{tofetch}->add($f);
  418. $self->log('F', $f);
  419. $notyet = 1;
  420. }
  421. }
  422. return if $notyet;
  423. my $has = {};
  424. $self->adjust_depends1($v, $has);
  425. $self->adjust_depends2($v, $has);
  426. }
  427. sub requeue
  428. {
  429. my ($self, $v) = @_;
  430. $self->{buildable}->add($v);
  431. $self->{sizer}->finished($v);
  432. }
  433. sub requeue_dist
  434. {
  435. my ($self, $v) = @_;
  436. $self->{tofetch}->add($v);
  437. }
  438. sub rescan
  439. {
  440. my ($self, $v) = @_;
  441. push(@{$self->{requeued}}, $v->path);
  442. }
  443. sub add_fatal
  444. {
  445. my ($self, $v, $error, @messages) = @_;
  446. push(@{$self->{errors}}, $v);
  447. $self->log('!', $v, " $error");
  448. if ($self->{heldlocks}{$v}) {
  449. print {$self->{heldlocks}{$v}} "error=$error\n";
  450. delete $self->{heldlocks}{$v};
  451. } else {
  452. my $fh = $self->{locker}->lock($v);
  453. print $fh "error=$error\n" if $fh;
  454. }
  455. $self->{logger}->log_error($v, $error, @messages);
  456. $self->stub_out($v);
  457. }
  458. sub rebuild_info
  459. {
  460. my ($self, $core) = @_;
  461. my @l = @{$self->{requeued}};
  462. my %d = ();
  463. $self->{requeued} = [];
  464. my %subdirs = map {($_->pkgpath_and_flavors, 1)} @l;
  465. for my $v (@l) {
  466. $self->{buildable}->detain($v);
  467. if (defined $v->{info}{FDEPENDS}) {
  468. for my $f (values %{$v->{info}{FDEPENDS}}) {
  469. $f->forget;
  470. $self->{tofetch}->detain($f);
  471. $d{$f} = $f;
  472. }
  473. }
  474. delete $v->{info};
  475. }
  476. $self->{state}->grabber->forget_cache;
  477. $self->{state}->grabber->grab_subdirs($core, \%subdirs, undef);
  478. for my $v (@l) {
  479. $self->{buildable}->release($v);
  480. }
  481. for my $f (values %d) {
  482. $self->{tofetch}->release($f);
  483. }
  484. }
  485. sub start_new_job
  486. {
  487. my $self = shift;
  488. my $r = $self->{buildable}->start;
  489. $self->flush;
  490. return $r;
  491. }
  492. sub start_new_fetch
  493. {
  494. my $self = shift;
  495. my $r = $self->{tofetch}->start;
  496. $self->flush;
  497. return $r;
  498. }
  499. sub can_build
  500. {
  501. my $self = shift;
  502. return $self->{buildable}->non_empty || @{$self->{requeued}} > 0;
  503. }
  504. sub can_fetch
  505. {
  506. my $self = shift;
  507. return $self->{tofetch}->non_empty;
  508. }
  509. sub dump_category
  510. {
  511. my ($self, $k, $fh) = @_;
  512. $fh //= \*STDOUT;
  513. $k =~ m/^./;
  514. my $q = "\u$&: ";
  515. my $cache = {};
  516. for my $v (sort {$a->fullpkgpath cmp $b->fullpkgpath}
  517. values %{$self->{$k}}) {
  518. print $fh $q;
  519. if (defined $cache->{$v->{info}}) {
  520. print $fh $v->fullpkgpath, " same as ",
  521. $cache->{$v->{info}}, "\n";
  522. } else {
  523. $v->quick_dump($fh);
  524. $cache->{$v->{info}} = $v->fullpkgpath;
  525. }
  526. }
  527. }
  528. sub info_dump
  529. {
  530. my ($self, $fh) = @_;
  531. for my $k (qw(tobuild built)) {
  532. $self->dump_category($k, $fh);
  533. }
  534. $self->{buildable}->dump('Q', $fh);
  535. print $fh "\n";
  536. }
  537. sub end_dump
  538. {
  539. my ($self, $fh) = @_;
  540. $fh //= \*STDOUT;
  541. for my $v (values %{$self->{built}}) {
  542. $self->adjust($v, 'RDEPENDS');
  543. }
  544. for my $k (qw(tobuild built)) {
  545. $self->dump_category($k, $fh);
  546. }
  547. print $fh "\n";
  548. }
  549. sub smart_dump
  550. {
  551. my ($self, $fh) = @_;
  552. my $h = {};
  553. for my $v (values %{$self->{tobuild}}) {
  554. $v->{info}{problem} = 'not built';
  555. $v->{info}{missing} = $v->{info}{DEPENDS};
  556. $h->{$v} = $v;
  557. }
  558. for my $v (values %{$self->{built}}) {
  559. $v->{info}{problem} = 'not installable';
  560. $v->{info}{missing} = $v->{info}{RDEPENDS};
  561. $h->{$v} = $v;
  562. }
  563. for my $v (@{$self->{errors}}) {
  564. $v->{info}{problem} = "errored";
  565. $h->{$v} = $v;
  566. }
  567. for my $v (@{$self->{locks}}) {
  568. $v->{info}{problem} = "locked";
  569. $h->{$v} = $v;
  570. }
  571. my $cache = {};
  572. for my $v (sort {$a->fullpkgpath cmp $b->fullpkgpath}
  573. values %$h) {
  574. if (defined $cache->{$v->{info}}) {
  575. print $fh $v->fullpkgpath, " same as ",
  576. $cache->{$v->{info}}, "\n";
  577. next;
  578. }
  579. print $fh $v->fullpkgpath, " ", $v->{info}{problem};
  580. if (defined $v->{info}{missing}) {
  581. $self->follow_thru($v, $fh, $v->{info}{missing});
  582. #print $fh " ", $v->{info}{missing}->string;
  583. }
  584. print $fh "\n";
  585. $cache->{$v->{info}} = $v->fullpkgpath;
  586. }
  587. print $fh '-'x70, "\n";
  588. }
  589. sub follow_thru
  590. {
  591. my ($self, $v, $fh, $list) = @_;
  592. my @d = ();
  593. my $known = {$v => $v};
  594. while (1) {
  595. my $w = (values %$list)[0];
  596. push(@d, $w);
  597. if (defined $known->{$w}) {
  598. last;
  599. }
  600. $known->{$w} = $w;
  601. if (defined $w->{info}{missing}) {
  602. $list = $w->{info}{missing};
  603. } else {
  604. last;
  605. }
  606. }
  607. print $fh " ", join(' -> ', map {$_->logname} @d);
  608. }
  609. sub dump
  610. {
  611. my ($self, $fh) = @_;
  612. $fh //= \*STDOUT;
  613. for my $k (qw(built tobuild installable)) {
  614. $self->dump_category($k, $fh);
  615. }
  616. print $fh "\n";
  617. }
  618. # special case: dump all dependencies at end of listing, and use that to
  619. # restart dpb quicker if we abort and restart.
  620. #
  621. # namely, scan the most important ports first.
  622. #
  623. # use case: when we restart dpb after a few hours, we want the listing job
  624. # to get to groff very quickly, as the queue will stay desperately empty
  625. # otherwise...
  626. sub dump_dependencies
  627. {
  628. my $self = shift;
  629. my $cache = {};
  630. for my $v (DPB::PkgPath->seen) {
  631. next unless exists $v->{info};
  632. for my $k (qw(DEPENDS RDEPENDS EXTRA)) {
  633. next unless exists $v->{info}{$k};
  634. for my $d (values %{$v->{info}{$k}}) {
  635. $cache->{$d->fullpkgpath}++;
  636. }
  637. }
  638. }
  639. my $state = $self->{state};
  640. $state->{log_user}->rewrite_file($state, $state->{dependencies_log},
  641. sub {
  642. my $log = shift;
  643. for my $k (sort {$cache->{$b} <=> $cache->{$a}} keys %$cache) {
  644. print $log "$k $cache->{$k}\n";
  645. }
  646. });
  647. }
  648. sub find_best
  649. {
  650. my ($self, $file, $limit) = @_;
  651. my $list = [];
  652. if (open my $fh, '<', $file) {
  653. my $i = 0;
  654. while (<$fh>) {
  655. if (m/^(\S+)\s\d+$/) {
  656. push(@$list, $1);
  657. $i++;
  658. }
  659. last if $i > $limit;
  660. }
  661. }
  662. return $list;
  663. }
  664. package DPB::Stats;
  665. use DPB::Clock;
  666. sub new
  667. {
  668. my ($class, $state) = @_;
  669. my $o = bless {
  670. fh => DPB::Util->make_hot($state->logger->append("stats")),
  671. lost_time => 0,
  672. statline => ''},
  673. $class;
  674. DPB::Clock->register($o);
  675. return $o;
  676. }
  677. sub log
  678. {
  679. my ($self, $ts, $line) = @_;
  680. return if $line eq $self->{statline};
  681. $self->{statline} = $line;
  682. print {$self->{fh}} join(' ', $$, int($ts),
  683. int($ts-$self->{lost_time}), $line), "\n";
  684. }
  685. sub stopped_clock
  686. {
  687. my ($self, $gap) = @_;
  688. $self->{lost_time} += $gap;
  689. }
  690. 1;