| 1 | // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
|---|
| 2 | // for details. All rights reserved. Use of this source code is governed by a | 
|---|
| 3 | // BSD-style license that can be found in the LICENSE file. | 
|---|
| 4 |  | 
|---|
| 5 | #ifndef RUNTIME_VM_PROFILER_SERVICE_H_ | 
|---|
| 6 | #define RUNTIME_VM_PROFILER_SERVICE_H_ | 
|---|
| 7 |  | 
|---|
| 8 | #include "platform/text_buffer.h" | 
|---|
| 9 | #include "vm/allocation.h" | 
|---|
| 10 | #include "vm/code_observers.h" | 
|---|
| 11 | #include "vm/globals.h" | 
|---|
| 12 | #include "vm/growable_array.h" | 
|---|
| 13 | #include "vm/object.h" | 
|---|
| 14 | #include "vm/profiler.h" | 
|---|
| 15 | #include "vm/tags.h" | 
|---|
| 16 | #include "vm/thread_interrupter.h" | 
|---|
| 17 | #include "vm/token_position.h" | 
|---|
| 18 |  | 
|---|
| 19 | // CPU Profile model and service protocol bits. | 
|---|
| 20 | // NOTE: For sampling and stack walking related code, see profiler.h. | 
|---|
| 21 |  | 
|---|
| 22 | namespace dart { | 
|---|
| 23 |  | 
|---|
| 24 | // Forward declarations. | 
|---|
| 25 | class Code; | 
|---|
| 26 | class Function; | 
|---|
| 27 | class JSONArray; | 
|---|
| 28 | class JSONStream; | 
|---|
| 29 | class ProfileFunctionTable; | 
|---|
| 30 | class ProfileCodeTable; | 
|---|
| 31 | class SampleFilter; | 
|---|
| 32 | class ProcessedSample; | 
|---|
| 33 | class ProcessedSampleBuffer; | 
|---|
| 34 | class Profile; | 
|---|
| 35 |  | 
|---|
| 36 | class ProfileFunctionSourcePosition { | 
|---|
| 37 | public: | 
|---|
| 38 | explicit ProfileFunctionSourcePosition(TokenPosition token_pos); | 
|---|
| 39 |  | 
|---|
| 40 | void Tick(bool exclusive); | 
|---|
| 41 |  | 
|---|
| 42 | TokenPosition token_pos() const { return token_pos_; } | 
|---|
| 43 | intptr_t exclusive_ticks() const { return exclusive_ticks_; } | 
|---|
| 44 | intptr_t inclusive_ticks() const { return inclusive_ticks_; } | 
|---|
| 45 |  | 
|---|
| 46 | private: | 
|---|
| 47 | TokenPosition token_pos_; | 
|---|
| 48 | intptr_t exclusive_ticks_; | 
|---|
| 49 | intptr_t inclusive_ticks_; | 
|---|
| 50 |  | 
|---|
| 51 | DISALLOW_ALLOCATION(); | 
|---|
| 52 | }; | 
|---|
| 53 |  | 
|---|
| 54 | class ProfileCodeInlinedFunctionsCache : public ZoneAllocated { | 
|---|
| 55 | public: | 
|---|
| 56 | ProfileCodeInlinedFunctionsCache() : cache_cursor_(0), last_hit_(0) { | 
|---|
| 57 | for (intptr_t i = 0; i < kCacheSize; i++) { | 
|---|
| 58 | cache_[i].Reset(); | 
|---|
| 59 | } | 
|---|
| 60 | cache_hit_ = 0; | 
|---|
| 61 | cache_miss_ = 0; | 
|---|
| 62 | } | 
|---|
| 63 |  | 
|---|
| 64 | ~ProfileCodeInlinedFunctionsCache() { | 
|---|
| 65 | if (FLAG_trace_profiler) { | 
|---|
| 66 | intptr_t total = cache_hit_ + cache_miss_; | 
|---|
| 67 | OS::PrintErr( "LOOKUPS: %"Pd " HITS: %"Pd " MISSES: %"Pd "\n", total, | 
|---|
| 68 | cache_hit_, cache_miss_); | 
|---|
| 69 | } | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | void Get(uword pc, | 
|---|
| 73 | const Code& code, | 
|---|
| 74 | ProcessedSample* sample, | 
|---|
| 75 | intptr_t frame_index, | 
|---|
| 76 | // Outputs: | 
|---|
| 77 | GrowableArray<const Function*>** inlined_functions, | 
|---|
| 78 | GrowableArray<TokenPosition>** inlined_token_positions, | 
|---|
| 79 | TokenPosition* token_position); | 
|---|
| 80 |  | 
|---|
| 81 | private: | 
|---|
| 82 | bool FindInCache(uword pc, | 
|---|
| 83 | intptr_t offset, | 
|---|
| 84 | GrowableArray<const Function*>** inlined_functions, | 
|---|
| 85 | GrowableArray<TokenPosition>** inlined_token_positions, | 
|---|
| 86 | TokenPosition* token_position); | 
|---|
| 87 |  | 
|---|
| 88 | // Add to cache and fill in outputs. | 
|---|
| 89 | void Add(uword pc, | 
|---|
| 90 | const Code& code, | 
|---|
| 91 | ProcessedSample* sample, | 
|---|
| 92 | intptr_t frame_index, | 
|---|
| 93 | // Outputs: | 
|---|
| 94 | GrowableArray<const Function*>** inlined_functions, | 
|---|
| 95 | GrowableArray<TokenPosition>** inlined_token_positions, | 
|---|
| 96 | TokenPosition* token_position); | 
|---|
| 97 |  | 
|---|
| 98 | intptr_t NextFreeIndex() { | 
|---|
| 99 | cache_cursor_ = (cache_cursor_ + 1) % kCacheSize; | 
|---|
| 100 | return cache_cursor_; | 
|---|
| 101 | } | 
|---|
| 102 |  | 
|---|
| 103 | intptr_t OffsetForPC(uword pc, | 
|---|
| 104 | const Code& code, | 
|---|
| 105 | ProcessedSample* sample, | 
|---|
| 106 | intptr_t frame_index); | 
|---|
| 107 | struct CacheEntry { | 
|---|
| 108 | void Reset() { | 
|---|
| 109 | pc = 0; | 
|---|
| 110 | offset = 0; | 
|---|
| 111 | inlined_functions.Clear(); | 
|---|
| 112 | inlined_token_positions.Clear(); | 
|---|
| 113 | } | 
|---|
| 114 | uword pc; | 
|---|
| 115 | intptr_t offset; | 
|---|
| 116 | GrowableArray<const Function*> inlined_functions; | 
|---|
| 117 | GrowableArray<TokenPosition> inlined_token_positions; | 
|---|
| 118 | TokenPosition token_position; | 
|---|
| 119 | }; | 
|---|
| 120 |  | 
|---|
| 121 | static const intptr_t kCacheSize = 128; | 
|---|
| 122 | intptr_t cache_cursor_; | 
|---|
| 123 | intptr_t last_hit_; | 
|---|
| 124 | CacheEntry cache_[kCacheSize]; | 
|---|
| 125 | intptr_t cache_miss_; | 
|---|
| 126 | intptr_t cache_hit_; | 
|---|
| 127 | }; | 
|---|
| 128 |  | 
|---|
| 129 | // Profile data related to a |Function|. | 
|---|
| 130 | class ProfileFunction : public ZoneAllocated { | 
|---|
| 131 | public: | 
|---|
| 132 | enum Kind { | 
|---|
| 133 | kDartFunction,     // Dart function. | 
|---|
| 134 | kNativeFunction,   // Synthetic function for Native (C/C++). | 
|---|
| 135 | kTagFunction,      // Synthetic function for a VM or User tag. | 
|---|
| 136 | kStubFunction,     // Synthetic function for stub code. | 
|---|
| 137 | kUnknownFunction,  // A singleton function for unknown objects. | 
|---|
| 138 | }; | 
|---|
| 139 |  | 
|---|
| 140 | ProfileFunction(Kind kind, | 
|---|
| 141 | const char* name, | 
|---|
| 142 | const Function& function, | 
|---|
| 143 | const intptr_t table_index); | 
|---|
| 144 |  | 
|---|
| 145 | const char* name() const { | 
|---|
| 146 | ASSERT(name_ != NULL); | 
|---|
| 147 | return name_; | 
|---|
| 148 | } | 
|---|
| 149 |  | 
|---|
| 150 | const char* Name() const; | 
|---|
| 151 |  | 
|---|
| 152 | const Function* function() const { return &function_; } | 
|---|
| 153 |  | 
|---|
| 154 | // Returns the resolved_url for the script containing this function. | 
|---|
| 155 | const char* ResolvedScriptUrl() const; | 
|---|
| 156 |  | 
|---|
| 157 | bool is_visible() const; | 
|---|
| 158 |  | 
|---|
| 159 | intptr_t table_index() const { return table_index_; } | 
|---|
| 160 |  | 
|---|
| 161 | Kind kind() const { return kind_; } | 
|---|
| 162 |  | 
|---|
| 163 | intptr_t exclusive_ticks() const { return exclusive_ticks_; } | 
|---|
| 164 | intptr_t inclusive_ticks() const { return inclusive_ticks_; } | 
|---|
| 165 |  | 
|---|
| 166 | void IncInclusiveTicks() { inclusive_ticks_++; } | 
|---|
| 167 |  | 
|---|
| 168 | void Tick(bool exclusive, | 
|---|
| 169 | intptr_t inclusive_serial, | 
|---|
| 170 | TokenPosition token_position); | 
|---|
| 171 |  | 
|---|
| 172 | static const char* KindToCString(Kind kind); | 
|---|
| 173 |  | 
|---|
| 174 | void PrintToJSONArray(JSONArray* functions); | 
|---|
| 175 |  | 
|---|
| 176 | // Returns true if the call was successful and |pfsp| is set. | 
|---|
| 177 | bool GetSinglePosition(ProfileFunctionSourcePosition* pfsp); | 
|---|
| 178 |  | 
|---|
| 179 | void TickSourcePosition(TokenPosition token_position, bool exclusive); | 
|---|
| 180 |  | 
|---|
| 181 | intptr_t NumSourcePositions() const { | 
|---|
| 182 | return source_position_ticks_.length(); | 
|---|
| 183 | } | 
|---|
| 184 |  | 
|---|
| 185 | const ProfileFunctionSourcePosition& GetSourcePosition(intptr_t i) const { | 
|---|
| 186 | return source_position_ticks_.At(i); | 
|---|
| 187 | } | 
|---|
| 188 |  | 
|---|
| 189 | private: | 
|---|
| 190 | const Kind kind_; | 
|---|
| 191 | const char* name_; | 
|---|
| 192 | const Function& function_; | 
|---|
| 193 | const intptr_t table_index_; | 
|---|
| 194 | ZoneGrowableArray<intptr_t> profile_codes_; | 
|---|
| 195 | ZoneGrowableArray<ProfileFunctionSourcePosition> source_position_ticks_; | 
|---|
| 196 |  | 
|---|
| 197 | intptr_t exclusive_ticks_; | 
|---|
| 198 | intptr_t inclusive_ticks_; | 
|---|
| 199 | intptr_t inclusive_serial_; | 
|---|
| 200 |  | 
|---|
| 201 | void PrintToJSONObject(JSONObject* func); | 
|---|
| 202 | // A |ProfileCode| that contains this function. | 
|---|
| 203 | void AddProfileCode(intptr_t code_table_index); | 
|---|
| 204 |  | 
|---|
| 205 | friend class ProfileCode; | 
|---|
| 206 | friend class ProfileBuilder; | 
|---|
| 207 | }; | 
|---|
| 208 |  | 
|---|
| 209 | class ProfileCodeAddress { | 
|---|
| 210 | public: | 
|---|
| 211 | explicit ProfileCodeAddress(uword pc); | 
|---|
| 212 |  | 
|---|
| 213 | void Tick(bool exclusive); | 
|---|
| 214 |  | 
|---|
| 215 | uword pc() const { return pc_; } | 
|---|
| 216 | intptr_t exclusive_ticks() const { return exclusive_ticks_; } | 
|---|
| 217 | intptr_t inclusive_ticks() const { return inclusive_ticks_; } | 
|---|
| 218 |  | 
|---|
| 219 | private: | 
|---|
| 220 | uword pc_; | 
|---|
| 221 | intptr_t exclusive_ticks_; | 
|---|
| 222 | intptr_t inclusive_ticks_; | 
|---|
| 223 | }; | 
|---|
| 224 |  | 
|---|
| 225 | // Profile data related to a |Code|. | 
|---|
| 226 | class ProfileCode : public ZoneAllocated { | 
|---|
| 227 | public: | 
|---|
| 228 | enum Kind { | 
|---|
| 229 | kDartCode,       // Live Dart code. | 
|---|
| 230 | kCollectedCode,  // Dead Dart code. | 
|---|
| 231 | kNativeCode,     // Native code. | 
|---|
| 232 | kReusedCode,     // Dead Dart code that has been reused by new kDartCode. | 
|---|
| 233 | kTagCode,        // A special kind of code representing a tag. | 
|---|
| 234 | }; | 
|---|
| 235 |  | 
|---|
| 236 | ProfileCode(Kind kind, | 
|---|
| 237 | uword start, | 
|---|
| 238 | uword end, | 
|---|
| 239 | int64_t timestamp, | 
|---|
| 240 | const AbstractCode code); | 
|---|
| 241 |  | 
|---|
| 242 | Kind kind() const { return kind_; } | 
|---|
| 243 |  | 
|---|
| 244 | uword start() const { return start_; } | 
|---|
| 245 | void set_start(uword start) { start_ = start; } | 
|---|
| 246 |  | 
|---|
| 247 | uword end() const { return end_; } | 
|---|
| 248 | void set_end(uword end) { end_ = end; } | 
|---|
| 249 |  | 
|---|
| 250 | void ExpandLower(uword start); | 
|---|
| 251 | void ExpandUpper(uword end); | 
|---|
| 252 | void TruncateLower(uword start); | 
|---|
| 253 | void TruncateUpper(uword end); | 
|---|
| 254 |  | 
|---|
| 255 | bool Contains(uword pc) const { return (pc >= start_) && (pc < end_); } | 
|---|
| 256 |  | 
|---|
| 257 | bool Overlaps(const ProfileCode* other) const; | 
|---|
| 258 |  | 
|---|
| 259 | int64_t compile_timestamp() const { return compile_timestamp_; } | 
|---|
| 260 | void set_compile_timestamp(int64_t timestamp) { | 
|---|
| 261 | compile_timestamp_ = timestamp; | 
|---|
| 262 | } | 
|---|
| 263 |  | 
|---|
| 264 | intptr_t exclusive_ticks() const { return exclusive_ticks_; } | 
|---|
| 265 | void set_exclusive_ticks(intptr_t exclusive_ticks) { | 
|---|
| 266 | exclusive_ticks_ = exclusive_ticks; | 
|---|
| 267 | } | 
|---|
| 268 | void IncExclusiveTicks() { exclusive_ticks_++; } | 
|---|
| 269 |  | 
|---|
| 270 | intptr_t inclusive_ticks() const { return inclusive_ticks_; } | 
|---|
| 271 | void set_inclusive_ticks(intptr_t inclusive_ticks) { | 
|---|
| 272 | inclusive_ticks_ = inclusive_ticks; | 
|---|
| 273 | } | 
|---|
| 274 | void IncInclusiveTicks() { inclusive_ticks_++; } | 
|---|
| 275 |  | 
|---|
| 276 | bool IsOptimizedDart() const; | 
|---|
| 277 | const AbstractCode code() const { return code_; } | 
|---|
| 278 |  | 
|---|
| 279 | const char* name() const { return name_; } | 
|---|
| 280 | void SetName(const char* name); | 
|---|
| 281 | void GenerateAndSetSymbolName(const char* prefix); | 
|---|
| 282 |  | 
|---|
| 283 | static const char* KindToCString(Kind kind); | 
|---|
| 284 |  | 
|---|
| 285 | void PrintToJSONArray(JSONArray* codes); | 
|---|
| 286 |  | 
|---|
| 287 | ProfileFunction* function() const { return function_; } | 
|---|
| 288 |  | 
|---|
| 289 | intptr_t code_table_index() const { return code_table_index_; } | 
|---|
| 290 |  | 
|---|
| 291 | private: | 
|---|
| 292 | void Tick(uword pc, bool exclusive, intptr_t serial); | 
|---|
| 293 | void TickAddress(uword pc, bool exclusive); | 
|---|
| 294 |  | 
|---|
| 295 | ProfileFunction* SetFunctionAndName(ProfileFunctionTable* table); | 
|---|
| 296 |  | 
|---|
| 297 | void PrintNativeCode(JSONObject* profile_code_obj); | 
|---|
| 298 | void PrintCollectedCode(JSONObject* profile_code_obj); | 
|---|
| 299 | void PrintOverwrittenCode(JSONObject* profile_code_obj); | 
|---|
| 300 | void PrintTagCode(JSONObject* profile_code_obj); | 
|---|
| 301 |  | 
|---|
| 302 | void set_code_table_index(intptr_t index) { code_table_index_ = index; } | 
|---|
| 303 |  | 
|---|
| 304 | const Kind kind_; | 
|---|
| 305 | uword start_; | 
|---|
| 306 | uword end_; | 
|---|
| 307 | intptr_t exclusive_ticks_; | 
|---|
| 308 | intptr_t inclusive_ticks_; | 
|---|
| 309 | intptr_t inclusive_serial_; | 
|---|
| 310 |  | 
|---|
| 311 | const AbstractCode code_; | 
|---|
| 312 | char* name_; | 
|---|
| 313 | int64_t compile_timestamp_; | 
|---|
| 314 | ProfileFunction* function_; | 
|---|
| 315 | intptr_t code_table_index_; | 
|---|
| 316 | ZoneGrowableArray<ProfileCodeAddress> address_ticks_; | 
|---|
| 317 |  | 
|---|
| 318 | friend class ProfileBuilder; | 
|---|
| 319 | }; | 
|---|
| 320 |  | 
|---|
| 321 | class ProfileCodeTable : public ZoneAllocated { | 
|---|
| 322 | public: | 
|---|
| 323 | ProfileCodeTable() : table_(8) {} | 
|---|
| 324 |  | 
|---|
| 325 | intptr_t length() const { return table_.length(); } | 
|---|
| 326 |  | 
|---|
| 327 | ProfileCode* At(intptr_t index) const { | 
|---|
| 328 | ASSERT(index >= 0); | 
|---|
| 329 | ASSERT(index < length()); | 
|---|
| 330 | return table_[index]; | 
|---|
| 331 | } | 
|---|
| 332 |  | 
|---|
| 333 | // Find the table index to the ProfileCode containing pc. | 
|---|
| 334 | // Returns < 0 if not found. | 
|---|
| 335 | intptr_t FindCodeIndexForPC(uword pc) const; | 
|---|
| 336 |  | 
|---|
| 337 | ProfileCode* FindCodeForPC(uword pc) const { | 
|---|
| 338 | intptr_t index = FindCodeIndexForPC(pc); | 
|---|
| 339 | if (index < 0) { | 
|---|
| 340 | return NULL; | 
|---|
| 341 | } | 
|---|
| 342 | return At(index); | 
|---|
| 343 | } | 
|---|
| 344 |  | 
|---|
| 345 | // Insert |new_code| into the table. Returns the table index where |new_code| | 
|---|
| 346 | // was inserted. Will merge with an overlapping ProfileCode if one is present. | 
|---|
| 347 | intptr_t InsertCode(ProfileCode* new_code); | 
|---|
| 348 |  | 
|---|
| 349 | private: | 
|---|
| 350 | void FindNeighbors(uword pc, | 
|---|
| 351 | intptr_t* lo, | 
|---|
| 352 | intptr_t* hi, | 
|---|
| 353 | ProfileCode** lo_code, | 
|---|
| 354 | ProfileCode** hi_code) const; | 
|---|
| 355 |  | 
|---|
| 356 | void VerifyOrder(); | 
|---|
| 357 | void VerifyOverlap(); | 
|---|
| 358 |  | 
|---|
| 359 | ZoneGrowableArray<ProfileCode*> table_; | 
|---|
| 360 | }; | 
|---|
| 361 |  | 
|---|
| 362 |  | 
|---|
| 363 | // The model for a profile. Most of the model is zone allocated, therefore | 
|---|
| 364 | // a zone must be created that lives longer than this object. | 
|---|
| 365 | class Profile : public ValueObject { | 
|---|
| 366 | public: | 
|---|
| 367 | explicit Profile(Isolate* isolate); | 
|---|
| 368 |  | 
|---|
| 369 | // Build a filtered model using |filter|. | 
|---|
| 370 | void Build(Thread* thread, SampleFilter* filter, SampleBuffer* sample_buffer); | 
|---|
| 371 |  | 
|---|
| 372 | // After building: | 
|---|
| 373 | int64_t min_time() const { return min_time_; } | 
|---|
| 374 | int64_t max_time() const { return max_time_; } | 
|---|
| 375 | int64_t GetTimeSpan() const { return max_time() - min_time(); } | 
|---|
| 376 | intptr_t sample_count() const { return sample_count_; } | 
|---|
| 377 | ProcessedSample* SampleAt(intptr_t index); | 
|---|
| 378 |  | 
|---|
| 379 | intptr_t NumFunctions() const; | 
|---|
| 380 |  | 
|---|
| 381 | ProfileFunction* GetFunction(intptr_t index); | 
|---|
| 382 | ProfileCode* GetCode(intptr_t index); | 
|---|
| 383 | ProfileCode* GetCodeFromPC(uword pc, int64_t timestamp); | 
|---|
| 384 |  | 
|---|
| 385 | void PrintProfileJSON(JSONStream* stream, bool include_code_samples); | 
|---|
| 386 |  | 
|---|
| 387 | ProfileFunction* FindFunction(const Function& function); | 
|---|
| 388 |  | 
|---|
| 389 | private: | 
|---|
| 390 | void (JSONObject* obj); | 
|---|
| 391 | void ProcessSampleFrameJSON(JSONArray* stack, | 
|---|
| 392 | ProfileCodeInlinedFunctionsCache* cache, | 
|---|
| 393 | ProcessedSample* sample, | 
|---|
| 394 | intptr_t frame_index); | 
|---|
| 395 | void ProcessInlinedFunctionFrameJSON(JSONArray* stack, | 
|---|
| 396 | const Function* inlined_function); | 
|---|
| 397 | void PrintFunctionFrameIndexJSON(JSONArray* stack, ProfileFunction* function); | 
|---|
| 398 | void PrintCodeFrameIndexJSON(JSONArray* stack, | 
|---|
| 399 | ProcessedSample* sample, | 
|---|
| 400 | intptr_t frame_index); | 
|---|
| 401 | void PrintSamplesJSON(JSONObject* obj, bool code_samples); | 
|---|
| 402 |  | 
|---|
| 403 | Isolate* isolate_; | 
|---|
| 404 | Zone* zone_; | 
|---|
| 405 | ProcessedSampleBuffer* samples_; | 
|---|
| 406 | ProfileCodeTable* live_code_; | 
|---|
| 407 | ProfileCodeTable* dead_code_; | 
|---|
| 408 | ProfileCodeTable* tag_code_; | 
|---|
| 409 | ProfileFunctionTable* functions_; | 
|---|
| 410 | intptr_t dead_code_index_offset_; | 
|---|
| 411 | intptr_t tag_code_index_offset_; | 
|---|
| 412 |  | 
|---|
| 413 | int64_t min_time_; | 
|---|
| 414 | int64_t max_time_; | 
|---|
| 415 |  | 
|---|
| 416 | intptr_t sample_count_; | 
|---|
| 417 |  | 
|---|
| 418 | friend class ProfileBuilder; | 
|---|
| 419 | }; | 
|---|
| 420 |  | 
|---|
| 421 | class ProfilerService : public AllStatic { | 
|---|
| 422 | public: | 
|---|
| 423 | static void PrintJSON(JSONStream* stream, | 
|---|
| 424 | int64_t time_origin_micros, | 
|---|
| 425 | int64_t time_extent_micros, | 
|---|
| 426 | bool include_code_samples); | 
|---|
| 427 |  | 
|---|
| 428 | static void PrintAllocationJSON(JSONStream* stream, | 
|---|
| 429 | const Class& cls, | 
|---|
| 430 | int64_t time_origin_micros, | 
|---|
| 431 | int64_t time_extent_micros); | 
|---|
| 432 |  | 
|---|
| 433 | static void PrintNativeAllocationJSON(JSONStream* stream, | 
|---|
| 434 | int64_t time_origin_micros, | 
|---|
| 435 | int64_t time_extent_micros, | 
|---|
| 436 | bool include_code_samples); | 
|---|
| 437 |  | 
|---|
| 438 | static void ClearSamples(); | 
|---|
| 439 |  | 
|---|
| 440 | private: | 
|---|
| 441 | static void PrintJSONImpl(Thread* thread, | 
|---|
| 442 | JSONStream* stream, | 
|---|
| 443 | SampleFilter* filter, | 
|---|
| 444 | SampleBuffer* sample_buffer, | 
|---|
| 445 | bool include_code_samples); | 
|---|
| 446 | }; | 
|---|
| 447 |  | 
|---|
| 448 | }  // namespace dart | 
|---|
| 449 |  | 
|---|
| 450 | #endif  // RUNTIME_VM_PROFILER_SERVICE_H_ | 
|---|
| 451 |  | 
|---|