Skip to content

Commit 8bc4631

Browse files
authored
Wrap various convert subtasks in signpost intervals (#1112)
* Add a no-op shim for os.OSSignpost * Bump platform versions to be able to use OSSignposter * Fix deprecation warnings about UTType API * Wrap various major convert tasks in signpost intervals * Add a no-op shim for os.Logger * Add support for interpolating errors in no-op log messages * Address code review feedback: - Add signpost intervals around a few more tasks
1 parent d82da96 commit 8bc4631

File tree

8 files changed

+396
-68
lines changed

8 files changed

+396
-68
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ let swiftSettings: [SwiftSetting] = [
1919
let package = Package(
2020
name: "SwiftDocC",
2121
platforms: [
22-
.macOS(.v10_15),
23-
.iOS(.v13)
22+
.macOS(.v12),
23+
.iOS(.v15)
2424
],
2525
products: [
2626
.library(

Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@
1010

1111
import Foundation
1212

13+
#if canImport(os)
14+
import os
15+
#endif
16+
1317
package enum ConvertActionConverter {
18+
#if canImport(os)
19+
static package let signposter = OSSignposter(subsystem: "org.swift.docc", category: "Convert")
20+
#else
21+
static package let signposter = NoOpSignposterShim()
22+
#endif
1423

1524
/// Converts the documentation bundle in the given context and passes its output to a given consumer.
1625
///
@@ -30,8 +39,12 @@ package enum ConvertActionConverter {
3039
emitDigest: Bool,
3140
documentationCoverageOptions: DocumentationCoverageOptions
3241
) throws -> [Problem] {
42+
let signposter = Self.signposter
43+
3344
defer {
34-
context.diagnosticEngine.flush()
45+
signposter.withIntervalSignpost("Display diagnostics", id: signposter.makeSignpostID()) {
46+
context.diagnosticEngine.flush()
47+
}
3548
}
3649

3750
let processingDurationMetric = benchmark(begin: Benchmark.Duration(id: "documentation-processing"))
@@ -47,7 +60,9 @@ package enum ConvertActionConverter {
4760
}
4861

4962
// Precompute the render context
50-
let renderContext = RenderContext(documentationContext: context, bundle: bundle)
63+
let renderContext = signposter.withIntervalSignpost("Build RenderContext", id: signposter.makeSignpostID()) {
64+
RenderContext(documentationContext: context, bundle: bundle)
65+
}
5166
try outputConsumer.consume(renderReferenceStore: renderContext.store)
5267

5368
// Copy images, sample files, and other static assets.
@@ -89,6 +104,8 @@ package enum ConvertActionConverter {
89104
let resultsSyncQueue = DispatchQueue(label: "Convert Serial Queue", qos: .unspecified, attributes: [])
90105
let resultsGroup = DispatchGroup()
91106

107+
let renderSignpostHandle = signposter.beginInterval("Render", id: signposter.makeSignpostID(), "Render \(context.knownPages.count) pages")
108+
92109
var conversionProblems: [Problem] = context.knownPages.concurrentPerform { identifier, results in
93110
// If cancelled skip all concurrent conversion work in this block.
94111
guard !Task.isCancelled else { return }
@@ -146,37 +163,45 @@ package enum ConvertActionConverter {
146163
// Wait for any concurrent updates to complete.
147164
resultsGroup.wait()
148165

166+
signposter.endInterval("Render", renderSignpostHandle)
167+
149168
guard !Task.isCancelled else { return [] }
150169

151170
// Write various metadata
152171
if emitDigest {
153-
do {
154-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
155-
try outputConsumer.consume(indexingRecords: indexingRecords)
156-
try outputConsumer.consume(assets: assets)
157-
} catch {
158-
recordProblem(from: error, in: &conversionProblems, withIdentifier: "metadata")
172+
signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) {
173+
do {
174+
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
175+
try outputConsumer.consume(indexingRecords: indexingRecords)
176+
try outputConsumer.consume(assets: assets)
177+
} catch {
178+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "metadata")
179+
}
159180
}
160181
}
161182

162183
if FeatureFlags.current.isExperimentalLinkHierarchySerializationEnabled {
163-
do {
164-
let serializableLinkInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.id)
165-
try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation)
166-
167-
if !emitDigest {
168-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
184+
signposter.withIntervalSignpost("Serialize link hierarchy", id: signposter.makeSignpostID()) {
185+
do {
186+
let serializableLinkInformation = try context.linkResolver.localResolver.prepareForSerialization(bundleID: bundle.id)
187+
try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation)
188+
189+
if !emitDigest {
190+
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
191+
}
192+
} catch {
193+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "link-resolver")
169194
}
170-
} catch {
171-
recordProblem(from: error, in: &conversionProblems, withIdentifier: "link-resolver")
172195
}
173196
}
174197

175198
if emitDigest {
176-
do {
177-
try outputConsumer.consume(problems: context.problems + conversionProblems)
178-
} catch {
179-
recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems")
199+
signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) {
200+
do {
201+
try outputConsumer.consume(problems: context.problems + conversionProblems)
202+
} catch {
203+
recordProblem(from: error, in: &conversionProblems, withIdentifier: "problems")
204+
}
180205
}
181206
}
182207

Sources/SwiftDocC/Infrastructure/DocumentationContext.swift

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public typealias BundleIdentifier = String
8383
/// - ``parents(of:)``
8484
///
8585
public class DocumentationContext {
86+
private let signposter = ConvertActionConverter.signposter
8687

8788
/// An error that's encountered while interacting with a ``SwiftDocC/DocumentationContext``.
8889
public enum ContextError: DescribedError {
@@ -563,6 +564,11 @@ public class DocumentationContext {
563564
Attempt to resolve links in curation-only documentation, converting any ``TopicReferences`` from `.unresolved` to `.resolved` where possible.
564565
*/
565566
private func resolveLinks(curatedReferences: Set<ResolvedTopicReference>, bundle: DocumentationBundle) {
567+
let signpostHandle = signposter.beginInterval("Resolve links", id: signposter.makeSignpostID())
568+
defer {
569+
signposter.endInterval("Resolve links", signpostHandle)
570+
}
571+
566572
let references = Array(curatedReferences)
567573
let results = Synchronized<[LinkResolveResult]>([])
568574
results.sync({ $0.reserveCapacity(references.count) })
@@ -708,6 +714,11 @@ public class DocumentationContext {
708714
tutorialArticles: [SemanticResult<TutorialArticle>],
709715
bundle: DocumentationBundle
710716
) {
717+
let signpostHandle = signposter.beginInterval("Resolve links", id: signposter.makeSignpostID())
718+
defer {
719+
signposter.endInterval("Resolve links", signpostHandle)
720+
}
721+
711722
let sourceLanguages = soleRootModuleReference.map { self.sourceLanguages(for: $0) } ?? [.swift]
712723

713724
// Tutorial table-of-contents
@@ -1147,6 +1158,11 @@ public class DocumentationContext {
11471158
) throws {
11481159
// Making sure that we correctly let decoding memory get released, do not remove the autorelease pool.
11491160
try autoreleasepool {
1161+
let signpostHandle = signposter.beginInterval("Register symbols", id: signposter.makeSignpostID())
1162+
defer {
1163+
signposter.endInterval("Register symbols", signpostHandle)
1164+
}
1165+
11501166
/// We need only unique relationships so we'll collect them in a set.
11511167
var combinedRelationshipsBySelector = [UnifiedSymbolGraph.Selector: Set<SymbolGraph.Relationship>]()
11521168
/// Also track the unique relationships across all languages and platforms
@@ -1157,7 +1173,9 @@ public class DocumentationContext {
11571173
var moduleReferences = [String: ResolvedTopicReference]()
11581174

11591175
// Build references for all symbols in all of this module's symbol graphs.
1160-
let symbolReferences = linkResolver.localResolver.referencesForSymbols(in: symbolGraphLoader.unifiedGraphs, bundle: bundle, context: self)
1176+
let symbolReferences = signposter.withIntervalSignpost("Disambiguate references") {
1177+
linkResolver.localResolver.referencesForSymbols(in: symbolGraphLoader.unifiedGraphs, bundle: bundle, context: self)
1178+
}
11611179

11621180
// Set the index and cache storage capacity to avoid ad-hoc storage resizing.
11631181
documentationCache.reserveCapacity(symbolReferences.count)
@@ -1223,7 +1241,9 @@ public class DocumentationContext {
12231241
let moduleSymbolReference = SymbolReference(moduleName, interfaceLanguages: moduleInterfaceLanguages, defaultSymbol: moduleSymbol)
12241242
moduleReference = ResolvedTopicReference(symbolReference: moduleSymbolReference, moduleName: moduleName, bundle: bundle)
12251243

1226-
addSymbolsToTopicGraph(symbolGraph: unifiedSymbolGraph, url: fileURL, symbolReferences: symbolReferences, moduleReference: moduleReference)
1244+
signposter.withIntervalSignpost("Add symbols to topic graph", id: signposter.makeSignpostID()) {
1245+
addSymbolsToTopicGraph(symbolGraph: unifiedSymbolGraph, url: fileURL, symbolReferences: symbolReferences, moduleReference: moduleReference)
1246+
}
12271247

12281248
// For inherited symbols we remove the source docs (if inheriting docs is disabled) before creating their documentation nodes.
12291249
for (_, relationships) in unifiedSymbolGraph.relationshipsByLanguage {
@@ -1375,15 +1395,17 @@ public class DocumentationContext {
13751395
)
13761396

13771397
// Parse and prepare the nodes' content concurrently.
1378-
let updatedNodes = Array(documentationCache.symbolReferences).concurrentMap { finalReference in
1379-
// Match the symbol's documentation extension and initialize the node content.
1380-
let match = uncuratedDocumentationExtensions[finalReference]
1381-
let updatedNode = nodeWithInitializedContent(reference: finalReference, match: match)
1382-
1383-
return ((
1384-
node: updatedNode,
1385-
matchedArticleURL: match?.source
1386-
))
1398+
let updatedNodes = signposter.withIntervalSignpost("Parse symbol markup", id: signposter.makeSignpostID()) {
1399+
Array(documentationCache.symbolReferences).concurrentMap { finalReference in
1400+
// Match the symbol's documentation extension and initialize the node content.
1401+
let match = uncuratedDocumentationExtensions[finalReference]
1402+
let updatedNode = nodeWithInitializedContent(reference: finalReference, match: match)
1403+
1404+
return ((
1405+
node: updatedNode,
1406+
matchedArticleURL: match?.source
1407+
))
1408+
}
13871409
}
13881410

13891411
// Update cache with up-to-date nodes
@@ -2177,9 +2199,16 @@ public class DocumentationContext {
21772199
)
21782200

21792201
do {
2180-
try symbolGraphLoader.loadAll()
2181-
let pathHierarchy = PathHierarchy(symbolGraphLoader: symbolGraphLoader, bundleName: urlReadablePath(bundle.displayName), knownDisambiguatedPathComponents: configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents)
2182-
hierarchyBasedResolver = PathHierarchyBasedLinkResolver(pathHierarchy: pathHierarchy)
2202+
try signposter.withIntervalSignpost("Load symbols", id: signposter.makeSignpostID()) {
2203+
try symbolGraphLoader.loadAll()
2204+
}
2205+
hierarchyBasedResolver = signposter.withIntervalSignpost("Build PathHierarchy", id: signposter.makeSignpostID()) {
2206+
PathHierarchyBasedLinkResolver(pathHierarchy: PathHierarchy(
2207+
symbolGraphLoader: symbolGraphLoader,
2208+
bundleName: urlReadablePath(bundle.displayName),
2209+
knownDisambiguatedPathComponents: configuration.convertServiceConfiguration.knownDisambiguatedSymbolPathComponents
2210+
))
2211+
}
21832212
} catch {
21842213
// Pipe the error out of the dispatch queue.
21852214
discoveryError.sync({
@@ -2191,7 +2220,9 @@ public class DocumentationContext {
21912220
// First, all the resources are added since they don't reference anything else.
21922221
discoveryGroup.async(queue: discoveryQueue) { [unowned self] in
21932222
do {
2194-
try self.registerMiscResources(from: bundle)
2223+
try signposter.withIntervalSignpost("Load resources", id: signposter.makeSignpostID()) {
2224+
try self.registerMiscResources(from: bundle)
2225+
}
21952226
} catch {
21962227
// Pipe the error out of the dispatch queue.
21972228
discoveryError.sync({
@@ -2215,7 +2246,9 @@ public class DocumentationContext {
22152246

22162247
discoveryGroup.async(queue: discoveryQueue) { [unowned self] in
22172248
do {
2218-
result = try self.registerDocuments(from: bundle)
2249+
result = try signposter.withIntervalSignpost("Load documents", id: signposter.makeSignpostID()) {
2250+
try self.registerDocuments(from: bundle)
2251+
}
22192252
} catch {
22202253
// Pipe the error out of the dispatch queue.
22212254
discoveryError.sync({
@@ -2226,7 +2259,9 @@ public class DocumentationContext {
22262259

22272260
discoveryGroup.async(queue: discoveryQueue) { [unowned self] in
22282261
do {
2229-
try linkResolver.loadExternalResolvers(dependencyArchives: configuration.externalDocumentationConfiguration.dependencyArchives)
2262+
try signposter.withIntervalSignpost("Load external resolvers", id: signposter.makeSignpostID()) {
2263+
try linkResolver.loadExternalResolvers(dependencyArchives: configuration.externalDocumentationConfiguration.dependencyArchives)
2264+
}
22302265
} catch {
22312266
// Pipe the error out of the dispatch queue.
22322267
discoveryError.sync({
@@ -2361,7 +2396,9 @@ public class DocumentationContext {
23612396
try shouldContinueRegistration()
23622397

23632398
// Fourth, automatically curate all symbols that haven't been curated manually
2364-
let automaticallyCurated = autoCurateSymbolsInTopicGraph()
2399+
let automaticallyCurated = signposter.withIntervalSignpost("Auto-curate symbols ", id: signposter.makeSignpostID()) {
2400+
autoCurateSymbolsInTopicGraph()
2401+
}
23652402

23662403
// Crawl the rest of the symbols that haven't been crawled so far in hierarchy pre-order.
23672404
allCuratedReferences = try crawlSymbolCuration(in: automaticallyCurated.map(\.symbol), bundle: bundle, initial: allCuratedReferences)
@@ -2407,7 +2444,9 @@ public class DocumentationContext {
24072444
}
24082445

24092446
// Seventh, the complete topic graph—with all nodes and all edges added—is analyzed.
2410-
topicGraphGlobalAnalysis()
2447+
signposter.withIntervalSignpost("Analyze topic graph", id: signposter.makeSignpostID()) {
2448+
topicGraphGlobalAnalysis()
2449+
}
24112450

24122451
preResolveModuleNames()
24132452
}
@@ -2606,6 +2645,11 @@ public class DocumentationContext {
26062645
/// - Returns: The references of all the symbols that were curated.
26072646
@discardableResult
26082647
func crawlSymbolCuration(in references: [ResolvedTopicReference], bundle: DocumentationBundle, initial: Set<ResolvedTopicReference> = []) throws -> Set<ResolvedTopicReference> {
2648+
let signpostHandle = signposter.beginInterval("Curate symbols", id: signposter.makeSignpostID())
2649+
defer {
2650+
signposter.endInterval("Curate symbols", signpostHandle)
2651+
}
2652+
26092653
var crawler = DocumentationCurator(in: self, bundle: bundle, initial: initial)
26102654

26112655
for reference in references {

Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ struct SymbolGraphLoader {
5454
///
5555
/// - Throws: If loading and decoding any of the symbol graph files throws, this method re-throws one of the encountered errors.
5656
mutating func loadAll() throws {
57+
let signposter = ConvertActionConverter.signposter
58+
5759
let loadingLock = Lock()
5860

5961
var loadedGraphs = [URL: (usesExtensionSymbolFormat: Bool?, graph: SymbolKit.SymbolGraph)]()
@@ -118,6 +120,8 @@ struct SymbolGraphLoader {
118120
}
119121
#endif
120122

123+
let numberOfSymbolGraphs = bundle.symbolGraphURLs.count
124+
let decodeSignpostHandle = signposter.beginInterval("Decode symbol graphs", id: signposter.makeSignpostID(), "Decode \(numberOfSymbolGraphs) symbol graphs")
121125
switch decodingStrategy {
122126
case .concurrentlyAllFiles:
123127
// Concurrently load and decode all symbol graphs
@@ -127,12 +131,14 @@ struct SymbolGraphLoader {
127131
// Serially load and decode all symbol graphs, each one in concurrent batches.
128132
bundle.symbolGraphURLs.forEach(loadGraphAtURL)
129133
}
134+
signposter.endInterval("Decode symbol graphs", decodeSignpostHandle)
130135

131136
// define an appropriate merging strategy based on the graph formats
132137
let foundGraphUsingExtensionSymbolFormat = loadedGraphs.values.map(\.usesExtensionSymbolFormat).contains(true)
133138

134139
let usingExtensionSymbolFormat = foundGraphUsingExtensionSymbolFormat
135-
140+
141+
let mergeSignpostHandle = signposter.beginInterval("Build unified symbol graph", id: signposter.makeSignpostID())
136142
let graphLoader = GraphCollector(extensionGraphAssociationStrategy: usingExtensionSymbolFormat ? .extendingGraph : .extendedGraph)
137143

138144
// feed the loaded graphs into the `graphLoader`
@@ -150,7 +156,13 @@ struct SymbolGraphLoader {
150156
(self.unifiedGraphs, self.graphLocations) = graphLoader.finishLoading(
151157
createOverloadGroups: FeatureFlags.current.isExperimentalOverloadedSymbolPresentationEnabled
152158
)
159+
signposter.endInterval("Build unified symbol graph", mergeSignpostHandle)
153160

161+
let availabilitySignpostHandle = signposter.beginInterval("Add missing availability", id: signposter.makeSignpostID())
162+
defer {
163+
signposter.endInterval("Add missing availability", availabilitySignpostHandle)
164+
}
165+
154166
for var unifiedGraph in unifiedGraphs.values {
155167
var defaultUnavailablePlatforms = [PlatformName]()
156168
var defaultAvailableInformation = [DefaultAvailability.ModuleAvailability]()

Sources/SwiftDocC/Servers/FileServer.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import SymbolKit
1313
#if canImport(FoundationNetworking)
1414
import FoundationNetworking
1515
#endif
16+
#if canImport(UniformTypeIdentifiers)
17+
import UniformTypeIdentifiers
18+
#endif
1619
#if os(Windows)
1720
import WinSDK
1821
#endif
@@ -116,15 +119,7 @@ public class FileServer {
116119

117120
#if os(macOS)
118121

119-
let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)
120-
guard let fileUTI = unmanagedFileUTI?.takeRetainedValue() else {
121-
return defaultMimeType
122-
}
123-
guard let mimeType = UTTypeCopyPreferredTagWithClass (fileUTI, kUTTagClassMIMEType)?.takeRetainedValue() else {
124-
return defaultMimeType
125-
}
126-
127-
return (mimeType as NSString) as String
122+
return UTType(filenameExtension: ext)?.preferredMIMEType ?? defaultMimeType
128123

129124
#elseif os(Windows)
130125

0 commit comments

Comments
 (0)