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 double roundingTable
[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
51 Utils::Misc::SizeUnit unit
;
52 qint64
sizeInBytes() const
54 return Utils::Misc::sizeInBytes(arg
, unit
);
58 SplittedValue
getRoundedYScale(double 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 double roundedValue
{std::ceil(value
/ 40) * 40};
75 return {roundedValue
, calculatedUnit
};
80 const double 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 double 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 + C_NON_BREAKING_SPACE
98 + unitString(unit
, true);
102 SpeedPlotView::Averager::Averager(const milliseconds duration
, const milliseconds resolution
)
103 : m_resolution
{resolution
}
104 , m_maxDuration
{duration
}
105 , m_sink
{static_cast<DataCircularBuffer::size_type
>(duration
/ resolution
)}
107 m_lastSampleTime
.start();
110 bool SpeedPlotView::Averager::push(const SampleData
&sampleData
)
112 // Accumulator overflow will be hit in worst case on longest used averaging span,
113 // defined by resolution. Maximum resolution is 144 seconds
114 // Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
115 // With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
116 // This speed is inaccessible to an ordinary user.
118 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
119 m_accumulator
[id
] += sampleData
[id
];
121 // system may go to sleep, that can cause very big elapsed interval
122 const milliseconds updateInterval
{static_cast<int64_t>(BitTorrent::Session::instance()->refreshInterval() * 1.25)};
123 const milliseconds maxElapsed
{std::max(updateInterval
, m_resolution
)};
124 const milliseconds elapsed
{std::min(milliseconds
{m_lastSampleTime
.elapsed()}, maxElapsed
)};
125 if (elapsed
< m_resolution
)
126 return false; // still accumulating
128 // it is time final averaging calculations
129 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
130 m_accumulator
[id
] /= m_counter
;
132 m_currentDuration
+= elapsed
;
134 // remove extra data from front if we reached max duration
135 if (m_currentDuration
> m_maxDuration
)
137 // once we go above the max duration never go below that
138 // otherwise it will cause empty space in graphs
139 while (!m_sink
.empty()
140 && ((m_currentDuration
- m_sink
.front().duration
) >= m_maxDuration
))
142 m_currentDuration
-= m_sink
.front().duration
;
147 // now flush out averaged data
148 Q_ASSERT(m_sink
.size() < m_sink
.capacity());
149 m_sink
.push_back({elapsed
, m_accumulator
});
154 m_lastSampleTime
.restart();
158 const SpeedPlotView::DataCircularBuffer
&SpeedPlotView::Averager::data() const
163 SpeedPlotView::SpeedPlotView(QWidget
*parent
)
164 : QGraphicsView
{parent
}
167 greenPen
.setWidthF(1.5);
168 greenPen
.setColor(QColor(134, 196, 63));
170 bluePen
.setWidthF(1.5);
171 bluePen
.setColor(QColor(50, 153, 255));
173 m_properties
[UP
] = GraphProperties(tr("Total Upload"), bluePen
);
174 m_properties
[DOWN
] = GraphProperties(tr("Total Download"), greenPen
);
176 bluePen
.setStyle(Qt::DashLine
);
177 greenPen
.setStyle(Qt::DashLine
);
178 m_properties
[PAYLOAD_UP
] = GraphProperties(tr("Payload Upload"), bluePen
);
179 m_properties
[PAYLOAD_DOWN
] = GraphProperties(tr("Payload Download"), greenPen
);
181 bluePen
.setStyle(Qt::DashDotLine
);
182 greenPen
.setStyle(Qt::DashDotLine
);
183 m_properties
[OVERHEAD_UP
] = GraphProperties(tr("Overhead Upload"), bluePen
);
184 m_properties
[OVERHEAD_DOWN
] = GraphProperties(tr("Overhead Download"), greenPen
);
186 bluePen
.setStyle(Qt::DashDotDotLine
);
187 greenPen
.setStyle(Qt::DashDotDotLine
);
188 m_properties
[DHT_UP
] = GraphProperties(tr("DHT Upload"), bluePen
);
189 m_properties
[DHT_DOWN
] = GraphProperties(tr("DHT Download"), greenPen
);
191 bluePen
.setStyle(Qt::DotLine
);
192 greenPen
.setStyle(Qt::DotLine
);
193 m_properties
[TRACKER_UP
] = GraphProperties(tr("Tracker Upload"), bluePen
);
194 m_properties
[TRACKER_DOWN
] = GraphProperties(tr("Tracker Download"), greenPen
);
197 void SpeedPlotView::setGraphEnable(GraphID id
, bool enable
)
199 m_properties
[id
].enable
= enable
;
200 viewport()->update();
203 void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData
&point
)
205 for (Averager
*averager
: {&m_averager5Min
, &m_averager30Min
206 , &m_averager6Hour
, &m_averager12Hour
207 , &m_averager24Hour
})
209 if (averager
->push(point
))
211 if (m_currentAverager
== averager
)
212 viewport()->update();
217 void SpeedPlotView::setPeriod(const TimePeriod period
)
221 case SpeedPlotView::MIN1
:
222 m_currentMaxDuration
= 1min
;
223 m_currentAverager
= &m_averager5Min
;
225 case SpeedPlotView::MIN5
:
226 m_currentMaxDuration
= 5min
;
227 m_currentAverager
= &m_averager5Min
;
229 case SpeedPlotView::MIN30
:
230 m_currentMaxDuration
= 30min
;
231 m_currentAverager
= &m_averager30Min
;
233 case SpeedPlotView::HOUR3
:
234 m_currentMaxDuration
= 3h
;
235 m_currentAverager
= &m_averager6Hour
;
237 case SpeedPlotView::HOUR6
:
238 m_currentMaxDuration
= 6h
;
239 m_currentAverager
= &m_averager6Hour
;
241 case SpeedPlotView::HOUR12
:
242 m_currentMaxDuration
= 12h
;
243 m_currentAverager
= &m_averager12Hour
;
245 case SpeedPlotView::HOUR24
:
246 m_currentMaxDuration
= 24h
;
247 m_currentAverager
= &m_averager24Hour
;
251 viewport()->update();
254 const SpeedPlotView::DataCircularBuffer
&SpeedPlotView::currentData() const
256 return m_currentAverager
->data();
259 quint64
SpeedPlotView::maxYValue() const
261 const DataCircularBuffer
&queue
= currentData();
263 quint64 maxYValue
= 0;
264 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
267 if (!m_properties
[static_cast<GraphID
>(id
)].enable
)
270 milliseconds duration
{0};
271 for (int i
= static_cast<int>(queue
.size()) - 1; i
>= 0; --i
)
273 maxYValue
= std::max(maxYValue
, queue
[i
].data
[id
]);
274 duration
+= queue
[i
].duration
;
275 if (duration
>= m_currentMaxDuration
)
283 void SpeedPlotView::paintEvent(QPaintEvent
*)
285 QPainter
painter(viewport());
287 QRect fullRect
= viewport()->rect();
288 QRect rect
= viewport()->rect();
289 QFontMetrics fontMetrics
= painter
.fontMetrics();
291 rect
.adjust(4, 4, 0, -4); // Add padding
292 const SplittedValue niceScale
= getRoundedYScale(maxYValue());
293 rect
.adjust(0, fontMetrics
.height(), 0, 0); // Add top padding for top speed text
295 // draw Y axis speed labels
296 const QVector
<QString
> speedLabels
=
298 formatLabel(niceScale
.arg
, niceScale
.unit
),
299 formatLabel((0.75 * niceScale
.arg
), niceScale
.unit
),
300 formatLabel((0.50 * niceScale
.arg
), niceScale
.unit
),
301 formatLabel((0.25 * niceScale
.arg
), niceScale
.unit
),
302 formatLabel(0.0, niceScale
.unit
),
306 for (const QString
&label
: speedLabels
)
308 if (fontMetrics
.horizontalAdvance(label
) > yAxisWidth
)
309 yAxisWidth
= fontMetrics
.horizontalAdvance(label
);
313 for (const QString
&label
: speedLabels
)
315 QRectF
labelRect(rect
.topLeft() + QPointF(-yAxisWidth
, (i
++) * 0.25 * rect
.height() - fontMetrics
.height()),
316 QSizeF(2 * yAxisWidth
, fontMetrics
.height()));
317 painter
.drawText(labelRect
, label
, Qt::AlignRight
| Qt::AlignTop
);
321 rect
.adjust(yAxisWidth
+ 4, 0, 0, 0);
324 gridPen
.setStyle(Qt::DashLine
);
325 gridPen
.setWidthF(1);
326 gridPen
.setColor(QColor(128, 128, 128, 128));
327 painter
.setPen(gridPen
);
329 painter
.drawLine(fullRect
.left(), rect
.top(), rect
.right(), rect
.top());
330 painter
.drawLine(fullRect
.left(), rect
.top() + 0.25 * rect
.height(), rect
.right(), rect
.top() + 0.25 * rect
.height());
331 painter
.drawLine(fullRect
.left(), rect
.top() + 0.50 * rect
.height(), rect
.right(), rect
.top() + 0.50 * rect
.height());
332 painter
.drawLine(fullRect
.left(), rect
.top() + 0.75 * rect
.height(), rect
.right(), rect
.top() + 0.75 * rect
.height());
333 painter
.drawLine(fullRect
.left(), rect
.bottom(), rect
.right(), rect
.bottom());
335 const int TIME_AXIS_DIVISIONS
= 6;
336 for (int i
= 0; i
< TIME_AXIS_DIVISIONS
; ++i
)
338 const int x
= rect
.left() + (i
* rect
.width()) / TIME_AXIS_DIVISIONS
;
339 painter
.drawLine(x
, fullRect
.top(), x
, fullRect
.bottom());
342 // Set antialiasing for graphs
343 painter
.setRenderHints(QPainter::Antialiasing
);
346 // averager is duration based, it may go little above the maxDuration
347 painter
.setClipping(true);
348 painter
.setClipRect(rect
);
350 const DataCircularBuffer
&queue
= currentData();
352 // last point will be drawn at x=0, so we don't need it in the calculation of xTickSize
353 const milliseconds lastDuration
{queue
.empty() ? 0ms
: queue
.back().duration
};
354 const double xTickSize
= static_cast<double>(rect
.width()) / (m_currentMaxDuration
- lastDuration
).count();
355 const double yMultiplier
= (niceScale
.arg
== 0) ? 0 : (static_cast<double>(rect
.height()) / niceScale
.sizeInBytes());
357 for (int id
= UP
; id
< NB_GRAPHS
; ++id
)
359 if (!m_properties
[static_cast<GraphID
>(id
)].enable
)
362 QVector
<QPoint
> points
;
363 milliseconds duration
{0};
365 for (int i
= static_cast<int>(queue
.size()) - 1; i
>= 0; --i
)
367 const int newX
= rect
.right() - (duration
.count() * xTickSize
);
368 const int newY
= rect
.bottom() - (queue
[i
].data
[id
] * yMultiplier
);
369 points
.push_back(QPoint(newX
, newY
));
371 duration
+= queue
[i
].duration
;
372 if (duration
>= m_currentMaxDuration
)
376 painter
.setPen(m_properties
[static_cast<GraphID
>(id
)].pen
);
377 painter
.drawPolyline(points
.data(), points
.size());
379 painter
.setClipping(false);
382 QPoint
legendTopLeft(rect
.left() + 4, fullRect
.top() + 4);
384 double legendHeight
= 0;
386 for (const auto &property
: asConst(m_properties
))
388 if (!property
.enable
)
391 if (fontMetrics
.horizontalAdvance(property
.name
) > legendWidth
)
392 legendWidth
= fontMetrics
.horizontalAdvance(property
.name
);
393 legendHeight
+= 1.5 * fontMetrics
.height();
396 QRectF
legendBackgroundRect(QPoint(legendTopLeft
.x() - 4, legendTopLeft
.y() - 4), QSizeF(legendWidth
+ 8, legendHeight
+ 8));
397 QColor legendBackgroundColor
= QWidget::palette().color(QWidget::backgroundRole());
398 legendBackgroundColor
.setAlpha(128); // 50% transparent
399 painter
.fillRect(legendBackgroundRect
, legendBackgroundColor
);
402 for (const auto &property
: asConst(m_properties
))
404 if (!property
.enable
)
407 int nameSize
= fontMetrics
.horizontalAdvance(property
.name
);
408 double indent
= 1.5 * (i
++) * fontMetrics
.height();
410 painter
.setPen(property
.pen
);
411 painter
.drawLine(legendTopLeft
+ QPointF(0, indent
+ fontMetrics
.height()),
412 legendTopLeft
+ QPointF(nameSize
, indent
+ fontMetrics
.height()));
413 painter
.drawText(QRectF(legendTopLeft
+ QPointF(0, indent
), QSizeF(2 * nameSize
, fontMetrics
.height())),
414 property
.name
, QTextOption(Qt::AlignVCenter
));
418 SpeedPlotView::GraphProperties::GraphProperties()
423 SpeedPlotView::GraphProperties::GraphProperties(const QString
&name
, const QPen
&pen
, bool enable
)