1 /************************************************************************
2 * file name         : arbitrary_value_inspector.cpp
3 * ----------------- :
4 * creation time     : 2017/11/30
5 * author            : Victor Zarubkin
6 * email             : v.s.zarubkin@gmail.com
7 * ----------------- :
8 * description       : The file contains implementation of .
9 * ----------------- :
10 * change log        : * 2017/11/30 Victor Zarubkin: initial commit.
11 *                   :
12 *                   : *
13 * ----------------- :
14 * license           : Lightweight profiler library for c++
15 *                   : Copyright(C) 2016-2017  Sergey Yagovtsev, Victor Zarubkin
16 *                   :
17 *                   : Licensed under either of
18 *                   :     * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
19 *                   :     * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
20 *                   : at your option.
21 *                   :
22 *                   : The MIT License
23 *                   :
24 *                   : Permission is hereby granted, free of charge, to any person obtaining a copy
25 *                   : of this software and associated documentation files (the "Software"), to deal
26 *                   : in the Software without restriction, including without limitation the rights
27 *                   : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28 *                   : of the Software, and to permit persons to whom the Software is furnished
29 *                   : to do so, subject to the following conditions:
30 *                   :
31 *                   : The above copyright notice and this permission notice shall be included in all
32 *                   : copies or substantial portions of the Software.
33 *                   :
34 *                   : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
35 *                   : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
36 *                   : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
37 *                   : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
38 *                   : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
39 *                   : USE OR OTHER DEALINGS IN THE SOFTWARE.
40 *                   :
41 *                   : The Apache License, Version 2.0 (the "License")
42 *                   :
43 *                   : You may not use this file except in compliance with the License.
44 *                   : You may obtain a copy of the License at
45 *                   :
46 *                   : http://www.apache.org/licenses/LICENSE-2.0
47 *                   :
48 *                   : Unless required by applicable law or agreed to in writing, software
49 *                   : distributed under the License is distributed on an "AS IS" BASIS,
50 *                   : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
51 *                   : See the License for the specific language governing permissions and
52 *                   : limitations under the License.
53 ************************************************************************/
54 
55 #include <QPainter>
56 #include <QGraphicsScene>
57 #include <QColor>
58 #include <QHeaderView>
59 #include <QVBoxLayout>
60 #include <QResizeEvent>
61 #include <QVariant>
62 #include <list>
63 #include "arbitrary_value_inspector.h"
64 #include "treeview_first_column_delegate.h"
65 #include "globals.h"
66 
67 //////////////////////////////////////////////////////////////////////////
68 
getChartPoints(const ArbitraryValuesCollection & _collection,Points & _points,qreal & _minValue,qreal & _maxValue)69 void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _points, qreal& _minValue, qreal& _maxValue)
70 {
71     _minValue =  1e300;
72     _maxValue = -1e300;
73 
74     const auto size = _collection.size();
75     _points.clear();
76     _points.reserve(size);
77 
78     if (size == 0)
79         return;
80 
81     const auto& valuesByThread = _collection.valuesMap();
82 
83     if (valuesByThread.size() == 1)
84     {
85         const auto& values = valuesByThread.begin()->second;
86         for (auto value : values)
87         {
88             const qreal x = sceneX(value->begin());
89             const qreal y = profiler_gui::value2real(*value);
90             _points.emplace_back(x, y);
91 
92             if (y > _maxValue)
93                 _maxValue = y;
94             if (y < _minValue)
95                 _minValue = y;
96         }
97     }
98     else
99     {
100         std::list<profiler::thread_id_t> threadIds;
101         for (const auto& it : valuesByThread)
102             threadIds.push_back(it.first);
103 
104         size_t i = 0;
105         while (!threadIds.empty())
106         {
107             for (auto it = threadIds.begin(); it != threadIds.end();)
108             {
109                 const auto& values = valuesByThread.at(*it);
110                 if (i >= values.size())
111                 {
112                     it = threadIds.erase(it);
113                     continue;
114                 }
115 
116                 const qreal x = sceneX(values[i]->begin());
117                 const qreal y = profiler_gui::value2real(*values[i]);
118                 _points.emplace_back(x, y);
119 
120                 if (y > _maxValue)
121                     _maxValue = y;
122                 if (y < _minValue)
123                     _minValue = y;
124 
125                 ++it;
126             }
127         }
128 
129         std::sort(_points.begin(), _points.end(), [](const QPointF& lhs, const QPointF& rhs) -> bool {
130             return lhs.x() < rhs.x();
131         });
132     }
133 }
134 
135 //////////////////////////////////////////////////////////////////////////
136 
ArbitraryValuesCollection()137 ArbitraryValuesCollection::ArbitraryValuesCollection()
138     : m_beginTime(0)
139     , m_minValue(1e300)
140     , m_maxValue(-1e300)
141     , m_jobType(0)
142 {
143     m_status = ATOMIC_VAR_INIT(Idle);
144     m_bInterrupt = ATOMIC_VAR_INIT(false);
145 }
146 
~ArbitraryValuesCollection()147 ArbitraryValuesCollection::~ArbitraryValuesCollection()
148 {
149     interrupt();
150 }
151 
valuesMap() const152 const ArbitraryValuesMap& ArbitraryValuesCollection::valuesMap() const
153 {
154     return m_values;
155 }
156 
points() const157 const Points& ArbitraryValuesCollection::points() const
158 {
159     return m_points;
160 }
161 
status() const162 ArbitraryValuesCollection::JobStatus ArbitraryValuesCollection::status() const
163 {
164     return static_cast<JobStatus>(m_status.load(std::memory_order_acquire));
165 }
166 
size() const167 size_t ArbitraryValuesCollection::size() const
168 {
169     size_t totalSize = 0;
170     for (const auto& it : m_values)
171         totalSize += it.second.size();
172     return totalSize;
173 }
174 
minValue() const175 qreal ArbitraryValuesCollection::minValue() const
176 {
177     return m_minValue;
178 }
179 
maxValue() const180 qreal ArbitraryValuesCollection::maxValue() const
181 {
182     return m_maxValue;
183 }
184 
collectValues(profiler::thread_id_t _threadId,profiler::vin_t _valueId,const char * _valueName)185 void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName)
186 {
187     interrupt();
188 
189     setStatus(InProgress);
190     m_points.clear();
191     m_jobType = ValuesJob;
192 
193     if (_valueId == 0)
194         m_collectorThread = std::thread(&This::collectByName, this, _threadId, _valueName);
195     else
196         m_collectorThread = std::thread(&This::collectById, this, _threadId, _valueId);
197 }
198 
collectValues(profiler::thread_id_t _threadId,profiler::vin_t _valueId,const char * _valueName,profiler::timestamp_t _beginTime)199 void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::timestamp_t _beginTime)
200 {
201     interrupt();
202 
203     setStatus(InProgress);
204     m_points.clear();
205     m_beginTime = _beginTime;
206     m_minValue =  1e300;
207     m_maxValue = -1e300;
208     m_jobType = ValuesJob | PointsJob;
209 
210     if (_valueId == 0)
211         m_collectorThread = std::thread(&This::collectByName, this, _threadId, _valueName);
212     else
213         m_collectorThread = std::thread(&This::collectById, this, _threadId, _valueId);
214 }
215 
calculatePoints(profiler::timestamp_t _beginTime)216 bool ArbitraryValuesCollection::calculatePoints(profiler::timestamp_t _beginTime)
217 {
218     if (status() != Ready || m_values.empty())
219         return false;
220 
221     if (m_collectorThread.joinable())
222         m_collectorThread.join();
223 
224     setStatus(InProgress);
225     m_points.clear();
226     m_beginTime = _beginTime;
227     m_minValue =  1e300;
228     m_maxValue = -1e300;
229     m_jobType = PointsJob;
230 
231     m_collectorThread = std::thread([this]
232     {
233         getChartPoints(*this, m_points, m_minValue, m_maxValue);
234         setStatus(Ready);
235     });
236 
237     return true;
238 }
239 
interrupt()240 void ArbitraryValuesCollection::interrupt()
241 {
242     if (!m_collectorThread.joinable())
243         return;
244 
245     m_bInterrupt.store(true, std::memory_order_release);
246     m_collectorThread.join();
247     m_bInterrupt.store(false, std::memory_order_release);
248 
249     setStatus(Idle);
250     m_jobType = None;
251     m_values.clear();
252 }
253 
setStatus(JobStatus _status)254 void ArbitraryValuesCollection::setStatus(JobStatus _status)
255 {
256     m_status.store(static_cast<uint8_t>(_status), std::memory_order_release);
257 }
258 
collectById(profiler::thread_id_t _threadId,profiler::vin_t _valueId)259 void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId)
260 {
261     if (_threadId == 0)
262     {
263         const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
264         const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1;
265 
266         for (const auto& it : EASY_GLOBALS.profiler_blocks)
267         {
268             if (!collectByIdForThread(it.second, _valueId, calculatePointsInner))
269                 return;
270         }
271 
272         if ((m_jobType & PointsJob) != 0 && threadsCount > 1)
273             getChartPoints(*this, m_points, m_minValue, m_maxValue);
274     }
275     else
276     {
277         const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
278         if (t != EASY_GLOBALS.profiler_blocks.end() && !collectByIdForThread(t->second, _valueId, (m_jobType & PointsJob) != 0))
279             return;
280     }
281 
282     setStatus(Ready);
283 }
284 
collectByIdForThread(const profiler::BlocksTreeRoot & _threadRoot,profiler::vin_t _valueId,bool _calculatePoints)285 bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot,
286                                                      profiler::vin_t _valueId, bool _calculatePoints)
287 {
288     auto& valuesList = m_values[_threadRoot.thread_id];
289 
290     for (auto i : _threadRoot.events)
291     {
292         if (m_bInterrupt.load(std::memory_order_acquire))
293             return false;
294 
295         const auto& block = easyBlock(i).tree;
296         const auto& desc = easyDescriptor(block.node->id());
297         if (desc.type() != profiler::BlockType::Value)
298             continue;
299 
300         const auto value = block.value;
301         if (value->value_id() != _valueId)
302             continue;
303 
304         valuesList.push_back(value);
305 
306         if (_calculatePoints)
307         {
308             const auto p = point(*block.value);
309 
310             if (p.y() > m_maxValue)
311                 m_maxValue = p.y();
312             if (p.y() < m_minValue)
313                 m_minValue = p.y();
314 
315             m_points.push_back(p);
316         }
317     }
318 
319     return true;
320 }
321 
collectByName(profiler::thread_id_t _threadId,const std::string _valueName)322 void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName)
323 {
324     if (_threadId == 0)
325     {
326         const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
327         const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1;
328 
329         for (const auto& it : EASY_GLOBALS.profiler_blocks)
330         {
331             if (!collectByNameForThread(it.second, _valueName, calculatePointsInner))
332                 return;
333         }
334 
335         if ((m_jobType & PointsJob) != 0 && threadsCount > 1)
336             getChartPoints(*this, m_points, m_minValue, m_maxValue);
337     }
338     else
339     {
340         const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
341         if (t != EASY_GLOBALS.profiler_blocks.end() && !collectByNameForThread(t->second, _valueName, (m_jobType & PointsJob) != 0))
342             return;
343     }
344 
345     setStatus(Ready);
346 }
347 
collectByNameForThread(const profiler::BlocksTreeRoot & _threadRoot,const std::string & _valueName,bool _calculatePoints)348 bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot,
349                                                        const std::string& _valueName, bool _calculatePoints)
350 {
351     auto& valuesList = m_values[_threadRoot.thread_id];
352 
353     for (auto i : _threadRoot.events)
354     {
355         if (m_bInterrupt.load(std::memory_order_acquire))
356             return false;
357 
358         const auto& block = easyBlock(i).tree;
359         const auto& desc = easyDescriptor(block.node->id());
360         if (desc.type() != profiler::BlockType::Value || _valueName != desc.name())
361             continue;
362 
363         valuesList.push_back(block.value);
364 
365         if (_calculatePoints)
366         {
367             const auto p = point(*block.value);
368 
369             if (p.y() > m_maxValue)
370                 m_maxValue = p.y();
371             if (p.y() < m_minValue)
372                 m_minValue = p.y();
373 
374             m_points.push_back(p);
375         }
376     }
377 
378     return true;
379 }
380 
point(const profiler::ArbitraryValue & _value) const381 QPointF ArbitraryValuesCollection::point(const profiler::ArbitraryValue& _value) const
382 {
383     const qreal x = PROF_MICROSECONDS(qreal(_value.begin() - m_beginTime));
384     const qreal y = profiler_gui::value2real(_value);
385     return {x, y};
386 }
387 
388 //////////////////////////////////////////////////////////////////////////
389 
EasyArbitraryValuesChartItem()390 EasyArbitraryValuesChartItem::EasyArbitraryValuesChartItem()
391     : Parent(nullptr)
392 {
393 }
394 
~EasyArbitraryValuesChartItem()395 EasyArbitraryValuesChartItem::~EasyArbitraryValuesChartItem()
396 {
397 
398 }
399 
paint(QPainter * _painter,const QStyleOptionGraphicsItem *,QWidget *)400 void EasyArbitraryValuesChartItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
401 {
402     if (m_collections.empty())
403         return;
404 
405     const auto& chart = *reinterpret_cast<const EasyGraphicsChart*>(scene()->parent());
406     const auto scale = chart.xscale();
407 
408     qreal minValue = 1e300, maxValue = -1e300;
409     for (const auto& c : m_collections)
410     {
411         const auto& collection = *c.ptr;
412         if (minValue > collection.minValue())
413             minValue = collection.minValue();
414         if (maxValue < collection.maxValue())
415             maxValue = collection.maxValue();
416     }
417 
418     const qreal height = std::max(maxValue - minValue, 1.);
419 
420     auto r = scene()->sceneRect();
421 
422 
423     _painter->save();
424 
425     for (const auto& c : m_collections)
426     {
427         const auto& points = c.ptr->points();
428         if (points.empty())
429             continue;
430 
431         if (c.selected)
432         {
433             auto pen = _painter->pen();
434             pen.setColor(QColor::fromRgba(c.color));
435             pen.setWidth(3);
436             _painter->setPen(pen);
437         }
438         else
439         {
440             _painter->setPen(QColor::fromRgba(c.color));
441         }
442 
443         if (points.size() == 1)
444             _painter->drawPoint(points.front());
445         else
446         {
447             auto gety = [&r, &minValue, &maxValue, &height] (qreal y)
448             {
449                 y = maxValue - y;
450                 y /= height;
451                 y *= r.height() - 10;
452                 y += r.top() + 5;
453                 return y;
454             };
455 
456             if (c.chartType == ChartType::Points)
457             {
458                 for (const auto& p : points)
459                     _painter->drawPoint(QPointF {p.x() * scale, gety(p.y())});
460             }
461             else
462             {
463                 QPointF p1 = points.front();
464                 for (int i = 1; i < points.size(); ++i)
465                 {
466                     const auto& p2 = points[i];
467                     _painter->drawLine(QPointF {p1.x() * scale, gety(p1.y())}, QPointF {p2.x() * scale, gety(p2.y())});
468                     p1 = p2;
469                 }
470             }
471             //_painter->drawPolyline(points.data(), static_cast<int>(points.size()));
472         }
473     }
474 
475     _painter->restore();
476 }
477 
boundingRect() const478 QRectF EasyArbitraryValuesChartItem::boundingRect() const
479 {
480     return m_boundingRect;
481 }
482 
setBoundingRect(const QRectF & _rect)483 void EasyArbitraryValuesChartItem::setBoundingRect(const QRectF& _rect)
484 {
485     m_boundingRect = _rect;
486 }
487 
setBoundingRect(qreal _left,qreal _top,qreal _width,qreal _height)488 void EasyArbitraryValuesChartItem::setBoundingRect(qreal _left, qreal _top, qreal _width, qreal _height)
489 {
490     m_boundingRect.setRect(_left, _top, _width, _height);
491 }
492 
update(Collections _collections)493 void EasyArbitraryValuesChartItem::update(Collections _collections)
494 {
495     m_collections = std::move(_collections);
496 }
497 
update(const ArbitraryValuesCollection * _selected)498 void EasyArbitraryValuesChartItem::update(const ArbitraryValuesCollection* _selected)
499 {
500     for (auto& collection : m_collections)
501         collection.selected = collection.ptr == _selected;
502 }
503 
504 //////////////////////////////////////////////////////////////////////////
505 
EasyGraphicsChart(QWidget * _parent)506 EasyGraphicsChart::EasyGraphicsChart(QWidget* _parent)
507     : Parent(_parent)
508     , m_chartItem(new EasyArbitraryValuesChartItem())
509     , m_left(0)
510     , m_right(100)
511     , m_offset(0)
512     , m_xscale(1)
513     , m_visibleRegionWidth(100)
514     , m_bBindMode(false)
515 {
516     setCacheMode(QGraphicsView::CacheNone);
517     setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
518     //setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
519     setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
520     setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
521 
522     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
523     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
524 
525     setContentsMargins(0, 0, 0, 0);
526 
527     setScene(new QGraphicsScene(this));
528     scene()->setSceneRect(0, -250, 500, 500);
529     scene()->addItem(m_chartItem);
530 
531     connect(&EASY_GLOBALS.events, &profiler_gui::EasyGlobalSignals::sceneSizeChanged,
532             this, &This::onSceneSizeChanged, Qt::QueuedConnection);
533 
534     onSceneSizeChanged();
535 }
536 
~EasyGraphicsChart()537 EasyGraphicsChart::~EasyGraphicsChart()
538 {
539 
540 }
541 
onSceneSizeChanged()542 void EasyGraphicsChart::onSceneSizeChanged()
543 {
544     setRange(EASY_GLOBALS.scene_left, EASY_GLOBALS.scene_right);
545 }
546 
resizeEvent(QResizeEvent * _event)547 void EasyGraphicsChart::resizeEvent(QResizeEvent* _event)
548 {
549     auto size = _event->size();
550     onWindowSizeChanged(size.width(), size.height());
551     scene()->update();
552 }
553 
clear()554 void EasyGraphicsChart::clear()
555 {
556     m_chartItem->update(Collections {});
557 }
558 
bindMode() const559 bool EasyGraphicsChart::bindMode() const
560 {
561     return m_bBindMode;
562 }
563 
xscale() const564 qreal EasyGraphicsChart::xscale() const
565 {
566     return m_xscale;
567 }
568 
left() const569 qreal EasyGraphicsChart::left() const
570 {
571     return m_left;
572 }
573 
right() const574 qreal EasyGraphicsChart::right() const
575 {
576     return m_right;
577 }
578 
range() const579 qreal EasyGraphicsChart::range() const
580 {
581     return m_right - m_left;
582 }
583 
offset() const584 qreal EasyGraphicsChart::offset() const
585 {
586     return m_bBindMode ? m_offset : 0;
587 }
588 
region() const589 qreal EasyGraphicsChart::region() const
590 {
591     return m_bBindMode ? m_visibleRegionWidth : range();
592 }
593 
setOffset(qreal _offset)594 void EasyGraphicsChart::setOffset(qreal _offset)
595 {
596     m_offset = std::min(std::max(m_left, m_offset), m_right - m_visibleRegionWidth);
597 }
598 
setRange(qreal _left,qreal _right)599 void EasyGraphicsChart::setRange(qreal _left, qreal _right)
600 {
601     const auto oldRange = range();
602     const auto oldOffsetPart = oldRange < 1e-3 ? 0.0 : m_offset / oldRange;
603 
604     m_left = _left;
605     m_right = _right;
606 
607     if (m_left > m_right)
608         std::swap(m_left, m_right);
609 
610     const auto sceneRange = range();
611     //scene()->setSceneRect(m_left, -(height() >> 1), sceneRange, height());
612     //m_chartItem->setBoundingRect(scene()->sceneRect());
613 
614     m_offset = m_left + oldOffsetPart * sceneRange;
615     m_visibleRegionWidth = std::min(m_visibleRegionWidth, sceneRange);
616 
617     //const auto oldXScale = m_xscale;
618     m_xscale = sceneRange < 1e-3 ? 1.0 : width() / sceneRange;
619     //scale(m_xscale / oldXScale, 1);
620 
621     scene()->update();
622 }
623 
setRegion(qreal _visibleRegionWidth)624 void EasyGraphicsChart::setRegion(qreal _visibleRegionWidth)
625 {
626     m_visibleRegionWidth = std::min(_visibleRegionWidth, range());
627     setOffset(m_offset);
628 }
629 
onWindowSizeChanged(qreal _width,qreal _height)630 void EasyGraphicsChart::onWindowSizeChanged(qreal _width, qreal _height)
631 {
632     //const auto oldXScale = m_xscale;
633     const auto sceneRange = range();
634 
635     m_xscale = sceneRange < 1e-3 ? 1.0 : _width / sceneRange;
636 
637     scene()->setSceneRect(0, -_height * 0.5, _width, _height);
638     //scene()->setSceneRect(m_left, -_height * 0.5, sceneRange, _height);
639     m_chartItem->setBoundingRect(scene()->sceneRect());
640     //scale(m_xscale / oldXScale, 1);
641 }
642 
update(Collections _collections)643 void EasyGraphicsChart::update(Collections _collections)
644 {
645     m_chartItem->update(std::move(_collections));
646     scene()->update();
647 }
648 
update(const ArbitraryValuesCollection * _selected)649 void EasyGraphicsChart::update(const ArbitraryValuesCollection* _selected)
650 {
651     m_chartItem->update(_selected);
652     scene()->update();
653 }
654 
655 //////////////////////////////////////////////////////////////////////////
656 
657 enum class ArbitraryColumns : uint8_t
658 {
659     Name = 0,
660     Type,
661     Value,
662     Vin,
663 
664     Count
665 };
666 
667 EASY_CONSTEXPR auto CheckColumn = int_cast(ArbitraryColumns::Name);
668 EASY_CONSTEXPR auto StdItemType = QTreeWidgetItem::UserType;
669 EASY_CONSTEXPR auto ValueItemType = QTreeWidgetItem::UserType + 1;
670 
671 struct UsedValueTypes {
672     EasyArbitraryTreeWidgetItem* items[int_cast(profiler::DataType::TypesCount)];
UsedValueTypesUsedValueTypes673     UsedValueTypes(int = 0) { memset(items, 0, sizeof(items)); }
674 };
675 
676 //////////////////////////////////////////////////////////////////////////
677 
EasyArbitraryTreeWidgetItem(QTreeWidgetItem * _parent,profiler::color_t _color,profiler::vin_t _vin)678 EasyArbitraryTreeWidgetItem::EasyArbitraryTreeWidgetItem(QTreeWidgetItem* _parent, profiler::color_t _color, profiler::vin_t _vin)
679     : Parent(_parent, ValueItemType)
680     , m_vin(_vin)
681     , m_color(_color)
682     , m_widthHint(0)
683 {
684     setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
685     setCheckState(CheckColumn, Qt::Unchecked);
686 }
687 
~EasyArbitraryTreeWidgetItem()688 EasyArbitraryTreeWidgetItem::~EasyArbitraryTreeWidgetItem()
689 {
690 
691 }
692 
data(int _column,int _role) const693 QVariant EasyArbitraryTreeWidgetItem::data(int _column, int _role) const
694 {
695     if (_column == CheckColumn && _role == Qt::SizeHintRole)
696         return QSize(m_widthHint, 26);
697     return Parent::data(_column, _role);
698 }
699 
setWidthHint(int _width)700 void EasyArbitraryTreeWidgetItem::setWidthHint(int _width)
701 {
702     m_widthHint = _width;
703 }
704 
collection() const705 const ArbitraryValuesCollection* EasyArbitraryTreeWidgetItem::collection() const
706 {
707     return m_collection.get();
708 }
709 
collectValues(profiler::thread_id_t _threadId)710 void EasyArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId)
711 {
712     if (!m_collection)
713         m_collection = CollectionPtr(new ArbitraryValuesCollection);
714     else
715         m_collection->interrupt();
716 
717     m_collection->collectValues(_threadId, m_vin, text(int_cast(ArbitraryColumns::Name)).toStdString().c_str(), EASY_GLOBALS.begin_time);
718 }
719 
interrupt()720 void EasyArbitraryTreeWidgetItem::interrupt()
721 {
722     if (!m_collection)
723         return;
724 
725     m_collection->interrupt();
726     m_collection.release();
727 }
728 
color() const729 profiler::color_t EasyArbitraryTreeWidgetItem::color() const
730 {
731     return m_color;
732 }
733 
734 //////////////////////////////////////////////////////////////////////////
735 
EasyArbitraryValuesWidget(QWidget * _parent)736 EasyArbitraryValuesWidget::EasyArbitraryValuesWidget(QWidget* _parent)
737     : Parent(_parent)
738     , m_treeWidget(new QTreeWidget(this))
739     , m_chart(new EasyGraphicsChart(this))
740 {
741     auto layout = new QHBoxLayout(this);
742     layout->setContentsMargins(0, 0, 0, 0);
743     layout->addWidget(m_treeWidget);
744     layout->addWidget(m_chart);
745     layout->setStretch(0, 1);
746     layout->setStretch(1, 1);
747 
748     m_treeWidget->setAutoFillBackground(false);
749     m_treeWidget->setAlternatingRowColors(true);
750     m_treeWidget->setItemsExpandable(true);
751     m_treeWidget->setAnimated(true);
752     //m_treeWidget->setSortingEnabled(false);
753     m_treeWidget->setColumnCount(int_cast(ArbitraryColumns::Count));
754     m_treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
755     m_treeWidget->setItemDelegateForColumn(0, new EasyTreeViewFirstColumnItemDelegate(this));
756 
757     auto headerItem = new QTreeWidgetItem();
758     headerItem->setText(int_cast(ArbitraryColumns::Type), "Type");
759     headerItem->setText(int_cast(ArbitraryColumns::Name), "Name");
760     headerItem->setText(int_cast(ArbitraryColumns::Value), "Value");
761     headerItem->setText(int_cast(ArbitraryColumns::Vin), "ID");
762     m_treeWidget->setHeaderItem(headerItem);
763 
764 //    auto mainLayout = new QVBoxLayout(this);
765 //    mainLayout->setContentsMargins(1, 1, 1, 1);
766 //    mainLayout->addWidget(m_treeWidget);
767 
768     connect(&m_timer, &QTimer::timeout, this, &This::rebuild);
769     connect(&m_collectionsTimer, &QTimer::timeout, this, &This::onCollectionsTimeout);
770 
771     connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &This::onItemDoubleClicked);
772     connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
773     connect(m_treeWidget, &QTreeWidget::currentItemChanged, this, &This::onCurrentItemChanged);
774 
775     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChanged);
776     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChanged);
777     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockIdChanged, this, &This::onSelectedBlockIdChanged);
778 }
779 
~EasyArbitraryValuesWidget()780 EasyArbitraryValuesWidget::~EasyArbitraryValuesWidget()
781 {
782 
783 }
784 
clear()785 void EasyArbitraryValuesWidget::clear()
786 {
787     if (m_collectionsTimer.isActive())
788         m_collectionsTimer.stop();
789     if (m_timer.isActive())
790         m_timer.stop();
791     m_checkedItems.clear();
792     m_treeWidget->clear();
793 }
794 
onSelectedThreadChanged(::profiler::thread_id_t)795 void EasyArbitraryValuesWidget::onSelectedThreadChanged(::profiler::thread_id_t)
796 {
797     if (!m_timer.isActive())
798         m_timer.start(100);
799 }
800 
onSelectedBlockChanged(uint32_t)801 void EasyArbitraryValuesWidget::onSelectedBlockChanged(uint32_t)
802 {
803     if (!m_timer.isActive())
804         m_timer.start(100);
805 }
806 
onSelectedBlockIdChanged(::profiler::block_id_t)807 void EasyArbitraryValuesWidget::onSelectedBlockIdChanged(::profiler::block_id_t)
808 {
809     if (!m_timer.isActive())
810         m_timer.start(100);
811 }
812 
onItemDoubleClicked(QTreeWidgetItem * _item,int)813 void EasyArbitraryValuesWidget::onItemDoubleClicked(QTreeWidgetItem* _item, int)
814 {
815     if (_item == nullptr || _item->type() != ValueItemType)
816         return;
817 
818     _item->setCheckState(CheckColumn, _item->checkState(CheckColumn) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
819 }
820 
onItemChanged(QTreeWidgetItem * _item,int _column)821 void EasyArbitraryValuesWidget::onItemChanged(QTreeWidgetItem* _item, int _column)
822 {
823     if (_item == nullptr || _item->type() != ValueItemType || _column != CheckColumn)
824         return;
825 
826     auto item = static_cast<EasyArbitraryTreeWidgetItem*>(_item);
827 
828     if (item->checkState(CheckColumn) == Qt::Checked)
829     {
830         m_checkedItems.push_back(item);
831         item->collectValues(EASY_GLOBALS.selected_thread);
832         if (!m_collectionsTimer.isActive())
833             m_collectionsTimer.start(100);
834     }
835     else
836     {
837         m_checkedItems.removeOne(item);
838         item->interrupt();
839         onCollectionsTimeout();
840     }
841 }
842 
onCurrentItemChanged(QTreeWidgetItem * _current,QTreeWidgetItem *)843 void EasyArbitraryValuesWidget::onCurrentItemChanged(QTreeWidgetItem* _current, QTreeWidgetItem*)
844 {
845     if (_current == nullptr || _current->type() != ValueItemType)
846     {
847         m_chart->update(nullptr);
848         return;
849     }
850 
851     auto item = static_cast<const EasyArbitraryTreeWidgetItem*>(_current);
852     m_chart->update(item->collection());
853 }
854 
rebuild()855 void EasyArbitraryValuesWidget::rebuild()
856 {
857     clear();
858 
859     buildTree(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block, EASY_GLOBALS.selected_block_id);
860 
861     m_treeWidget->expandAll();
862     for (int i = 0, columns = m_treeWidget->columnCount(); i < columns; ++i)
863         m_treeWidget->resizeColumnToContents(i);
864 }
865 
onCollectionsTimeout()866 void EasyArbitraryValuesWidget::onCollectionsTimeout()
867 {
868     if (m_checkedItems.isEmpty())
869     {
870         if (m_collectionsTimer.isActive())
871             m_collectionsTimer.stop();
872         m_chart->update(Collections {});
873         return;
874     }
875 
876     Collections collections;
877     collections.reserve(m_checkedItems.size());
878     for (auto item : m_checkedItems)
879     {
880         if (item->collection()->status() != ArbitraryValuesCollection::InProgress)
881         {
882             collections.push_back(EasyCollectionPaintData {item->collection(), item->color(),
883                 ChartType::Line, item == m_treeWidget->currentItem()});
884         }
885     }
886 
887     if (collections.size() == m_checkedItems.size())
888     {
889         if (m_collectionsTimer.isActive())
890             m_collectionsTimer.stop();
891         m_chart->update(std::move(collections));
892     }
893 }
894 
buildTree(profiler::thread_id_t _threadId,profiler::block_index_t _blockIndex,profiler::block_id_t _blockId)895 void EasyArbitraryValuesWidget::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
896 {
897     m_treeWidget->clear();
898     m_treeWidget->setColumnHidden(int_cast(ArbitraryColumns::Value), profiler_gui::is_max(_blockIndex));
899 
900     if (_threadId != 0)
901     {
902         auto it = EASY_GLOBALS.profiler_blocks.find(_threadId);
903         if (it != EASY_GLOBALS.profiler_blocks.end())
904         {
905             auto threadItem = buildTreeForThread(it->second, _blockIndex, _blockId);
906             m_treeWidget->addTopLevelItem(threadItem);
907         }
908     }
909     else
910     {
911         for (const auto& it : EASY_GLOBALS.profiler_blocks)
912         {
913             auto threadItem = buildTreeForThread(it.second, _blockIndex, _blockId);
914             m_treeWidget->addTopLevelItem(threadItem);
915         }
916     }
917 }
918 
buildTreeForThread(const profiler::BlocksTreeRoot & _threadRoot,profiler::block_index_t _blockIndex,profiler::block_id_t _blockId)919 QTreeWidgetItem* EasyArbitraryValuesWidget::buildTreeForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
920 {
921     auto fm = m_treeWidget->fontMetrics();
922 
923     auto rootItem = new QTreeWidgetItem(StdItemType);
924     rootItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Thread"));
925     rootItem->setText(int_cast(ArbitraryColumns::Name),
926         profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, _threadRoot, EASY_GLOBALS.hex_thread_id));
927 
928     const bool hasConcreteBlock = !profiler_gui::is_max(_blockIndex);
929     if (hasConcreteBlock)
930     {
931         const auto& block = easyBlocksTree(_blockIndex);
932         const auto& desc = easyDescriptor(block.node->id());
933         if (desc.type() == profiler::BlockType::Value)
934         {
935             auto valueItem = new EasyArbitraryTreeWidgetItem(rootItem, desc.color(), block.value->value_id());
936             valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*block.value));
937             valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
938             valueItem->setText(int_cast(ArbitraryColumns::Vin), QString("0x%1").arg(block.value->value_id(), 0, 16));
939             valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*block.value));
940 
941             const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
942             valueItem->setWidthHint(std::max(sizeHintWidth, fm.width(valueItem->text(CheckColumn))) + 32);
943 
944             return rootItem;
945         }
946 
947         _blockId = block.node->id();
948     }
949 
950     const bool noId = profiler_gui::is_max(_blockId);
951     QTreeWidgetItem* blockItem = nullptr;
952     if (!noId)
953     {
954         blockItem = new QTreeWidgetItem(rootItem, StdItemType);
955         blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
956         if (hasConcreteBlock)
957             blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(_blockIndex));
958         else
959             blockItem->setText(int_cast(ArbitraryColumns::Name), easyDescriptor(_blockId).name());
960     }
961 
962     std::unordered_map<profiler::block_id_t, QTreeWidgetItem*, estd::hash<profiler::block_id_t> > blocks;
963     std::unordered_map<profiler::vin_t, UsedValueTypes, estd::hash<profiler::vin_t> > vins;
964     std::unordered_map<std::string, UsedValueTypes> names;
965 
966     std::vector<profiler::block_index_t> stack;
967     for (auto childIndex : _threadRoot.children)
968     {
969         stack.push_back(childIndex);
970         while (!stack.empty())
971         {
972             const auto i = stack.back();
973             stack.pop_back();
974 
975             const auto& block = easyBlocksTree(i);
976             if (noId || block.node->id() == _blockId || easyDescriptor(block.node->id()).id() == _blockId)
977             {
978                 for (auto c : block.children)
979                 {
980                     if (noId)
981                         stack.push_back(c);
982 
983                     const auto& child = easyBlocksTree(c);
984                     const auto& desc = easyDescriptor(child.node->id());
985                     if (desc.type() != profiler::BlockType::Value)
986                         continue;
987 
988                     if (blockItem == nullptr)
989                     {
990                         const auto id = block.node->id();
991                         auto it = blocks.find(id);
992                         if (it != blocks.end())
993                         {
994                             blockItem = it->second;
995                         }
996                         else
997                         {
998                             blockItem = new QTreeWidgetItem(rootItem, StdItemType);
999                             blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
1000                             blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(block));
1001                             blocks.emplace(id, blockItem);
1002                         }
1003                     }
1004 
1005                     const auto typeIndex = int_cast(child.value->type());
1006                     auto vin = child.value->value_id();
1007 
1008                     EasyArbitraryTreeWidgetItem** usedItems = nullptr;
1009                     EasyArbitraryTreeWidgetItem* valueItem = nullptr;
1010                     if (vin == 0)
1011                     {
1012                         auto result = names.emplace(desc.name(), 0);
1013                         usedItems = result.first->second.items;
1014                         if (!result.second && (valueItem = *(usedItems + typeIndex)))
1015                         {
1016                             if (i == _blockIndex)
1017                                 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1018                             continue; // already in set
1019                         }
1020                     }
1021                     else
1022                     {
1023                         auto result = vins.emplace(vin, 0);
1024                         usedItems = result.first->second.items;
1025                         if (!result.second && (valueItem = *(usedItems + typeIndex)))
1026                         {
1027                             if (i == _blockIndex)
1028                                 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1029                             continue; // already in set
1030                         }
1031                     }
1032 
1033                     valueItem = new EasyArbitraryTreeWidgetItem(blockItem, desc.color(), vin);
1034                     valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*child.value));
1035                     valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
1036                     valueItem->setText(int_cast(ArbitraryColumns::Vin), QString("0x%1").arg(vin, 0, 16));
1037 
1038                     if (i == _blockIndex)
1039                         valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1040 
1041                     const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
1042                     valueItem->setWidthHint(std::max(sizeHintWidth, fm.width(valueItem->text(CheckColumn))) + 32);
1043 
1044                     *(usedItems + typeIndex) = valueItem;
1045                 }
1046 
1047                 if (noId)
1048                     blockItem = nullptr;
1049             }
1050             else
1051             {
1052                 for (auto c : block.children)
1053                     stack.push_back(c);
1054             }
1055         }
1056     }
1057 
1058     return rootItem;
1059 }
1060 
1061