2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
4 * Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "speedplotview.h"
38 #include "base/bittorrent/session.h"
39 #include "base/global.h"
40 #include "base/unicodestrings.h"
41 #include "base/utils/misc.h"
45 // table of supposed nice steps for grid marks to get nice looking quarters of scale
46 const qreal roundingTable
[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
51 Utils::Misc::SizeUnit unit
{};
52 qlonglong
sizeInBytes() const
54 return Utils::Misc::sizeInBytes(arg
, unit
);
58 SplitValue
getRoundedYScale(qreal value
)
60 using Utils::Misc::SizeUnit
;
62 if (value
== 0.0) return {0, SizeUnit::Byte
};
63 if (value
<= 12.0) return {12, SizeUnit::Byte
};
65 SizeUnit calculatedUnit
= SizeUnit::Byte
;
69 calculatedUnit
= static_cast<SizeUnit
>(static_cast<int>(calculatedUnit
) + 1);
74 const qreal roundedValue
{std::ceil(value
/ 40) * 40};
75 return {roundedValue
, calculatedUnit
};
80 const qreal roundedValue
{std::ceil(value
/ 4) * 4};
81 return {roundedValue
, calculatedUnit
};
84 for (const auto &roundedValue
: roundingTable
)
86 if (value
<= roundedValue
)
87 return {roundedValue
, calculatedUnit
};
89 return {10.0, calculatedUnit
};
92 QString
formatLabel(const qreal argValue
, const Utils::Misc::SizeUnit unit
)
94 // check is there need for digits after decimal separator
95 const int precision
= (argValue
< 10) ? friendlyUnitPrecision(unit
) : 0;
96 return QLocale::system().toString(argValue
, 'f', precision
)
97 + QChar::Nbsp
+ unitString(unit
, true);
101 SpeedPlotView::Averager::Averager(const milliseconds duration
, const milliseconds resolution
)
102 : m_resolution
{resolution
}
103 , m_maxDuration
{duration
}
104 , m_sink
{static_cast<DataCircularBuffer::size_type
>(duration
/ resolution
)}
106 m_lastSampleTime
.start();
109 bool SpeedPlotView::Averager::push(const SampleData
&sampleData
)
111 // Accumulator overflow will be hit in worst case on longest used averaging span,
112 // defined by resolution. Maximum resolution is 144 seconds
113 // Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
114 // With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
115 // This speed is inaccessible to an ordinary user.
117 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
118 m_accumulator
[id
] += sampleData
[id
];
120 // system may go to sleep, that can cause very big elapsed interval
121 const milliseconds updateInterval
{static_cast<int64_t>(BitTorrent::Session::instance()->refreshInterval() * 1.25)};
122 const milliseconds maxElapsed
{std::max(updateInterval
, m_resolution
)};
123 const milliseconds elapsed
{std::min(milliseconds
{m_lastSampleTime
.elapsed()}, maxElapsed
)};
124 if (elapsed
< m_resolution
)
125 return false; // still accumulating
127 // it is time final averaging calculations
128 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
129 m_accumulator
[id
] /= m_counter
;
131 m_currentDuration
+= elapsed
;
133 // remove extra data from front if we reached max duration
134 if (m_currentDuration
> m_maxDuration
)
136 // once we go above the max duration never go below that
137 // otherwise it will cause empty space in graphs
138 while (!m_sink
.empty()
139 && ((m_currentDuration
- m_sink
.front().duration
) >= m_maxDuration
))
141 m_currentDuration
-= m_sink
.front().duration
;
146 // now flush out averaged data
147 Q_ASSERT(m_sink
.size() < m_sink
.capacity());
148 m_sink
.push_back({elapsed
, m_accumulator
});
153 m_lastSampleTime
.restart();
157 const SpeedPlotView::DataCircularBuffer
&SpeedPlotView::Averager::data() const
162 SpeedPlotView::SpeedPlotView(QWidget
*parent
)
163 : QGraphicsView
{parent
}
166 greenPen
.setWidthF(1.5);
167 greenPen
.setColor(QColor(134, 196, 63));
169 bluePen
.setWidthF(1.5);
170 bluePen
.setColor(QColor(50, 153, 255));
172 m_properties
[UP
] = GraphProperties(tr("Total Upload"), bluePen
);
173 m_properties
[DOWN
] = GraphProperties(tr("Total Download"), greenPen
);
175 bluePen
.setStyle(Qt::DashLine
);
176 greenPen
.setStyle(Qt::DashLine
);
177 m_properties
[PAYLOAD_UP
] = GraphProperties(tr("Payload Upload"), bluePen
);
178 m_properties
[PAYLOAD_DOWN
] = GraphProperties(tr("Payload Download"), greenPen
);
180 bluePen
.setStyle(Qt::DashDotLine
);
181 greenPen
.setStyle(Qt::DashDotLine
);
182 m_properties
[OVERHEAD_UP
] = GraphProperties(tr("Overhead Upload"), bluePen
);
183 m_properties
[OVERHEAD_DOWN
] = GraphProperties(tr("Overhead Download"), greenPen
);
185 bluePen
.setStyle(Qt::DashDotDotLine
);
186 greenPen
.setStyle(Qt::DashDotDotLine
);
187 m_properties
[DHT_UP
] = GraphProperties(tr("DHT Upload"), bluePen
);
188 m_properties
[DHT_DOWN
] = GraphProperties(tr("DHT Download"), greenPen
);
190 bluePen
.setStyle(Qt::DotLine
);
191 greenPen
.setStyle(Qt::DotLine
);
192 m_properties
[TRACKER_UP
] = GraphProperties(tr("Tracker Upload"), bluePen
);
193 m_properties
[TRACKER_DOWN
] = GraphProperties(tr("Tracker Download"), greenPen
);
196 void SpeedPlotView::setGraphEnable(GraphID id
, bool enable
)
198 m_properties
[id
].enable
= enable
;
199 viewport()->update();
202 void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData
&point
)
204 for (Averager
*averager
: {&m_averager5Min
, &m_averager30Min
205 , &m_averager6Hour
, &m_averager12Hour
206 , &m_averager24Hour
})
208 if (averager
->push(point
))
210 if (m_currentAverager
== averager
)
211 viewport()->update();
216 void SpeedPlotView::setPeriod(const TimePeriod period
)
220 case SpeedPlotView::MIN1
:
221 m_currentMaxDuration
= 1min
;
222 m_currentAverager
= &m_averager5Min
;
224 case SpeedPlotView::MIN5
:
225 m_currentMaxDuration
= 5min
;
226 m_currentAverager
= &m_averager5Min
;
228 case SpeedPlotView::MIN30
:
229 m_currentMaxDuration
= 30min
;
230 m_currentAverager
= &m_averager30Min
;
232 case SpeedPlotView::HOUR3
:
233 m_currentMaxDuration
= 3h
;
234 m_currentAverager
= &m_averager6Hour
;
236 case SpeedPlotView::HOUR6
:
237 m_currentMaxDuration
= 6h
;
238 m_currentAverager
= &m_averager6Hour
;
240 case SpeedPlotView::HOUR12
:
241 m_currentMaxDuration
= 12h
;
242 m_currentAverager
= &m_averager12Hour
;
244 case SpeedPlotView::HOUR24
:
245 m_currentMaxDuration
= 24h
;
246 m_currentAverager
= &m_averager24Hour
;
250 viewport()->update();
253 const SpeedPlotView::DataCircularBuffer
&SpeedPlotView::currentData() const
255 return m_currentAverager
->data();
258 quint64
SpeedPlotView::maxYValue() const
260 const DataCircularBuffer
&queue
= currentData();
262 quint64 maxYValue
= 0;
263 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
266 if (!m_properties
[static_cast<GraphID
>(id
)].enable
)
269 milliseconds duration
{0};
270 for (int i
= static_cast<int>(queue
.size()) - 1; i
>= 0; --i
)
272 maxYValue
= std::max(maxYValue
, queue
[i
].data
[id
]);
273 duration
+= queue
[i
].duration
;
274 if (duration
>= m_currentMaxDuration
)
282 void SpeedPlotView::paintEvent(QPaintEvent
*)
284 QPainter
painter(viewport());
286 QRect fullRect
= viewport()->rect();
287 QRect rect
= viewport()->rect();
288 QFontMetrics fontMetrics
= painter
.fontMetrics();
290 rect
.adjust(4, 4, 0, -4); // Add padding
291 const SplitValue niceScale
= getRoundedYScale(maxYValue());
292 rect
.adjust(0, fontMetrics
.height(), 0, 0); // Add top padding for top speed text
294 // draw Y axis speed labels
295 const QList
<QString
> speedLabels
=
297 formatLabel(niceScale
.arg
, niceScale
.unit
),
298 formatLabel((0.75 * niceScale
.arg
), niceScale
.unit
),
299 formatLabel((0.50 * niceScale
.arg
), niceScale
.unit
),
300 formatLabel((0.25 * niceScale
.arg
), niceScale
.unit
),
301 formatLabel(0.0, niceScale
.unit
),
305 for (const QString
&label
: speedLabels
)
307 if (fontMetrics
.horizontalAdvance(label
) > yAxisWidth
)
308 yAxisWidth
= fontMetrics
.horizontalAdvance(label
);
312 for (const QString
&label
: speedLabels
)
314 QRectF
labelRect(rect
.topLeft() + QPointF(-yAxisWidth
, (i
++) * 0.25 * rect
.height() - fontMetrics
.height()),
315 QSizeF(2 * yAxisWidth
, fontMetrics
.height()));
316 painter
.drawText(labelRect
, label
, Qt::AlignRight
| Qt::AlignTop
);
320 rect
.adjust(yAxisWidth
+ 4, 0, 0, 0);
323 gridPen
.setStyle(Qt::DashLine
);
324 gridPen
.setWidthF(1);
325 gridPen
.setColor(QColor(128, 128, 128, 128));
326 painter
.setPen(gridPen
);
328 painter
.drawLine(fullRect
.left(), rect
.top(), rect
.right(), rect
.top());
329 painter
.drawLine(fullRect
.left(), rect
.top() + 0.25 * rect
.height(), rect
.right(), rect
.top() + 0.25 * rect
.height());
330 painter
.drawLine(fullRect
.left(), rect
.top() + 0.50 * rect
.height(), rect
.right(), rect
.top() + 0.50 * rect
.height());
331 painter
.drawLine(fullRect
.left(), rect
.top() + 0.75 * rect
.height(), rect
.right(), rect
.top() + 0.75 * rect
.height());
332 painter
.drawLine(fullRect
.left(), rect
.bottom(), rect
.right(), rect
.bottom());
334 const int TIME_AXIS_DIVISIONS
= 6;
335 for (int i
= 0; i
< TIME_AXIS_DIVISIONS
; ++i
)
337 const int x
= rect
.left() + (i
* rect
.width()) / TIME_AXIS_DIVISIONS
;
338 painter
.drawLine(x
, fullRect
.top(), x
, fullRect
.bottom());
341 // Set antialiasing for graphs
342 painter
.setRenderHints(QPainter::Antialiasing
);
345 // averager is duration based, it may go little above the maxDuration
346 painter
.setClipping(true);
347 painter
.setClipRect(rect
);
349 const DataCircularBuffer
&queue
= currentData();
351 // last point will be drawn at x=0, so we don't need it in the calculation of xTickSize
352 const milliseconds lastDuration
{queue
.empty() ? 0ms
: queue
.back().duration
};
353 const qreal xTickSize
= static_cast<qreal
>(rect
.width()) / (m_currentMaxDuration
- lastDuration
).count();
354 const qreal yMultiplier
= (niceScale
.arg
== 0) ? 0 : (static_cast<qreal
>(rect
.height()) / niceScale
.sizeInBytes());
356 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
358 if (!m_properties
[static_cast<GraphID
>(id
)].enable
)
361 QList
<QPoint
> points
;
362 milliseconds duration
{0};
364 for (int i
= static_cast<int>(queue
.size()) - 1; i
>= 0; --i
)
366 const int newX
= rect
.right() - (duration
.count() * xTickSize
);
367 const int newY
= rect
.bottom() - (queue
[i
].data
[id
] * yMultiplier
);
368 points
.push_back(QPoint(newX
, newY
));
370 duration
+= queue
[i
].duration
;
371 if (duration
>= m_currentMaxDuration
)
375 painter
.setPen(m_properties
[static_cast<GraphID
>(id
)].pen
);
376 painter
.drawPolyline(points
.data(), points
.size());
378 painter
.setClipping(false);
381 QPoint
legendTopLeft(rect
.left() + 4, fullRect
.top() + 4);
383 qreal legendHeight
= 0;
385 for (const auto &property
: asConst(m_properties
))
387 if (!property
.enable
)
390 if (fontMetrics
.horizontalAdvance(property
.name
) > legendWidth
)
391 legendWidth
= fontMetrics
.horizontalAdvance(property
.name
);
392 legendHeight
+= 1.5 * fontMetrics
.height();
395 QRectF
legendBackgroundRect(QPoint(legendTopLeft
.x() - 4, legendTopLeft
.y() - 4), QSizeF(legendWidth
+ 8, legendHeight
+ 8));
396 QColor legendBackgroundColor
= QWidget::palette().color(QWidget::backgroundRole());
397 legendBackgroundColor
.setAlpha(128); // 50% transparent
398 painter
.fillRect(legendBackgroundRect
, legendBackgroundColor
);
401 for (const auto &property
: asConst(m_properties
))
403 if (!property
.enable
)
406 const int nameSize
= fontMetrics
.horizontalAdvance(property
.name
);
407 const qreal indent
= 1.5 * (i
++) * fontMetrics
.height();
409 painter
.setPen(property
.pen
);
410 painter
.drawLine(legendTopLeft
+ QPointF(0, indent
+ fontMetrics
.height()),
411 legendTopLeft
+ QPointF(nameSize
, indent
+ fontMetrics
.height()));
412 painter
.drawText(QRectF(legendTopLeft
+ QPointF(0, indent
), QSizeF(2 * nameSize
, fontMetrics
.height())),
413 property
.name
, QTextOption(Qt::AlignVCenter
));
417 SpeedPlotView::GraphProperties::GraphProperties()
422 SpeedPlotView::GraphProperties::GraphProperties(const QString
&name
, const QPen
&pen
, bool enable
)