Skip to content

Commit

Permalink
Merge pull request #8 from signalfx/bg/spike_new_properties
Browse files Browse the repository at this point in the history
APMI-3417 New Crash Time Metrics
  • Loading branch information
bgustafson authored Dec 8, 2022
2 parents 24630d0 + 8b89c8b commit ba5efe2
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
xcuserdata/
.build
Package.resolved
.idea
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
86461EEB26972906007C6DC0 /* SplunkRumCrashReporting.h in Headers */ = {isa = PBXBuildFile; fileRef = 86461EDD26972906007C6DC0 /* SplunkRumCrashReporting.h */; settings = {ATTRIBUTES = (Public, ); }; };
86461EF726972964007C6DC0 /* CrashReporting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86461EF626972964007C6DC0 /* CrashReporting.swift */; };
86461EFC269729C0007C6DC0 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 86461EFB269729C0007C6DC0 /* CrashReporter */; };
86461F0526972A11007C6DC0 /* sample.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 86461F0426972A11007C6DC0 /* sample.plcrash */; };
86461F0526972A11007C6DC0 /* sample_v1.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 86461F0426972A11007C6DC0 /* sample_v1.plcrash */; };
86D3180A271655B300B43379 /* SplunkOtel in Frameworks */ = {isa = PBXBuildFile; productRef = 86D31809271655B300B43379 /* SplunkOtel */; };
D774545D28E38CF40056159F /* DeviceStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = D774545C28E38CF40056159F /* DeviceStats.swift */; };
D7C64D1228E494C50086368D /* DeviceStatsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C64D1128E494C50086368D /* DeviceStatsTests.swift */; };
D7D14290293804A200CAD87E /* sample_v2.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = D7D1428F293804A200CAD87E /* sample_v2.plcrash */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -34,7 +37,10 @@
86461EE826972906007C6DC0 /* CrashTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashTests.swift; sourceTree = "<group>"; };
86461EEA26972906007C6DC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
86461EF626972964007C6DC0 /* CrashReporting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporting.swift; sourceTree = "<group>"; };
86461F0426972A11007C6DC0 /* sample.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = sample.plcrash; sourceTree = "<group>"; };
86461F0426972A11007C6DC0 /* sample_v1.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file.plcrash; path = sample_v1.plcrash; sourceTree = "<group>"; };
D774545C28E38CF40056159F /* DeviceStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStats.swift; sourceTree = "<group>"; };
D7C64D1128E494C50086368D /* DeviceStatsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatsTests.swift; sourceTree = "<group>"; };
D7D1428F293804A200CAD87E /* sample_v2.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = sample_v2.plcrash; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -80,6 +86,7 @@
isa = PBXGroup;
children = (
86461EF626972964007C6DC0 /* CrashReporting.swift */,
D774545C28E38CF40056159F /* DeviceStats.swift */,
86461EDD26972906007C6DC0 /* SplunkRumCrashReporting.h */,
86461EDE26972906007C6DC0 /* Info.plist */,
);
Expand All @@ -89,8 +96,10 @@
86461EE726972906007C6DC0 /* SplunkRumCrashReportingTests */ = {
isa = PBXGroup;
children = (
86461F0426972A11007C6DC0 /* sample.plcrash */,
D7D1428F293804A200CAD87E /* sample_v2.plcrash */,
86461F0426972A11007C6DC0 /* sample_v1.plcrash */,
86461EE826972906007C6DC0 /* CrashTests.swift */,
D7C64D1128E494C50086368D /* DeviceStatsTests.swift */,
86461EEA26972906007C6DC0 /* Info.plist */,
);
path = SplunkRumCrashReportingTests;
Expand Down Expand Up @@ -203,7 +212,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
86461F0526972A11007C6DC0 /* sample.plcrash in Resources */,
86461F0526972A11007C6DC0 /* sample_v1.plcrash in Resources */,
D7D14290293804A200CAD87E /* sample_v2.plcrash in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -215,6 +225,7 @@
buildActionMask = 2147483647;
files = (
86461EF726972964007C6DC0 /* CrashReporting.swift in Sources */,
D774545D28E38CF40056159F /* DeviceStats.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -223,6 +234,7 @@
buildActionMask = 2147483647;
files = (
86461EE926972906007C6DC0 /* CrashTests.swift in Sources */,
D7C64D1228E494C50086368D /* DeviceStatsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import CrashReporter
import SplunkOtel
import OpenTelemetryApi

let CrashReportingVersionString = "0.2.0"
let CrashReportingVersionString = "0.3.0"

var TheCrashReporter: PLCrashReporter?
private var customDataDictionary: [String: String] = [String: String]()

func initializeCrashReporting() {
let startupSpan = buildTracer().spanBuilder(spanName: "SplunkRumCrashReporting").startSpan()
Expand All @@ -46,6 +47,8 @@ func initializeCrashReporting() {
}
TheCrashReporter = crashReporter
updateCrashReportSessionId()
updateDeviceStats()
startPollingForDeviceStats()
SplunkRum.addSessionIdChangeCallback {
updateCrashReportSessionId()
}
Expand All @@ -71,7 +74,40 @@ private func buildTracer() -> Tracer {
}

func updateCrashReportSessionId() {
TheCrashReporter?.customData = SplunkRum.getSessionId().data(using: .utf8)
do {
customDataDictionary["sessionId"] = SplunkRum.getSessionId()
let customData = try NSKeyedArchiver.archivedData(withRootObject: customDataDictionary, requiringSecureCoding: false)
TheCrashReporter?.customData = customData
} catch {
// We have failed to archive the custom data dictionary.
SplunkRum.debugLog("Failed to add the sessionId to the crash reports custom data.")
}
}

private func updateDeviceStats() {
do {
customDataDictionary["batteryLevel"] = DeviceStats.batteryLevel
customDataDictionary["freeDiskSpace"] = DeviceStats.freeDiskSpace
customDataDictionary["freeMemory"] = DeviceStats.freeMemory
let customData = try NSKeyedArchiver.archivedData(withRootObject: customDataDictionary, requiringSecureCoding: false)
TheCrashReporter?.customData = customData
} catch {
// We have failed to archive the custom data dictionary.
SplunkRum.debugLog("Failed to add the device stats to the crash reports custom data.")
}
}

/*
Will poll every 5 seconds to update the device stats.
*/
private func startPollingForDeviceStats() {
let repeatSeconds: Double = 5
DispatchQueue.global(qos: .background).async {
let timer = Timer.scheduledTimer(withTimeInterval: repeatSeconds, repeats: true) { _ in
updateDeviceStats()
}
timer.fire()
}
}

func loadPendingCrashReport(_ data: Data!) throws {
Expand All @@ -86,7 +122,15 @@ func loadPendingCrashReport(_ data: Data!) throws {
let span = buildTracer().spanBuilder(spanName: exceptionType ?? "unknown").setStartTime(time: now).setNoParent().startSpan()
span.setAttribute(key: "component", value: "crash")
if report.customData != nil {
span.setAttribute(key: "crash.rumSessionId", value: String(decoding: report.customData, as: UTF8.self))
let customData = NSKeyedUnarchiver.unarchiveObject(with: report.customData) as? [String: String]
if customData != nil {
span.setAttribute(key: "crash.rumSessionId", value: customData!["sessionId"]!)
span.setAttribute(key: "crash.batteryLevel", value: customData!["batteryLevel"]!)
span.setAttribute(key: "crash.freeDiskSpace", value: customData!["freeDiskSpace"]!)
span.setAttribute(key: "crash.freeMemory", value: customData!["freeMemory"]!)
} else {
span.setAttribute(key: "crash.rumSessionId", value: String(decoding: report.customData, as: UTF8.self))
}
}
// "marketing version" here matches up to our use of CFBundleShortVersionString
span.setAttribute(key: "crash.app.version", value: report.applicationInfo.applicationMarketingVersion)
Expand All @@ -105,7 +149,7 @@ func loadPendingCrashReport(_ data: Data!) throws {
span.end(time: now)
}

// FIXME this is a messy copy+paste of select bits of PLCrashReportTextForamtter
// FIXME this is a messy copy+paste of select bits of PLCrashReportTextFormatter
func crashedThreadToStack(report: PLCrashReport, thread: PLCrashReportThreadInfo) -> String {
let text = NSMutableString()
text.appendFormat("Thread %ld", thread.threadNumber)
Expand Down
65 changes: 65 additions & 0 deletions SplunkRumCrashReporting/SplunkRumCrashReporting/DeviceStats.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
/*
Copyright 2021 Splunk Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import Foundation
import System
import UIKit

internal class DeviceStats {
class var batteryLevel: String {
UIDevice.current.isBatteryMonitoringEnabled = true
let level = abs(UIDevice.current.batteryLevel * 100)
return "\(level)%"
}
class var freeDiskSpace: String {
do {
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
let maybeFreeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value
guard let freeSpace = maybeFreeSpace else {
return "Unknown"
}
return ByteCountFormatter.string(fromByteCount: freeSpace, countStyle: .file)
} catch {
return "Unknown"
}
}
// https://stackoverflow.com/questions/5012886/determining-the-available-amount-of-ram-on-an-ios-device/8540665#8540665
class var freeMemory: String {
var usedBytes: Float = 0
let totalBytes = Float(ProcessInfo.processInfo.physicalMemory)
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
task_info(
mach_task_self_,
task_flavor_t(MACH_TASK_BASIC_INFO),
$0,
&count
)
}
}
if kerr == KERN_SUCCESS {
usedBytes = Float(info.resident_size)
} else {
return "Unknown"
}
let freeBytes = totalBytes - usedBytes
return ByteCountFormatter.string(fromByteCount: Int64(freeBytes), countStyle: .memory)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class TestSpanExporter: SpanExporter {
}

class CrashTests: XCTestCase {
func testBasics() throws {
let crashPath = Bundle(for: CrashTests.self).url(forResource: "sample", withExtension: "plcrash")!
func testBasics_v1() throws {
let crashPath = Bundle(for: CrashTests.self).url(forResource: "sample_v1", withExtension: "plcrash")!
let crashData = try Data(contentsOf: crashPath)

SplunkRum.initialize(beaconUrl: "http://127.0.0.1:8989/v1/traces", rumAuth: "FAKE", options: SplunkRumOptions(allowInsecureBeacon: true, debug: true))
Expand Down Expand Up @@ -75,4 +75,39 @@ class CrashTests: XCTestCase {
XCTAssertEqual(startup!.attributes["component"]?.description, "appstart")

}
func testBasics_v2() throws {
let crashPath = Bundle(for: CrashTests.self).url(forResource: "sample_v2", withExtension: "plcrash")!
let crashData = try Data(contentsOf: crashPath)

SplunkRum.initialize(beaconUrl: "http://127.0.0.1:8989/v1/traces", rumAuth: "FAKE", options: SplunkRumOptions(allowInsecureBeacon: true, debug: true))
OpenTelemetrySDK.instance.tracerProvider.addSpanProcessor(SimpleSpanProcessor(spanExporter: TestSpanExporter()))
localSpans.removeAll()

SplunkRumCrashReporting.start()
try loadPendingCrashReport(crashData)

XCTAssertEqual(localSpans.count, 4)
let crashReport = localSpans.first(where: { (span) -> Bool in
return span.name == "SIGTRAP"
})
let startup = localSpans.first(where: { (span) -> Bool in
return span.name == "SplunkRumCrashReporting"
})

XCTAssertNotNil(crashReport)
XCTAssertNotEqual(crashReport!.attributes["splunk.rumSessionId"], crashReport!.attributes["crash.rumSessionId"])
XCTAssertEqual(crashReport!.attributes["crash.rumSessionId"]?.description, "388e59237de675ef8e9751fcf2b0f936")
XCTAssertEqual(crashReport!.attributes["crash.address"]?.description, "7595465412")
XCTAssertEqual(crashReport!.attributes["component"]?.description, "crash")
XCTAssertEqual(crashReport!.attributes["error"]?.description, "true")
XCTAssertEqual(crashReport!.attributes["exception.type"]?.description, "SIGTRAP")
XCTAssertTrue(crashReport!.attributes["exception.stacktrace"]?.description.contains("UIKitCore") ?? false)
XCTAssertEqual(crashReport!.attributes["crash.batteryLevel"]?.description, "91.0%")
XCTAssertEqual(crashReport!.attributes["crash.freeDiskSpace"]?.description, "197.23 GB")
XCTAssertEqual(crashReport!.attributes["crash.freeMemory"]?.description, "5.54 GB")

XCTAssertNotNil(startup)
XCTAssertEqual(startup!.attributes["component"]?.description, "appstart")

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
/*
Copyright 2021 Splunk Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

@testable import SplunkRumCrashReporting
import Foundation
import XCTest

class DeviceStatsTests: XCTestCase {
func testBattery() throws {
let batteryLevel = DeviceStats.batteryLevel
XCTAssertEqual(batteryLevel, "100.0%")
}
func testFreeDiskSpace() throws {
let diskSpace = DeviceStats.freeDiskSpace
let space = Int(diskSpace.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? 0
XCTAssertTrue(space > 0)
}
func testFreeMemory() throws {
let freeMemory = DeviceStats.freeMemory
let space = Int(freeMemory.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? 0
XCTAssertTrue(space > 0)
}
}
Binary file not shown.

0 comments on commit ba5efe2

Please sign in to comment.