Heuristics.pm 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. # ex:ts=8 sw=4:
  2. # $OpenBSD: Heuristics.pm,v 1.32 2017/05/14 12:43:55 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. # this package is responsible for the initial weighing of pkgpaths, and handling
  20. # consequences
  21. package DPB::Heuristics;
  22. # for now, we don't create a separate object, we assume everything here is
  23. # "global"
  24. my (%bad_weight, %needed_by);
  25. our %weight;
  26. sub new
  27. {
  28. my ($class, $state) = @_;
  29. bless {state => $state}, $class;
  30. }
  31. sub random
  32. {
  33. my $self = shift;
  34. bless $self, "DPB::Heuristics::random";
  35. }
  36. # we set the "unknown" weight as max if we parsed a file.
  37. my $default = 1;
  38. sub finished_parsing
  39. {
  40. my $self = shift;
  41. while (my ($k, $v) = each %bad_weight) {
  42. $self->set_weight($k, $v);
  43. }
  44. if (keys %weight > 0) {
  45. my @l = sort values %weight;
  46. $default = pop @l;
  47. }
  48. }
  49. sub intrinsic_weight
  50. {
  51. my ($self, $v) = @_;
  52. $weight{$v} // $default;
  53. }
  54. sub equates
  55. {
  56. my ($class, $h) = @_;
  57. for my $v (values %$h) {
  58. next unless defined $weight{$v};
  59. for my $w (values %$h) {
  60. $weight{$w} //= $weight{$v};
  61. }
  62. return;
  63. }
  64. }
  65. sub set_weight
  66. {
  67. my ($self, $v, $w) = @_;
  68. return unless defined $w;
  69. if (ref $v && $v->{scaled}) {
  70. $weight{$v} = $w * $v->{scaled};
  71. delete $v->{scaled};
  72. } else {
  73. $weight{$v} = $w;
  74. }
  75. }
  76. my $cache;
  77. sub mark_depend
  78. {
  79. my ($self, $d, $v) = @_;
  80. if (!defined $needed_by{$d}{$v}) {
  81. $needed_by{$d}{$v} = $v;
  82. $cache = {};
  83. }
  84. }
  85. sub compute_measure
  86. {
  87. my ($self, $v) = @_;
  88. my $dependencies = {$v => $v};
  89. my @todo = values %{$needed_by{$v}};
  90. while (my $k = pop (@todo)) {
  91. next if $dependencies->{$k};
  92. $dependencies->{$k} = $k;
  93. push(@todo, values %{$needed_by{$k}});
  94. }
  95. my $sum = 0;
  96. for my $k (values %$dependencies) {
  97. $sum += $self->intrinsic_weight($k);
  98. }
  99. return $sum;
  100. }
  101. sub measure
  102. {
  103. my ($self, $v) = @_;
  104. $cache->{$v} //= $self->compute_measure($v);
  105. }
  106. sub compare
  107. {
  108. my ($self, $a, $b) = @_;
  109. # XXX if we don't know, we prefer paths "later in the game"
  110. # so if you abort dpb and restart it, it will start doing
  111. # things earlier.
  112. return $self->measure($a) <=> $self->measure($b) ||
  113. $a->pkgpath cmp $b->pkgpath;
  114. }
  115. my $sf_per_host = {};
  116. my $max_sf;
  117. sub calibrate
  118. {
  119. my ($self, @cores) = @_;
  120. for my $core (@cores) {
  121. $sf_per_host->{$core->fullhostname} = $core->sf;
  122. $max_sf //= $core->sf;
  123. if ($core->sf > $max_sf) {
  124. $max_sf = $core->sf;
  125. }
  126. }
  127. }
  128. sub add_build_info
  129. {
  130. my ($self, $pkgpath, $host, $time, $sz) = @_;
  131. if (defined $sf_per_host->{$host}) {
  132. $time *= $sf_per_host->{$host};
  133. $time /= $max_sf;
  134. $self->set_weight($pkgpath, $time);
  135. } else {
  136. $bad_weight{$pkgpath} //= $time;
  137. }
  138. }
  139. sub compare_weights
  140. {
  141. my ($self, $a, $b) = @_;
  142. return $self->intrinsic_weight($a) <=> $self->intrinsic_weight($b);
  143. }
  144. sub new_queue
  145. {
  146. my $self = shift;
  147. if (DPB::HostProperties->has_sf) {
  148. require DPB::Heuristics::SpeedFactor;
  149. return DPB::Heuristics::Queue::Part->new($self);
  150. } else {
  151. return DPB::Heuristics::Queue->new($self);
  152. }
  153. }
  154. # this specific stuff keeps track of the time we need to do stuff
  155. my $todo = {};
  156. my $total = 0;
  157. sub todo
  158. {
  159. my ($self, $path) = @_;
  160. my $p = $path->pkgpath_and_flavors;
  161. if (!defined $todo->{$p}) {
  162. $todo->{$p} = 1;
  163. $total += $self->intrinsic_weight($p);
  164. }
  165. }
  166. sub done
  167. {
  168. my ($self, $path) = @_;
  169. my $p = $path->pkgpath_and_flavors;
  170. if (defined $todo->{$p}) {
  171. delete $todo->{$p};
  172. $total -= $self->intrinsic_weight($p);
  173. }
  174. }
  175. sub report
  176. {
  177. my $time = time;
  178. return DPB::Util->time2string($time)." [$$]\n";
  179. # okay, I need to sit down and do the actual computation, sigh.
  180. my $all = DPB::Core->all_sf;
  181. my $sum_sf = 0;
  182. for my $sf (@$all) {
  183. $sum_sf += $sf;
  184. }
  185. return scalar(keys %$todo)." ".$total*$max_sf." $sum_sf\n".DPB::Util->time2string($time)." -> ".
  186. DPB::Util->time2string($time+$total*$max_sf*$max_sf/$sum_sf)." [$$]\n";
  187. }
  188. package DPB::Heuristics::SimpleSorter;
  189. sub new
  190. {
  191. my ($class, $o) = @_;
  192. bless $o->sorted_values, $class;
  193. }
  194. sub next
  195. {
  196. my $self = shift;
  197. return pop @$self;
  198. }
  199. # that's the queue used by squiggles
  200. # "squiggles" will build small ports preferentially,
  201. # trying to do stuff which has depends first, up to a point.
  202. package DPB::Heuristics::ReverseSorter;
  203. our @ISA = (qw(DPB::Heuristics::SimpleSorter));
  204. sub new
  205. {
  206. my ($class, $o) = @_;
  207. bless {l => $o->sorted_values, l2 => []}, $class;
  208. }
  209. # return smallest stuff with depends preferably
  210. sub next
  211. {
  212. my $self = shift;
  213. # grab stuff from the normal queue
  214. while (my $v = shift @{$self->{l}}) {
  215. # XXX when requeuing a job with L= on the side, this might not
  216. # be defined yet.
  217. if (defined $v->{info}) {
  218. my $dep = $v->{info}->solve_depends;
  219. # it has depends, return it
  220. if (%$dep) {
  221. return $v;
  222. }
  223. }
  224. # otherwise keep it for later.
  225. push(@{$self->{l2}}, $v);
  226. # XXX but when the diff grows too much, give up!
  227. # 200 is completely arbitrary
  228. last if DPB::Heuristics->measure($v) >
  229. 200 * DPB::Heuristics->measure($self->{l2}[0]);
  230. }
  231. return shift @{$self->{l2}};
  232. }
  233. package DPB::Heuristics::Sorter;
  234. sub new
  235. {
  236. my ($class, $list) = @_;
  237. my $o = bless {list => $list, l => []}, $class;
  238. $o->next_bin;
  239. return $o;
  240. }
  241. sub next_bin
  242. {
  243. my $self = shift;
  244. if (my $bin = pop @{$self->{list}}) {
  245. $self->{l} = $bin->sorted_values;
  246. } else {
  247. return;
  248. }
  249. }
  250. sub next
  251. {
  252. my $self = shift;
  253. if (my $r = pop @{$self->{l}}) {
  254. return $r;
  255. } else {
  256. if ($self->next_bin) {
  257. return $self->next;
  258. } else {
  259. return;
  260. }
  261. }
  262. }
  263. package DPB::Heuristics::Bin;
  264. sub new
  265. {
  266. my ($class, $h) = @_;
  267. bless {o => {}, weight => 0, h => $h}, $class;
  268. }
  269. sub add
  270. {
  271. my ($self, $v) = @_;
  272. $self->{o}{$v} = $v;
  273. }
  274. sub contains
  275. {
  276. my ($self, $v) = @_;
  277. return exists $self->{o}{$v};
  278. }
  279. sub remove
  280. {
  281. my ($self, $v) = @_;
  282. delete $self->{o}{$v};
  283. }
  284. sub weight
  285. {
  286. my $self = shift;
  287. return $self->{weight};
  288. }
  289. sub count
  290. {
  291. my $self = shift;
  292. return scalar keys %{$self->{o}};
  293. }
  294. sub non_empty
  295. {
  296. my $self = shift;
  297. return scalar(keys %{$self->{o}}) != 0;
  298. }
  299. sub sorted_values
  300. {
  301. my $self = shift;
  302. return [sort {$self->{h}->compare($a, $b)} values %{$self->{o}}];
  303. }
  304. package DPB::Heuristics::Queue;
  305. our @ISA = qw(DPB::Heuristics::Bin);
  306. sub sorted
  307. {
  308. my ($self, $core) = @_;
  309. if ($core->{squiggle}) {
  310. return DPB::Heuristics::ReverseSorter->new($self);
  311. }
  312. return $self->find_sorter($core);
  313. }
  314. sub find_sorter
  315. {
  316. my ($self, $core) = @_;
  317. return DPB::Heuristics::SimpleSorter->new($self);
  318. }
  319. package DPB::Heuristics::random;
  320. our @ISA = qw(DPB::Heuristics);
  321. my %any;
  322. sub compare
  323. {
  324. my ($self, $a, $b) = @_;
  325. return ($any{$a} //= rand) <=> ($any{$b} //= rand);
  326. }
  327. sub new_queue
  328. {
  329. my $self = shift;
  330. return DPB::Heuristics::Queue->new($self);
  331. }
  332. 1;