]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/jobgraphs/jobplot.cpp
96cc02b919cbdf180d94bd69f421398e074cb29e
[bacula/bacula] / bacula / src / qt-console / jobgraphs / jobplot.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2007-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28  
29 /*
30  *   Version $Id$
31  *
32  *  JobPlots Class
33  *
34  *   Dirk Bartley, March 2007
35  *
36  */ 
37
38 #include "bat.h"
39 #include <QtGui>
40 #include "util/comboutil.h"
41 #include "jobgraphs/jobplot.h"
42
43
44 JobPlotPass::JobPlotPass()
45 {
46    use = false;
47 }
48
49 JobPlotPass& JobPlotPass::operator=(const JobPlotPass &cp)
50 {
51    use = cp.use;
52    recordLimitCheck = cp.recordLimitCheck;
53    daysLimitCheck = cp.daysLimitCheck;
54    recordLimitSpin = cp.recordLimitSpin;
55    daysLimitSpin = cp.daysLimitSpin;
56    jobCombo = cp.jobCombo;
57    clientCombo = cp.clientCombo;
58    volumeCombo = cp.volumeCombo;
59    fileSetCombo = cp.fileSetCombo;
60    purgedCombo = cp.purgedCombo;
61    levelCombo = cp.levelCombo;
62    statusCombo = cp.statusCombo;
63    return *this;
64 }
65
66 /*
67  * Constructor for the controls class which inherits QScrollArea and a ui header
68  */
69 JobPlotControls::JobPlotControls()
70 {
71    setupUi(this);
72 }
73
74 /*
75  * Constructor, this class does not inherit anything but pages.
76  */
77 JobPlot::JobPlot(QTreeWidgetItem *parentTreeWidgetItem, JobPlotPass &passVals)
78 {
79    setupUserInterface();
80    pgInitialize(tr("JobPlot"), parentTreeWidgetItem);
81    readSplitterSettings();
82    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
83    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/applications-graphics.png")));
84    m_drawn = false;
85
86    /* this invokes the pass values = operator function */
87    m_pass = passVals;
88    dockPage();
89    /* If the values of the controls are predetermined (from joblist), then set
90     * this class as current window at the front of the stack */
91    if (m_pass.use)
92       setCurrent();
93    m_jobPlot->replot();
94 }
95
96 /*
97  * Kill, crush Destroy
98  */
99 JobPlot::~JobPlot()
100 {
101    if (m_drawn)
102       writeSettings();
103    m_pjd.clear();
104 }
105
106 /*
107  * This is called when the page selector has this page selected
108  */
109 void JobPlot::currentStackItem()
110 {
111    if (!m_drawn) {
112       setupControls();
113       reGraph();
114       m_drawn=true;
115    }
116 }
117
118 /*
119  * Slot for the refresh push button, also called from constructor.
120  */
121 void JobPlot::reGraph()
122 {
123    /* clear m_pjd */
124    m_pjd.clear();
125    runQuery();
126    m_jobPlot->clear();
127    addCurve();
128    m_jobPlot->replot();
129 }
130
131 /*
132  * Setup the control widgets for the graph, this are the objects from JobPlotControls
133  */
134 void JobPlot::setupControls()
135 {
136    QStringList graphType = QStringList() << /* tr("Fitted") <<*/ tr("Sticks")
137                                          << tr("Lines") << tr("Steps") << tr("None");
138    controls->plotTypeCombo->addItems(graphType);
139
140    fillSymbolCombo(controls->fileSymbolTypeCombo);
141    fillSymbolCombo(controls->byteSymbolTypeCombo);
142
143    readControlSettings();
144
145    controls->fileCheck->setCheckState(Qt::Checked);
146    controls->byteCheck->setCheckState(Qt::Checked);
147    connect(controls->plotTypeCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(setPlotType(QString)));
148    connect(controls->fileSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setFileSymbolType(int)));
149    connect(controls->byteSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setByteSymbolType(int)));
150    connect(controls->fileCheck, SIGNAL(stateChanged(int)), this, SLOT(fileCheckChanged(int)));
151    connect(controls->byteCheck, SIGNAL(stateChanged(int)), this, SLOT(byteCheckChanged(int)));
152    connect(controls->refreshButton, SIGNAL(pressed()), this, SLOT(reGraph()));
153
154    controls->clientComboBox->addItem(tr("Any"));
155    controls->clientComboBox->addItems(m_console->client_list);
156
157    QStringList volumeList;
158    getVolumeList(volumeList);
159    controls->volumeComboBox->addItem(tr("Any"));
160    controls->volumeComboBox->addItems(volumeList);
161    controls->jobComboBox->addItem(tr("Any"));
162    controls->jobComboBox->addItems(m_console->job_list);
163
164    levelComboFill(controls->levelComboBox);
165
166    boolComboFill(controls->purgedComboBox);
167
168    controls->fileSetComboBox->addItem(tr("Any"));
169    controls->fileSetComboBox->addItems(m_console->fileset_list);
170    QStringList statusLongList;
171    getStatusList(statusLongList);
172    controls->statusComboBox->addItem(tr("Any"));
173    controls->statusComboBox->addItems(statusLongList);
174
175    if (m_pass.use) {
176       controls->limitCheckBox->setCheckState(m_pass.recordLimitCheck);
177       controls->limitSpinBox->setValue(m_pass.recordLimitSpin);
178       controls->daysCheckBox->setCheckState(m_pass.daysLimitCheck);
179       controls->daysSpinBox->setValue(m_pass.daysLimitSpin);
180
181       comboSel(controls->jobComboBox, m_pass.jobCombo);
182       comboSel(controls->clientComboBox, m_pass.clientCombo);
183       comboSel(controls->volumeComboBox, m_pass.volumeCombo);
184       comboSel(controls->fileSetComboBox, m_pass.fileSetCombo);
185       comboSel(controls->purgedComboBox, m_pass.purgedCombo);
186       comboSel(controls->levelComboBox, m_pass.levelCombo);
187       comboSel(controls->statusComboBox, m_pass.statusCombo);
188
189    } else {
190       /* Set Defaults for check and spin for limits */
191       controls->limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
192       controls->limitSpinBox->setValue(mainWin->m_recordLimitVal);
193       controls->daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
194       controls->daysSpinBox->setValue(mainWin->m_daysLimitVal);
195    } 
196 }
197
198 /*
199  * Setup the control widgets for the graph, this are the objects from JobPlotControls
200  */
201 void JobPlot::runQuery()
202 {
203    /* Set up query */
204    QString query("");
205    query += "SELECT DISTINCT "
206             " Job.Starttime AS JobStart,"
207             " Job.Jobfiles AS FileCount,"
208             " Job.JobBytes AS Bytes,"
209             " Job.JobId AS JobId"
210             " FROM Job"
211             " JOIN Client ON (Client.ClientId=Job.ClientId)"
212             " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
213             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)";
214
215    QStringList conditions;
216    comboCond(conditions, controls->jobComboBox, "Job.Name");
217    comboCond(conditions, controls->clientComboBox, "Client.Name");
218    int volumeIndex = controls->volumeComboBox->currentIndex();
219    if ((volumeIndex != -1) && (controls->volumeComboBox->itemText(volumeIndex) != tr("Any"))) {
220       query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
221                " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
222       conditions.append("Media.VolumeName='" + controls->volumeComboBox->itemText(volumeIndex) + "'");
223    }
224    comboCond(conditions, controls->fileSetComboBox, "FileSet.FileSet");
225    boolComboCond(conditions, controls->purgedComboBox, "Job.PurgedFiles");
226    levelComboCond(conditions, controls->levelComboBox, "Job.Level");
227    comboCond(conditions, controls->statusComboBox, "Status.JobStatusLong");
228
229    /* If Limit check box For limit by days is checked  */
230    if (controls->daysCheckBox->checkState() == Qt::Checked) {
231       QDateTime stamp = QDateTime::currentDateTime().addDays(-controls->daysSpinBox->value());
232       QString since = stamp.toString(Qt::ISODate);
233       conditions.append("Job.Starttime>'" + since + "'");
234    }
235    bool first = true;
236    foreach (QString condition, conditions) {
237       if (first) {
238          query += " WHERE " + condition;
239          first = false;
240       } else {
241          query += " AND " + condition;
242       }
243    }
244    /* Descending */
245    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
246    /* If Limit check box for limit records returned is checked  */
247    if (controls->limitCheckBox->checkState() == Qt::Checked) {
248       QString limit;
249       limit.setNum(controls->limitSpinBox->value());
250       query += " LIMIT " + limit;
251    }
252
253    if (mainWin->m_sqlDebug) {
254       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
255    }
256    QString resultline;
257    QStringList results;
258    if (m_console->sql_cmd(query, results)) {
259
260       QString field;
261       QStringList fieldlist;
262    
263       int row = 0;
264       /* Iterate through the record returned from the query */
265       foreach (resultline, results) {
266          PlotJobData *plotJobData = new PlotJobData();
267          fieldlist = resultline.split("\t");
268          int column = 0;
269          QString statusCode("");
270          /* Iterate through fields in the record */
271          foreach (field, fieldlist) {
272             field = field.trimmed();  /* strip leading & trailing spaces */
273             if (column == 0) {
274                plotJobData->dt = QDateTime::fromString(field, mainWin->m_dtformat);
275             } else if (column == 1) {
276                plotJobData->files = field.toDouble();
277             } else if (column == 2) {
278                plotJobData->bytes = field.toDouble();
279             }
280             column++;
281             m_pjd.prepend(plotJobData);
282          }
283          row++;
284       }
285    } 
286    if ((controls->volumeComboBox->itemText(volumeIndex) != tr("Any")) && (results.count() == 0)){
287       /* for context sensitive searches, let the user know if there were no
288        *        * results */
289       QMessageBox::warning(this, "Bat",
290           tr("The Jobs query returned no results.\n"
291          "Press OK to continue?"), QMessageBox::Ok );
292    }
293 }
294
295 /*
296  * The user interface that used to be in the ui header.  I wanted to have a
297  * scroll area which is not in designer.
298  */
299 void JobPlot::setupUserInterface()
300 {
301    QSizePolicy sizePolicy(static_cast<QSizePolicy::Policy>(1), static_cast<QSizePolicy::Policy>(5));
302    sizePolicy.setHorizontalStretch(0);
303    sizePolicy.setVerticalStretch(0);
304    sizePolicy.setVerticalStretch(0);
305    sizePolicy.setVerticalPolicy(QSizePolicy::Ignored);
306    sizePolicy.setHorizontalPolicy(QSizePolicy::Ignored);
307    m_gridLayout = new QGridLayout(this);
308    m_gridLayout->setSpacing(6);
309    m_gridLayout->setMargin(9);
310    m_gridLayout->setObjectName(QString::fromUtf8("m_gridLayout"));
311    m_splitter = new QSplitter(this);
312    m_splitter->setObjectName(QString::fromUtf8("m_splitter"));
313    m_splitter->setOrientation(Qt::Horizontal);
314    m_jobPlot = new QwtPlot(m_splitter);
315    m_jobPlot->setObjectName(QString::fromUtf8("m_jobPlot"));
316    m_jobPlot->setSizePolicy(sizePolicy);
317    m_jobPlot->setMinimumSize(QSize(0, 0));
318    QScrollArea *area = new QScrollArea(m_splitter);
319    area->setObjectName(QString::fromUtf8("area"));
320    controls = new JobPlotControls();
321    area->setWidget(controls);
322    
323    m_splitter->addWidget(m_jobPlot);
324    m_splitter->addWidget(area);
325
326    m_gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
327 }
328
329 /*
330  * Add the curves to the plot
331  */
332 void JobPlot::addCurve()
333 {
334    m_jobPlot->setTitle(tr("Files and Bytes backed up"));
335    m_jobPlot->insertLegend(new QwtLegend(), QwtPlot::RightLegend);
336
337    // Set axis titles
338    m_jobPlot->enableAxis(QwtPlot::yRight);
339    m_jobPlot->setAxisTitle(QwtPlot::yRight, tr("<-- Bytes Kb"));
340    m_jobPlot->setAxisTitle(m_jobPlot->xBottom, tr("date of backup -->"));
341    m_jobPlot->setAxisTitle(m_jobPlot->yLeft, tr("Number of Files -->"));
342    m_jobPlot->setAxisScaleDraw(QwtPlot::xBottom, new DateTimeScaleDraw());
343
344    // Insert new curves
345    m_fileCurve = new QwtPlotCurve( tr("Files") );
346    m_fileCurve->setPen(QPen(Qt::red));
347    m_fileCurve->setCurveType(m_fileCurve->Yfx);
348    m_fileCurve->setYAxis(QwtPlot::yLeft);
349
350    m_byteCurve = new QwtPlotCurve(tr("Bytes"));
351    m_byteCurve->setPen(QPen(Qt::blue));
352    m_byteCurve->setCurveType(m_byteCurve->Yfx);
353    m_byteCurve->setYAxis(QwtPlot::yRight);
354    setPlotType(controls->plotTypeCombo->currentText());
355    setFileSymbolType(controls->fileSymbolTypeCombo->currentIndex());
356    setByteSymbolType(controls->byteSymbolTypeCombo->currentIndex());
357
358    m_fileCurve->attach(m_jobPlot);
359    m_byteCurve->attach(m_jobPlot);
360
361    // attach data
362    int size = m_pjd.count();
363    int j = 0;
364 #if defined(__GNU_C)
365    double tval[size];
366    double fval[size];
367    double bval[size];
368 #else
369    double *tval;
370    double *fval;
371    double *bval;
372
373    tval = (double *)malloc(size * sizeof(double));
374    fval = (double *)malloc(size * sizeof(double));
375    bval = (double *)malloc(size * sizeof(double));
376 #endif
377
378    foreach (PlotJobData* plotJobData, m_pjd) {
379 //      printf("%.0f %.0f %s\n", plotJobData->bytes, plotJobData->files,
380 //              plotJobData->dt.toString(mainWin->m_dtformat).toUtf8().data());
381       fval[j] = plotJobData->files;
382       bval[j] = plotJobData->bytes / 1024;
383       tval[j] = plotJobData->dt.toTime_t();
384 //      printf("%i %.0f %.0f %.0f\n", j, tval[j], fval[j], bval[j]);
385       j++;
386    }
387    m_fileCurve->setData(tval,fval,size);
388    m_byteCurve->setData(tval,bval,size);
389
390    for (int year=2000; year<2010; year++) {
391       for (int month=1; month<=12; month++) {
392          QString monthBegin;
393          if (month > 9) {
394             QTextStream(&monthBegin) << year << "-" << month << "-01 00:00:00";
395          } else {
396             QTextStream(&monthBegin) << year << "-0" << month << "-01 00:00:00";
397          }
398          QDateTime mdt = QDateTime::fromString(monthBegin, mainWin->m_dtformat);
399          double monbeg = mdt.toTime_t();
400    
401          //  ...a vertical line at the first of each month
402          QwtPlotMarker *mX = new QwtPlotMarker();
403          mX->setLabel(mdt.toString("MMM-d"));
404          mX->setLabelAlignment(Qt::AlignRight|Qt::AlignTop);
405          mX->setLineStyle(QwtPlotMarker::VLine);
406          QPen pen(Qt::darkGray);
407          pen.setStyle(Qt::DashDotDotLine);
408          mX->setLinePen(pen);
409          mX->setXValue(monbeg);
410          mX->attach(m_jobPlot);
411       }
412    }
413
414 #if !defined(__GNU_C)
415    free(tval);
416    free(fval);
417    free(bval);
418 #endif
419 }
420
421 /*
422  * slot to respond to the plot type combo changing
423  */
424 void JobPlot::setPlotType(QString currentText)
425 {
426    QwtPlotCurve::CurveStyle style = QwtPlotCurve::NoCurve;
427    if (currentText == tr("Fitted")) {
428       style = QwtPlotCurve::Lines;
429       m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted);
430       m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted);
431    } else if (currentText == tr("Sticks")) {
432       style = QwtPlotCurve::Sticks;
433    } else if (currentText == tr("Lines")) {
434       style = QwtPlotCurve::Lines;
435       m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted);
436       m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted);
437    } else if (currentText == tr("Steps")) {
438       style = QwtPlotCurve::Steps;
439    } else if (currentText == tr("None")) {
440       style = QwtPlotCurve::NoCurve;
441    }
442    m_fileCurve->setStyle(style);
443    m_byteCurve->setStyle(style);
444    m_jobPlot->replot();
445 }
446
447 void JobPlot::fillSymbolCombo(QComboBox *q)
448 {
449   q->addItem( tr("Ellipse"), (int)QwtSymbol::Ellipse);
450   q->addItem( tr("Rect"), (int)QwtSymbol::Rect); 
451   q->addItem( tr("Diamond"), (int)QwtSymbol::Diamond);
452   q->addItem( tr("Triangle"), (int)QwtSymbol::Triangle);
453   q->addItem( tr("DTrianle"), (int)QwtSymbol::DTriangle);
454   q->addItem( tr("UTriangle"), (int)QwtSymbol::UTriangle);
455   q->addItem( tr("LTriangle"), (int)QwtSymbol::LTriangle);
456   q->addItem( tr("RTriangle"), (int)QwtSymbol::RTriangle);
457   q->addItem( tr("Cross"), (int)QwtSymbol::Cross);
458   q->addItem( tr("XCross"), (int)QwtSymbol::XCross);
459   q->addItem( tr("HLine"), (int)QwtSymbol::HLine);
460   q->addItem( tr("Vline"), (int)QwtSymbol::VLine);
461   q->addItem( tr("Star1"), (int)QwtSymbol::Star1);
462   q->addItem( tr("Star2"), (int)QwtSymbol::Star2);
463   q->addItem( tr("Hexagon"), (int)QwtSymbol::Hexagon); 
464   q->addItem( tr("None"), (int)QwtSymbol::NoSymbol);
465 }
466
467
468 /*
469  * slot to respond to the symbol type combo changing
470  */
471 void JobPlot::setFileSymbolType(int index)
472 {
473    setSymbolType(index, 0);
474 }
475
476 void JobPlot::setByteSymbolType(int index)
477 {
478    setSymbolType(index, 1);
479 }
480 void JobPlot::setSymbolType(int index, int type)
481 {
482    QwtSymbol sym;
483    sym.setPen(QColor(Qt::black));
484    sym.setSize(7);
485
486    QVariant style;
487    if (0 == type) {
488       style = controls->fileSymbolTypeCombo->itemData(index);
489       sym.setStyle( (QwtSymbol::Style)style.toInt() );
490       sym.setBrush(QColor(Qt::yellow));
491       m_fileCurve->setSymbol(sym);
492    
493    } else {
494       style = controls->byteSymbolTypeCombo->itemData(index);
495       sym.setStyle( (QwtSymbol::Style)style.toInt() );
496       sym.setBrush(QColor(Qt::blue));
497       m_byteCurve->setSymbol(sym);
498
499    }
500    m_jobPlot->replot();
501 }
502
503 /*
504  * slot to respond to the file check box changing state
505  */
506 void JobPlot::fileCheckChanged(int newstate)
507 {
508    if (newstate == Qt::Unchecked) {
509       m_fileCurve->detach();
510       m_jobPlot->enableAxis(QwtPlot::yLeft, false);
511    } else {
512       m_fileCurve->attach(m_jobPlot);
513       m_jobPlot->enableAxis(QwtPlot::yLeft);
514    }
515    m_jobPlot->replot();
516 }
517
518 /*
519  * slot to respond to the byte check box changing state
520  */
521 void JobPlot::byteCheckChanged(int newstate)
522 {
523    if (newstate == Qt::Unchecked) {
524       m_byteCurve->detach();
525       m_jobPlot->enableAxis(QwtPlot::yRight, false);
526    } else {
527       m_byteCurve->attach(m_jobPlot);
528       m_jobPlot->enableAxis(QwtPlot::yRight);
529    }
530    m_jobPlot->replot();
531 }
532
533 /*
534  * Save user settings associated with this page
535  */
536 void JobPlot::writeSettings()
537 {
538    QSettings settings(m_console->m_dir->name(), "bat");
539    settings.beginGroup("JobPlot");
540    settings.setValue("m_splitterSizes", m_splitter->saveState());
541    settings.setValue("fileSymbolTypeCombo", controls->fileSymbolTypeCombo->currentText());
542    settings.setValue("byteSymbolTypeCombo", controls->byteSymbolTypeCombo->currentText());
543    settings.setValue("plotTypeCombo", controls->plotTypeCombo->currentText());
544    settings.endGroup();
545 }
546
547 /* 
548  * Read settings values for Controls
549  */
550 void JobPlot::readControlSettings()
551 {
552    QSettings settings(m_console->m_dir->name(), "bat");
553    settings.beginGroup("JobPlot");
554    int fileSymbolTypeIndex = controls->fileSymbolTypeCombo->findText(settings.value("fileSymbolTypeCombo").toString(), Qt::MatchExactly);
555    if (fileSymbolTypeIndex == -1) fileSymbolTypeIndex = 2;
556       controls->fileSymbolTypeCombo->setCurrentIndex(fileSymbolTypeIndex);
557    int byteSymbolTypeIndex = controls->byteSymbolTypeCombo->findText(settings.value("byteSymbolTypeCombo").toString(), Qt::MatchExactly);
558    if (byteSymbolTypeIndex == -1) byteSymbolTypeIndex = 3;
559       controls->byteSymbolTypeCombo->setCurrentIndex(byteSymbolTypeIndex);
560    int plotTypeIndex = controls->plotTypeCombo->findText(settings.value("plotTypeCombo").toString(), Qt::MatchExactly);
561    if (plotTypeIndex == -1) plotTypeIndex = 2;
562       controls->plotTypeCombo->setCurrentIndex(plotTypeIndex);
563    settings.endGroup();
564 }
565
566 /*
567  * Read and restore user settings associated with this page
568  */
569 void JobPlot::readSplitterSettings()
570 {
571    QSettings settings(m_console->m_dir->name(), "bat");
572    settings.beginGroup("JobPlot");
573    if (settings.contains("m_splitterSizes")) { 
574       m_splitter->restoreState(settings.value("m_splitterSizes").toByteArray());
575    }
576    settings.endGroup();
577 }