1
// Copyright © 2016 Canonical Ltd.
2
// Author: Loïc Molinari <loic.molinari@canonical.com>
4
// This file is part of Ubuntu UI Toolkit.
6
// Ubuntu UI Toolkit is free software: you can redistribute it and/or modify it
7
// under the terms of the GNU Lesser General Public License as published by the
8
// Free Software Foundation; version 3.
10
// Ubuntu UI Toolkit is distributed in the hope that it will be useful, but
11
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15
// You should have received a copy of the GNU Lesser General Public License
16
// along with Ubuntu UI Toolkit. If not, see <http://www.gnu.org/licenses/>.
18
#include "overlay_p.h"
19
#include "ubuntumetricsglobal_p.h"
20
#include <QtCore/QSysInfo>
21
#include <QtGui/QGuiApplication>
25
static const QPointF position = QPointF(5.0f, 5.0f);
26
static const float opacity = 0.85f;
28
// Keep in sync with corresponding enum!
30
const char* const name;
33
{ "qtVersion", sizeof("qtVersion") - 1 },
34
{ "qtPlatform", sizeof("qtPlatform") - 1 },
35
{ "glVersion", sizeof("glVersion") - 1 },
36
{ "cpuModel", sizeof("cpuModel") - 1 },
37
{ "gpuModel", sizeof("gpuModel") - 1 }
40
QtVersion = 0, QtPlatform, GlVersion, CpuModel, GpuModel, KeywordCount
42
Q_STATIC_ASSERT(ARRAY_SIZE(keywordInfo) == KeywordCount);
44
// Keep in sync with corresponding enum!
46
const char* const name;
51
{ "cpuUsage", sizeof("cpuUsage") - 1, 3, UMEvent::Process },
52
{ "threadCount", sizeof("threadCount") - 1, 3, UMEvent::Process },
53
{ "vszMemory", sizeof("vszMemory") - 1, 8, UMEvent::Process },
54
{ "rssMemory", sizeof("rssMemory") - 1, 8, UMEvent::Process },
55
{ "windowId", sizeof("windowId") - 1, 2, UMEvent::Window },
56
{ "windowSize", sizeof("windowSize") - 1, 9, UMEvent::Window },
57
{ "frameNumber", sizeof("frameNumber") - 1, 7, UMEvent::Frame },
58
{ "deltaTime", sizeof("deltaTime") - 1, 7, UMEvent::Frame },
59
{ "syncTime", sizeof("syncTime") - 1, 7, UMEvent::Frame },
60
{ "renderTime", sizeof("renderTime") - 1, 7, UMEvent::Frame },
61
{ "gpuTime", sizeof("gpuTime") - 1, 7, UMEvent::Frame },
62
{ "totalTime", sizeof("totalTime") - 1, 7, UMEvent::Frame }
65
CpuUsage = 0, ThreadCount, VszMemory, RssMemory, WindowId, WindowSize, FrameNumber, DeltaTime,
66
SyncTime, RenderTime, GpuTime, TotalTime, MetricCount
68
Q_STATIC_ASSERT(ARRAY_SIZE(metricInfo) == MetricCount);
70
const int maxMetricWidth = 32;
71
const int maxKeywordStringSize = 128;
72
const int bufferSize = 128;
74
bufferSize >= maxMetricWidth
75
&& bufferSize >= maxKeywordStringSize);
76
const int bufferAlignment = 64;
78
const int maxParsedTextSize = 1024; // Including '\0'.
80
static char cpuModelString[maxKeywordStringSize] = { 0 };
81
static int cpuModelStringSize = 0;
83
Overlay::Overlay(const char* text, int windowId)
84
: m_parsedText(new char [maxParsedTextSize])
85
#if !defined QT_NO_DEBUG
88
, m_text(QString::fromLatin1(text))
91
, m_windowId(windowId)
92
, m_flags(DirtyText | DirtyProcessEvent)
96
m_buffer = aligned_alloc(bufferAlignment, bufferSize);
97
memset(&m_processEvent, 0, sizeof(m_processEvent));
98
m_processEvent.type = UMEvent::Process;
103
DASSERT(!(m_flags & Initialised));
106
delete [] m_parsedText;
109
bool Overlay::initialise()
111
DASSERT(!(m_flags & Initialised));
112
DASSERT(QOpenGLContext::currentContext());
114
#if !defined QT_NO_DEBUG
115
m_context = QOpenGLContext::currentContext();
118
const bool initialised = m_bitmapText.initialise();
120
m_bitmapText.bindProgram();
121
m_bitmapText.setOpacity(opacity);
122
m_flags |= Initialised;
129
void Overlay::finalise()
131
DASSERT(m_flags & Initialised);
132
DASSERT(m_context == QOpenGLContext::currentContext());
134
m_bitmapText.finalise();
135
m_flags &= ~Initialised;
137
#if !defined QT_NO_DEBUG
142
void Overlay::setProcessEvent(const UMEvent& processEvent)
144
DASSERT(processEvent.type == UMEvent::Process);
146
memcpy(&m_processEvent, &processEvent, sizeof(m_processEvent));
147
m_flags |= DirtyProcessEvent;
150
void Overlay::render(const UMEvent& frameEvent, const QSize& frameSize)
152
DASSERT(m_flags & Initialised);
153
DASSERT(m_context == QOpenGLContext::currentContext());
155
m_bitmapText.bindProgram();
156
if (m_flags & DirtyText) {
158
m_bitmapText.setText(m_parsedText);
159
m_flags &= ~DirtyText;
161
if (m_frameSize != frameSize) {
162
updateWindowMetrics(m_windowId, frameSize);
163
m_bitmapText.setTransform(frameSize, position);
164
m_frameSize = frameSize;
166
if (m_flags & DirtyProcessEvent) {
167
updateProcessMetrics();
168
m_flags &= ~DirtyProcessEvent;
170
updateFrameMetrics(frameEvent);
171
m_bitmapText.render();
174
// Writes a 64-bit unsigned integer as text. The string is right
175
// aligned. Returns the remaining width.
176
static int integerMetricToText(quint64 metric, char* text, int width)
182
text[--width] = (metric % 10) + '0';
183
if (width == 0) return 0;
185
} while (metric != 0);
190
// Writes a 64-bit unsigned integer representing time in nanoseconds as text in
191
// milliseconds with two decimal digits. The string is right aligned. Returns
192
// the remaining width.
193
static int timeMetricToText(quint64 metric, char* text, int width)
198
metric /= 10000; // 10^−9 to 10^−5 (to keep 2 valid decimal digits).
199
const int decimalCount = 2;
200
const char decimalPoint = '.';
204
// Handle the decimal digits part.
205
text[--width] = (metric % 10) + '0';
206
if (width == 0) return 0;
208
} while (++i < decimalCount && metric != 0);
210
// Handle the decimal point and integer parts.
211
text[--width] = decimalPoint;
214
text[--width] = (metric % 10) + '0';
216
} while (metric != 0 && width > 0);
219
// Handle metric ms value less than decimalCount digits.
222
if (width == 0) return 0;
224
text[--width] = decimalPoint;
233
void Overlay::updateFrameMetrics(const UMEvent& event)
235
DASSERT(m_flags & Initialised);
236
Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
238
char* text = static_cast<char*>(m_buffer);
239
for (int i = 0; i < m_metricsSize[UMEvent::Frame]; i++) {
240
int textWidth = m_metrics[UMEvent::Frame][i].width;
241
DASSERT(textWidth <= maxMetricWidth);
242
memset(text, ' ', maxMetricWidth);
244
switch (m_metrics[UMEvent::Frame][i].index) {
246
integerMetricToText(event.frame.number, text, textWidth);
249
timeMetricToText(event.frame.deltaTime, text, textWidth);
252
timeMetricToText(event.frame.syncTime, text, textWidth);
255
timeMetricToText(event.frame.renderTime, text, textWidth);
258
if (event.frame.gpuTime > 0) {
259
timeMetricToText(event.frame.gpuTime, text, textWidth);
261
const char* const na = "N/A";
262
int naSize = sizeof("N/A") - 1;
263
do { text[--textWidth] = na[--naSize]; } while (textWidth > 0 && naSize > 0);
268
event.frame.syncTime + event.frame.renderTime + event.frame.gpuTime;
269
timeMetricToText(time, text, textWidth);
277
m_bitmapText.updateText(
278
text, m_metrics[UMEvent::Frame][i].textIndex,
279
m_metrics[UMEvent::Frame][i].width);
283
void Overlay::updateWindowMetrics(quint32 windowId, const QSize& frameSize)
285
DASSERT(m_flags & Initialised);
286
Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
288
char* text = static_cast<char*>(m_buffer);
289
for (int i = 0; i < m_metricsSize[UMEvent::Window]; i++) {
290
int textWidth = m_metrics[UMEvent::Window][i].width;
291
DASSERT(textWidth <= maxMetricWidth);
292
memset(text, ' ', maxMetricWidth);
294
switch (m_metrics[UMEvent::Window][i].index) {
296
textWidth = integerMetricToText(windowId, text, textWidth);
299
textWidth = integerMetricToText(frameSize.height(), text, textWidth);
300
if (textWidth >= 2) {
301
text[textWidth - 1] = 'x';
302
integerMetricToText(frameSize.width(), text, textWidth - 1);
303
} else if (textWidth == 1) {
304
text[textWidth - 1] = 'x';
313
m_bitmapText.updateText(
314
text, m_metrics[UMEvent::Window][i].textIndex,
315
m_metrics[UMEvent::Window][i].width);
319
void Overlay::updateProcessMetrics()
321
DASSERT(m_flags & Initialised);
322
Q_STATIC_ASSERT(IS_POWER_OF_TWO(maxMetricWidth));
324
char* text = static_cast<char*>(m_buffer);
325
for (int i = 0; i < m_metricsSize[UMEvent::Process]; i++) {
326
int textWidth = m_metrics[UMEvent::Process][i].width;
327
DASSERT(textWidth <= maxMetricWidth);
328
memset(text, ' ', maxMetricWidth);
330
switch (m_metrics[UMEvent::Process][i].index) {
332
integerMetricToText(m_processEvent.process.cpuUsage, text, textWidth);
335
integerMetricToText(m_processEvent.process.threadCount, text, textWidth);
338
integerMetricToText(m_processEvent.process.vszMemory, text, textWidth);
341
integerMetricToText(m_processEvent.process.rssMemory, text, textWidth);
348
m_bitmapText.updateText(
349
text, m_metrics[UMEvent::Process][i].textIndex,
350
m_metrics[UMEvent::Process][i].width);
354
static int cpuModel(char* buffer, int bufferSize)
357
DASSERT(bufferSize > 0);
359
const char* architecture = QSysInfo::currentCpuArchitecture().toLatin1().constData();
360
const int sourceBufferSize = 128;
361
char sourceBuffer[sourceBufferSize];
364
if (!strncmp(architecture, "x86", 3) || !strncmp(architecture, "X86", 3)) {
365
// /proc/cpuinfo depends on the architecture, this only works on x86.
366
int fd = open("/proc/cpuinfo", O_RDONLY);
368
DWARN("ApplicationMonitor: Can't open '/proc/cpuinfo'.");
371
if (read(fd, sourceBuffer, sourceBufferSize) != sourceBufferSize) {
372
DWARN("ApplicationMonitor: Can't read '/proc/cpuinfo'.");
378
// Skip the five first ': ' occurences to reach model name.
379
int sourceIndex = 0, colonCount = 0;
380
while (colonCount < 5) {
381
if (sourceIndex < sourceBufferSize - 1) {
382
if (sourceBuffer[sourceIndex] != ':' || sourceBuffer[sourceIndex + 1] != ' ') {
389
DNOT_REACHED(); // Consider increasing sourceBufferSize.
394
while (sourceBuffer[sourceIndex] != '\n' && index < bufferSize) {
395
if (sourceIndex < sourceBufferSize) {
396
buffer[index++] = sourceBuffer[sourceIndex++];
398
DNOT_REACHED(); // Consider increasing sourceBufferSize.
405
// Symply use the CPU architecture.
406
for (; index < bufferSize; index++) {
407
if (architecture[index] == '\0') break;
408
buffer[index] = architecture[index];
412
// Add the core count.
413
const int cpuOnlineCores = sysconf(_SC_NPROCESSORS_ONLN);
414
if (cpuOnlineCores > 1) {
415
const int maxSize = sizeof(" (%d cores)") - 2 + 3; // Adds space for a 3 digits core count.
416
const int size = snprintf(sourceBuffer, maxSize, " (%d cores)", cpuOnlineCores);
417
if (index + size < bufferSize) {
418
memcpy(&buffer[index], sourceBuffer, size);
426
// Stores the keyword string corresponding to the given index in a preallocated
427
// buffer of size bufferSize, the terminating null byte ('\0') is not
428
// written. Returns the number of characters written. Requires an OpenGL context
429
// to be bound to the current thread.
430
int Overlay::keywordString(int index, char* buffer, int bufferSize)
432
DASSERT(index < KeywordCount);
434
DASSERT(bufferSize > 0);
440
const char* version = "Qt " QT_VERSION_STR;
441
for (; size < bufferSize; size++) {
442
if (version[size] == '\0') break;
443
buffer[size] = version[size];
448
const char* platform = QGuiApplication::platformName().toLatin1().constData();
449
for (; size < bufferSize; size++) {
450
if (platform[size] == '\0') break;
451
buffer[size] = platform[size];
456
QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
457
const char* version = reinterpret_cast<const char*>(functions->glGetString(GL_VERSION));
458
if (size < (bufferSize - 7) && QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
459
memcpy(&buffer[size], "OpenGL ", 7);
462
for (int i = 0; size < bufferSize; i++, size++) {
463
if (version[i] == '\0') break;
464
buffer[size] = version[i];
469
if (cpuModelStringSize == 0) {
470
cpuModelStringSize = cpuModel(cpuModelString, maxKeywordStringSize);
471
if (cpuModelStringSize == 0) {
472
const char* const defaultModel = "Unknown CPU";
473
const int defaultModelSize = sizeof("Unknown CPU") - 1;
474
memcpy(cpuModelString, defaultModel, defaultModelSize);
475
cpuModelStringSize = defaultModelSize;
478
memcpy(buffer, cpuModelString, cpuModelStringSize);
479
size = cpuModelStringSize;
483
QOpenGLFunctions* functions = QOpenGLContext::currentContext()->functions();
484
const char* vendor = reinterpret_cast<const char*>(functions->glGetString(GL_VENDOR));
485
const char* renderer = reinterpret_cast<const char*>(functions->glGetString(GL_RENDERER));
486
for (int i = 0; size < bufferSize; i++, size++) {
487
if (vendor[i] == '\0') break;
488
buffer[size] = vendor[i];
490
if (size < (bufferSize - 1)) {
491
buffer[size++] = ' ';
493
for (int i = 0; size < bufferSize; i++, size++) {
494
if (renderer[i] == '\0') break;
495
buffer[size] = renderer[i];
507
void Overlay::parseText()
509
QByteArray textLatin1 = m_text.toLatin1();
510
const char* const text = textLatin1.constData();
511
const int textSize = textLatin1.size();
512
char* keywordBuffer = static_cast<char*>(m_buffer);
515
for (int i = 0; i <= textSize; i++) {
516
const char character = text[i];
517
if (character != '%') {
519
m_parsedText[characters++] = character;
520
} else if (text[i+1] == '%') {
522
m_parsedText[characters++] = '%';
525
bool keywordFound = false;
526
// Search for keywords.
527
for (int j = 0; j < KeywordCount; j++) {
528
if (!strncmp(&text[i+1], keywordInfo[j].name, keywordInfo[j].size)) {
529
const int stringSize = keywordString(j, keywordBuffer, maxKeywordStringSize);
530
if (stringSize < maxParsedTextSize - characters) {
531
strcpy(&m_parsedText[characters], keywordBuffer);
532
characters += stringSize;
533
i += keywordInfo[j].size;
539
// Search for metrics.
541
int width, widthOffset = 0;
542
if (!isdigit(text[i+1+widthOffset])) {
545
width = text[i+1+widthOffset] - '0';
547
if (isdigit(text[i+1+widthOffset])) {
548
width = width * 10 + text[i+1+widthOffset] - '0';
551
width = qBound(1, width, maxMetricWidth);
553
for (int j = 0; j < MetricCount; j++) {
554
const int type = metricInfo[j].type;
556
DASSERT(type < UMEvent::TypeCount);
557
if (m_metricsSize[type] < maxMetricsPerType &&
558
!strncmp(&text[i+1+widthOffset], metricInfo[j].name, metricInfo[j].size)) {
560
width = metricInfo[j].defaultWidth;
562
if (width < maxParsedTextSize - characters) {
563
m_metrics[type][m_metricsSize[type]].index = j;
564
m_metrics[type][m_metricsSize[type]].textIndex = characters;
565
m_metrics[type][m_metricsSize[type]].width = width;
566
// Must be initialised since it might contain non
567
// printable characters and break setText otherwise.
568
memset(&m_parsedText[characters], '?', width);
570
i += widthOffset + metricInfo[j].size;
571
m_metricsSize[type]++;
578
// Set string terminator and quit once the max size is reached.
579
if (characters >= (maxParsedTextSize - 1)) {
580
m_parsedText[maxParsedTextSize - 1] = '\0';