1 // Copyright Ferdinand Majerech 2014.2 // Distributed under the Boost Software License, Version 1.0.3 // (See accompanying file LICENSE_1_0.txt or copy at4 // http://www.boost.org/LICENSE_1_0.txt)5 6 7 /// Despiker backend.8 moduledespiker.backend;
9 10 11 importstd.algorithm;
12 importstd.array;
13 importstd.stdio;
14 15 importtharsis.prof;
16 17 importdespiker.profdatasource: ProfileDataChunk;
18 19 20 /** Maximum threads supported by Despiker at the moment.
21 *
22 * Increase when 1024 is too few, in 2025 or so.
23 */24 enummaxThreads = 1024;
25 26 27 /** Information about a 'frame zone' - stored in a random access array.
28 *
29 * All frames are stored in a random-access array for quick access, so this should be
30 * as small as possible to avoid wasting memory.
31 */32 structFrameInfo33 {
34 // Slice extents to get a slice of all events in the frame from ChunkyEventList.35 ChunkyEventList.SliceExtentsextents;
36 // Start time of the frame in hnsecs.37 ulongstartTime;
38 // Duration of the frame in hnsecs.39 ulongduration;
40 41 // End time of the frame in hnsecs.42 ulongendTime() @safepurenothrowconst @nogc43 {
44 returnstartTime + duration;
45 }
46 }
47 48 /** Despiker backend.
49 *
50 * Handles storage of, processing of and access to profile data.
51 */52 finalclassBackend53 {
54 public:
55 /// Function that determines if a zone represents an entire frame.56 aliasFrameFilter = booldelegate(ZoneDatazone) @safenothrow @nogc;
57 58 private:
59 /// Default number of chunk structs (not the actual chunk data) to preallocate per thread.60 enumdefaultChunkBufferSize = 60 * 3600;
61 62 /// Profiling state kept for each profiled thread.63 structThreadState64 {
65 /// 'chunk buffer' used by eventList to store chunk structs (not the actual chunk data)66 ChunkyEventList.Chunk[] chunkBuffer;
67 /// Stores profiling data for this thread and provides API to read profiling events.68 ChunkyEventListeventList;
69 /// Generates zones from events in eventList on-the-fly as new chunks are added.70 ChunkyZoneGeneratorzoneGenerator;
71 /** Stores information about frame zones (as determined by Backend.frameFilter_).
72 *
73 * Used to regenerate all events in a frame.
74 */75 FrameInfo[] frames;
76 }
77 78 /// Thread state for all profiled thread.79 ThreadState[] threads_;
80 81 /// Function that determines if a zone represents an entire frame.82 FrameFilterframeFilter_;
83 84 public:
85 /** Construct Backend.
86 *
87 * Params:
88 *
89 * filter = Function to decide if a zone represents an entire frame.
90 */91 this(FrameFilterframeFilter) @safepurenothrow @nogc92 {
93 frameFilter_ = frameFilter;
94 }
95 96 /** Add a chunk of profiling data.
97 *
98 * Thread index of the chunk must be lower than maxThreads.
99 *
100 * Whichever thread the chunk belongs to, its first event must have time at least
101 * equal to the last event in the last chunk from that thread. (This can be achieved
102 * by prefixing the chunk with a checkpoint event that stores absolute time - which
103 * of course must be at least as late as any event in the previous chunk).
104 */105 voidaddChunk(ProfileDataChunkchunk) @systemnothrow106 {
107 consttid = chunk.threadId;
108 assert(tid <= maxThreads, "No more than 1024 threads are supported");
109 110 // If we were unaware of this thread till now, add thread state for it 111 // (and for any missing threads with lower indices).112 while(tid >= threads_.length)
113 {
114 threads_.assumeSafeAppend();
115 threads_ ~= ThreadState.init;
116 with(threads_.back)
117 {
118 chunkBuffer = newChunkyEventList.Chunk[defaultChunkBufferSize];
119 eventList = ChunkyEventList(chunkBuffer);
120 zoneGenerator = ChunkyZoneGenerator(eventList.generator);
121 }
122 }
123 124 // Add the chunk.125 with(threads_[tid])
126 {
127 if(eventList.addChunk(chunk.data)) { return; }
128 129 // If we failed to add chunk, it's time to reallocate.130 chunkBuffer = newChunkyEventList.Chunk[chunkBuffer.length * 2];
131 eventList.provideStorage(chunkBuffer);
132 // TODO If needed, delete old chunkBuffer here 2014-10-02133 if(!eventList.addChunk(chunk.data))
134 {
135 assert(false, "Can't add chunk; probably start time lower than end time "136 "of last chunk");
137 }
138 }
139 }
140 141 /// Get the number of profiled threads (so far).142 size_tthreadCount() @safepurenothrowconst @nogc143 {
144 returnthreads_.length;
145 }
146 147 /** Get access to frame info for all frames in specified profiled thread.
148 *
149 * Params:
150 *
151 * threadIdx = Index of the thread. Must be less than threadCount().
152 */153 const(FrameInfo)[] frames(size_tthreadIdx) @safepurenothrowconst @nogc154 {
155 returnthreads_[threadIdx].frames;
156 }
157 158 /** Get read-only access to ChunkyEventList for specified profiled thread.
159 *
160 * Used e.g. to get event slices based on slice extents of a frame (read through
161 * frames()).
162 *
163 * Params:
164 *
165 * threadIdx = Index of the thread. Must be less than threadCount().
166 */167 refconst(ChunkyEventList) events(size_tthreadIdx) @safepurenothrowconst @nogc168 {
169 returnthreads_[threadIdx].eventList;
170 }
171 172 /** Update the Backend between Despiker frames.
173 *
174 * Must be called on each event loop update.
175 */176 voidupdate() @systemnothrow177 {
178 foreach(i, refthread; threads_)
179 {
180 // Generate zones for any chunks that have been added since the last update().181 ChunkyZoneGenerator.GeneratedZoneDatazone;
182 while(thread.zoneGenerator.generate(zone)) if(frameFilter_(zone))
183 {
184 thread.frames.assumeSafeAppend();
185 thread.frames ~= FrameInfo(zone.extents, zone.startTime, zone.duration);
186 }
187 }
188 }
189 }
190 unittest191 {
192 writeln("Backend unittest");
193 scope(success) { writeln("Backend unittest SUCCESS"); }
194 scope(failure) { writeln("Backend unittest FAILURE"); }
195 196 constframeCount = 16;
197 198 boolfilterFrames(ZoneDatazone) @safenothrow @nogc199 {
200 returnzone.info == "frame";
201 }
202 203 importstd.typecons;
204 autoprofiler = scoped!Profiler(newubyte[Profiler.maxEventBytes + 2048]);
205 autobackend = newBackend(&filterFrames);
206 207 size_tlastChunkEnd = 0;
208 profiler.checkpointEvent();
209 // std.typecons.scoped! stores the Profiler on the stack.210 // Simulate 16 'frames'211 foreach(frame; 0 .. frameCount)
212 {
213 ZonetopLevel = Zone(profiler, "frame");
214 215 // Simulate frame overhead. Replace this with your frame code.216 {
217 Zonenested1 = Zone(profiler, "frameStart");
218 foreach(i; 0 .. 1000) { continue; }
219 }
220 {
221 Zonenested2 = Zone(profiler, "frameCore");
222 foreach(i; 0 .. 10000) { continue; }
223 }
224 225 autochunkData = profiler.profileData[lastChunkEnd .. $].idup;
226 // Simulate adding chunks for multiple threads.227 backend.addChunk(ProfileDataChunk(0, chunkData));
228 backend.addChunk(ProfileDataChunk(1, chunkData));
229 backend.update();
230 231 lastChunkEnd = profiler.profileData.length;
232 profiler.checkpointEvent();
233 }
234 235 autochunkData = profiler.profileData[lastChunkEnd .. $].idup;
236 // Simulate adding chunks for multiple threads.237 backend.addChunk(ProfileDataChunk(0, chunkData));
238 backend.addChunk(ProfileDataChunk(1, chunkData));
239 backend.update();
240 241 // Check that the time slices in 'frames' of each ThreadState match the zones filtered242 // as frames using filterFrames()243 assert(backend.threads_.length == 2);
244 foreach(refthread; backend.threads_)
245 {
246 autoframeZones = profiler.profileData.zoneRange.filter!filterFrames;
247 foreach(frame; thread.frames)
248 {
249 constextents = frame.extents;
250 constexpectedFrameZone = frameZones.front;
251 252 autozonesInSlice = ZoneRange!ChunkyEventSlice(thread.eventList.slice(extents));
253 // The 'frame' zone should be the last zone by end time in the slice254 // (ZoneRange is sorted by zone end time).255 ZoneDatalastZone;
256 foreach(zone; zonesInSlice) { lastZone = zone; }
257 258 // Other members (ID, nesting, parent ID) will be different since we're259 // using a zone range generated from an event slice that doesn't include260 // all the parent zones.261 assert(expectedFrameZone.startTime == lastZone.startTime &&
262 expectedFrameZone.endTime == lastZone.endTime &&
263 expectedFrameZone.info == lastZone.info,
264 "Frame slices generated by Backend don't match frame zones");
265 266 frameZones.popFront;
267 }
268 }
269 }