Filename | /home/ss5/perl5/perlbrew/perls/perl-5.22.0/lib/site_perl/5.22.0/BenchmarkAnything/Storage/Backend/SQL.pm |
Statements | Executed 167097 statements in 656ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1000 | 1 | 1 | 414ms | 27.9s | add_single_benchmark | BenchmarkAnything::Storage::Backend::SQL::
1000 | 1 | 1 | 160ms | 53.0s | process_queued_multi_benchmark | BenchmarkAnything::Storage::Backend::SQL::
1000 | 1 | 1 | 34.7ms | 28.0s | add_multi_benchmark | BenchmarkAnything::Storage::Backend::SQL::
1000 | 1 | 1 | 31.0ms | 941ms | get_single_benchmark_point | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 1.24ms | 39.7ms | new | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 96µs | 98µs | BEGIN@8 | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 52µs | 34.3ms | gc | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 11µs | 11µs | BEGIN@7 | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 7µs | 14µs | BEGIN@10 | BenchmarkAnything::Storage::Backend::SQL::
1 | 1 | 1 | 6µs | 7µs | BEGIN@9 | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | __ANON__[:105] | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | _get_additional_key_id | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | benchmark_operators | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | default_columns | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | enqueue_multi_benchmark | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | get_full_benchmark_points | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | get_stats | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | init_search_engine | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | list_additional_keys | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | list_benchmark_names | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | search | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | search_array | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | search_hash | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | subsume | BenchmarkAnything::Storage::Backend::SQL::
0 | 0 | 0 | 0s | 0s | sync_search_engine | BenchmarkAnything::Storage::Backend::SQL::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package BenchmarkAnything::Storage::Backend::SQL; | ||||
2 | # git description: v0.022-5-g18cf68b | ||||
3 | |||||
4 | 1 | 400ns | our $AUTHORITY = 'cpan:TAPPER'; | ||
5 | # ABSTRACT: Autonomous SQL backend to store benchmarks | ||||
6 | 1 | 100ns | $BenchmarkAnything::Storage::Backend::SQL::VERSION = '0.023'; | ||
7 | 2 | 28µs | 1 | 11µs | # spent 11µs within BenchmarkAnything::Storage::Backend::SQL::BEGIN@7 which was called:
# once (11µs+0s) by BenchmarkAnything::Storage::Frontend::Lib::connect at line 7 # spent 11µs making 1 call to BenchmarkAnything::Storage::Backend::SQL::BEGIN@7 |
8 | 2 | 105µs | 2 | 99µs | # spent 98µs (96+1) within BenchmarkAnything::Storage::Backend::SQL::BEGIN@8 which was called:
# once (96µs+1µs) by BenchmarkAnything::Storage::Frontend::Lib::connect at line 8 # spent 98µs making 1 call to BenchmarkAnything::Storage::Backend::SQL::BEGIN@8
# spent 2µs making 1 call to utf8::import |
9 | 2 | 15µs | 2 | 8µs | # spent 7µs (6+1) within BenchmarkAnything::Storage::Backend::SQL::BEGIN@9 which was called:
# once (6µs+1µs) by BenchmarkAnything::Storage::Frontend::Lib::connect at line 9 # spent 7µs making 1 call to BenchmarkAnything::Storage::Backend::SQL::BEGIN@9
# spent 1µs making 1 call to strict::import |
10 | 2 | 2.39ms | 2 | 20µs | # spent 14µs (7+7) within BenchmarkAnything::Storage::Backend::SQL::BEGIN@10 which was called:
# once (7µs+7µs) by BenchmarkAnything::Storage::Frontend::Lib::connect at line 10 # spent 14µs making 1 call to BenchmarkAnything::Storage::Backend::SQL::BEGIN@10
# spent 7µs making 1 call to warnings::import |
11 | |||||
12 | 1 | 5µs | my $hr_default_config = { | ||
13 | select_cache => 0, | ||||
14 | default_aggregation => 'min', | ||||
15 | tables => { | ||||
16 | unit_table => 'bench_units', | ||||
17 | benchmark_table => 'benchs', | ||||
18 | benchmark_value_table => 'bench_values', | ||||
19 | subsume_type_table => 'bench_subsume_types', | ||||
20 | benchmark_backup_value_table => 'bench_backup_values', | ||||
21 | additional_type_table => 'bench_additional_types', | ||||
22 | additional_value_table => 'bench_additional_values', | ||||
23 | additional_relation_table => 'bench_additional_relations', | ||||
24 | additional_type_relation_table => 'bench_additional_type_relations', | ||||
25 | backup_additional_relation_table => 'bench_backup_additional_relations', | ||||
26 | }, | ||||
27 | }; | ||||
28 | |||||
29 | 1 | 900ns | my $hr_column_ba_mapping = { | ||
30 | bench_value_id => 'VALUE_ID', | ||||
31 | bench => 'NAME', | ||||
32 | bench_value => 'VALUE', | ||||
33 | bench_unit => 'UNIT', | ||||
34 | created_at => 'CREATED', | ||||
35 | }; | ||||
36 | |||||
37 | my $fn_add_subsumed_point = sub { | ||||
38 | |||||
39 | my ( $or_self, $hr_atts ) = @_; | ||||
40 | |||||
41 | $or_self->{query}->start_transaction(); | ||||
42 | |||||
43 | eval { | ||||
44 | |||||
45 | # insert subsumed benchmark value | ||||
46 | $or_self->{query}->insert_benchmark_value( | ||||
47 | $hr_atts->{rows}[0]{bench_id}, | ||||
48 | $hr_atts->{type_id}, | ||||
49 | $hr_atts->{VALUE}, | ||||
50 | ); | ||||
51 | my $i_bench_value_id = $or_self->{query}->last_insert_id( | ||||
52 | $or_self->{config}{tables}{benchmark_value_table}, | ||||
53 | 'bench_value_id', | ||||
54 | ); | ||||
55 | |||||
56 | # insert subsumed benchmark additional values | ||||
57 | $or_self->{query}->copy_additional_values({ | ||||
58 | new_bench_value_id => $i_bench_value_id, | ||||
59 | old_bench_value_id => $hr_atts->{rows}[0]{bench_value_id}, | ||||
60 | }); | ||||
61 | |||||
62 | for my $hr_backup_row ( @{$hr_atts->{rows}} ) { | ||||
63 | |||||
64 | if ( $hr_backup_row->{bench_subsume_type_rank} == 1 ) { | ||||
65 | if ( $hr_atts->{backup} ) { | ||||
66 | # copy data rows to backup table | ||||
67 | $or_self->{query}->copy_benchmark_backup_value({ | ||||
68 | new_bench_value_id => $i_bench_value_id, | ||||
69 | old_bench_value_id => $hr_backup_row->{bench_value_id}, | ||||
70 | }); | ||||
71 | my $i_bench_backup_value_id = $or_self->{query}->last_insert_id( | ||||
72 | $or_self->{config}{tables}{benchmark_backup_value_table}, | ||||
73 | 'bench_backup_value_id', | ||||
74 | ); | ||||
75 | $or_self->{query}->copy_benchmark_backup_additional_relations({ | ||||
76 | new_bench_value_id => $i_bench_backup_value_id, | ||||
77 | old_bench_value_id => $hr_backup_row->{bench_value_id}, | ||||
78 | }); | ||||
79 | } | ||||
80 | } | ||||
81 | else { | ||||
82 | # update bench_value_id in backup table | ||||
83 | $or_self->{query}->update_benchmark_backup_value({ | ||||
84 | new_bench_value_id => $i_bench_value_id, | ||||
85 | old_bench_value_id => $hr_backup_row->{bench_value_id}, | ||||
86 | }); | ||||
87 | } | ||||
88 | |||||
89 | # now lets remove the old rows | ||||
90 | $or_self->{query}->delete_benchmark_additional_relations( | ||||
91 | $hr_backup_row->{bench_value_id}, | ||||
92 | ); | ||||
93 | $or_self->{query}->delete_benchmark_value( | ||||
94 | $hr_backup_row->{bench_value_id}, | ||||
95 | ); | ||||
96 | |||||
97 | } | ||||
98 | |||||
99 | }; | ||||
100 | |||||
101 | $or_self->{query}->finish_transaction( $@ ); | ||||
102 | |||||
103 | return 1; | ||||
104 | |||||
105 | 1 | 3µs | }; | ||
106 | |||||
107 | # spent 39.7ms (1.24+38.4) within BenchmarkAnything::Storage::Backend::SQL::new which was called:
# once (1.24ms+38.4ms) by BenchmarkAnything::Storage::Frontend::Lib::connect at line 209 of BenchmarkAnything/Storage/Frontend/Lib.pm | ||||
108 | |||||
109 | 1 | 500ns | my ( $s_self, $hr_atts ) = @_; | ||
110 | |||||
111 | 1 | 1µs | my $or_self = bless {}, $s_self; | ||
112 | |||||
113 | 1 | 500ns | for my $s_key (qw/ dbh /) { | ||
114 | 1 | 700ns | if (! $hr_atts->{$s_key} ) { | ||
115 | require Carp; | ||||
116 | Carp::confess("missing '$s_key' parameter"); | ||||
117 | return; | ||||
118 | } | ||||
119 | } | ||||
120 | |||||
121 | # get tapper benchmark configuration | ||||
122 | 1 | 5µs | $or_self->{config} = { %{$hr_default_config} }; | ||
123 | |||||
124 | 1 | 500ns | if ( $hr_atts->{config} ) { | ||
125 | require Hash::Merge; | ||||
126 | $or_self->{config} = { | ||||
127 | Hash::Merge | ||||
128 | ->new('LEFT_PRECEDENT') | ||||
129 | ->merge( | ||||
130 | %{$hr_atts->{config}}, | ||||
131 | %{$or_self->{config}}, | ||||
132 | ) | ||||
133 | }; | ||||
134 | } | ||||
135 | |||||
136 | 1 | 58µs | require CHI; | ||
137 | 1 | 1µs | if ( $or_self->{config}{select_cache} ) { | ||
138 | $or_self->{cache} = CHI->new( driver => 'RawMemory', global => 1 ); | ||||
139 | } | ||||
140 | |||||
141 | 1 | 28µs | 2 | 10µs | my $s_module = "BenchmarkAnything::Storage::Backend::SQL::Query::$hr_atts->{dbh}{Driver}{Name}"; # spent 10µs making 2 calls to DBI::common::FETCH, avg 5µs/call |
142 | |||||
143 | 1 | 200ns | my $fn_new_sub; | ||
144 | 1 | 400ns | eval { | ||
145 | 1 | 54µs | require Module::Load; | ||
146 | 1 | 1µs | 1 | 2µs | Module::Load::load( $s_module ); # spent 2µs making 1 call to Module::Load::load |
147 | 1 | 8µs | 1 | 2µs | $fn_new_sub = $s_module->can('new'); # spent 2µs making 1 call to UNIVERSAL::can |
148 | }; | ||||
149 | |||||
150 | 1 | 2µs | if ( $@ || !$fn_new_sub ) { | ||
151 | require Carp; | ||||
152 | Carp::confess("database engine '$hr_atts->{dbh}{Driver}{Name}' not supported"); | ||||
153 | return; | ||||
154 | } | ||||
155 | else { | ||||
156 | $or_self->{query} = $s_module->new({ | ||||
157 | dbh => $hr_atts->{dbh}, | ||||
158 | driver => $hr_atts->{dbh}{Driver}{Name}, | ||||
159 | debug => $hr_atts->{debug} || 0, | ||||
160 | verbose=> $hr_atts->{verbose} || 0, | ||||
161 | config => $or_self->{config}, | ||||
162 | 1 | 15µs | 3 | 11µs | }); # spent 6µs making 1 call to BenchmarkAnything::Storage::Backend::SQL::Query::new
# spent 5µs making 2 calls to DBI::common::FETCH, avg 2µs/call |
163 | } | ||||
164 | |||||
165 | 1 | 1µs | $or_self->{searchengine} = $hr_atts->{searchengine} if $hr_atts->{searchengine}; | ||
166 | 1 | 700ns | $or_self->{debug} = $hr_atts->{debug} || 0; | ||
167 | 1 | 600ns | $or_self->{verbose} = $hr_atts->{verbose} || 0; | ||
168 | 1 | 500ns | $or_self->{dbh_config} = $hr_atts->{dbh_config}; | ||
169 | |||||
170 | 1 | 5µs | return $or_self; | ||
171 | |||||
172 | } | ||||
173 | |||||
174 | |||||
175 | # spent 27.9s (414ms+27.5) within BenchmarkAnything::Storage::Backend::SQL::add_single_benchmark which was called 1000 times, avg 27.9ms/call:
# 1000 times (414ms+27.5s) by BenchmarkAnything::Storage::Backend::SQL::add_multi_benchmark at line 505, avg 27.9ms/call | ||||
176 | |||||
177 | 1000 | 525µs | my ( $or_self, $hr_benchmark, $hr_options ) = @_; | ||
178 | |||||
179 | 1000 | 1.01ms | my $hr_config = $or_self->{config}; | ||
180 | |||||
181 | 1000 | 415µs | my $VALUE_ID; # same spelling as reserved key in BenchmarkAnything schema | ||
182 | |||||
183 | # benchmark | ||||
184 | my $i_benchmark_id; | ||||
185 | 1000 | 1.92ms | if ( $hr_benchmark->{NAME} ) { | ||
186 | 1000 | 15.3ms | 3000 | 262ms | if ( # spent 180ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_benchmark, avg 180µs/call
# spent 57.8ms making 1000 calls to DBI::st::fetchrow_hashref, avg 58µs/call
# spent 23.9ms making 1000 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 24µs/call |
187 | my $hr_bench_select = $or_self->{query} | ||||
188 | ->select_benchmark( $hr_benchmark->{NAME} ) | ||||
189 | ->fetchrow_hashref() | ||||
190 | ) { | ||||
191 | $i_benchmark_id = $hr_bench_select->{bench_id}; | ||||
192 | } | ||||
193 | else { | ||||
194 | my $i_unit_id; | ||||
195 | if ( $hr_benchmark->{UNIT} ) { | ||||
196 | if ( | ||||
197 | my $hr_unit_select = $or_self->{query} | ||||
198 | ->select_unit( $hr_benchmark->{UNIT} ) | ||||
199 | ->fetchrow_hashref() | ||||
200 | ) { | ||||
201 | $i_unit_id = $hr_unit_select->{bench_unit_id}; | ||||
202 | } | ||||
203 | else { | ||||
204 | $or_self->{query}->insert_unit( | ||||
205 | $hr_benchmark->{UNIT}, | ||||
206 | ); | ||||
207 | $i_unit_id = $or_self->{query}->last_insert_id( | ||||
208 | $hr_config->{tables}{unit_table}, | ||||
209 | 'bench_unit_id', | ||||
210 | ); | ||||
211 | } | ||||
212 | } | ||||
213 | $or_self->{query}->insert_benchmark( | ||||
214 | $hr_benchmark->{NAME}, $i_unit_id, | ||||
215 | ); | ||||
216 | $i_benchmark_id = $or_self->{query}->last_insert_id( | ||||
217 | $hr_config->{tables}{benchmark_table}, | ||||
218 | 'bench_id', | ||||
219 | ); | ||||
220 | } | ||||
221 | } | ||||
222 | else { | ||||
223 | require Carp; | ||||
224 | Carp::confess('missing element "NAME"'); | ||||
225 | return 0; | ||||
226 | } | ||||
227 | |||||
228 | 1000 | 2.77ms | if ( | ||
229 | $hr_benchmark->{data} | ||||
230 | && ref( $hr_benchmark->{data} ) eq 'ARRAY' | ||||
231 | && @{$hr_benchmark->{data}} | ||||
232 | ) { | ||||
233 | |||||
234 | my $i_benchmark_subsume_type_id = $or_self->{query} | ||||
235 | ->select_min_subsume_type() | ||||
236 | ->fetchrow_hashref() | ||||
237 | ->{bench_subsume_type_id} | ||||
238 | 1000 | 7.24ms | 3000 | 195ms | ; # spent 150ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_min_subsume_type, avg 150µs/call
# spent 23.7ms making 1000 calls to DBI::st::fetchrow_hashref, avg 24µs/call
# spent 21.4ms making 1000 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 21µs/call |
239 | |||||
240 | 1000 | 568µs | my $i_counter = 1; | ||
241 | 1000 | 2.00ms | for my $hr_point ( @{$hr_benchmark->{data}} ) { | ||
242 | |||||
243 | 1000 | 600µs | if ( not exists $hr_point->{VALUE} ) { | ||
244 | require Carp; | ||||
245 | if ( $hr_options->{force} ) { | ||||
246 | Carp::cluck("missing parameter 'VALUE' in element $i_counter"); | ||||
247 | } | ||||
248 | else { | ||||
249 | Carp::confess("missing parameter 'VALUE' in element $i_counter"); | ||||
250 | } | ||||
251 | } | ||||
252 | |||||
253 | # benchmark value | ||||
254 | $or_self->{query}->insert_benchmark_value( | ||||
255 | $i_benchmark_id, $i_benchmark_subsume_type_id, $hr_point->{VALUE}, | ||||
256 | 1000 | 4.00ms | 1000 | 247ms | ); # spent 247ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::mysql::insert_benchmark_value, avg 247µs/call |
257 | my $i_benchmark_value_id = $or_self->{query}->last_insert_id( | ||||
258 | $hr_config->{tables}{benchmark_value_table}, | ||||
259 | 1000 | 9.48ms | 1000 | 7.75ms | 'bench_value_id', # spent 7.75ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::last_insert_id, avg 8µs/call |
260 | ); | ||||
261 | 1000 | 625µs | $VALUE_ID = $i_benchmark_value_id; | ||
262 | |||||
263 | 1000 | 3.84ms | ADDITIONAL: for my $s_key ( keys %{$hr_point} ) { | ||
264 | |||||
265 | 5650 | 3.25ms | next ADDITIONAL if $s_key eq 'VALUE'; | ||
266 | 4650 | 2.58ms | next ADDITIONAL if not defined $hr_point->{$s_key}; | ||
267 | |||||
268 | # additional type | ||||
269 | 4650 | 939µs | my $i_addtype_id; | ||
270 | 4650 | 1.87ms | if ( $or_self->{cache} ) { | ||
271 | $i_addtype_id = $or_self->{cache}->get("addtype||$s_key"); | ||||
272 | } | ||||
273 | 4650 | 2.00ms | if ( !$i_addtype_id ) { | ||
274 | 4650 | 33.4ms | 13950 | 852ms | if ( # spent 646ms making 4650 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_addtype, avg 139µs/call
# spent 110ms making 4650 calls to DBI::st::fetchrow_hashref, avg 24µs/call
# spent 95.4ms making 4650 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 21µs/call |
275 | my $hr_addtype_select = $or_self->{query} | ||||
276 | ->select_addtype( $s_key ) | ||||
277 | ->fetchrow_hashref() | ||||
278 | ) { | ||||
279 | $i_addtype_id = $hr_addtype_select->{bench_additional_type_id}; | ||||
280 | } | ||||
281 | else { | ||||
282 | $or_self->{query}->insert_addtype( | ||||
283 | $s_key, | ||||
284 | ); | ||||
285 | $i_addtype_id = $or_self->{query}->last_insert_id( | ||||
286 | $hr_config->{tables}{addition_type_table}, | ||||
287 | 'bench_additional_type_id', | ||||
288 | ); | ||||
289 | } | ||||
290 | 4650 | 4.34ms | if ( $or_self->{cache} ) { | ||
291 | $or_self->{cache}->set( "addtype||$s_key" => $i_addtype_id ); | ||||
292 | } | ||||
293 | } | ||||
294 | |||||
295 | # benchmark - additional type - relation | ||||
296 | 4650 | 1.30ms | my $b_inserted = 0; | ||
297 | 4650 | 3.99ms | my $s_addtyperel = "$i_benchmark_id|$i_addtype_id"; | ||
298 | 4650 | 1.21ms | if ( $or_self->{cache} ) { | ||
299 | if ( $or_self->{cache}->get("addtyperel||$s_addtyperel") ) { | ||||
300 | $b_inserted = 1; | ||||
301 | } | ||||
302 | } | ||||
303 | 4650 | 1.98ms | if (! $b_inserted ) { | ||
304 | 4650 | 37.5ms | 13950 | 891ms | if(! # spent 676ms making 4650 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_addtyperelation, avg 145µs/call
# spent 118ms making 4650 calls to DBI::st::fetchrow_hashref, avg 25µs/call
# spent 96.5ms making 4650 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 21µs/call |
305 | $or_self->{query} | ||||
306 | ->select_addtyperelation( $i_benchmark_id, $i_addtype_id ) | ||||
307 | ->fetchrow_hashref() | ||||
308 | ) { | ||||
309 | $or_self->{query} | ||||
310 | ->insert_addtyperelation( $i_benchmark_id, $i_addtype_id ) | ||||
311 | ; | ||||
312 | } | ||||
313 | 4650 | 2.67ms | if ( $or_self->{cache} ) { | ||
314 | $or_self->{cache}->set("addtyperel||$s_addtyperel" => 1 ); | ||||
315 | } | ||||
316 | } | ||||
317 | |||||
318 | # additional value | ||||
319 | 4650 | 836µs | my $i_addvalue_id; | ||
320 | 4650 | 5.05ms | my $s_addvalue_key = "$i_addtype_id|$hr_point->{$s_key}"; | ||
321 | 4650 | 1.08ms | if ( $or_self->{cache} ) { | ||
322 | $i_addvalue_id = $or_self->{cache}->get("addvalue||$s_addvalue_key"); | ||||
323 | } | ||||
324 | 4650 | 1.87ms | if (! $i_addvalue_id ) { | ||
325 | 4650 | 43.8ms | 13950 | 827ms | if ( # spent 651ms making 4650 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_addvalue, avg 140µs/call
# spent 95.7ms making 4650 calls to DBI::st::fetchrow_hashref, avg 21µs/call
# spent 80.4ms making 4650 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 17µs/call |
326 | my $hr_addvalue_select = $or_self->{query} | ||||
327 | ->select_addvalue( $i_addtype_id, $hr_point->{$s_key} ) | ||||
328 | ->fetchrow_hashref() | ||||
329 | ) { | ||||
330 | $i_addvalue_id = $hr_addvalue_select->{bench_additional_value_id}; | ||||
331 | } | ||||
332 | else { | ||||
333 | $or_self->{query}->insert_addvalue( | ||||
334 | 28 | 76µs | 28 | 4.65ms | $i_addtype_id, $hr_point->{$s_key}, # spent 4.65ms making 28 calls to BenchmarkAnything::Storage::Backend::SQL::Query::mysql::insert_addvalue, avg 166µs/call |
335 | ); | ||||
336 | $i_addvalue_id = $or_self->{query}->last_insert_id( | ||||
337 | $hr_config->{tables}{addition_type_table}, | ||||
338 | 28 | 81µs | 28 | 158µs | 'bench_additional_value_id', # spent 158µs making 28 calls to BenchmarkAnything::Storage::Backend::SQL::Query::last_insert_id, avg 6µs/call |
339 | ); | ||||
340 | } | ||||
341 | 4650 | 3.87ms | if ( $or_self->{cache} ) { | ||
342 | $or_self->{cache}->set( "addvalue||$s_addvalue_key" => $i_addvalue_id ); | ||||
343 | } | ||||
344 | } | ||||
345 | |||||
346 | # additional value relation | ||||
347 | $or_self->{query}->insert_addvaluerelation( | ||||
348 | 4650 | 12.6ms | 4650 | 941ms | $i_benchmark_value_id, $i_addvalue_id, # spent 941ms making 4650 calls to BenchmarkAnything::Storage::Backend::SQL::Query::mysql::insert_addvaluerelation, avg 202µs/call |
349 | ); | ||||
350 | |||||
351 | } # ADDITIONAL | ||||
352 | |||||
353 | 1000 | 1.11ms | $i_counter++; | ||
354 | |||||
355 | } | ||||
356 | } | ||||
357 | else { | ||||
358 | require Carp; | ||||
359 | Carp::cluck('no benchmark data found'); | ||||
360 | return 0; | ||||
361 | } | ||||
362 | |||||
363 | 1000 | 4.38ms | 1000 | 1.46ms | if ( $or_self->{searchengine}{elasticsearch}{index_single_added_values_immediately} ) # spent 1.46ms making 1000 calls to Cpanel::JSON::XS::DESTROY, avg 1µs/call |
364 | { | ||||
365 | 1000 | 1.93ms | require BenchmarkAnything::Storage::Search::Elasticsearch; | ||
366 | my ($or_es, $s_index, $s_type) = BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client | ||||
367 | ( | ||||
368 | 1000 | 5.30ms | 1000 | 1.87s | {searchengine => $or_self->{searchengine}, ownjson => 1} # spent 1.87s making 1000 calls to BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client, avg 1.87ms/call |
369 | ); | ||||
370 | |||||
371 | # Sic, we re-read from DB to get the very same data we | ||||
372 | # *really got* stored, not just what we wish it should | ||||
373 | # have stored. That gives us translations like | ||||
374 | # num->string, CREATED date, etc., etc. | ||||
375 | |||||
376 | 1000 | 3.40ms | 1000 | 941ms | my $hr_bmk = $or_self->get_single_benchmark_point($VALUE_ID); # spent 941ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::get_single_benchmark_point, avg 941µs/call |
377 | 1000 | 77.7ms | 1000 | 20.8s | my $ret = $or_es->index(index => $s_index, # spent 20.8s making 1000 calls to Search::Elasticsearch::Role::Client::Direct::__ANON__[Search/Elasticsearch/Role/Client/Direct.pm:140], avg 20.8ms/call |
378 | type => $s_type, | ||||
379 | id => $VALUE_ID, | ||||
380 | body => $hr_bmk); | ||||
381 | } | ||||
382 | |||||
383 | 1000 | 11.2ms | return 1; | ||
384 | |||||
385 | } | ||||
386 | |||||
387 | sub enqueue_multi_benchmark { | ||||
388 | |||||
389 | my ( $or_self, $ar_data_points, $hr_options ) = @_; | ||||
390 | |||||
391 | require Sereal::Encoder; | ||||
392 | |||||
393 | my $s_serialized = Sereal::Encoder->new->encode($ar_data_points); | ||||
394 | $or_self->{query}->insert_raw_bench_bundle($s_serialized); | ||||
395 | |||||
396 | return 1; | ||||
397 | |||||
398 | } | ||||
399 | |||||
400 | # dequeues a single bundle (can contain multiple data points) | ||||
401 | # spent 53.0s (160ms+52.8) within BenchmarkAnything::Storage::Backend::SQL::process_queued_multi_benchmark which was called 1000 times, avg 53.0ms/call:
# 1000 times (160ms+52.8s) by BenchmarkAnything::Storage::Frontend::Lib::process_raw_result_queue at line 689 of BenchmarkAnything/Storage/Frontend/Lib.pm, avg 53.0ms/call | ||||
402 | |||||
403 | 1000 | 1.24ms | my ( $or_self, $hr_options ) = @_; | ||
404 | |||||
405 | 1000 | 409µs | my $i_id; | ||
406 | my $s_serialized; | ||||
407 | my $ar_data_points; | ||||
408 | my $ar_results; | ||||
409 | my $or_result; | ||||
410 | 1000 | 13.5ms | 2000 | 4.32ms | my $driver = $or_self->{query}{dbh}{Driver}{Name}; # spent 4.32ms making 2000 calls to DBI::common::FETCH, avg 2µs/call |
411 | |||||
412 | # ===== exclusively pick single raw entry ===== | ||||
413 | # Lock single row via processing=1 so that only one worker handles it! | ||||
414 | 1000 | 67.2ms | 1000 | 60.5ms | $or_self->{query}{dbh}->do("set transaction isolation level read committed") if $driver eq "mysql"; # avoid deadlocks due to gap locking # spent 60.5ms making 1000 calls to DBI::db::do, avg 61µs/call |
415 | 1000 | 3.84ms | 2000 | 110ms | $or_self->{query}->start_transaction; # spent 108ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::start_transaction, avg 108µs/call
# spent 2.08ms making 1000 calls to DBI::common::STORE, avg 2µs/call |
416 | 1000 | 766µs | eval { | ||
417 | 1000 | 4.17ms | 1000 | 406ms | $ar_results = $or_self->{query}->select_raw_bench_bundle_for_lock; # spent 406ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_raw_bench_bundle_for_lock, avg 406µs/call |
418 | 1000 | 7.14ms | 2000 | 62.7ms | $or_result = $ar_results->fetchrow_hashref; # spent 33.1ms making 1000 calls to DBI::st::fetchrow_hashref, avg 33µs/call
# spent 29.6ms making 1000 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 30µs/call |
419 | 1000 | 644µs | $i_id = $or_result->{raw_bench_bundle_id}; | ||
420 | 1000 | 407µs | if (!$i_id) { | ||
421 | $or_self->{query}->finish_transaction( $@ ); | ||||
422 | $or_self->{query}{dbh}->do("set transaction isolation level repeatable read") if $driver eq "mysql"; # reset to normal gap locking | ||||
423 | goto RETURN ; | ||||
424 | } | ||||
425 | 1000 | 2.96ms | 1000 | 335ms | $or_self->{query}->start_processing_raw_bench_bundle($i_id); # spent 335ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::start_processing_raw_bench_bundle, avg 335µs/call |
426 | }; | ||||
427 | 1000 | 2.62ms | 1000 | 11.8s | $or_self->{query}->finish_transaction( $@ ); # spent 11.8s making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::finish_transaction, avg 11.8ms/call |
428 | 1000 | 73.8ms | 1000 | 60.2ms | $or_self->{query}{dbh}->do("set transaction isolation level repeatable read") if $driver eq "mysql"; # reset to normal gap locking # spent 60.2ms making 1000 calls to DBI::db::do, avg 60µs/call |
429 | |||||
430 | # ===== process that single raw entry ===== | ||||
431 | 1000 | 4.27ms | 2000 | 106ms | $or_self->{query}->start_transaction; # spent 104ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::start_transaction, avg 104µs/call
# spent 2.12ms making 1000 calls to DBI::common::STORE, avg 2µs/call |
432 | 1000 | 885µs | eval { | ||
433 | 1000 | 1.68ms | require Sereal::Decoder; | ||
434 | |||||
435 | 1000 | 5.04ms | 1000 | 251ms | $ar_results = $or_self->{query}->select_raw_bench_bundle_for_processing($i_id); # spent 251ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_raw_bench_bundle_for_processing, avg 251µs/call |
436 | 1000 | 15.8ms | 2000 | 65.9ms | $s_serialized = $ar_results->fetchrow_hashref->{raw_bench_bundle_serialized}; # spent 37.6ms making 1000 calls to DBI::st::fetchrow_hashref, avg 38µs/call
# spent 28.3ms making 1000 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 28µs/call |
437 | 1000 | 28.6ms | 1000 | 14.1ms | $ar_data_points = Sereal::Decoder::decode_sereal($s_serialized); # spent 14.1ms making 1000 calls to Sereal::Decoder::decode_sereal, avg 14µs/call |
438 | |||||
439 | # preserve order, otherwise add_multi_benchmark() would reorder to optimize insert | ||||
440 | 1000 | 6.77ms | 1000 | 28.0s | $or_self->add_multi_benchmark([$_], $hr_options) foreach @$ar_data_points; # spent 28.0s making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::add_multi_benchmark, avg 28.0ms/call |
441 | 1000 | 6.00ms | 1000 | 673ms | $or_self->{query}->update_raw_bench_bundle_set_processed($i_id); # spent 673ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::update_raw_bench_bundle_set_processed, avg 673µs/call |
442 | }; | ||||
443 | 1000 | 3.54ms | 1000 | 11.0s | $or_self->{query}->finish_transaction( $@ ); # spent 11.0s making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::finish_transaction, avg 11.0ms/call |
444 | |||||
445 | 1000 | 11.6ms | RETURN: | ||
446 | return $@ ? undef : $i_id; | ||||
447 | |||||
448 | } | ||||
449 | |||||
450 | # garbage collect - initially raw_bench_bundles, but also other stuff. | ||||
451 | # spent 34.3ms (52µs+34.3) within BenchmarkAnything::Storage::Backend::SQL::gc which was called:
# once (52µs+34.3ms) by BenchmarkAnything::Storage::Frontend::Lib::gc at line 671 of BenchmarkAnything/Storage/Frontend/Lib.pm | ||||
452 | |||||
453 | 1 | 1µs | my ( $or_self, $hr_options ) = @_; | ||
454 | |||||
455 | 1 | 12µs | 1 | 23.5ms | $or_self->{query}->delete_processed_raw_bench_bundles; # spent 23.5ms making 1 call to BenchmarkAnything::Storage::Backend::SQL::Query::common::delete_processed_raw_bench_bundles |
456 | 1 | 8µs | if ($or_self->{searchengine}{elasticsearch}{enable_query}) { | ||
457 | 1 | 1µs | require BenchmarkAnything::Storage::Search::Elasticsearch; | ||
458 | my ($or_es, $s_index, $s_type) = BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client | ||||
459 | ( | ||||
460 | {searchengine => $or_self->{searchengine}} | ||||
461 | 1 | 6µs | 1 | 3.75ms | ); # spent 3.75ms making 1 call to BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client |
462 | 1 | 9µs | 2 | 3.81ms | $or_es->indices->clear_cache(index => $s_index); # spent 3.54ms making 1 call to Search::Elasticsearch::Role::Client::Direct::__ANON__[Search/Elasticsearch/Role/Client/Direct.pm:140]
# spent 273µs making 1 call to Search::Elasticsearch::Client::5_0::Direct::indices |
463 | } | ||||
464 | } | ||||
465 | |||||
466 | # spent 28.0s (34.7ms+27.9) within BenchmarkAnything::Storage::Backend::SQL::add_multi_benchmark which was called 1000 times, avg 28.0ms/call:
# 1000 times (34.7ms+27.9s) by BenchmarkAnything::Storage::Backend::SQL::process_queued_multi_benchmark at line 440, avg 28.0ms/call | ||||
467 | |||||
468 | 1000 | 706µs | my ( $or_self, $ar_data_points, $hr_options ) = @_; | ||
469 | |||||
470 | 1000 | 587µs | my $i_counter = 1; | ||
471 | 1000 | 1.37ms | my %h_benchmarks = (); | ||
472 | 1000 | 1.14ms | for my $hr_data_point ( @{$ar_data_points} ) { | ||
473 | |||||
474 | 1000 | 989µs | for my $s_param (qw/ NAME VALUE /) { | ||
475 | 2000 | 1.72ms | if ( not exists $hr_data_point->{$s_param} ) { | ||
476 | require Carp; | ||||
477 | if ( $hr_options->{force} ) { | ||||
478 | Carp::cluck("missing parameter '$s_param' in element $i_counter"); | ||||
479 | } | ||||
480 | else { | ||||
481 | Carp::confess("missing parameter '$s_param' in element $i_counter"); | ||||
482 | } | ||||
483 | } | ||||
484 | } | ||||
485 | |||||
486 | 1000 | 2.78ms | my ( $s_name, $s_unit ) = delete @{$hr_data_point}{qw/ NAME UNIT /}; | ||
487 | |||||
488 | 1000 | 4.58ms | if (! $h_benchmarks{$s_name} ) { | ||
489 | $h_benchmarks{$s_name} = { | ||||
490 | NAME => $s_name, | ||||
491 | UNIT => $s_unit, | ||||
492 | data => [], | ||||
493 | }; | ||||
494 | } | ||||
495 | else { | ||||
496 | $h_benchmarks{$s_name}{UNIT} ||= $s_unit; | ||||
497 | } | ||||
498 | |||||
499 | 1000 | 1.92ms | push @{$h_benchmarks{$s_name}{data}}, $hr_data_point; | ||
500 | |||||
501 | 1000 | 828µs | $i_counter++; | ||
502 | |||||
503 | } | ||||
504 | 1000 | 1.53ms | for my $hr_benchmark ( values %h_benchmarks ) { | ||
505 | 1000 | 3.79ms | 1000 | 27.9s | $or_self->add_single_benchmark( $hr_benchmark, $hr_options ); # spent 27.9s making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::add_single_benchmark, avg 27.9ms/call |
506 | } | ||||
507 | |||||
508 | 1000 | 5.81ms | return 1; | ||
509 | |||||
510 | } | ||||
511 | |||||
512 | sub search { | ||||
513 | |||||
514 | my ( $or_self, $hr_search ) = @_; | ||||
515 | |||||
516 | return $or_self->{query}->select_benchmark_values( | ||||
517 | $hr_search | ||||
518 | ); | ||||
519 | |||||
520 | } | ||||
521 | |||||
522 | sub list_benchmark_names { | ||||
523 | |||||
524 | my ( $or_self, $s_pattern ) = @_; | ||||
525 | |||||
526 | my $ar_pattern = defined($s_pattern) ? [$s_pattern] : []; | ||||
527 | |||||
528 | my $s_key; | ||||
529 | if ( $or_self->{cache} ) { | ||||
530 | require JSON::XS; | ||||
531 | $s_key = JSON::XS::encode_json($ar_pattern); | ||||
532 | if ( my $ar_search_data = $or_self->{cache}->get("list_benchmark_names||$s_key") ) { | ||||
533 | return $ar_search_data; | ||||
534 | } | ||||
535 | } | ||||
536 | |||||
537 | my $ar_result = $or_self->{query} | ||||
538 | ->select_benchmark_names( @$ar_pattern ) | ||||
539 | ->fetchall_arrayref([0]); | ||||
540 | my $ar_benchmark_names = [ map { $_->[0] } @$ar_result ]; | ||||
541 | |||||
542 | if ( $or_self->{cache} ) { | ||||
543 | $or_self->{cache}->set( "list_benchmark_names||$s_key" => $ar_benchmark_names ); | ||||
544 | } | ||||
545 | |||||
546 | return $ar_benchmark_names; | ||||
547 | |||||
548 | } | ||||
549 | |||||
550 | sub list_additional_keys { | ||||
551 | |||||
552 | my ( $or_self, $s_pattern ) = @_; | ||||
553 | |||||
554 | my $ar_pattern = defined($s_pattern) ? [$s_pattern] : []; | ||||
555 | |||||
556 | my $s_key; | ||||
557 | if ( $or_self->{cache} ) { | ||||
558 | require JSON::XS; | ||||
559 | $s_key = JSON::XS::encode_json($ar_pattern); | ||||
560 | if ( my $ar_search_data = $or_self->{cache}->get("list_additional_keys||$s_key") ) { | ||||
561 | return $ar_search_data; | ||||
562 | } | ||||
563 | } | ||||
564 | |||||
565 | my $ar_result = $or_self->{query} | ||||
566 | ->select_additional_keys( @$ar_pattern ) | ||||
567 | ->fetchall_arrayref([0]); | ||||
568 | my $ar_key_names = [ map { $_->[0] } @$ar_result ]; | ||||
569 | |||||
570 | if ( $or_self->{cache} ) { | ||||
571 | $or_self->{cache}->set( "list_additional_keys||$s_key" => $ar_key_names ); | ||||
572 | } | ||||
573 | |||||
574 | return $ar_key_names; | ||||
575 | |||||
576 | } | ||||
577 | |||||
578 | sub get_stats { | ||||
579 | |||||
580 | my ( $or_self ) = @_; | ||||
581 | |||||
582 | my %h_searchengine_stats = (); | ||||
583 | my %h_flat_searchengine_stats = (); | ||||
584 | my %stats = (); | ||||
585 | |||||
586 | # Not strictly *stats* but useful information. | ||||
587 | if ( $or_self->{searchengine}{elasticsearch}{index} ) | ||||
588 | { | ||||
589 | require BenchmarkAnything::Storage::Search::Elasticsearch; | ||||
590 | my ($or_es, $s_index, $s_type) = BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client | ||||
591 | ( | ||||
592 | {searchengine => $or_self->{searchengine}} | ||||
593 | ); | ||||
594 | |||||
595 | $stats{count_datapoints} = (map {chomp; $_} split(qr/ +/, $or_es->cat->count))[2]; | ||||
596 | %h_searchengine_stats = | ||||
597 | ( | ||||
598 | index => $or_self->{searchengine}{elasticsearch}{index} || 'UNKNOWN', | ||||
599 | type => $or_self->{searchengine}{elasticsearch}{type} || 'UNKNOWN', | ||||
600 | enable_query => $or_self->{searchengine}{elasticsearch}{enable_query} || 0, | ||||
601 | cluster_health => $or_es->cluster->health, | ||||
602 | index_single_added_values_immediately => $or_self->{searchengine}{elasticsearch}{index_single_added_values_immediately} || 0, | ||||
603 | ); | ||||
604 | # boolean -> 0/1 | ||||
605 | for (values %{$h_searchengine_stats{elasticsearch}{cluster_health}}) { | ||||
606 | $_ = $_ ? 1 : 0 if ref eq 'JSON::XS::Boolean'; | ||||
607 | } | ||||
608 | $h_flat_searchengine_stats{"elasticsearch_$_"} = $h_searchengine_stats{$_} | ||||
609 | for qw(index type enable_query index_single_added_values_immediately); | ||||
610 | $h_flat_searchengine_stats{"elasticsearch_cluster_health_$_"} = $h_searchengine_stats{cluster_health}{$_} | ||||
611 | for qw(cluster_name active_shards_percent_as_number active_primary_shards number_of_nodes status); | ||||
612 | } | ||||
613 | |||||
614 | $stats{count_datapoints} ||= 0+$or_self->{query}->select_count_datapoints->fetch->[0]; | ||||
615 | $stats{count_datapointkeys} = 0+$or_self->{query}->select_count_datapointkeys->fetch->[0] if $or_self->{verbose}; | ||||
616 | $stats{count_metrics} = 0+$or_self->{query}->select_count_metrics->fetch->[0] if $or_self->{verbose}; | ||||
617 | $stats{count_keys} = 0+$or_self->{query}->select_count_keys->fetch->[0] if $or_self->{verbose}; | ||||
618 | |||||
619 | %stats = (%stats, %h_flat_searchengine_stats); | ||||
620 | |||||
621 | return \%stats; | ||||
622 | } | ||||
623 | |||||
624 | # spent 941ms (31.0+910) within BenchmarkAnything::Storage::Backend::SQL::get_single_benchmark_point which was called 1000 times, avg 941µs/call:
# 1000 times (31.0ms+910ms) by BenchmarkAnything::Storage::Backend::SQL::add_single_benchmark at line 376, avg 941µs/call | ||||
625 | |||||
626 | 1000 | 618µs | my ( $or_self, $i_bench_value_id ) = @_; | ||
627 | |||||
628 | 1000 | 359µs | return {} unless $i_bench_value_id; | ||
629 | |||||
630 | # cache? | ||||
631 | 1000 | 291µs | my $s_key; | ||
632 | 1000 | 705µs | if ( $or_self->{cache} ) { | ||
633 | require JSON::XS; | ||||
634 | $s_key = JSON::XS::encode_json({bench_value_id => $i_bench_value_id}); | ||||
635 | if ( my $hr_search_data = $or_self->{cache}->get("get_single_benchmark_point||$s_key") ) { | ||||
636 | return $hr_search_data; | ||||
637 | } | ||||
638 | } | ||||
639 | |||||
640 | # fetch all additional key/value fields | ||||
641 | my $ar_query_result = $or_self->{query} | ||||
642 | 1000 | 14.5ms | 3000 | 769ms | ->select_complete_benchmark_point( $i_bench_value_id ) # spent 534ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_complete_benchmark_point, avg 534µs/call
# spent 121ms making 1000 calls to DBI::st::fetchall_arrayref, avg 121µs/call
# spent 113ms making 1000 calls to DBD::_::st::fetchall_arrayref, avg 113µs/call |
643 | ->fetchall_arrayref({}); | ||||
644 | |||||
645 | # fetch essentials, like NAME, VALUE, UNIT | ||||
646 | my $hr_essentials = $or_self->{query} | ||||
647 | 1000 | 8.03ms | 3000 | 276ms | ->select_benchmark_point_essentials( $i_bench_value_id ) # spent 230ms making 1000 calls to BenchmarkAnything::Storage::Backend::SQL::Query::common::select_benchmark_point_essentials, avg 230µs/call
# spent 24.8ms making 1000 calls to DBI::st::fetchrow_hashref, avg 25µs/call
# spent 21.8ms making 1000 calls to DBD::mysql::st::__ANON__[DBD/mysql.pm:870], avg 22µs/call |
648 | ->fetchrow_hashref(); | ||||
649 | |||||
650 | # create complete BenchmarkAnything-like key/value entry | ||||
651 | 1000 | 334µs | my $hr_result; | ||
652 | 1000 | 6.23ms | $hr_result = { map { ($_->{bench_additional_type} => $_->{bench_additional_value} ) } @$ar_query_result }; | ||
653 | 1000 | 1.18ms | $hr_result->{NAME} = $hr_essentials->{bench}; | ||
654 | 1000 | 747µs | $hr_result->{VALUE} = $hr_essentials->{bench_value}; | ||
655 | 1000 | 986µs | $hr_result->{VALUE_ID} = $hr_essentials->{bench_value_id}; | ||
656 | 1000 | 890µs | $hr_result->{CREATED} = $hr_essentials->{created_at}; | ||
657 | 1000 | 408µs | $hr_result->{UNIT} = $hr_essentials->{bench_unit} if $hr_essentials->{bench_unit}; | ||
658 | |||||
659 | # cache! | ||||
660 | 1000 | 447µs | if ( $or_self->{cache} ) { | ||
661 | $or_self->{cache}->set( "get_single_benchmark_point||$s_key" => $hr_result ); | ||||
662 | } | ||||
663 | |||||
664 | 1000 | 4.73ms | return $hr_result; | ||
665 | } | ||||
666 | |||||
667 | sub get_full_benchmark_points { | ||||
668 | |||||
669 | my ( $or_self, $i_bench_value_id, $i_count ) = @_; | ||||
670 | |||||
671 | return [] unless $i_bench_value_id; | ||||
672 | |||||
673 | $i_count ||= 1; | ||||
674 | |||||
675 | # cache? | ||||
676 | my $s_key; | ||||
677 | if ( $or_self->{cache} ) { | ||||
678 | require JSON::XS; | ||||
679 | $s_key = JSON::XS::encode_json({bench_value_id => $i_bench_value_id}); | ||||
680 | if ( my $hr_search_data = $or_self->{cache}->get("get_full_benchmark_points||$s_key") ) { | ||||
681 | return $hr_search_data; | ||||
682 | } | ||||
683 | } | ||||
684 | |||||
685 | # fetch essentials, like NAME, VALUE, UNIT | ||||
686 | my $ar_essentials = $or_self->{query} | ||||
687 | ->select_multiple_benchmark_points_essentials($i_bench_value_id, $i_count) | ||||
688 | ->fetchall_arrayref({}); | ||||
689 | # additional key/value pairs | ||||
690 | my $ar_additional_values = $or_self->{query} | ||||
691 | ->select_multiple_benchmark_points_additionals($i_bench_value_id, $i_count) | ||||
692 | ->fetchall_arrayref({}); | ||||
693 | |||||
694 | # map columns into BenchmarkAnything schema | ||||
695 | my $hr_bmk; | ||||
696 | foreach my $k (keys %$hr_column_ba_mapping) | ||||
697 | { | ||||
698 | my $K = $hr_column_ba_mapping->{$k}; | ||||
699 | foreach my $e (@$ar_essentials) { | ||||
700 | $hr_bmk->{$e->{bench_value_id}}{$K} = $e->{$k} if $k ne 'bench_unit' or defined $e->{$k}; | ||||
701 | } | ||||
702 | } | ||||
703 | foreach (@$ar_additional_values) { | ||||
704 | $hr_bmk->{$_->{bench_value_id}}{$_->{bench_additional_type}} = $_->{bench_additional_value}; | ||||
705 | } | ||||
706 | # sorted (by VALUE_ID) array of BenchmarkAnything entries | ||||
707 | my @a_bmk = map { $hr_bmk->{$_} } sort keys %$hr_bmk; | ||||
708 | |||||
709 | # cache! | ||||
710 | if ( $or_self->{cache} ) { | ||||
711 | $or_self->{cache}->set( "get_full_benchmark_points||$s_key" => \@a_bmk ); | ||||
712 | } | ||||
713 | |||||
714 | return \@a_bmk; | ||||
715 | } | ||||
716 | |||||
717 | sub search_array { | ||||
718 | |||||
719 | my ( $or_self, $hr_search ) = @_; | ||||
720 | |||||
721 | my $debug = $or_self->{debug} || $or_self->{searchengine}{elasticsearch}{debug}; | ||||
722 | |||||
723 | my $s_key; | ||||
724 | if ( $or_self->{cache} ) { | ||||
725 | require JSON::XS; | ||||
726 | $s_key = JSON::XS::encode_json($hr_search); | ||||
727 | if ( my $ar_search_data = $or_self->{cache}->get("search_array||$s_key") ) { | ||||
728 | return $ar_search_data; | ||||
729 | } | ||||
730 | } | ||||
731 | |||||
732 | if ( $debug ) | ||||
733 | { | ||||
734 | require JSON::XS; | ||||
735 | require Data::Dumper; | ||||
736 | print STDERR ',-------------------'."\n"; | ||||
737 | print STDERR "benchmarkanything query:\n"; | ||||
738 | print STDERR "benchmarkanything-storage search -d '\n"; | ||||
739 | print STDERR JSON::XS->new->pretty->encode($hr_search); | ||||
740 | print STDERR "'\n"; | ||||
741 | print STDERR '`-------------------'."\n"; | ||||
742 | } | ||||
743 | |||||
744 | if ( $or_self->{searchengine}{elasticsearch}{enable_query} ) | ||||
745 | { | ||||
746 | # If anything goes wrong with Elasticsearch we just continue | ||||
747 | # below with relational backend query. | ||||
748 | |||||
749 | require BenchmarkAnything::Storage::Search::Elasticsearch; | ||||
750 | my $hr_es_query = BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_query($hr_search); | ||||
751 | |||||
752 | if ($debug) | ||||
753 | { | ||||
754 | require JSON::XS; | ||||
755 | require Data::Dumper; | ||||
756 | print STDERR ',-------------------'."\n"; | ||||
757 | print STDERR "elasticsearch query:\n"; | ||||
758 | print STDERR "curl -s -XGET 'http://localhost:9200/tapper/benchmarkanything/_search?pretty' -d '\n"; | ||||
759 | print STDERR JSON::XS->new->pretty->encode($hr_es_query); | ||||
760 | print STDERR "'\n"; | ||||
761 | print STDERR '`-------------------'."\n"; | ||||
762 | } | ||||
763 | |||||
764 | # If we could transform the query then we run it against Elasticsearch and return its result. | ||||
765 | if (defined $hr_es_query) | ||||
766 | { | ||||
767 | # ===== client ===== | ||||
768 | |||||
769 | require BenchmarkAnything::Storage::Search::Elasticsearch; | ||||
770 | my ($or_es, $s_index, $s_type) = BenchmarkAnything::Storage::Search::Elasticsearch::get_elasticsearch_client | ||||
771 | ( | ||||
772 | {searchengine => $or_self->{searchengine}, ownjson => 1} | ||||
773 | ); | ||||
774 | |||||
775 | # ===== prepare ===== | ||||
776 | |||||
777 | # If sort fields are of type 'text' then those fields needs to | ||||
778 | # get their properties being declared as "fielddata":true. | ||||
779 | my $field_mapping = {}; | ||||
780 | my @sort_fields = map {keys %$_} @{$hr_es_query->{sort}||[]}; | ||||
781 | if (@sort_fields) { | ||||
782 | $field_mapping = $or_es->indices->get_mapping->{$s_index}{mappings}{$s_type}{properties}; | ||||
783 | } | ||||
784 | foreach my $sort_field (@sort_fields) | ||||
785 | { | ||||
786 | if ($field_mapping->{$sort_field}{type} and $field_mapping->{$sort_field}{type} eq 'text') | ||||
787 | { | ||||
788 | require BenchmarkAnything::Storage::Backend::SQL::Search; | ||||
789 | $or_es->indices->put_mapping | ||||
790 | ( | ||||
791 | index => $s_index, | ||||
792 | type => $s_type, | ||||
793 | body => { $s_type => { properties => { $sort_field => { type => 'text', | ||||
794 | fielddata => BenchmarkAnything::Storage::Backend::SQL::Search::json_true(), | ||||
795 | }}}} | ||||
796 | ); | ||||
797 | } | ||||
798 | } | ||||
799 | |||||
800 | # ===== search ===== | ||||
801 | my $hr_es_answer = $or_es->search(index => $s_index, type => $s_type, body => $hr_es_query); | ||||
802 | |||||
803 | if ( | ||||
804 | !$hr_es_answer->{timed_out} and | ||||
805 | !$hr_es_answer->{_shards}{failed} | ||||
806 | ) | ||||
807 | { | ||||
808 | my @ar_es_result = map { $_->{_source} } @{$hr_es_answer->{hits}{hits} || []}; | ||||
809 | return \@ar_es_result; | ||||
810 | } | ||||
811 | } | ||||
812 | |||||
813 | # Else no-op, continue with relational backend query. | ||||
814 | } | ||||
815 | |||||
816 | my $ar_result = $or_self | ||||
817 | ->search( $hr_search ) | ||||
818 | ->fetchall_arrayref({}) | ||||
819 | ; | ||||
820 | |||||
821 | if ( $or_self->{cache} ) { | ||||
822 | $or_self->{cache}->set( "search_array||$s_key" => $ar_result ); | ||||
823 | } | ||||
824 | |||||
825 | return $ar_result; | ||||
826 | |||||
827 | } | ||||
828 | |||||
829 | sub search_hash { | ||||
830 | |||||
831 | my ( $or_self, $hr_search ) = @_; | ||||
832 | |||||
833 | my $s_key; | ||||
834 | if ( $or_self->{cache} ) { | ||||
835 | require JSON::XS; | ||||
836 | $s_key = JSON::XS::encode_json($hr_search); | ||||
837 | if ( my $hr_search_data = $or_self->{cache}->get( "search_hash||$s_key" ) ) { | ||||
838 | return $hr_search_data; | ||||
839 | } | ||||
840 | } | ||||
841 | |||||
842 | if (! $hr_search->{keys} ) { | ||||
843 | require Carp; | ||||
844 | Carp::confess(q#cannot get hash search result without 'keys'#); | ||||
845 | return; | ||||
846 | } | ||||
847 | |||||
848 | my $hr_result = $or_self | ||||
849 | ->search( $hr_search ) | ||||
850 | ->fetchall_hashref($hr_search->{keys}) | ||||
851 | ; | ||||
852 | |||||
853 | if ( $or_self->{cache} ) { | ||||
854 | $or_self->{cache}->set( "search_hash||$s_key" => $hr_result ) | ||||
855 | } | ||||
856 | |||||
857 | return $hr_result; | ||||
858 | |||||
859 | } | ||||
860 | |||||
861 | sub subsume { | ||||
862 | |||||
863 | my ( $or_self, $hr_options ) = @_; | ||||
864 | |||||
865 | for my $s_parameter (qw/ subsume_type /) { | ||||
866 | if (! $hr_options->{$s_parameter}) { | ||||
867 | require Carp; | ||||
868 | Carp::confess("missing parameter '$s_parameter'"); | ||||
869 | return; | ||||
870 | } | ||||
871 | } | ||||
872 | |||||
873 | # check if subsume type exists | ||||
874 | my $hr_subsume_type = $or_self->{query} | ||||
875 | ->select_subsume_type( $hr_options->{subsume_type} ) | ||||
876 | ->fetchrow_hashref() | ||||
877 | ; | ||||
878 | if (! $hr_subsume_type ) { | ||||
879 | require Carp; | ||||
880 | Carp::confess("subsume type '$hr_options->{subsume_type}' not exists"); | ||||
881 | return; | ||||
882 | } | ||||
883 | if ( $hr_subsume_type->{bench_subsume_type_rank} == 1 ) { | ||||
884 | require Carp; | ||||
885 | Carp::confess("cannot subsume with type '$hr_options->{subsume_type}'"); | ||||
886 | return; | ||||
887 | } | ||||
888 | |||||
889 | # looking for values with with a higher rank subsume type | ||||
890 | if ( | ||||
891 | $or_self->{query} | ||||
892 | ->select_check_subsumed_values({ | ||||
893 | date_to => $hr_options->{date_to}, | ||||
894 | date_from => $hr_options->{date_from}, | ||||
895 | subsume_type_id => $hr_subsume_type->{bench_subsume_type_id}, | ||||
896 | }) | ||||
897 | ->rows() | ||||
898 | ) { | ||||
899 | require Carp; | ||||
900 | Carp::confess( | ||||
901 | "cannot use subsume type '$hr_options->{subsume_type}' " . | ||||
902 | 'because a higher rank subsume type is already used for this date period' | ||||
903 | ); | ||||
904 | return; | ||||
905 | } | ||||
906 | |||||
907 | # look if excluded additional types really exists | ||||
908 | my @a_excluded_adds; | ||||
909 | if ( $hr_options->{exclude_additionals} ) { | ||||
910 | for my $s_additional_type ( @{$hr_options->{exclude_additionals}} ) { | ||||
911 | if ( | ||||
912 | my $hr_addtype = $or_self->{query} | ||||
913 | ->select_addtype( $s_additional_type ) | ||||
914 | ->fetchrow_hashref() | ||||
915 | ) { | ||||
916 | push @a_excluded_adds, $hr_addtype->{bench_additional_type_id} | ||||
917 | } | ||||
918 | else { | ||||
919 | require Carp; | ||||
920 | Carp::confess( "additional type '$s_additional_type' not exists" ); | ||||
921 | return; | ||||
922 | } | ||||
923 | } | ||||
924 | } | ||||
925 | |||||
926 | # get all data points for subsume | ||||
927 | my $or_data_values = $or_self->{query}->select_data_values_for_subsume({ | ||||
928 | date_to => $hr_options->{date_to}, | ||||
929 | date_from => $hr_options->{date_from}, | ||||
930 | exclude_additionals => \@a_excluded_adds, | ||||
931 | subsume_type_id => $hr_subsume_type->{bench_subsume_type_id}, | ||||
932 | }); | ||||
933 | |||||
934 | require DateTime::Format::Strptime; | ||||
935 | my $or_strp = DateTime::Format::Strptime->new( pattern => '%F %T', ); | ||||
936 | |||||
937 | my @a_rows; | ||||
938 | my $i_counter = 0; | ||||
939 | my $i_sum_value = 0; | ||||
940 | my $b_backup = ((not exists $hr_options->{backup}) || $hr_options->{backup}) ? 1 : 0; | ||||
941 | my $s_last_key = q##; | ||||
942 | |||||
943 | while ( my $hr_values = $or_data_values->fetchrow_hashref() ) { | ||||
944 | |||||
945 | my $s_act_key = join '__', | ||||
946 | $hr_values->{bench_id}, | ||||
947 | $or_strp->parse_datetime( $hr_values->{created_at} )->strftime( $hr_subsume_type->{datetime_strftime_pattern} ), | ||||
948 | $hr_values->{additionals} || q##, | ||||
949 | ; | ||||
950 | |||||
951 | if ( $s_last_key ne $s_act_key ) { | ||||
952 | |||||
953 | if ( $i_counter ) { | ||||
954 | $or_self->$fn_add_subsumed_point({ | ||||
955 | rows => \@a_rows, | ||||
956 | VALUE => $i_sum_value / $i_counter, | ||||
957 | backup => $b_backup, | ||||
958 | type_id => $hr_subsume_type->{bench_subsume_type_id} | ||||
959 | }); | ||||
960 | } | ||||
961 | |||||
962 | @a_rows = (); | ||||
963 | $i_counter = 0; | ||||
964 | $i_sum_value = 0; | ||||
965 | $s_last_key = $s_act_key; | ||||
966 | |||||
967 | } | ||||
968 | |||||
969 | $i_counter += 1; | ||||
970 | $i_sum_value += $hr_values->{bench_value}; | ||||
971 | |||||
972 | push @a_rows, $hr_values; | ||||
973 | |||||
974 | } | ||||
975 | |||||
976 | if ( $i_counter ) { | ||||
977 | $or_self->$fn_add_subsumed_point({ | ||||
978 | rows => \@a_rows, | ||||
979 | VALUE => $i_sum_value / $i_counter, | ||||
980 | backup => $b_backup, | ||||
981 | type_id => $hr_subsume_type->{bench_subsume_type_id} | ||||
982 | }); | ||||
983 | } | ||||
984 | |||||
985 | return 1; | ||||
986 | |||||
987 | } | ||||
988 | |||||
989 | sub _get_additional_key_id { | ||||
990 | |||||
991 | my ( $or_self, $s_key ) = @_; | ||||
992 | |||||
993 | return $or_self->{query}->select_additional_key_id($s_key)->fetch->[0]; | ||||
994 | |||||
995 | } | ||||
996 | |||||
997 | sub default_columns { | ||||
998 | |||||
999 | my ( $or_self ) = @_; | ||||
1000 | |||||
1001 | return $or_self->{query}->default_columns; | ||||
1002 | |||||
1003 | } | ||||
1004 | |||||
1005 | sub benchmark_operators { | ||||
1006 | |||||
1007 | my ( $or_self ) = @_; | ||||
1008 | |||||
1009 | return $or_self->{query}->benchmark_operators; | ||||
1010 | |||||
1011 | } | ||||
1012 | |||||
1013 | sub init_search_engine | ||||
1014 | { | ||||
1015 | my ( $or_self, $b_force) = @_; | ||||
1016 | |||||
1017 | require BenchmarkAnything::Storage::Backend::SQL::Search; | ||||
1018 | BenchmarkAnything::Storage::Backend::SQL::Search::init_search_engine (@_); | ||||
1019 | } | ||||
1020 | |||||
1021 | sub sync_search_engine | ||||
1022 | { | ||||
1023 | require BenchmarkAnything::Storage::Backend::SQL::Search; | ||||
1024 | BenchmarkAnything::Storage::Backend::SQL::Search::sync_search_engine (@_); | ||||
1025 | } | ||||
1026 | |||||
1027 | 1 | 4µs | 1; | ||
1028 | |||||
1029 | __END__ |