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