Signal-iOS/Signal/test/PerformanceTests/MessageSendingPerformanceTest.swift
Evan Hahn 370ff654e7
Change license to AGPL
Change license to AGPL

This commit:

- Updates the `LICENSE` file

- Start every file with something like:

      // Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
      // SPDX-License-Identifier: AGPL-3.0-only

---

First, I removed existing license headers with this Ruby 3.1.2 script:

    require 'set'

    EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']

    same = 0
    different = 0

    all_files = `git ls-files`.lines.map { |line| line.strip }
    all_files.each do |relative_path|
      if relative_path == 'Pods'
        next
      end

      unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
        next
      end

      path = File.expand_path(relative_path)

      contents = File.read(path)
      new_contents = contents.sub(/\/\/\n\/\/  Copyright .*\n\/\/\n\n/, '')

      if contents == new_contents
        same += 1
      else
        different += 1
      end

      File.write(path, new_contents)
    end

    puts "updated #{different} file(s), left #{same} untouched"

I'm sure this script could be improved, but it worked well enough.

Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.

Then I fixed some stragglers and updated the precommit script.

See [a similar change in the Desktop app][0].

[0]: 8bfaf598af
2022-10-13 08:25:37 -05:00

236 lines
8.6 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import XCTest
@testable import SignalServiceKit
import GRDB
class MessageSendingPerformanceTest: PerformanceBaseTest {
// MARK: -
let stubbableNetworkManager = StubbableNetworkManager()
var dbObserverBlock: (() -> Void)?
private var dbObserver: BlockObserver?
let localE164Identifier = "+13235551234"
let localUUID = UUID()
let localClient = LocalSignalClient()
let runner = TestProtocolRunner()
// MARK: - Hooks
override func setUp() {
super.setUp()
let sskEnvironment = SSKEnvironment.shared as! MockSSKEnvironment
sskEnvironment.networkManagerRef = self.stubbableNetworkManager
// use the *real* message sender to measure it's perf
sskEnvironment.messageSenderRef = MessageSender()
Self.messageSenderJobQueue.setup()
try! databaseStorage.grdbStorage.setup()
// Observe DB changes so we can know when all the async processing is done
let dbObserver = BlockObserver(block: { self.dbObserverBlock?() })
self.dbObserver = dbObserver
databaseStorage.appendDatabaseChangeDelegate(dbObserver)
}
override func tearDown() {
dbObserver = nil
super.tearDown()
}
// MARK: -
func testPerf_messageSending_contactThread() {
// This is an example of a performance test case.
try! databaseStorage.grdbStorage.setupDatabaseChangeObserver()
measureMetrics(XCTestCase.defaultPerformanceMetrics, automaticallyStartMeasuring: false) {
sendMessages_contactThread()
}
databaseStorage.grdbStorage.testing_tearDownDatabaseChangeObserver()
}
func testPerf_messageSending_groupThread() {
// This is an example of a performance test case.
try! databaseStorage.grdbStorage.setupDatabaseChangeObserver()
measureMetrics(XCTestCase.defaultPerformanceMetrics, automaticallyStartMeasuring: false) {
sendMessages_groupThread()
}
databaseStorage.grdbStorage.testing_tearDownDatabaseChangeObserver()
}
func sendMessages_groupThread() {
// ensure local client has necessary "registered" state
identityManager.generateNewIdentityKey(for: .aci)
tsAccountManager.registerForTests(withLocalNumber: localE164Identifier, uuid: localUUID)
// Session setup
let groupMemberClients: [FakeSignalClient] = (0..<5).map { _ in
return FakeSignalClient.generate(e164Identifier: CommonGenerator.e164())
}
for client in groupMemberClients {
write { transaction in
try! self.runner.initialize(senderClient: self.localClient,
recipientClient: client,
transaction: transaction)
}
}
let threadFactory = GroupThreadFactory()
threadFactory.memberAddressesBuilder = {
groupMemberClients.map { $0.address }
}
let thread: TSGroupThread = databaseStorage.write { transaction in
XCTAssertEqual(0, TSMessage.anyCount(transaction: transaction))
XCTAssertEqual(0, TSThread.anyCount(transaction: transaction))
return threadFactory.create(transaction: transaction)
}
sendMessages(thread: thread)
}
func sendMessages_contactThread() {
// ensure local client has necessary "registered" state
identityManager.generateNewIdentityKey(for: .aci)
tsAccountManager.registerForTests(withLocalNumber: localE164Identifier, uuid: localUUID)
// Session setup
let bobClient = FakeSignalClient.generate(e164Identifier: "+18083235555")
write { transaction in
XCTAssertEqual(0, TSMessage.anyCount(transaction: transaction))
XCTAssertEqual(0, TSThread.anyCount(transaction: transaction))
try! self.runner.initialize(senderClient: self.localClient,
recipientClient: bobClient,
transaction: transaction)
}
let threadFactory = ContactThreadFactory()
threadFactory.contactAddressBuilder = { bobClient.address }
let thread = threadFactory.create()
sendMessages(thread: thread)
}
func sendMessages(thread: TSThread) {
let totalNumberToSend = DebugFlags.fastPerfTests ? 5 : 50
let expectMessagesSent = expectation(description: "messages sent")
let hasFulfilled = AtomicBool(false)
let fulfillOnce = {
if hasFulfilled.tryToSetFlag() {
expectMessagesSent.fulfill()
}
}
self.dbObserverBlock = {
let (messageCount, attemptingOutCount): (UInt, Int) = self.databaseStorage.read { transaction in
let messageCount = TSInteraction.anyCount(transaction: transaction)
let attemptingOutCount = InteractionFinder.attemptingOutInteractionIds(transaction: transaction).count
return (messageCount, attemptingOutCount)
}
if messageCount == totalNumberToSend && attemptingOutCount == 0 {
fulfillOnce()
}
}
startMeasuring()
for _ in (0..<totalNumberToSend) {
// Each is intentionally in a separate transaction, to be closer to the app experience
// of sending each message
self.read { transaction in
let messageBody = MessageBody(text: CommonGenerator.paragraph,
ranges: MessageBodyRanges.empty)
ThreadUtil.enqueueMessage(body: messageBody,
thread: thread,
transaction: transaction)
}
}
waitForExpectations(timeout: 20.0) { _ in
self.stopMeasuring()
self.dbObserverBlock = nil
// There's some async stuff that happens in message sender that will explode if
// we delete these models too early - e.g. sending a sync message, which we can't
// easily wait for in an explicit way.
sleep(1)
self.write { transaction in
TSInteraction.anyRemoveAllWithInstantation(transaction: transaction)
TSThread.anyRemoveAllWithInstantation(transaction: transaction)
SSKMessageSenderJobRecord.anyRemoveAllWithInstantation(transaction: transaction)
OWSRecipientIdentity.anyRemoveAllWithInstantation(transaction: transaction)
}
}
}
}
private class BlockObserver: DatabaseChangeDelegate {
let block: () -> Void
init(block: @escaping () -> Void) {
self.block = block
}
func databaseChangesDidUpdate(databaseChanges: DatabaseChanges) {
block()
}
func databaseChangesDidUpdateExternally() {
block()
}
func databaseChangesDidReset() {
block()
}
}
class StubbableNetworkManager: NetworkManager {
typealias NetworkManagerSuccess = (HTTPResponse) -> Void
typealias NetworkManagerFailure = (Error) -> Void
var block: (TSRequest, NetworkManagerSuccess, NetworkManagerFailure) -> Void = { request, success, _ in
Logger.info("faking success for request: \(request)")
let response = HTTPResponseImpl(requestUrl: request.url!,
status: 200,
headers: OWSHttpHeaders(),
bodyData: nil)
success(response)
}
public override func makePromise(request: TSRequest,
websocketSupportsRequest: Bool = false,
remainingRetryCount: Int = 0) -> Promise<HTTPResponse> {
Logger.info("Ignoring request: \(request)")
// This latency is optimistic because I didn't want to slow
// the tests down too much. But I did want to introduce some
// non-trivial latency to make any interactions with the various
// async's a little more realistic.
let fakeNetworkLatency = DispatchTimeInterval.milliseconds(25)
let block = self.block
let (promise, future) = Promise<HTTPResponse>.pending()
DispatchQueue.global().asyncAfter(deadline: .now() + fakeNetworkLatency) {
let success = { response in
future.resolve(response)
}
let failure = { error in
future.reject(error)
}
block(request, success, failure)
}
return promise
}
}