-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathgraphdata.d
More file actions
306 lines (263 loc) · 11.2 KB
/
graphdata.d
File metadata and controls
306 lines (263 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// Copyright Ferdinand Majerech 2010 - 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
///Struct managing statistics displayed by graphs.
module graphdata;
import std.algorithm;
import math.math;
import time.time;
import time.timer;
import containers.vector;
///Graph modes.
enum GraphMode
{
///Show totals per time unit.
Sum,
///Show averages over measurements (e.g. frames).
Average
}
/**
* Stores graph data accumulating over time.
*/
final class GraphData
{
private:
///Graphs of measured values.
Graph[] graphs_;
///Time when this GraphData was created
real startTime_;
///Shortest time period to accumulate values for.
real timeResolution_ = 0.0625;
///Timer used to time graph updates.
Timer updateTimer_;
public:
///Graph data related to measurement of single value over time.
class Graph
{
private:
///Stores values accumulated over a time period set by GraphData's timeResolution.
static align(4) struct Value
{
///Time point of the value (set to the start of measurement).
real time;
///Sum of the values measured.
real value = 0.0;
///Number of values accumulated.
uint valueCount = 0;
}
///Value we're currently accumulating to.
Value currentValue_;
///Recorded values sorted from earliest to latest.
Vector!Value values_;
///Memory for arrays returned by dataPoints().
Vector!real dataPoints_;
///Memory for value counts array used to compute data points in average mode.
Vector!uint valueCounts_;
public:
/**
* Accumulate values recorded over a time window to data points, one
* point per period specified, each data point is a sum or average of
* values over the period, depending on graph mode.
*
* Params: start = Start of the time window.
* end = End of the time window.
* period = Time period to represent by single data point.
* mode = Graph mode (average per measurement or sums over time).
*
* Returns: Array of data points in specified time window.
*/
const(real)[] dataPoints(real start, real end, real period, GraphMode mode)
in
{
assert(start < end,
"Can't get data points for a time window that ends before it starts");
}
body
{
dataPoints_.length = cast(size_t)((end - start) / period);
(dataPoints_.ptrUnsafe[0 .. dataPoints_.length])[] = 0.0;
if(empty){return dataPoints_[];}
if(mode == GraphMode.Sum){dataPointsSum(start, period);}
else if(mode == GraphMode.Average){dataPointsAverage(start, period);}
else{assert(false, "Unsupported graph mode");}
return dataPoints_[];
}
/**
* Is the graph empty, i.e. are there no values stored?
*
* Note that the graph is empty until the first accumulated
* value is added, which depends on time resolution.
*/
@property bool empty() const pure {return values_.length == 0;}
/**
* Get start time of the graph, i.e. time of the first value in the graph.
*
* Only makes sense if the graph is not empty.
*/
@property real startTime() const pure
in{assert(!empty(), "Can't get start time of an empty graph");}
body{return values_[0].time;}
/**
* Add a value to the graph.
*
* Params: value = Value to add.
*/
void updateValue(real value)
{
//accumulate to currentValue_
currentValue_.value += value;
currentValue_.valueCount++;
}
private:
/**
* Construct a graph with specified starting time.
*
* Params: time = Starting time of the graph.
*/
this(real time)
{
currentValue_.time = time;
}
/**
* Update the graph and finish accumulating a value.
*
* Params: time = End time of the accumulated value.
*/
void update(const real time)
{
values_ ~= currentValue_;
clear(currentValue_);
currentValue_.time = time;
}
/**
* Get the index of the first value in a time window starting at start.
*
* Params: start = Start time to get data points from.
*
* Returns: Index of the first value to aggregate.
*/
size_t firstValueIndex(real start)
{
//ugly, but optimized
//guess the first value based on time resolution.
const real age = start - startTime;
const size_t valueIdx = clamp(floor!int(age / timeResolution_),
0 , cast(int)values_.length - 1);
const(Value)* valuesStart = values_.ptrUnsafe;
const Value* valuesEnd = valuesStart + values_.length;
const(Value)* valuePtr = valuesStart + valueIdx;
//move linearly to the desired value
while(valuePtr.time >= start && valuePtr > valuesStart)
{
valuePtr--;
}
while(valuePtr.time < start && valuePtr < valuesEnd)
{
valuePtr++;
}
return cast(size_t)(valuePtr - valuesStart);
}
/**
* Aggregate data points in sum mode.
*
* This depends on code in dataPoints() and can only be called from there.
*
* Params: start = Start time to take data points from.
* period = Time period to represent by single data point.
*/
void dataPointsSum(real start, real period)
{
//ugly, but optimized
const numPoints = dataPoints_.length;
real* pointsPtr = dataPoints_.ptrUnsafe;
const(Value)* valuesStart = values_.ptrUnsafe;
const Value* valuesEnd = valuesStart + values_.length;
//index of data point to add current value to.
int index;
const(Value)* value = valuesStart + firstValueIndex(start);
//iterate over values and aggregate data points
for(; value < valuesEnd; value++)
{
index = floor!int((value.time - start) / period);
if(index >= numPoints){return;}
*(pointsPtr + index) += value.value;
}
}
/**
* Aggregate data points in average mode.
*
* This depends on code in dataPoints() and can only be called from there.
*
* Params: start = Start time to take data points from.
* period = Time period to represent by single data point.
*/
void dataPointsAverage(real start, real period)
{
//ugly, but optimized
const numPoints = dataPoints_.length;
real* pointsPtr = dataPoints_.ptrUnsafe;
valueCounts_.length = numPoints;
uint* valueCountsStart = valueCounts_.ptrUnsafe;
const uint* valueCountsEnd = valueCountsStart + numPoints;
//zero all value counts.
valueCounts_[] = 0;
const(Value)* valuesStart = values_.ptrUnsafe;
const Value* valuesEnd = valuesStart + values_.length;
//index of data point to add current value to.
int index;
//iterate over values and aggregate data points, value counts
const(Value)* value = valuesStart + firstValueIndex(start);
for(; value < valuesEnd; value++)
{
index = floor!int((value.time - start) / period);
if(index >= numPoints){break;}
*(pointsPtr + index) += value.value;
*(valueCountsStart + index) += value.valueCount;
}
//divide data points by value counts to get averages.
real* point = pointsPtr;
const(uint)* count = valueCountsStart;
for(; count < valueCountsEnd; count++, point++)
{
if(*count == 0){continue;}
*point /= *count;
}
}
}
/**
* Construct graph data with specified number of graphs.
*
* Params: graphCount = Number of graphs to store.
*/
this(size_t graphCount)
{
startTime_ = getTime();
foreach(idx; 0 .. graphCount){graphs_ ~= new Graph(startTime_);}
updateTimer_ = Timer(timeResolution_, startTime_);
}
///Destroy this GraphData.
~this()
{
foreach(graph; graphs_){clear(graph);}
clear(graphs_);
}
///Get time resolution of the graph.
@property final real timeResolution() pure {return timeResolution_;}
///Get time when this graph started to exist.
@property final real startTime() const pure {return startTime_;}
///Get (non-const) access to graphs stored.
@property Graph[] graphs() pure
{
return graphs_;
}
///Update graph data memory representation.
void update()
{
if(updateTimer_.expired)
{
real time = getTime();
foreach(graph; graphs_){graph.update(time);}
}
}
}