Filename | /var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm |
Statements | Executed 351710 statements in 1.03s |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
52483 | 1 | 1 | 636ms | 3.89s | __ANON__[:579] | Foswiki::Search::InfoCache::
8760 | 1 | 1 | 320ms | 701ms | addTopic | Foswiki::Search::InfoCache::
81 | 1 | 1 | 179ms | 180ms | sortTopics | Foswiki::Search::InfoCache::
125 | 2 | 2 | 83.8ms | 350ms | addTopics | Foswiki::Search::InfoCache::
81 | 2 | 2 | 3.27ms | 353ms | new | Foswiki::Search::InfoCache::
121 | 2 | 2 | 2.52ms | 183ms | sortResults | Foswiki::Search::InfoCache::
81 | 2 | 2 | 1.93ms | 4.74ms | _getListOfWebs | Foswiki::Search::InfoCache::
41 | 1 | 1 | 1.39ms | 333ms | getTopicListIterator | Foswiki::Search::InfoCache::
41 | 1 | 1 | 713µs | 713µs | convertTopicPatternToRegex | Foswiki::Search::InfoCache::
1 | 1 | 1 | 407µs | 472µs | BEGIN@26 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 12µs | 25µs | BEGIN@3 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 9µs | 14µs | BEGIN@4 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 8µs | 21µs | BEGIN@23 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 4µs | 4µs | BEGIN@6 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 4µs | 4µs | BEGIN@24 | Foswiki::Search::InfoCache::
1 | 1 | 1 | 3µs | 3µs | BEGIN@25 | Foswiki::Search::InfoCache::
0 | 0 | 0 | 0s | 0s | _compare | Foswiki::Search::InfoCache::
0 | 0 | 0 | 0s | 0s | filterByDate | Foswiki::Search::InfoCache::
0 | 0 | 0 | 0s | 0s | getRev1Info | Foswiki::Search::InfoCache::
0 | 0 | 0 | 0s | 0s | isImmutable | Foswiki::Search::InfoCache::
0 | 0 | 0 | 0s | 0s | numberOfTopics | Foswiki::Search::InfoCache::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | package Foswiki::Search::InfoCache; | ||||
3 | 2 | 28µs | 2 | 38µs | # spent 25µs (12+13) within Foswiki::Search::InfoCache::BEGIN@3 which was called:
# once (12µs+13µs) by Foswiki::Search::BEGIN@19 at line 3 # spent 25µs making 1 call to Foswiki::Search::InfoCache::BEGIN@3
# spent 13µs making 1 call to strict::import |
4 | 2 | 26µs | 2 | 19µs | # spent 14µs (9+5) within Foswiki::Search::InfoCache::BEGIN@4 which was called:
# once (9µs+5µs) by Foswiki::Search::BEGIN@19 at line 4 # spent 14µs making 1 call to Foswiki::Search::InfoCache::BEGIN@4
# spent 5µs making 1 call to warnings::import |
5 | |||||
6 | 2 | 39µs | 1 | 4µs | # spent 4µs within Foswiki::Search::InfoCache::BEGIN@6 which was called:
# once (4µs+0s) by Foswiki::Search::BEGIN@19 at line 6 # spent 4µs making 1 call to Foswiki::Search::InfoCache::BEGIN@6 |
7 | 1 | 9µs | our @ISA = ('Foswiki::ListIterator'); | ||
8 | |||||
9 | =begin TML | ||||
10 | |||||
11 | ---+ package Foswiki::Search::InfoCache | ||||
12 | |||||
13 | Support package; cache of topic info. When information about search hits is | ||||
14 | compiled for output, this cache is used to avoid recovering the same info | ||||
15 | about the same topic more than once. | ||||
16 | |||||
17 | TODO: this is going to transform from an ugly duckling into the ResultSet Iterator | ||||
18 | |||||
19 | I have the feeling that we should make result sets immutable | ||||
20 | |||||
21 | =cut | ||||
22 | |||||
23 | 2 | 25µs | 2 | 34µs | # spent 21µs (8+13) within Foswiki::Search::InfoCache::BEGIN@23 which was called:
# once (8µs+13µs) by Foswiki::Search::BEGIN@19 at line 23 # spent 21µs making 1 call to Foswiki::Search::InfoCache::BEGIN@23
# spent 13µs making 1 call to Assert::import |
24 | 2 | 18µs | 1 | 4µs | # spent 4µs within Foswiki::Search::InfoCache::BEGIN@24 which was called:
# once (4µs+0s) by Foswiki::Search::BEGIN@19 at line 24 # spent 4µs making 1 call to Foswiki::Search::InfoCache::BEGIN@24 |
25 | 2 | 18µs | 1 | 3µs | # spent 3µs within Foswiki::Search::InfoCache::BEGIN@25 which was called:
# once (3µs+0s) by Foswiki::Search::BEGIN@19 at line 25 # spent 3µs making 1 call to Foswiki::Search::InfoCache::BEGIN@25 |
26 | 2 | 2.42ms | 1 | 472µs | # spent 472µs (407+64) within Foswiki::Search::InfoCache::BEGIN@26 which was called:
# once (407µs+64µs) by Foswiki::Search::BEGIN@19 at line 26 # spent 472µs making 1 call to Foswiki::Search::InfoCache::BEGIN@26 |
27 | |||||
28 | #use Monitor (); | ||||
29 | #Monitor::MonitorMethod('Foswiki::Search::InfoCache', 'getTopicListIterator'); | ||||
30 | |||||
31 | =begin TML | ||||
32 | |||||
33 | ---++ ClassMethod new($session, $defaultWeb, \@topicList) | ||||
34 | initialise a new list of topics, allowing their data to be lazy loaded if and when needed. | ||||
35 | |||||
36 | $defaultWeb is used to qualify topics that do not have a web specifier - should expect it to be the same as BASEWEB in most cases. | ||||
37 | |||||
38 | because this 'Iterator can be created and filled dynamically, once the Iterator hasNext() and next() methods are called, it is immutable. | ||||
39 | |||||
40 | TODO: duplicates??, what about topicExists? | ||||
41 | TODO: remove the iterator code from this __container__ and make a $this->getIterator() which can then be used. | ||||
42 | TODO: replace the Iterator->reset() function with a lightweight Iterator->copyConstructor? | ||||
43 | TODO: or..... make reset() make the object muttable again, so we can change the elements in the list, but re-use the meta cache?? | ||||
44 | CONSIDER: convert the internals to a hash[tomAddress] = {matches->[list of resultint text bits], othermeta...} - except this does not give us order :/ | ||||
45 | |||||
46 | =cut | ||||
47 | |||||
48 | # spent 353ms (3.27+350) within Foswiki::Search::InfoCache::new which was called 81 times, avg 4.36ms/call:
# 41 times (561µs+363µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 101 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 23µs/call
# 40 times (2.71ms+350ms) by Foswiki::Store::SearchAlgorithms::Forking::_webQuery at line 298 of /var/www/foswiki11/lib/Foswiki/Store/SearchAlgorithms/Forking.pm, avg 8.81ms/call | ||||
49 | 81 | 328µs | my ( $class, $session, $defaultWeb, $topicList ) = @_; | ||
50 | |||||
51 | 81 | 1.29ms | 81 | 2.22ms | my $this = $class->SUPER::new( [] ); # spent 2.22ms making 81 calls to Foswiki::ListIterator::new, avg 27µs/call |
52 | 81 | 192µs | $this->{_session} = $session; | ||
53 | 81 | 172µs | $this->{_defaultWeb} = $defaultWeb; | ||
54 | 81 | 227µs | $this->{count} = 0; | ||
55 | 81 | 452µs | 40 | 348ms | if ( defined($topicList) ) { # spent 348ms making 40 calls to Foswiki::Search::InfoCache::addTopics, avg 8.70ms/call |
56 | $this->addTopics( $defaultWeb, @$topicList ); | ||||
57 | } | ||||
58 | |||||
59 | 81 | 338µs | return $this; | ||
60 | } | ||||
61 | |||||
62 | sub isImmutable { | ||||
63 | my $this = shift; | ||||
64 | |||||
65 | return ( $this->{index} != 0 ); | ||||
66 | } | ||||
67 | |||||
68 | # spent 350ms (83.8+266) within Foswiki::Search::InfoCache::addTopics which was called 125 times, avg 2.80ms/call:
# 85 times (956µs+1.05ms) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 227 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 24µs/call
# 40 times (82.9ms+265ms) by Foswiki::Search::InfoCache::new at line 55, avg 8.70ms/call | ||||
69 | 125 | 1.35ms | my ( $this, $defaultWeb, @list ) = @_; | ||
70 | |||||
71 | 125 | 187µs | 125 | 140µs | ASSERT( !$this->isImmutable() ) # spent 140µs making 125 calls to Assert::ASSERTS_OFF, avg 1µs/call |
72 | if DEBUG; #cannot modify list once its being used as an iterator. | ||||
73 | 125 | 149µs | 125 | 109µs | ASSERT( defined($defaultWeb) ) if DEBUG; # spent 109µs making 125 calls to Assert::ASSERTS_OFF, avg 872ns/call |
74 | |||||
75 | 125 | 293µs | foreach my $t (@list) { | ||
76 | 8845 | 33.7ms | 8845 | 266ms | my ( $web, $topic ) = # spent 266ms making 8845 calls to Foswiki::Func::normalizeWebTopicName, avg 30µs/call |
77 | Foswiki::Func::normalizeWebTopicName( $defaultWeb, $t ); | ||||
78 | 8845 | 13.9ms | push( @{ $this->{list} }, "$web.$topic" ); | ||
79 | 8845 | 12.1ms | $this->{count}++; | ||
80 | } | ||||
81 | 125 | 1.37ms | undef $this->{sorted}; | ||
82 | } | ||||
83 | |||||
84 | #TODO: what if it isa Meta obj | ||||
85 | #TODO: or an infoCache obj.. | ||||
86 | # spent 701ms (320+381) within Foswiki::Search::InfoCache::addTopic which was called 8760 times, avg 80µs/call:
# 8760 times (320ms+381ms) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 243 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 80µs/call | ||||
87 | 8760 | 5.52ms | my ( $this, $meta ) = @_; | ||
88 | |||||
89 | 8760 | 8.62ms | 8760 | 7.02ms | ASSERT( !$this->isImmutable() ) # spent 7.02ms making 8760 calls to Assert::ASSERTS_OFF, avg 802ns/call |
90 | if DEBUG; #cannot modify list once its being used as an iterator. | ||||
91 | |||||
92 | 8760 | 14.6ms | 8760 | 23.7ms | my $web = $meta->web(); # spent 23.7ms making 8760 calls to Foswiki::Meta::web, avg 3µs/call |
93 | 8760 | 13.0ms | 8760 | 16.7ms | my $topic = $meta->topic(); # spent 16.7ms making 8760 calls to Foswiki::Meta::topic, avg 2µs/call |
94 | |||||
95 | 8760 | 16.9ms | 8760 | 187ms | my ( $w, $t ) = Foswiki::Func::normalizeWebTopicName( $web, $topic ); # spent 187ms making 8760 calls to Foswiki::Func::normalizeWebTopicName, avg 21µs/call |
96 | 8760 | 7.06ms | my $webtopic = "$w.$t"; | ||
97 | 8760 | 8.81ms | push( @{ $this->{list} }, $webtopic ); | ||
98 | 8760 | 3.05ms | $this->{count}++; | ||
99 | 8760 | 40.2ms | 26280 | 147ms | if ( defined($meta) ) { # spent 110ms making 8760 calls to Foswiki::MetaCache::get, avg 13µs/call
# spent 19.6ms making 8760 calls to Foswiki::search, avg 2µs/call
# spent 17.6ms making 8760 calls to Foswiki::Search::metacache, avg 2µs/call |
100 | $this->{_session}->search->metacache->get( $web, $topic, $meta ); | ||||
101 | } | ||||
102 | 8760 | 52.0ms | undef $this->{sorted}; | ||
103 | } | ||||
104 | |||||
105 | sub numberOfTopics { | ||||
106 | my $this = shift; | ||||
107 | |||||
108 | # can't use this, as it lies once its gone through the 'sortResults' hack | ||||
109 | #return scalar(@{ $this->{list} }); | ||||
110 | # when fixed, the count update in filterByDate should be removed | ||||
111 | |||||
112 | return $this->{count}; | ||||
113 | } | ||||
114 | |||||
115 | =begin TML | ||||
116 | |||||
117 | ---++ ObjectMethod sortResults($params) | ||||
118 | |||||
119 | the implementation of %SORT{"" limit="" order="" reverse="" date=""}% | ||||
120 | |||||
121 | it should be possible for the search engine to pre-sort, making this a nop, or to | ||||
122 | delay evaluated, partially evaluated, or even delegated to the DB/SQL | ||||
123 | |||||
124 | can call repeatedly, the list will only be re-sorted if new elements are added. | ||||
125 | |||||
126 | =cut | ||||
127 | |||||
128 | # spent 183ms (2.52+180) within Foswiki::Search::InfoCache::sortResults which was called 121 times, avg 1.51ms/call:
# 81 times (587µs+53.3ms) by Foswiki::Search::ResultSet::sortResults at line 194 of /var/www/foswiki11/lib/Foswiki/Search/ResultSet.pm, avg 665µs/call
# 40 times (1.93ms+127ms) by Foswiki::Store::SearchAlgorithms::Forking::query at line 184 of /var/www/foswiki11/lib/Foswiki/Store/SearchAlgorithms/Forking.pm, avg 3.22ms/call | ||||
129 | 121 | 110µs | my ( $this, $params ) = @_; | ||
130 | |||||
131 | #TODO: for now assume we do not change the sort order later | ||||
132 | 121 | 364µs | return if ( defined( $this->{sorted} ) ); | ||
133 | 81 | 62µs | $this->{sorted} = 1; | ||
134 | |||||
135 | 81 | 102µs | my $session = $this->{_session}; | ||
136 | |||||
137 | 81 | 122µs | my $sortOrder = $params->{order} || ''; | ||
138 | 81 | 596µs | 81 | 598µs | my $revSort = Foswiki::isTrue( $params->{reverse} ); # spent 598µs making 81 calls to Foswiki::isTrue, avg 7µs/call |
139 | 81 | 164µs | my $limit = $params->{limit} || ''; | ||
140 | |||||
141 | #SMELL: duplicated code - removeme | ||||
142 | # Limit search results | ||||
143 | 81 | 121µs | if ( $limit =~ /(^\d+$)/o ) { | ||
144 | |||||
145 | # only digits, all else is the same as | ||||
146 | # an empty string. "+10" won't work. | ||||
147 | $limit = $1; | ||||
148 | } | ||||
149 | else { | ||||
150 | |||||
151 | # change 'all' to 0, then to big number | ||||
152 | 81 | 40µs | $limit = 0; | ||
153 | } | ||||
154 | 81 | 34µs | $limit = 32000 unless ($limit); | ||
155 | |||||
156 | #TODO: this is really an ugly hack to get around the rather horrible limit 'performance' hack | ||||
157 | 81 | 50µs | if ( defined( $params->{pager_show_results_to} ) | ||
158 | and $params->{pager_show_results_to} > 0 ) | ||||
159 | { | ||||
160 | $limit = | ||||
161 | $params->{pager_skip_results_from} + $params->{pager_show_results_to}; | ||||
162 | } | ||||
163 | |||||
164 | # sort the topic list by date, author or topic name, and cache the | ||||
165 | # info extracted to do the sorting | ||||
166 | 81 | 191µs | if ( $sortOrder eq 'modified' ) { | ||
167 | |||||
168 | # For performance: | ||||
169 | # * sort by approx time (to get a rough list) | ||||
170 | # * shorten list to the limit + some slack | ||||
171 | # * sort by rev date on shortened list to get the accurate list | ||||
172 | # SMELL: Cairo had efficient two stage handling of modified sort. | ||||
173 | # SMELL: In Dakar this seems to be pointless since latest rev | ||||
174 | # time is taken from topic instead of dir list. | ||||
175 | my $slack = 10; | ||||
176 | if ( $limit + 2 * $slack < scalar( @{ $this->{list} } ) ) { | ||||
177 | |||||
178 | # sort by approx latest rev time | ||||
179 | my @tmpList = | ||||
180 | map { $_->[1] } | ||||
181 | sort { $a->[0] <=> $b->[0] } | ||||
182 | map { | ||||
183 | my ( $web, $topic ) = | ||||
184 | Foswiki::Func::normalizeWebTopicName( $this->{_defaultWeb}, | ||||
185 | $_ ); | ||||
186 | [ $session->getApproxRevTime( $web, $topic ), $_ ] | ||||
187 | } @{ $this->{list} }; | ||||
188 | @tmpList = reverse(@tmpList) if ($revSort); | ||||
189 | |||||
190 | # then shorten list and build the hashes for date and author | ||||
191 | my $idx = $limit + $slack; | ||||
192 | @{ $this->{list} } = (); | ||||
193 | foreach (@tmpList) { | ||||
194 | push( @{ $this->{list} }, $_ ); | ||||
195 | $idx -= 1; | ||||
196 | last if $idx <= 0; | ||||
197 | } | ||||
198 | } | ||||
199 | |||||
200 | } | ||||
201 | elsif ( | ||||
202 | $sortOrder =~ /^creat/ || # topic creation time | ||||
203 | $sortOrder eq 'editby' || # author | ||||
204 | $sortOrder =~ s/^formfield\(([^\)]+)\)$/$1/ # form field | ||||
205 | ) | ||||
206 | { | ||||
207 | } | ||||
208 | else { | ||||
209 | |||||
210 | #default to topic sorting | ||||
211 | 81 | 39µs | $sortOrder = 'topic'; | ||
212 | } | ||||
213 | 81 | 686µs | 81 | 180ms | sortTopics( $this->{list}, $sortOrder, !$revSort ); # spent 180ms making 81 calls to Foswiki::Search::InfoCache::sortTopics, avg 2.22ms/call |
214 | } | ||||
215 | |||||
216 | =begin TML | ||||
217 | |||||
218 | ---++ filterByDate( $date ) | ||||
219 | |||||
220 | Filter the list by date interval; see System.TimeSpecifications. | ||||
221 | |||||
222 | <verbatim> | ||||
223 | $infoCache->filterByDate( $date ); | ||||
224 | </verbatim> | ||||
225 | |||||
226 | =cut | ||||
227 | |||||
228 | sub filterByDate { | ||||
229 | my ( $this, $date ) = @_; | ||||
230 | |||||
231 | my $session = $Foswiki::Plugins::SESSION; | ||||
232 | |||||
233 | require Foswiki::Time; | ||||
234 | my @ends = Foswiki::Time::parseInterval($date); | ||||
235 | my @resultList = (); | ||||
236 | foreach my $webtopic ( @{ $this->{list} } ) { | ||||
237 | |||||
238 | # if date falls out of interval: exclude topic from result | ||||
239 | my ( $web, $topic ) = | ||||
240 | Foswiki::Func::normalizeWebTopicName( $this->{_defaultWeb}, | ||||
241 | $webtopic ); | ||||
242 | my $topicdate = $session->getApproxRevTime( $web, $topic ); | ||||
243 | push( @resultList, $webtopic ) | ||||
244 | unless ( $topicdate < $ends[0] || $topicdate > $ends[1] ); | ||||
245 | } | ||||
246 | $this->{list} = \@resultList; | ||||
247 | |||||
248 | # use this hack until numberOfTopics reads the length of list | ||||
249 | $this->{count} = scalar @{ $this->{list} }; | ||||
250 | } | ||||
251 | |||||
252 | ######OLD methods | ||||
253 | |||||
254 | # Determins, and caches, the topic revision info of the base version, | ||||
255 | sub getRev1Info { | ||||
256 | my ( $webtopic, $attr ) = @_; | ||||
257 | |||||
258 | my $session = $Foswiki::Plugins::SESSION; | ||||
259 | my $metacache = $session->search->metacache; | ||||
260 | |||||
261 | my ( $web, $topic ) = | ||||
262 | Foswiki::Func::normalizeWebTopicName( $Foswiki::cfg{UsersWebName}, | ||||
263 | $webtopic ); | ||||
264 | |||||
265 | my $info = $metacache->get( $web, $topic ); | ||||
266 | unless ( defined $info->{$attr} ) { | ||||
267 | my $ri = $info->{rev1info}; | ||||
268 | unless ($ri) { | ||||
269 | my $tmp = Foswiki::Meta->load( $session, $web, $topic, 1 ); | ||||
270 | $info->{rev1info} = $ri = $tmp->getRevisionInfo(); | ||||
271 | } | ||||
272 | |||||
273 | if ( $attr eq 'createusername' ) { | ||||
274 | $info->{createusername} = | ||||
275 | $session->{users}->getLoginName( $ri->{author} ); | ||||
276 | } | ||||
277 | elsif ( $attr eq 'createwikiname' ) { | ||||
278 | $info->{createwikiname} = | ||||
279 | $session->{users}->getWikiName( $ri->{author} ); | ||||
280 | } | ||||
281 | elsif ( $attr eq 'createwikiusername' ) { | ||||
282 | $info->{createwikiusername} = | ||||
283 | $session->{users}->webDotWikiName( $ri->{author} ); | ||||
284 | } | ||||
285 | elsif ($attr eq 'createdate' | ||||
286 | or $attr eq 'createlongdate' | ||||
287 | or $attr eq 'created' ) | ||||
288 | { | ||||
289 | $info->{created} = $ri->{date}; | ||||
290 | require Foswiki::Time; | ||||
291 | $info->{createdate} = Foswiki::Time::formatTime( $ri->{date} ); | ||||
292 | |||||
293 | #TODO: wow thats disgusting. | ||||
294 | $info->{created} = $info->{createlongdate} = $info->{createdate}; | ||||
295 | } | ||||
296 | } | ||||
297 | return $info->{$attr}; | ||||
298 | } | ||||
299 | |||||
300 | # Sort a topic list using cached info | ||||
301 | # spent 180ms (179+521µs) within Foswiki::Search::InfoCache::sortTopics which was called 81 times, avg 2.22ms/call:
# 81 times (179ms+521µs) by Foswiki::Search::InfoCache::sortResults at line 213, avg 2.22ms/call | ||||
302 | 81 | 229µs | my ( $listRef, $sortfield, $revSort ) = @_; | ||
303 | 81 | 306µs | 81 | 289µs | ASSERT($sortfield); # spent 289µs making 81 calls to Assert::dummyASSERT, avg 4µs/call |
304 | |||||
305 | #seriously, don't spend time doing stuff to an empty list (or a list of one!) | ||||
306 | 81 | 62µs | return if ( scalar(@$listRef) <= 0 ); | ||
307 | |||||
308 | 81 | 64µs | if ( $sortfield eq 'topic' ) { | ||
309 | |||||
310 | # simple sort, see Codev.SchwartzianTransformMisused | ||||
311 | # note no extraction of topic info here, as not needed | ||||
312 | # for the sort. Instead it will be read lazily, later on. | ||||
313 | #TODO: need to remove the web portion | ||||
314 | #mmm, need to profile if there is even a point to this - as all topics still need to be parsed to find permissions | ||||
315 | 81 | 5.19ms | if ($revSort) { | ||
316 | @{$listRef} = map { $_->[1] } | ||||
317 | 17605 | 111ms | sort { $a->[0] cmp $b->[0] } | ||
318 | 17605 | 17.8ms | map { $_ =~ /^(.*?)([^.]+)$/; [ $2, $_ ] } #quickhack to remove web | ||
319 | 81 | 43.4ms | @{$listRef}; | ||
320 | } | ||||
321 | else { | ||||
322 | @{$listRef} = map { $_->[1] } | ||||
323 | sort { $b->[0] cmp $a->[0] } | ||||
324 | map { $_ =~ /^(.*?)([^.]+)$/; [ $2, $_ ] } #quickhack to remove web | ||||
325 | @{$listRef}; | ||||
326 | } | ||||
327 | 81 | 263µs | 81 | 232µs | ASSERT( $listRef->[0] ) if DEBUG; # spent 232µs making 81 calls to Assert::ASSERTS_OFF, avg 3µs/call |
328 | 81 | 355µs | return; | ||
329 | } | ||||
330 | |||||
331 | my $metacache = $Foswiki::Plugins::SESSION->search->metacache; | ||||
332 | |||||
333 | # populate the cache for each topic | ||||
334 | foreach my $webtopic ( @{$listRef} ) { | ||||
335 | if ( $sortfield =~ /^creat/ ) { | ||||
336 | |||||
337 | # The act of getting the info will cache it | ||||
338 | getRev1Info( $webtopic, $sortfield ); | ||||
339 | } | ||||
340 | else { | ||||
341 | |||||
342 | #duplicated from above - I'd rather do it only here, but i'm not sure if i can. | ||||
343 | $sortfield =~ s/^formfield\((.*)\)$/$1/; # form field | ||||
344 | |||||
345 | my $info = $metacache->get($webtopic); | ||||
346 | if ( !defined( $info->{$sortfield} ) ) { | ||||
347 | |||||
348 | #under normal circumstances this code is not called, because the metacach has already filled it. | ||||
349 | if ( $sortfield eq 'modified' ) { | ||||
350 | my $ri = $info->{tom}->getRevisionInfo(); | ||||
351 | $info->{$sortfield} = $ri->{date}; | ||||
352 | } | ||||
353 | else { | ||||
354 | $info->{$sortfield} = | ||||
355 | Foswiki::Search::displayFormField( $info->{tom}, | ||||
356 | $sortfield ); | ||||
357 | } | ||||
358 | } | ||||
359 | } | ||||
360 | |||||
361 | # SMELL: CDot isn't clear why this is needed, but it is otherwise | ||||
362 | # we end up with the users all being identified as "undef" | ||||
363 | my $info = $metacache->get($webtopic); | ||||
364 | $info->{editby} = | ||||
365 | $info->{tom}->session->{users}->getWikiName( $info->{editby} ); | ||||
366 | } | ||||
367 | if ($revSort) { | ||||
368 | @{$listRef} = map { $_->[1] } | ||||
369 | sort { _compare( $b->[0], $a->[0] ) } | ||||
370 | map { [ $metacache->get($_)->{$sortfield}, $_ ] } @{$listRef}; | ||||
371 | } | ||||
372 | else { | ||||
373 | @{$listRef} = map { $_->[1] } | ||||
374 | sort { _compare( $a->[0], $b->[0] ) } | ||||
375 | map { [ $metacache->get($_)->{$sortfield}, $_ ] } @{$listRef}; | ||||
376 | } | ||||
377 | } | ||||
378 | |||||
379 | # RE for a full-spec floating-point number | ||||
380 | 1 | 100ns | our ($NUMBER); | ||
381 | 1 | 2µs | $NUMBER = qr/^[-+]?[0-9]+(\.[0-9]*)?([Ee][-+]?[0-9]+)?$/s; | ||
382 | |||||
383 | sub _compare { | ||||
384 | my $x = shift; | ||||
385 | my $y = shift; | ||||
386 | |||||
387 | ASSERT( defined($x) ) if DEBUG; | ||||
388 | ASSERT( defined($y) ) if DEBUG; | ||||
389 | |||||
390 | if ( $x =~ /$NUMBER/o && $y =~ /$NUMBER/o ) { | ||||
391 | |||||
392 | # when sorting numbers do it largest first; this is just because | ||||
393 | # this is what date comparisons need. | ||||
394 | return $y <=> $x; | ||||
395 | } | ||||
396 | |||||
397 | my $datex = undef; | ||||
398 | my $datey = undef; | ||||
399 | |||||
400 | # parseTime can error if you give it a date out of range so we skip | ||||
401 | # testing if pure number | ||||
402 | # We skip testing for dates the first character is not a digit | ||||
403 | # as all formats we recognise as dates are | ||||
404 | if ( $x =~ /^\d/ | ||||
405 | && $x !~ /$NUMBER/o | ||||
406 | && $y =~ /^\d/ | ||||
407 | && $y !~ /$NUMBER/o ) | ||||
408 | { | ||||
409 | $datex = Foswiki::Time::parseTime($x); | ||||
410 | $datey = Foswiki::Time::parseTime($y) if $datex; | ||||
411 | } | ||||
412 | |||||
413 | if ( $datex && $datey ) { | ||||
414 | return $datey <=> $datex; | ||||
415 | } | ||||
416 | else { | ||||
417 | return $y cmp $x; | ||||
418 | } | ||||
419 | } | ||||
420 | |||||
421 | #convert a comma separated list of webs into the list we'll process | ||||
422 | #TODO: this is part of the Store now, and so should not need to reference Meta - it rather uses the store.. | ||||
423 | # spent 4.74ms (1.93+2.81) within Foswiki::Search::InfoCache::_getListOfWebs which was called 81 times, avg 59µs/call:
# 41 times (974µs+1.80ms) by Foswiki::Store::QueryAlgorithms::BruteForce::query at line 56 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 68µs/call
# 40 times (953µs+1.02ms) by Foswiki::Store::SearchAlgorithms::Forking::query at line 161 of /var/www/foswiki11/lib/Foswiki/Store/SearchAlgorithms/Forking.pm, avg 49µs/call | ||||
424 | 81 | 65µs | my ( $webName, $recurse, $searchAllFlag ) = @_; | ||
425 | 81 | 30µs | my $session = $Foswiki::Plugins::SESSION; | ||
426 | |||||
427 | 81 | 14µs | my %excludeWeb; | ||
428 | 81 | 19µs | my @tmpWebs; | ||
429 | |||||
430 | #$web = Foswiki::Sandbox::untaint( $web,\&Foswiki::Sandbox::validateWebName ); | ||||
431 | |||||
432 | 81 | 46µs | if ($webName) { | ||
433 | 41 | 95µs | foreach my $web ( split( /[\,\s]+/, $webName ) ) { | ||
434 | 41 | 59µs | $web =~ s#\.#/#go; | ||
435 | |||||
436 | # the web processing loop filters for valid web names, | ||||
437 | # so don't do it here. | ||||
438 | 41 | 79µs | if ( $web =~ s/^-// ) { | ||
439 | $excludeWeb{$web} = 1; | ||||
440 | } | ||||
441 | else { | ||||
442 | 41 | 195µs | 41 | 339µs | if ( $web =~ /^(all|on)$/i # spent 339µs making 41 calls to Foswiki::isTrue, avg 8µs/call |
443 | || $Foswiki::cfg{EnableHierarchicalWebs} | ||||
444 | && Foswiki::isTrue($recurse) ) | ||||
445 | { | ||||
446 | require Foswiki::WebFilter; | ||||
447 | my $webObject; | ||||
448 | my $prefix = "$web/"; | ||||
449 | if ( $web =~ /^(all|on)$/i ) { | ||||
450 | $webObject = Foswiki::Meta->new($session); | ||||
451 | $prefix = ''; | ||||
452 | } | ||||
453 | else { | ||||
454 | $web = Foswiki::Sandbox::untaint( $web, | ||||
455 | \&Foswiki::Sandbox::validateWebName ); | ||||
456 | ASSERT($web); | ||||
457 | push( @tmpWebs, $web ); | ||||
458 | $webObject = Foswiki::Meta->new( $session, $web ); | ||||
459 | } | ||||
460 | my $it = $webObject->eachWeb(1); | ||||
461 | while ( $it->hasNext() ) { | ||||
462 | my $w = $prefix . $it->next(); | ||||
463 | next | ||||
464 | unless $Foswiki::WebFilter::user_allowed->ok( | ||||
465 | $session, $w ); | ||||
466 | $w = Foswiki::Sandbox::untaint( $w, | ||||
467 | \&Foswiki::Sandbox::validateWebName ); | ||||
468 | ASSERT($web); | ||||
469 | push( @tmpWebs, $w ); | ||||
470 | } | ||||
471 | } | ||||
472 | else { | ||||
473 | 41 | 109µs | 41 | 730µs | $web = Foswiki::Sandbox::untaint( $web, # spent 730µs making 41 calls to Foswiki::Sandbox::untaint, avg 18µs/call |
474 | \&Foswiki::Sandbox::validateWebName ); | ||||
475 | 41 | 32µs | push( @tmpWebs, $web ); | ||
476 | } | ||||
477 | } | ||||
478 | } | ||||
479 | |||||
480 | } | ||||
481 | else { | ||||
482 | |||||
483 | # default to current web | ||||
484 | 40 | 214µs | 40 | 1.47ms | my $web = # spent 1.47ms making 40 calls to Foswiki::Sandbox::untaint, avg 37µs/call |
485 | Foswiki::Sandbox::untaint( $session->{webName}, | ||||
486 | \&Foswiki::Sandbox::validateWebName ); | ||||
487 | 40 | 29µs | push( @tmpWebs, $web ); | ||
488 | 40 | 65µs | 40 | 278µs | if ( Foswiki::isTrue($recurse) ) { # spent 278µs making 40 calls to Foswiki::isTrue, avg 7µs/call |
489 | my $webObject = Foswiki::Meta->new( $session, $session->{webName} ); | ||||
490 | my $it = | ||||
491 | $webObject->eachWeb( $Foswiki::cfg{EnableHierarchicalWebs} ); | ||||
492 | while ( $it->hasNext() ) { | ||||
493 | my $w = $session->{webName} . '/' . $it->next(); | ||||
494 | next | ||||
495 | unless $Foswiki::WebFilter::user_allowed->ok( $session, $w ); | ||||
496 | $w = Foswiki::Sandbox::untaint( $w, | ||||
497 | \&Foswiki::Sandbox::validateWebName ); | ||||
498 | push( @tmpWebs, $w ); | ||||
499 | } | ||||
500 | } | ||||
501 | } | ||||
502 | |||||
503 | 81 | 20µs | my @webs; | ||
504 | 81 | 106µs | foreach my $web (@tmpWebs) { | ||
505 | 81 | 18µs | next unless defined $web; | ||
506 | 81 | 91µs | push( @webs, $web ) unless $excludeWeb{$web}; | ||
507 | 81 | 134µs | $excludeWeb{$web} = 1; # eliminate duplicates | ||
508 | } | ||||
509 | |||||
510 | 81 | 322µs | return @webs; | ||
511 | } | ||||
512 | ######################################### | ||||
513 | #TODO: this is _now_ a default utility method that can be used by search&query algo's to brute force file a list of topics to search. | ||||
514 | #if you can avoid it, you should - as it needs to do an opendir on the web, and if you have alot of topics, life gets slow | ||||
515 | # get a list of topics to search in the web, filtered by the $topic | ||||
516 | # spec | ||||
517 | # spent 333ms (1.39+331) within Foswiki::Search::InfoCache::getTopicListIterator which was called 41 times, avg 8.11ms/call:
# 41 times (1.39ms+331ms) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 168 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 8.11ms/call | ||||
518 | 41 | 42µs | my ( $webObject, $options ) = @_; | ||
519 | 41 | 72µs | my $casesensitive = | ||
520 | defined( $options->{casesensitive} ) ? $options->{casesensitive} : 1; | ||||
521 | |||||
522 | # E.g. "Web*, FooBar" ==> "^(Web.*|FooBar)$" | ||||
523 | 41 | 20µs | $options->{excludeTopics} = | ||
524 | convertTopicPatternToRegex( $options->{excludeTopics} ) | ||||
525 | if ( $options->{excludeTopics} ); | ||||
526 | |||||
527 | 41 | 12µs | my $topicFilter; | ||
528 | 41 | 7µs | my $it; | ||
529 | 41 | 38µs | if ( $options->{includeTopics} ) { | ||
530 | |||||
531 | # E.g. "Bug*, *Patch" ==> "^(Bug.*|.*Patch)$" | ||||
532 | 41 | 105µs | 41 | 713µs | $options->{includeTopics} = # spent 713µs making 41 calls to Foswiki::Search::InfoCache::convertTopicPatternToRegex, avg 17µs/call |
533 | convertTopicPatternToRegex( $options->{includeTopics} ); | ||||
534 | |||||
535 | # limit search to topic list | ||||
536 | 41 | 54µs | if ( $casesensitive | ||
537 | and $options->{includeTopics} =~ | ||||
538 | /^\^\([\_\-\+$Foswiki::regex{mixedAlphaNum}\|]+\)\$$/ ) | ||||
539 | { | ||||
540 | |||||
541 | # topic list without wildcards | ||||
542 | # for speed, do not get all topics in web | ||||
543 | # but convert topic pattern into topic list | ||||
544 | my $topics = $options->{includeTopics}; | ||||
545 | $topics =~ s/^\^\(//o; | ||||
546 | $topics =~ s/\)\$//o; | ||||
547 | |||||
548 | # build list from topic pattern | ||||
549 | my @list = split( /\|/, $topics ); | ||||
550 | $it = new Foswiki::ListIterator( \@list ); | ||||
551 | } | ||||
552 | elsif ( !$casesensitive ) { | ||||
553 | 41 | 216µs | $topicFilter = qr/$options->{includeTopics}/i; | ||
554 | } | ||||
555 | else { | ||||
556 | $topicFilter = qr/$options->{includeTopics}/; | ||||
557 | } | ||||
558 | } | ||||
559 | |||||
560 | 41 | 156µs | 41 | 330ms | $it = $webObject->eachTopic() unless ( defined($it) ); # spent 330ms making 41 calls to Foswiki::Meta::eachTopic, avg 8.05ms/call |
561 | |||||
562 | my $filterIter = new Foswiki::Iterator::FilterIterator( | ||||
563 | $it, | ||||
564 | # spent 3.89s (636ms+3.25) within Foswiki::Search::InfoCache::__ANON__[/var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm:579] which was called 52483 times, avg 74µs/call:
# 52483 times (636ms+3.25s) by Foswiki::Iterator::FilterIterator::hasNext at line 50 of /var/www/foswiki11/lib/Foswiki/Iterator/FilterIterator.pm, avg 74µs/call | ||||
565 | 52483 | 14.9ms | my $item = shift; | ||
566 | |||||
567 | #my $data = shift; | ||||
568 | 52483 | 208ms | return unless !$topicFilter || $item =~ /$topicFilter/; | ||
569 | |||||
570 | # exclude topics, Codev.ExcludeWebTopicsFromSearch | ||||
571 | 46125 | 30.0ms | if ( !$casesensitive && $options->{excludeTopics} ) { | ||
572 | return if $item =~ /$options->{excludeTopics}/i; | ||||
573 | } | ||||
574 | elsif ( $options->{excludeTopics} ) { | ||||
575 | return if $item =~ /$options->{excludeTopics}/; | ||||
576 | } | ||||
577 | 46125 | 357ms | 92250 | 3.25s | return $Foswiki::Plugins::SESSION->topicExists( $webObject->web, # spent 3.14s making 46125 calls to Foswiki::topicExists, avg 68µs/call
# spent 116ms making 46125 calls to Foswiki::Meta::web, avg 3µs/call |
578 | $item ); | ||||
579 | } | ||||
580 | 41 | 465µs | 41 | 671µs | ); # spent 671µs making 41 calls to Foswiki::Iterator::FilterIterator::new, avg 16µs/call |
581 | 41 | 126µs | return $filterIter; | ||
582 | } | ||||
583 | |||||
584 | # spent 713µs within Foswiki::Search::InfoCache::convertTopicPatternToRegex which was called 41 times, avg 17µs/call:
# 41 times (713µs+0s) by Foswiki::Search::InfoCache::getTopicListIterator at line 532, avg 17µs/call | ||||
585 | 41 | 34µs | my ($topic) = @_; | ||
586 | 41 | 13µs | return '' unless ($topic); | ||
587 | |||||
588 | # 'Web*, FooBar' ==> ( 'Web*', 'FooBar' ) ==> ( 'Web.*', "FooBar" ) | ||||
589 | 41 | 96µs | my @arr = | ||
590 | 123 | 417µs | map { s/[^\*\_\-\+$Foswiki::regex{mixedAlphaNum}]//go; s/\*/\.\*/go; $_ } | ||
591 | split( /(?:,\s*|\|)/, $topic ); | ||||
592 | 41 | 18µs | return '' unless (@arr); | ||
593 | |||||
594 | # ( 'Web.*', 'FooBar' ) ==> "^(Web.*|FooBar)$" | ||||
595 | 41 | 185µs | return '^(' . join( '|', @arr ) . ')$'; | ||
596 | } | ||||
597 | |||||
598 | 1 | 4µs | 1; | ||
599 | __END__ |