Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sample rate option #106

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ETTrace/CommunicationFrame/Public/CommunicationFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ enum {

typedef struct _PTStartFrame {
bool runAtStartup;
// Added in v1.6
uint32_t sampleRate;
} PTStartFrame;

typedef struct _PTMetadataFrame {
Expand Down
10 changes: 8 additions & 2 deletions ETTrace/ETTrace/EMGChannelListener.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,16 @@ - (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type
NSLog(@"Start received, with: %i", startFrame->runAtStartup);
BOOL runAtStartup = startFrame->runAtStartup;
BOOL recordAllThreads = type == PTFrameTypeStartMultiThread;
NSInteger sampleRate = 0;
// If the size is smaller it is a message from an older version of the CLI
// before sampleRate was added
if (payload.length >= sizeof(PTStartFrame)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good check

sampleRate = startFrame->sampleRate;
}
if (runAtStartup) {
[EMGPerfAnalysis setupRunAtStartup:recordAllThreads];
[EMGPerfAnalysis setupRunAtStartup:recordAllThreads rate:sampleRate];
} else {
[EMGPerfAnalysis startRecording:recordAllThreads];
[EMGPerfAnalysis startRecording:recordAllThreads rate:sampleRate];
}
} else if (type == PTFrameTypeStop) {
[EMGPerfAnalysis stopRecording];
Expand Down
10 changes: 6 additions & 4 deletions ETTrace/ETTrace/EMGPerfAnalysis.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ @implementation EMGPerfAnalysis
static EMGChannelListener *channelListener;
static NSMutableArray <NSDictionary *> *sSpanTimes;

+ (void)startRecording:(BOOL)recordAllThreads {
+ (void)startRecording:(BOOL)recordAllThreads rate:(NSInteger)sampleRate {
sSpanTimes = [NSMutableArray array];
[EMGTracer setupStackRecording:recordAllThreads];
[EMGTracer setupStackRecording:recordAllThreads rate:(useconds_t) sampleRate];
}

+ (void)setupRunAtStartup:(BOOL) recordAllThreads {
+ (void)setupRunAtStartup:(BOOL) recordAllThreads rate:(NSInteger)sampleRate {
[[NSUserDefaults standardUserDefaults] setBool:true forKey:@"runAtStartup"];
[[NSUserDefaults standardUserDefaults] setBool:recordAllThreads forKey:@"recordAllThreads"];
[[NSUserDefaults standardUserDefaults] setInteger:sampleRate forKey:@"ETTraceSampleRate"];
exit(0);
}

Expand Down Expand Up @@ -116,8 +117,9 @@ + (void)load {
fileEventsQueue = dispatch_queue_create("com.emerge.file_queue", DISPATCH_QUEUE_SERIAL);
BOOL infoPlistRunAtStartup = ((NSNumber *) NSBundle.mainBundle.infoDictionary[@"ETTraceRunAtStartup"]).boolValue;
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"runAtStartup"] || infoPlistRunAtStartup) {
NSInteger sampleRate = [[NSUserDefaults standardUserDefaults] integerForKey:@"ETTraceSampleRate"];
BOOL recordAllThreads = [[NSUserDefaults standardUserDefaults] boolForKey:@"recordAllThreads"];
[EMGPerfAnalysis startRecording:recordAllThreads];
[EMGPerfAnalysis startRecording:recordAllThreads rate:sampleRate];
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"runAtStartup"];
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"recordAllThreads"];
}
Expand Down
4 changes: 2 additions & 2 deletions ETTrace/ETTrace/EMGPerfAnalysis_Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
#import "PerfAnalysis.h"

@interface EMGPerfAnalysis (Private)
+ (void)startRecording:(BOOL) recordAllThreads;
+ (void)startRecording:(BOOL) recordAllThreads rate:(NSInteger)sampleRate;
+(void)stopRecording;
+ (void)setupRunAtStartup:(BOOL) recordAllThreads;
+ (void)setupRunAtStartup:(BOOL) recordAllThreads rate:(NSInteger)sampleRate;
+ (NSURL *)outputPath;
@end

Expand Down
8 changes: 4 additions & 4 deletions ETTrace/ETTraceRunner/Devices/DeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ protocol DeviceManager {
}

extension DeviceManager {
func sendStartRecording(_ runAtStartup: Bool, _ multiThread: Bool) async throws -> Void {
func sendStartRecording(_ runAtStartup: Bool, _ multiThread: Bool, _ sampleRate: UInt32) async throws -> Void {
return try await withCheckedThrowingContinuation { continuation in
var boolValue = runAtStartup ? 1 : 0
let data = Data(bytes: &boolValue, count: 2)
var startFrame = _PTStartFrame(runAtStartup: runAtStartup, sampleRate: sampleRate)
let data = Data(bytes: &startFrame, count: MemoryLayout<_PTStartFrame>.size)

let type = multiThread ? PTFrameTypeStartMultiThread : PTFrameTypeStart
communicationChannel.channel.sendFrame(type: UInt32(type), tag: UInt32(PTNoFrameTag), payload: data) { error in
if let error = error {
Expand Down
5 changes: 4 additions & 1 deletion ETTrace/ETTraceRunner/ETTrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ struct ETTrace: ParsableCommand {
@Flag(name: .shortAndLong, help: "Record all threads")
var multiThread: Bool = false

@Option(name: .long, help: "Sample rate")
var sampleRate: UInt32 = 0

mutating func run() throws {
if let dsym = dsyms, dsym.hasSuffix(".dSYM") {
ETTrace.exit(withError: ValidationError("The dsym argument should be set to a folder containing your dSYM files, not the dSYM itself"))
}
let helper = RunnerHelper(dsyms, launch, simulator, verbose, multiThread)
let helper = RunnerHelper(dsyms, launch, simulator, verbose, multiThread, sampleRate)
Task {
do {
try await helper.start()
Expand Down
1 change: 1 addition & 0 deletions ETTrace/ETTraceRunner/ResponseModels/ResponseModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct ResponseModel: Decodable {
let device: String
let events: [Event]
let threads: [String: Thread]
let sampleRate: UInt32?
}

struct LibraryInfo: Decodable {
Expand Down
7 changes: 5 additions & 2 deletions ETTrace/ETTraceRunner/RunnerHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ class RunnerHelper {
let useSimulator: Bool
let verbose: Bool
let multiThread: Bool
let sampleRate: UInt32

var server: HttpServer? = nil

init(_ dsyms: String?, _ launch: Bool, _ simulator: Bool, _ verbose: Bool, _ multiThread: Bool) {
init(_ dsyms: String?, _ launch: Bool, _ simulator: Bool, _ verbose: Bool, _ multiThread: Bool, _ sampleRate: UInt32) {
self.dsyms = dsyms
self.launch = launch
self.useSimulator = simulator
self.verbose = verbose
self.multiThread = multiThread
self.sampleRate = sampleRate
}

func start() async throws {
Expand All @@ -45,7 +47,7 @@ class RunnerHelper {

try await deviceManager.connect()

try await deviceManager.sendStartRecording(launch, multiThread)
try await deviceManager.sendStartRecording(launch, multiThread, sampleRate)

if launch {
print("Re-launch the app to start recording, then press return to exit")
Expand Down Expand Up @@ -93,6 +95,7 @@ class RunnerHelper {
let flamegraphs = FlamegraphGenerator.generate(
events: responseData.events,
threads: threads,
sampleRate: responseData.sampleRate,
loadedLibraries: responseData.libraryInfo.loadedLibraries,
symbolicator: symbolicator)
let outputUrl = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
Expand Down
11 changes: 8 additions & 3 deletions ETTrace/Symbolicator/FlamegraphGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,25 @@ import ETModels

public enum FlamegraphGenerator {

public static func generate(events: [Event], threads: [[Stack]], loadedLibraries: [LoadedLibrary], symbolicator: StackSymbolicator) -> [(FlameNode, [Double], String)] {
public static func generate(events: [Event], threads: [[Stack]], sampleRate: UInt32?, loadedLibraries: [LoadedLibrary], symbolicator: StackSymbolicator) -> [(FlameNode, [Double], String)] {
let syms = symbolicator.symbolicate(threads.flatMap { $0 }, loadedLibraries)
return threads.map { generateFlamegraphs(events: events, stacks: $0, syms: syms) }
return threads.map { generateFlamegraphs(events: events, stacks: $0, sampleRate: sampleRate, syms: syms) }
}

private static func generateFlamegraphs(
events: [Event],
stacks: [Stack],
sampleRate: UInt32?,
syms: SymbolicationResult) -> (FlameNode, [Double], String)
{
var eventTimes = [Double](repeating: 0, count: events.count)
let times = stacks.map { $0.time }
var timeDiffs: [Double] = []
let sampleInterval = 0.005
let rate = sampleRate == 0 ? 4500 : (sampleRate ?? 4500)
// The default sample rate is 4500 microseconds, add 500 because
// the samples have slightly more delay than the rate passed to usleep.
let sampleInterval = Double(rate + 500) / 1000000.0
print("The interval \(sampleInterval)")
var unattributedTime = 0.0
let partitions = partitions(times, size: 2, step: 1)
var eventTime: Double = 0
Expand Down
8 changes: 6 additions & 2 deletions ETTrace/Tracer/EMGTracer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

static thread_t sMainMachThread = {0};

static useconds_t sSampleRate = 0;

// To avoid static initialization order fiasco, we access it from a function
EMGStackTraceRecorder &getRecorder() {
static EMGStackTraceRecorder recorder;
Expand Down Expand Up @@ -64,6 +66,7 @@ + (NSDictionary *)getResults {
@"cpuType": cpuType,
@"device": [self deviceName],
@"threads": threads,
@"sampleRate": @(sSampleRate),
};
}

Expand Down Expand Up @@ -127,11 +130,12 @@ + (void)setup {
EMGBeginCollectingLibraries();
}

+ (void)setupStackRecording:(BOOL) recordAllThreads
+ (void)setupStackRecording:(BOOL)recordAllThreads rate:(useconds_t)sampleRate
{
if (sStackRecordingThread != nil) {
return;
}
sSampleRate = sampleRate;

// Make sure that +recordStack is always called on the same (non-main) thread.
// This is because a Process keeps its own "current thread" variable which we need
Expand All @@ -148,7 +152,7 @@ + (void)setupStackRecording:(BOOL) recordAllThreads
NSThread *thread = [NSThread currentThread];
while (!thread.cancelled) {
getRecorder().recordStackForAllThreads(recordAllThreads, sMainMachThread, etTraceThread);
usleep(4500);
usleep(sampleRate > 0 ? sampleRate : 4500);
}
}];
sStackRecordingThread.qualityOfService = NSQualityOfServiceUserInteractive;
Expand Down
2 changes: 1 addition & 1 deletion ETTrace/Tracer/Public/Tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void EMGBeginCollectingLibraries(void);

@interface EMGTracer : NSObject

+ (void)setupStackRecording:(BOOL)recordAllThreads;
+ (void)setupStackRecording:(BOOL)recordAllThreads rate:(useconds_t)sampleRate;
+ (void)stopRecording:(void (^)(NSDictionary *))stopped;
// Must be called on the main thread, before setupStackRecording is called
+ (void)setup;
Expand Down