Force Unix EOL even on Windows (i.e. Cygwin).
[gem5.git] / base / stats / mysql.cc
1 /*
2 * Copyright (c) 2004-2005 The Regents of The University of Michigan
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met: redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer;
9 * redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution;
12 * neither the name of the copyright holders nor the names of its
13 * contributors may be used to endorse or promote products derived from
14 * this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <cassert>
30 #include <map>
31 #include <sstream>
32 #include <string>
33 #include <vector>
34
35 #include "base/misc.hh"
36 #include "base/mysql.hh"
37 #include "base/statistics.hh"
38 #include "base/stats/flags.hh"
39 #include "base/stats/mysql.hh"
40 #include "base/stats/mysql_run.hh"
41 #include "base/stats/statdb.hh"
42 #include "base/stats/types.hh"
43 #include "base/str.hh"
44 #include "sim/host.hh"
45
46 using namespace std;
47
48 namespace Stats {
49
50 MySqlRun MySqlDB;
51
52 bool
53 MySqlConnected()
54 {
55 return MySqlDB.connected();
56 }
57
58 void
59 MySqlRun::connect(const string &host, const string &user, const string &passwd,
60 const string &db, const string &name, const string &sample,
61 const string &project)
62 {
63 if (connected())
64 panic("can only get one database connection at this time!");
65
66 mysql.connect(host, user, passwd, db);
67 if (mysql.error)
68 panic("could not connect to database server\n%s\n", mysql.error);
69
70 if (mysql.autocommit(false))
71 panic("could not set autocommit\n");
72
73 remove(name);
74 //cleanup();
75 setup(name, sample, user, project);
76 if (mysql.commit())
77 panic("could not commit transaction\n");
78 }
79
80 void
81 MySqlRun::setup(const string &name, const string &sample, const string &user,
82 const string &project)
83 {
84 assert(mysql.connected());
85
86 stringstream insert;
87 ccprintf(insert,
88 "INSERT INTO "
89 "runs(rn_name,rn_sample,rn_user,rn_project,rn_date,rn_expire)"
90 "values(\"%s\", \"%s\", \"%s\", \"%s\", NOW(),"
91 "DATE_ADD(CURDATE(), INTERVAL 31 DAY))",
92 name, sample, user, project);
93
94 mysql.query(insert);
95 if (mysql.error)
96 panic("could not get a run\n%s\n", mysql.error);
97
98 run_id = mysql.insert_id();
99 }
100
101 void
102 MySqlRun::remove(const string &name)
103 {
104 assert(mysql.connected());
105 stringstream sql;
106 ccprintf(sql, "DELETE FROM runs WHERE rn_name=\"%s\"", name);
107 mysql.query(sql);
108 if (mysql.error)
109 panic("could not delete run\n%s\n", mysql.error);
110 }
111
112 void
113 MySqlRun::cleanup()
114 {
115 assert(mysql.connected());
116
117 mysql.query("DELETE data "
118 "FROM data "
119 "LEFT JOIN runs ON dt_run=rn_id "
120 "WHERE rn_id IS NULL");
121
122 mysql.query("DELETE formula_ref "
123 "FROM formula_ref "
124 "LEFT JOIN runs ON fr_run=rn_id "
125 "WHERE rn_id IS NULL");
126
127 mysql.query("DELETE formulas "
128 "FROM formulas "
129 "LEFT JOIN formula_ref ON fm_stat=fr_stat "
130 "WHERE fr_stat IS NULL");
131
132 mysql.query("DELETE stats "
133 "FROM stats "
134 "LEFT JOIN data ON st_id=dt_stat "
135 "WHERE dt_stat IS NULL");
136
137 mysql.query("DELETE subdata "
138 "FROM subdata "
139 "LEFT JOIN data ON sd_stat=dt_stat "
140 "WHERE dt_stat IS NULL");
141
142 mysql.query("DELETE bins "
143 "FROM bins "
144 "LEFT JOIN data ON bn_id=dt_bin "
145 "WHERE dt_bin IS NULL");
146
147 mysql.query("DELETE events"
148 "FROM events"
149 "LEFT JOIN runs ON ev_run=rn_id"
150 "WHERE rn_id IS NULL");
151
152 mysql.query("DELETE event_names"
153 "FROM event_names"
154 "LEFT JOIN events ON en_id=ev_event"
155 "WHERE ev_event IS NULL");
156 }
157
158 void
159 SetupStat::init()
160 {
161 name = "";
162 descr = "";
163 type = "";
164 print = false;
165 prereq = 0;
166 prec = -1;
167 nozero = false;
168 nonan = false;
169 total = false;
170 pdf = false;
171 cdf = false;
172 min = 0;
173 max = 0;
174 bktsize = 0;
175 size = 0;
176 }
177
178 unsigned
179 SetupStat::setup()
180 {
181 MySQL::Connection &mysql = MySqlDB.conn();
182
183 stringstream insert;
184 ccprintf(insert,
185 "INSERT INTO "
186 "stats(st_name, st_descr, st_type, st_print, st_prereq, "
187 "st_prec, st_nozero, st_nonan, st_total, st_pdf, st_cdf, "
188 "st_min, st_max, st_bktsize, st_size)"
189 "values(\"%s\",\"%s\",\"%s\","
190 " %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
191 name, descr, type, print, prereq, (int)prec, nozero, nonan,
192 total, pdf, cdf,
193 min, max, bktsize, size);
194
195 mysql.query(insert);
196 if (!mysql.error)
197 return mysql.insert_id();
198
199 stringstream select;
200 ccprintf(select, "SELECT * FROM stats WHERE st_name=\"%s\"", name);
201
202 mysql.query(select);
203 MySQL::Result result = mysql.store_result();
204 if (!result)
205 panic("could not find stat\n%s\n", mysql.error);
206
207 if (mysql.commit())
208 panic("could not commit transaction\n");
209
210 assert(result.num_fields() == 16);
211 MySQL::Row row = result.fetch_row();
212 if (!row)
213 panic("could not get stat row\n%s\n", mysql.error);
214
215 bool tb;
216 int8_t ti8;
217 uint16_t tu16;
218 int64_t ti64;
219 uint64_t tu64;
220
221 if (name != (char *)row[1])
222 panic("failed stat check on %s:name. %s != %s\n",
223 name, name, row[1]);
224
225 if (descr != (char *)row[2])
226 panic("failed stat check on %s:descr. %s != %s\n",
227 name, descr, row[2]);
228
229 if (type != (char *)row[3])
230 panic("failed stat check on %s:type. %s != %s\n",
231 name, type, row[3]);
232
233 if (!to_number(row[4], tb) || print != tb)
234 panic("failed stat check on %s:print. %d != %d\n",
235 name, print, tb);
236
237 if (!to_number(row[6], ti8) || prec != ti8)
238 panic("failed stat check on %s:prec. %d != %d\n",
239 name, prec, ti8);
240
241 if (!to_number(row[7], tb) || nozero != tb)
242 panic("failed stat check on %s:nozero. %d != %d\n",
243 name, nozero, tb);
244
245 if (!to_number(row[8], tb) || nonan != tb)
246 panic("failed stat check on %s:nonan. %d != %d\n",
247 name, nonan, tb);
248
249 if (!to_number(row[9], tb) || total != tb)
250 panic("failed stat check on %s:total. %d != %d\n",
251 name, total, tb);
252
253 if (!to_number(row[10], tb) || pdf != tb)
254 panic("failed stat check on %s:pdf. %d != %d\n",
255 name, pdf, tb);
256
257 if (!to_number(row[11], tb) || cdf != tb)
258 panic("failed stat check on %s:cdf. %d != %d\n",
259 name, cdf, tb);
260
261 if (!to_number(row[12], ti64) || min != ti64)
262 panic("failed stat check on %s:min. %d != %d\n",
263 name, min, ti64);
264
265 if (!to_number(row[13], ti64) || max != ti64)
266 panic("failed stat check on %s:max. %d != %d\n",
267 name, max, ti64);
268
269 if (!to_number(row[14], tu64) || bktsize != tu64)
270 panic("failed stat check on %s:bktsize. %d != %d\n",
271 name, bktsize, tu64);
272
273 if (!to_number(row[15], tu16) || size != tu16)
274 panic("failed stat check on %s:size. %d != %d\n",
275 name, size, tu16);
276
277 to_number(row[5], prereq);
278 uint16_t statid;
279 to_number(row[0], statid);
280 return statid;
281 }
282
283 unsigned
284 SetupBin(const string &bin)
285 {
286 static map<string, int> binmap;
287
288 using namespace MySQL;
289 map<string,int>::const_iterator i = binmap.find(bin);
290 if (i != binmap.end())
291 return (*i).second;
292
293 Connection &mysql = MySqlDB.conn();
294 assert(mysql.connected());
295
296 if (mysql.commit())
297 panic("could not commit transaction\n");
298
299 uint16_t bin_id;
300
301 stringstream select;
302 stringstream insert;
303 ccprintf(select, "SELECT bn_id FROM bins WHERE bn_name=\"%s\"", bin);
304
305 mysql.query(select);
306 MySQL::Result result = mysql.store_result();
307 if (result) {
308 assert(result.num_fields() == 1);
309 MySQL::Row row = result.fetch_row();
310 if (row) {
311 to_number(row[0], bin_id);
312 goto exit;
313 }
314 }
315
316 ccprintf(insert, "INSERT INTO bins(bn_name) values(\"%s\")", bin);
317
318 mysql.query(insert);
319 if (mysql.error)
320 panic("could not get a bin\n%s\n", mysql.error);
321
322 bin_id = mysql.insert_id();
323 binmap.insert(make_pair(bin, bin_id));
324
325 exit:
326 if (mysql.commit())
327 panic("could not commit transaction\n");
328
329 return bin_id;
330 }
331
332 InsertData::InsertData()
333 {
334 query = new char[maxsize + 1];
335 size = 0;
336 flush();
337 }
338
339 InsertData::~InsertData()
340 {
341 delete [] query;
342 }
343
344 void
345 InsertData::flush()
346 {
347 if (size) {
348 MySQL::Connection &mysql = MySqlDB.conn();
349 assert(mysql.connected());
350 mysql.query(query);
351 if (mysql.error)
352 panic("could not insert data\n%s\n", mysql.error);
353 if (mysql.commit())
354 panic("could not commit transaction\n");
355 }
356
357 query[0] = '\0';
358 size = 0;
359 first = true;
360 strcpy(query, "INSERT INTO "
361 "data(dt_stat,dt_x,dt_y,dt_run,dt_tick,dt_bin,dt_data) "
362 "values");
363 size = strlen(query);
364 }
365
366 void
367 InsertData::insert()
368 {
369 if (size + 1024 > maxsize)
370 flush();
371
372 if (!first) {
373 query[size++] = ',';
374 query[size] = '\0';
375 }
376
377 first = false;
378
379 size += sprintf(query + size, "(%u,%d,%d,%u,%llu,%u,\"%f\")",
380 stat, x, y, MySqlDB.run(), (unsigned long long)tick,
381 bin, data);
382 }
383
384 struct InsertSubData
385 {
386 uint16_t stat;
387 int16_t x;
388 int16_t y;
389 string name;
390 string descr;
391
392 void setup();
393 };
394
395 void
396 InsertSubData::setup()
397 {
398 MySQL::Connection &mysql = MySqlDB.conn();
399 assert(mysql.connected());
400 stringstream insert;
401 ccprintf(insert,
402 "INSERT INTO subdata(sd_stat,sd_x,sd_y,sd_name,sd_descr) "
403 "values(%d,%d,%d,\"%s\",\"%s\")",
404 stat, x, y, name, descr);
405
406 mysql.query(insert);
407 // if (mysql.error)
408 // panic("could not insert subdata\n%s\n", mysql.error);
409
410 if (mysql.commit())
411 panic("could not commit transaction\n");
412 }
413
414 void
415 InsertFormula(uint16_t stat, const string &formula)
416 {
417 MySQL::Connection &mysql = MySqlDB.conn();
418 assert(mysql.connected());
419 stringstream insert_formula;
420 ccprintf(insert_formula,
421 "INSERT INTO formulas(fm_stat,fm_formula) values(%d, \"%s\")",
422 stat, formula);
423
424 mysql.query(insert_formula);
425 // if (mysql.error)
426 // panic("could not insert formula\n%s\n", mysql.error);
427
428 stringstream insert_ref;
429 ccprintf(insert_ref,
430 "INSERT INTO formula_ref(fr_stat,fr_run) values(%d, %d)",
431 stat, MySqlDB.run());
432
433 mysql.query(insert_ref);
434 // if (mysql.error)
435 // panic("could not insert formula reference\n%s\n", mysql.error);
436
437 if (mysql.commit())
438 panic("could not commit transaction\n");
439 }
440
441 void
442 UpdatePrereq(uint16_t stat, uint16_t prereq)
443 {
444 MySQL::Connection &mysql = MySqlDB.conn();
445 assert(mysql.connected());
446 stringstream update;
447 ccprintf(update, "UPDATE stats SET st_prereq=%d WHERE st_id=%d",
448 prereq, stat);
449 mysql.query(update);
450 if (mysql.error)
451 panic("could not update prereq\n%s\n", mysql.error);
452
453 if (mysql.commit())
454 panic("could not commit transaction\n");
455 }
456
457 void
458 MySql::configure()
459 {
460 /*
461 * set up all stats!
462 */
463 using namespace Database;
464
465 MySQL::Connection &mysql = MySqlDB.conn();
466 if (mysql.commit())
467 panic("could not commit transaction\n");
468
469 stat_list_t::const_iterator i, end = stats().end();
470 for (i = stats().begin(); i != end; ++i) {
471 (*i)->visit(*this);
472 }
473
474 for (i = stats().begin(); i != end; ++i) {
475 StatData *data = *i;
476 if (data->prereq) {
477 uint16_t stat_id = find(data->id);
478 uint16_t prereq_id = find(data->prereq->id);
479 assert(stat_id && prereq_id);
480
481 UpdatePrereq(stat_id, prereq_id);
482 }
483 }
484
485 if (mysql.commit())
486 panic("could not commit transaction\n");
487
488 configured = true;
489 }
490
491
492 bool
493 MySql::configure(const StatData &data, string type)
494 {
495 stat.init();
496 stat.name = data.name;
497 stat.descr = data.desc;
498 stat.type = type;
499 stat.print = data.flags & print;
500 stat.prec = data.precision;
501 stat.nozero = data.flags & nozero;
502 stat.nonan = data.flags & nonan;
503 stat.total = data.flags & total;
504 stat.pdf = data.flags & pdf;
505 stat.cdf = data.flags & cdf;
506
507 return stat.print;
508 }
509
510 void
511 MySql::configure(const ScalarData &data)
512 {
513 if (!configure(data, "SCALAR"))
514 return;
515
516 insert(data.id, stat.setup());
517 }
518
519 void
520 MySql::configure(const VectorData &data)
521 {
522 if (!configure(data, "VECTOR"))
523 return;
524
525 uint16_t statid = stat.setup();
526
527 if (!data.subnames.empty()) {
528 InsertSubData subdata;
529 subdata.stat = statid;
530 subdata.y = 0;
531 for (int i = 0; i < data.subnames.size(); ++i) {
532 subdata.x = i;
533 subdata.name = data.subnames[i];
534 subdata.descr = data.subdescs.empty() ? "" : data.subdescs[i];
535
536 if (!subdata.name.empty() || !subdata.descr.empty())
537 subdata.setup();
538 }
539 }
540
541 insert(data.id, statid);
542 }
543
544 void
545 MySql::configure(const DistData &data)
546 {
547 if (!configure(data, "DIST"))
548 return;
549
550 if (!data.data.fancy) {
551 stat.size = data.data.size;
552 stat.min = data.data.min;
553 stat.max = data.data.max;
554 stat.bktsize = data.data.bucket_size;
555 }
556 insert(data.id, stat.setup());
557 }
558
559 void
560 MySql::configure(const VectorDistData &data)
561 {
562 if (!configure(data, "VECTORDIST"))
563 return;
564
565 if (!data.data[0].fancy) {
566 stat.size = data.data[0].size;
567 stat.min = data.data[0].min;
568 stat.max = data.data[0].max;
569 stat.bktsize = data.data[0].bucket_size;
570 }
571
572 uint16_t statid = stat.setup();
573
574 if (!data.subnames.empty()) {
575 InsertSubData subdata;
576 subdata.stat = statid;
577 subdata.y = 0;
578 for (int i = 0; i < data.subnames.size(); ++i) {
579 subdata.x = i;
580 subdata.name = data.subnames[i];
581 subdata.descr = data.subdescs.empty() ? "" : data.subdescs[i];
582 if (!subdata.name.empty() || !subdata.descr.empty())
583 subdata.setup();
584 }
585 }
586
587 insert(data.id, statid);
588 }
589
590 void
591 MySql::configure(const Vector2dData &data)
592 {
593 if (!configure(data, "VECTOR2D"))
594 return;
595
596 uint16_t statid = stat.setup();
597
598 if (!data.subnames.empty()) {
599 InsertSubData subdata;
600 subdata.stat = statid;
601 subdata.y = -1;
602 for (int i = 0; i < data.subnames.size(); ++i) {
603 subdata.x = i;
604 subdata.name = data.subnames[i];
605 subdata.descr = data.subdescs.empty() ? "" : data.subdescs[i];
606 if (!subdata.name.empty() || !subdata.descr.empty())
607 subdata.setup();
608 }
609 }
610
611 if (!data.y_subnames.empty()) {
612 InsertSubData subdata;
613 subdata.stat = statid;
614 subdata.x = -1;
615 subdata.descr = "";
616 for (int i = 0; i < data.y_subnames.size(); ++i) {
617 subdata.y = i;
618 subdata.name = data.y_subnames[i];
619 if (!subdata.name.empty())
620 subdata.setup();
621 }
622 }
623
624 insert(data.id, statid);
625 }
626
627 void
628 MySql::configure(const FormulaData &data)
629 {
630 configure(data, "FORMULA");
631 insert(data.id, stat.setup());
632 InsertFormula(find(data.id), data.str());
633 }
634
635 void
636 MySql::output(MainBin *bin)
637 {
638 MySQL::Connection &mysql = MySqlDB.conn();
639
640 if (bin) {
641 bin->activate();
642 newdata.bin = SetupBin(bin->name());
643 } else {
644 newdata.bin = 0;
645 }
646
647 Database::stat_list_t::const_iterator i, end = Database::stats().end();
648 for (i = Database::stats().begin(); i != end; ++i) {
649 StatData *stat = *i;
650 if (bin && stat->binned() || !bin && !stat->binned()) {
651 stat->visit(*this);
652 if (mysql.commit())
653 panic("could not commit transaction\n");
654 }
655 }
656 }
657
658 bool
659 MySql::valid() const
660 {
661 return MySqlDB.connected();
662 }
663
664 void
665 MySql::output()
666 {
667 using namespace Database;
668 assert(valid());
669
670 if (!configured)
671 configure();
672
673 // store sample #
674 newdata.tick = curTick;
675
676 output(NULL);
677 if (!bins().empty()) {
678 bin_list_t::iterator i, end = bins().end();
679 for (i = bins().begin(); i != end; ++i)
680 output(*i);
681 }
682
683 newdata.flush();
684 }
685
686 void
687 MySql::output(const ScalarData &data)
688 {
689 if (!(data.flags & print))
690 return;
691
692 newdata.stat = find(data.id);
693 newdata.x = 0;
694 newdata.y = 0;
695 newdata.data = data.value();
696
697 newdata.insert();
698 }
699
700 void
701 MySql::output(const VectorData &data)
702 {
703 if (!(data.flags & print))
704 return;
705
706 newdata.stat = find(data.id);
707 newdata.y = 0;
708
709 const VCounter &cvec = data.value();
710 int size = data.size();
711 for (int x = 0; x < size; x++) {
712 newdata.x = x;
713 newdata.data = cvec[x];
714 newdata.insert();
715 }
716 }
717
718 void
719 MySql::output(const DistDataData &data)
720 {
721 const int db_sum = -1;
722 const int db_squares = -2;
723 const int db_samples = -3;
724 const int db_min_val = -4;
725 const int db_max_val = -5;
726 const int db_underflow = -6;
727 const int db_overflow = -7;
728
729 newdata.x = db_sum;
730 newdata.data = data.sum;
731 newdata.insert();
732
733 newdata.x = db_squares;
734 newdata.data = data.squares;
735 newdata.insert();
736
737 newdata.x = db_samples;
738 newdata.data = data.samples;
739 newdata.insert();
740
741 if (data.samples && !data.fancy) {
742 newdata.x = db_min_val;
743 newdata.data = data.min_val;
744 newdata.insert();
745
746 newdata.x = db_max_val;
747 newdata.data = data.max_val;
748 newdata.insert();
749
750 newdata.x = db_underflow;
751 newdata.data = data.underflow;
752 newdata.insert();
753
754 newdata.x = db_overflow;
755 newdata.data = data.overflow;
756 newdata.insert();
757
758 int size = data.cvec.size();
759 for (int x = 0; x < size; x++) {
760 newdata.x = x;
761 newdata.data = data.cvec[x];
762 newdata.insert();
763 }
764 }
765 }
766
767
768 void
769 MySql::output(const DistData &data)
770 {
771 if (!(data.flags & print))
772 return;
773
774 newdata.stat = find(data.id);
775 newdata.y = 0;
776 output(data.data);
777 }
778
779 void
780 MySql::output(const VectorDistData &data)
781 {
782 if (!(data.flags & print))
783 return;
784
785 newdata.stat = find(data.id);
786
787 int size = data.data.size();
788 for (int y = 0; y < size; ++y) {
789 newdata.y = y;
790 output(data.data[y]);
791 }
792 }
793
794 void
795 MySql::output(const Vector2dData &data)
796 {
797 if (!(data.flags & print))
798 return;
799
800 newdata.stat = find(data.id);
801
802 int index = 0;
803 for (int x = 0; x < data.x; x++) {
804 newdata.x = x;
805 for (int y = 0; y < data.y; y++) {
806 newdata.y = y;
807 newdata.data = data.cvec[index++];
808 newdata.insert();
809 }
810 }
811 }
812
813 void
814 MySql::output(const FormulaData &data)
815 {
816 }
817
818 /*
819 * Implement the visitor
820 */
821 void
822 MySql::visit(const ScalarData &data)
823 {
824 if (!configured)
825 configure(data);
826 else
827 output(data);
828 }
829
830 void
831 MySql::visit(const VectorData &data)
832 {
833 if (!configured)
834 configure(data);
835 else
836 output(data);
837 }
838
839 void
840 MySql::visit(const DistData &data)
841 {
842 if (!configured)
843 configure(data);
844 else
845 output(data);
846 }
847
848 void
849 MySql::visit(const VectorDistData &data)
850 {
851 if (!configured)
852 configure(data);
853 else
854 output(data);
855 }
856
857 void
858 MySql::visit(const Vector2dData &data)
859 {
860 if (!configured)
861 configure(data);
862 else
863 output(data);
864 }
865
866 void
867 MySql::visit(const FormulaData &data)
868 {
869 if (!configured)
870 configure(data);
871 else
872 output(data);
873 }
874
875 /* namespace Stats */ }