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