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