├── CMakeLists.txt
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── archive.sh
├── common
├── callback-queue.cc
├── callback-queue.h
├── log-text.cc
├── log-text.h
├── log-values.cc
└── log-values.h
├── demo
├── README.md
├── logging.cc
├── logging.h
├── main.cc
├── test.cc
├── tracing.cc
└── tracing.h
├── example
├── async.cc
├── connection.cc
├── function.cc
├── inherit.cc
├── limit.cc
├── manual.cc
├── mock.cc
├── multi.cc
├── paths.cc
├── simple.cc
├── speed.cc
├── threaded.cc
├── throttle.cc
└── trace.cc
├── include
├── thread-capture.h
└── thread-crosser.h
├── src
└── thread-crosser.cc
└── test
├── readme-test.cc
├── thread-capture-test.cc
└── thread-crosser-test.cc
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.6)
2 | project(capture-thread)
3 |
4 | include_directories(
5 | ${CMAKE_SOURCE_DIR}/include
6 | ${CMAKE_SOURCE_DIR}/common)
7 |
8 | set(CMAKE_CXX_FLAGS "-Wall -pedantic -std=c++11 -O2 -g -pthread")
9 |
10 | IF(NOT USE_PREFIX)
11 | SET(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}")
12 | ENDIF()
13 |
14 | SET(INSTALL_INCLUDEDIR "include")
15 | SET(INSTALL_LIBDIR "lib")
16 |
17 | SET(INSTALL_PATH "${CMAKE_INSTALL_PREFIX}")
18 | SET(INCLUDE_PATH "${INSTALL_PATH}/${INSTALL_INCLUDEDIR}")
19 | SET(LIBRARY_PATH "${INSTALL_PATH}/${INSTALL_LIBDIR}")
20 |
21 | INCLUDE(FindThreads)
22 | FIND_LIBRARY(PTHREAD_LIBRARY
23 | NAMES pthread
24 | QUIET
25 | ONLY_CMAKE_FIND_ROOT_PATH)
26 | IF(NOT PTHREAD_LIBRARY)
27 | SET(PTHREAD_LIBRARY "")
28 | ENDIF()
29 |
30 | add_library(capture-thread STATIC
31 | src/thread-crosser.cc)
32 |
33 | add_executable(async
34 | example/async.cc)
35 | target_link_libraries(async
36 | capture-thread
37 | ${PTHREAD_LIBRARY})
38 |
39 | add_executable(connection
40 | example/connection.cc)
41 | target_link_libraries(connection
42 | capture-thread)
43 |
44 | add_executable(function
45 | example/function.cc)
46 | target_link_libraries(function
47 | capture-thread)
48 |
49 | add_executable(inherit
50 | example/inherit.cc)
51 | target_link_libraries(inherit
52 | capture-thread)
53 |
54 | add_executable(limit
55 | example/limit.cc)
56 | target_link_libraries(
57 | limit
58 | capture-thread)
59 |
60 | add_executable(manual
61 | example/manual.cc)
62 | target_link_libraries(manual
63 | capture-thread
64 | ${PTHREAD_LIBRARY})
65 |
66 | add_executable(mock
67 | example/mock.cc)
68 | target_link_libraries(mock
69 | capture-thread)
70 |
71 | add_executable(multi
72 | example/multi.cc)
73 | target_link_libraries(multi
74 | capture-thread
75 | ${PTHREAD_LIBRARY})
76 |
77 | add_executable(paths
78 | example/paths.cc)
79 | target_link_libraries(
80 | paths
81 | capture-thread)
82 |
83 | add_executable(simple
84 | example/simple.cc)
85 | target_link_libraries(
86 | simple
87 | capture-thread)
88 |
89 | add_executable(speed
90 | example/speed.cc)
91 | target_link_libraries(
92 | speed
93 | capture-thread)
94 |
95 | add_executable(threaded
96 | example/threaded.cc)
97 | target_link_libraries(
98 | threaded
99 | capture-thread
100 | ${PTHREAD_LIBRARY})
101 |
102 | add_executable(throttle
103 | example/throttle.cc)
104 | target_link_libraries(
105 | throttle
106 | capture-thread
107 | ${PTHREAD_LIBRARY})
108 |
109 | add_executable(trace
110 | example/trace.cc)
111 | target_link_libraries(
112 | trace
113 | capture-thread
114 | ${PTHREAD_LIBRARY})
115 |
116 | add_executable(demo-main
117 | demo/main.cc
118 | demo/logging.cc
119 | demo/tracing.cc
120 | common/callback-queue.cc)
121 | target_link_libraries(demo-main
122 | capture-thread
123 | ${PTHREAD_LIBRARY})
124 |
125 | add_executable(readme-test
126 | test/readme-test.cc)
127 | target_link_libraries(readme-test
128 | capture-thread
129 | ${PTHREAD_LIBRARY})
130 |
131 | INSTALL(TARGETS
132 | capture-thread
133 | DESTINATION ${INSTALL_LIBDIR})
134 |
135 | INSTALL(FILES
136 | include/thread-capture.h
137 | include/thread-crosser.h
138 | DESTINATION ${INSTALL_INCLUDEDIR})
139 |
140 | find_package(GTest)
141 | if(GTEST_LIBRARIES)
142 |
143 | include_directories(${GTEST_INCLUDE_DIRS})
144 |
145 | add_executable(thread-capture-test
146 | test/thread-capture-test.cc
147 | common/log-text.cc
148 | common/log-values.cc
149 | common/callback-queue.cc)
150 | target_link_libraries(thread-capture-test
151 | gtest gmock gtest_main
152 | capture-thread
153 | ${PTHREAD_LIBRARY})
154 |
155 | add_executable(thread-crosser-test
156 | test/thread-crosser-test.cc
157 | common/log-text.cc
158 | common/log-values.cc
159 | common/callback-queue.cc)
160 | target_link_libraries(thread-crosser-test
161 | gtest gmock gtest_main
162 | capture-thread
163 | ${PTHREAD_LIBRARY})
164 |
165 | add_executable(demo-test
166 | demo/test.cc
167 | demo/logging.cc
168 | demo/tracing.cc
169 | common/callback-queue.cc)
170 | target_link_libraries(demo-test
171 | gtest gmock gtest_main
172 | capture-thread
173 | ${PTHREAD_LIBRARY})
174 |
175 | endif()
176 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Capture Thread Library][google/capture-thread]
2 |
3 | Framework for loggers, tracers, and mockers in multithreaded C++ programs.
4 |
5 | *(This is not an official Google product.)*
6 |
7 | ## Motivation
8 |
9 | When developing C++ projects, [instrumentation][instrumentation] is frequently
10 | used to collect information from the system, inject information into the system,
11 | or both. The role of this information within the system rarely lines up with the
12 | actual structure of the project.
13 |
14 | For example:
15 |
16 | - *Loggers* will collect information in a wide range of contexts within the
17 | code, and thus the logic connecting the log points to the logger will not
18 | match the structure of the project.
19 |
20 | - *Tracers* make available information that describes how call execution
21 | arrived at a certain point. In some cases this information can be passed
22 | along with the data being processed, but in most cases this is obtrusive and
23 | does not scale well.
24 |
25 | - *Mockers* replace default idioms (e.g., opening files) with alternate
26 | behavior for the purposes of testing. These actions generally occur deep
27 | within the code, and could not otherwise be swapped out for testing without
28 | leaking those details in the API.
29 |
30 | This library is designed to handle all of these situations with minimal
31 | intrusion into your project, and without leaking details in your API.
32 |
33 | ## Summary
34 |
35 | The **Capture Thread Library** is designed around the concept of
36 | thread-locality, which allows the sharing of static variables *only within the
37 | current thread*. Canonical static variables, on the other hand, are problematic
38 | due to ownership and thread-safety issues.
39 |
40 | This library establishes the following idiom *(using logging as an example)*:
41 |
42 | 1. The instrumentation is 100% passive unless it is explicitly enabled. *(For
43 | example, logging points that by default just print to `std::cerr`.)*
44 |
45 | 2. Instrumentation is enabled in the current thread by *instantiating* an
46 | implementation, and only remains enabled until that instance goes out of
47 | scope. *(For example, an implementation that captures logged lines while
48 | it's in scope.)*
49 |
50 | 3. While enabled, the instrumentation transparently alters the behavior of
51 | logic deep within the code that would otherwise use default behavior. *(For
52 | example, the log-capture instance redirects messages to a `std::list` only
53 | while it's in scope.)*
54 |
55 | 4. Instrumentation can be shared across threads in *explicitly-specified*
56 | points in the code, based entirely on how you compartmentalize your logic.
57 | *(For example, independent of the instrumentation, you logically split
58 | processing of a query into multiple threads, and want the instrumentation to
59 | treat it as a single process.)*
60 |
61 | ## Key Design Points
62 |
63 | The **Capture Thread Library** has several design points that make it efficient
64 | and reliable:
65 |
66 | - All data structures are immutable and contain no dynamic allocation.
67 | - All library logic is thread-safe without threads blocking each other.
68 | - The enabling and disabling of instrumentation is strictly scope-driven,
69 | making it impossible to have bad pointers when used correctly.
70 | - The work required to share instrumentation between threads *does not* depend
71 | on the number of types of instrumentation used. (For example, sharing a
72 | logger and a tracer is the same amount of work as just sharing a logger.)
73 | - Instrumentation classes derived (by you) from this library *cannot* be
74 | moved, copied, or dynamically allocated, ensuring that scoping rules are
75 | strictly enforced.
76 | - The library is designed so that instrumentation data structures and calls do
77 | not need to be visible in your project's API headers, making them low-risk
78 | to add, modify, or remove.
79 | - All of the library code is thoroughly unit-tested.
80 |
81 | ## Caveats
82 |
83 | In some cases, it might not be appropriate (or possible) to use the **Capture
84 | Thread Library**:
85 |
86 | - By design, this library is meant to help you *circumvent* the structure of
87 | your program, specifically so that you don't need to modify your design to
88 | facilitate instrumentation. Although you could technically structure the
89 | business logic of your program around this library, doing so would likely
90 | lead to difficult-to-follow code and baffling latent bugs.
91 |
92 | - If you want to share instrumentation between threads, you *must* be able to
93 | pass either a `std::function` or a pointer from the source thread to the
94 | destination thread. This is because those semantics are a part of your
95 | project's design, and thus cannot be automatically inferred by the library.
96 |
97 | - Since this library is scope-driven, you *cannot* share instrumentation
98 | outside of the scope it was created in. For example:
99 |
100 | ```c++
101 | void f() { MyLogger capture_messages; }
102 | void g() { MyLogger::Log("g was called"); }
103 |
104 | void Execute() {
105 | f();
106 | g();
107 | }
108 | ```
109 |
110 | In this example, the instrumentation is created in `f`, but goes out of
111 | scope before `g` is called. In this case, `g` just uses the default behavior
112 | for `MyLogger::Log`.
113 |
114 | This is actually dangerous if you return a wrapped function in a way that
115 | changes the scope:
116 |
117 | ```c++
118 | // Fine, because no wrapping is done.
119 | std::function f() {
120 | MyLogger capture_messages;
121 | return [] { MyLogger::Log("f was called"); };
122 | }
123 |
124 | // Fine, because no instrumentation goes out of scope.
125 | std::function g() {
126 | return ThreadCrosser::WrapCall([] { MyLogger::Log("g was called"); });
127 | }
128 |
129 | // DANGER! capture_messages goes out of scope, invalidating the function.
130 | std::function h() {
131 | MyLogger capture_messages;
132 | return ThreadCrosser::WrapCall([] { MyLogger::Log("h was called"); });
133 | }
134 |
135 | void Execute() {
136 | f()(); // Fine.
137 | g()(); // Fine.
138 | h()(); // SIGSEGV!
139 |
140 | // Fine. g captures capture_messages, but capture_messages doesn't go out of
141 | // scope until the worker thread is joined.
142 | MyLogger capture_messages;
143 | std::thread worker(g());
144 | worker.join();
145 | }
146 | ```
147 |
148 | ## Quick Start
149 |
150 | Instrumenting a project has four steps. These assume that your project is
151 | already functional, and is just lacking instrumentation.
152 |
153 | 1. Create an instrumentation class to contain the state to be shared.
154 | 2. Instrument your project with logging points, tracing points, or mocking
155 | substitutions, depending on which you implemented.
156 | 3. Where control is passed between threads, e.g., creating a thread or passing
157 | a callback between threads, use the logic in `ThreadCrosser` to ensure that
158 | the instrumentation crosses threads.
159 | 4. As needed, instantiate the instrumentation class(es) to enable the
160 | instrumentation within a specific scope.
161 |
162 | Complexity estimates below are estimates of how much additional work will be
163 | necessary as your project grows.
164 |
165 | ### Step 1: Instrumentation Class [`O(1)`]
166 |
167 | The instrumentation class(es) will generally be written once and then left
168 | alone. They might also be general enough for use in multiple projects.
169 |
170 | ```c++
171 | #include
172 | #include
173 | #include
174 | #include
175 | #include "thread-capture.h"
176 |
177 | // This class provides the instrumentation logic, both at the point the
178 | // instrumentation is used (e.g., logging points) and where it is enabled (e.g.,
179 | // log-capture points.) Note that instances of ThreadCapture cannot be moved,
180 | // copied, or dynamically allocated.
181 | class Logger : public capture_thread::ThreadCapture {
182 | public:
183 | Logger() : cross_and_capture_to_(this) {}
184 |
185 | // The static API is used at the instrumentation points. It will often be a
186 | // no-op if no instrumentation is in scope.
187 | static void Log(const std::string& line) {
188 | // GetCurrent() provides the instrumentation currently in scope, and is
189 | // always thread-safe and repeatable. The implementation of the
190 | // instrumentation must be explicitly made thread-safe, however.
191 | if (GetCurrent()) {
192 | GetCurrent()->LogLine(line);
193 | } else {
194 | std::cerr << "Not captured: \"" << line << "\"" << std::endl;
195 | }
196 | }
197 |
198 | // The non-static public API allows the creator of the instrumentation object
199 | // to access its contents. This is only necessary when the instrumentation is
200 | // gathering information, as opposed to propagating information.
201 | std::list GetLines() {
202 | std::lock_guard lock(lock_);
203 | return lines_;
204 | }
205 |
206 | private:
207 | // The private implementation applies to the instrumentation only when it's in
208 | // scope. This does not need to exactly mirror the static API, and in fact
209 | // only needs to differentiate between default and override behaviors.
210 | void LogLine(const std::string& line) {
211 | std::lock_guard lock(lock_);
212 | lines_.emplace_back(line);
213 | }
214 |
215 | std::mutex lock_;
216 | std::list lines_;
217 | // Add an AutoThreadCrosser to ensure that scoping is handled correctly. If
218 | // you absolutely don't want the instrumentation crossing threads, use
219 | // ScopedCapture instead. Always initialize with `this`.
220 | const AutoThreadCrosser cross_and_capture_to_;
221 | };
222 | ```
223 |
224 | ### Step 2: Instrument the Code [`O(n)`]
225 |
226 | Instrumenting the code with your new instrumentation class will generally
227 | consist of one-line additions throughout the code. There will often be a large
228 | number of instrumentation points in the code.
229 |
230 | ```c++
231 | // #include the header for your instrumentation class.
232 |
233 | // This function already exists in your code, and performs some sort of work for
234 | // which you want to use the instrumentation.
235 | void MyExistingFunction() {
236 | // Add calls to the static API where you need access to the instrumentation.
237 | Logger::Log("MyExistingFunction called");
238 | }
239 | ```
240 |
241 | ### Step 3: Cross Threads [`O(log n)`]
242 |
243 | Crossing threads is necessary when the process you are tracking splits work
244 | among multiple threads. The complexity here depends on both what you consider a
245 | single task (e.g., processing a query) and how that work is split among threads.
246 |
247 | ```c++
248 | #include
249 | #include "thread-crosser.h"
250 |
251 | // (You don't need to #include the header for your instrumentation class here.)
252 |
253 | // This function already exists in your code, and parallelizes some
254 | // functionality that needs to use the instrumentation, but doesn't need to use
255 | // the instrumentation itself.
256 | void ParallelizeWork() {
257 | // Previously, the code just created a thread.
258 | // std::thread worker(&MyExistingFunction);
259 |
260 | // To pass along the instrumentation, wrap the thread with WrapCall. Also use
261 | // WrapCall when passing work to a worker thread, e.g., a thread pool.
262 | std::thread worker(
263 | capture_thread::ThreadCrosser::WrapCall(&MyExistingFunction));
264 | worker.join();
265 | }
266 | ```
267 |
268 | ### Step 4: Enable Instrumentation [`O(1)`]
269 |
270 | Your instrumentation must have default behavior that makes sense when the
271 | instrumentation is not enabled. Instrumentation can only be enabled by
272 | instantiating the implementation, and will only be available until that instance
273 | goes out of scope. There should be *very few* instantiation points (i.e.,
274 | usually just one per instrumentation type) in your code.
275 |
276 | ```c++
277 | // #include the header for your instrumentation class.
278 |
279 | int main() {
280 | // If no instrumentation is in scope, the default behavior of the static API
281 | // is used where instrumentation calls are made. In this case, this will just
282 | // print the line to std::cerr.
283 | ParallelizeWork();
284 |
285 | // To make the instrumentation available within a given scope, just
286 | // instantiate your class. The framework will take care of the rest.
287 | Logger logger;
288 |
289 | // Since a Logger is in scope, the line will be captured to that instance,
290 | // rather than the default behavior of printing to std::cerr.
291 | ParallelizeWork();
292 |
293 | // In this case the instrumentation captures data, which is now available in
294 | // the local instance.
295 | for (const std::string& line : logger.GetLines()) {
296 | std::cerr << "The logger captured: \"" << line << "\"" << std::endl;
297 | }
298 | }
299 | ```
300 |
301 | The instantiation point will depend on the semantics you are going for. For
302 | example, if you are mocking, you might only instantiate the instrumentation in
303 | unit tests, and use the default behavior in the released code.
304 |
305 | ## Contributing
306 |
307 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for guidelines. All contributions must
308 | follow the Google C++ style guide at
309 | https://google.github.io/styleguide/cppguide.html. **Contributions should be
310 | made to the [`current`][current] branch, which will periodically be merged with
311 | [`master`][master] after a more thorough review.**
312 |
313 | [google/capture-thread]: https://github.com/google/capture-thread
314 | [master]: https://github.com/google/capture-thread/tree/master
315 | [current]: https://github.com/google/capture-thread/tree/current
316 | [instrumentation]: https://en.wikipedia.org/wiki/Instrumentation_(computer_programming)
317 |
--------------------------------------------------------------------------------
/archive.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname "$0")" || exit
4 |
5 | date=${1-now}
6 |
7 | filename="$(basename $(pwd))-$(date +%Y%m%d -d "$date").tar.gz"
8 |
9 | git ls-files | tar -cvvzf "$filename" -T -
10 |
--------------------------------------------------------------------------------
/common/callback-queue.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include "callback-queue.h"
20 |
21 | namespace capture_thread {
22 | namespace testing {
23 |
24 | void CallbackQueue::Push(std::function callback) {
25 | std::lock_guard lock(queue_lock_);
26 | if (!terminated_) {
27 | queue_.push(std::move(callback));
28 | condition_.notify_all();
29 | }
30 | }
31 |
32 | bool CallbackQueue::PopAndCall() {
33 | std::unique_lock lock(queue_lock_);
34 | while (!terminated_ && (!active_ || queue_.empty())) {
35 | condition_.wait(lock);
36 | }
37 | if (terminated_) {
38 | return false;
39 | } else {
40 | const auto callback = queue_.front();
41 | ++pending_;
42 | queue_.pop();
43 | lock.unlock();
44 | if (callback) {
45 | callback();
46 | }
47 | lock.lock();
48 | --pending_;
49 | condition_.notify_all();
50 | return true;
51 | }
52 | }
53 |
54 | void CallbackQueue::WaitUntilEmpty() {
55 | std::unique_lock lock(queue_lock_);
56 | while (!terminated_ && (!queue_.empty() || pending_ > 0)) {
57 | condition_.wait(lock);
58 | }
59 | }
60 |
61 | void CallbackQueue::Terminate() {
62 | std::lock_guard lock(queue_lock_);
63 | terminated_ = true;
64 | condition_.notify_all();
65 | }
66 |
67 | void CallbackQueue::Activate() {
68 | std::lock_guard lock(queue_lock_);
69 | active_ = true;
70 | condition_.notify_all();
71 | }
72 |
73 | } // namespace testing
74 | } // namespace capture_thread
75 |
--------------------------------------------------------------------------------
/common/callback-queue.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef CALLBACK_QUEUE_H_
20 | #define CALLBACK_QUEUE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | namespace capture_thread {
28 | namespace testing {
29 |
30 | // Queues and executes callbacks.
31 | class CallbackQueue {
32 | public:
33 | // If active is false, constructs the queue in a paused state. Use Activate()
34 | // to start the queue.
35 | CallbackQueue(bool active = true) : active_(active) {}
36 |
37 | void Push(std::function callback);
38 |
39 | // Blocks for a callback to execute, then pops and executes it. Does not block
40 | // other callers while executing the callback. Returns false if the queue has
41 | // been terminated.
42 | bool PopAndCall();
43 |
44 | void WaitUntilEmpty();
45 | void Activate();
46 |
47 | // Informs all callers to stop using the queue. No further callbacks will be
48 | // executed, even if the queue is non-empty. Makes Push a no-op.
49 | void Terminate();
50 |
51 | private:
52 | std::mutex queue_lock_;
53 | std::condition_variable condition_;
54 | int pending_ = 0;
55 | bool terminated_ = false;
56 | bool active_;
57 | std::queue> queue_;
58 | };
59 |
60 | } // namespace testing
61 | } // namespace capture_thread
62 |
63 | #endif // CALLBACK_QUEUE_H_
64 |
--------------------------------------------------------------------------------
/common/log-text.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include "log-text.h"
20 |
21 | namespace capture_thread {
22 | namespace testing {
23 |
24 | // static
25 | void LogText::Log(std::string line) {
26 | if (GetCurrent()) {
27 | GetCurrent()->LogLine(std::move(line));
28 | }
29 | }
30 |
31 | std::list LogTextMultiThread::GetLines() {
32 | std::lock_guard lock(data_lock_);
33 | return lines_;
34 | }
35 |
36 | void LogTextMultiThread::LogLine(std::string line) {
37 | std::lock_guard lock(data_lock_);
38 | lines_.emplace_back(std::move(line));
39 | }
40 |
41 | } // namespace testing
42 | } // namespace capture_thread
43 |
--------------------------------------------------------------------------------
/common/log-text.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef LOG_TEXT_H_
20 | #define LOG_TEXT_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | #include "thread-capture.h"
27 | #include "thread-crosser.h"
28 |
29 | namespace capture_thread {
30 | namespace testing {
31 |
32 | // Captures text log entries.
33 | class LogText : public ThreadCapture {
34 | public:
35 | static void Log(std::string line);
36 |
37 | // Allows callers to manually cross threads.
38 | using ThreadCapture::ThreadBridge;
39 | using ThreadCapture::CrossThreads;
40 |
41 | protected:
42 | LogText() = default;
43 | virtual ~LogText() = default;
44 |
45 | virtual void LogLine(std::string line) = 0;
46 | };
47 |
48 | // Captures text log entries, without automatic thread crossing.
49 | class LogTextSingleThread : public LogText {
50 | public:
51 | LogTextSingleThread() : capture_to_(this) {}
52 |
53 | const std::list& GetLines() { return lines_; }
54 |
55 | private:
56 | void LogLine(std::string line) override {
57 | lines_.emplace_back(std::move(line));
58 | }
59 |
60 | std::list lines_;
61 | const ScopedCapture capture_to_;
62 | };
63 |
64 | // Captures text log entries, with automatic thread crossing.
65 | class LogTextMultiThread : public LogText {
66 | public:
67 | LogTextMultiThread() : cross_and_capture_to_(this) {}
68 |
69 | std::list GetLines();
70 |
71 | private:
72 | void LogLine(std::string line) override;
73 |
74 | std::mutex data_lock_;
75 | std::list lines_;
76 | const AutoThreadCrosser cross_and_capture_to_;
77 | };
78 |
79 | } // namespace testing
80 | } // namespace capture_thread
81 |
82 | #endif // LOG_TEXT_H_
83 |
--------------------------------------------------------------------------------
/common/log-values.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include "log-values.h"
20 |
21 | namespace capture_thread {
22 | namespace testing {
23 |
24 | // static
25 | void LogValues::Count(int count) {
26 | if (GetCurrent()) {
27 | GetCurrent()->LogCount(count);
28 | }
29 | }
30 |
31 | std::list LogValuesMultiThread::GetCounts() {
32 | std::lock_guard lock(data_lock_);
33 | return counts_;
34 | }
35 |
36 | void LogValuesMultiThread::LogCount(int count) {
37 | std::lock_guard lock(data_lock_);
38 | counts_.emplace_back(count);
39 | }
40 |
41 | } // namespace testing
42 | } // namespace capture_thread
43 |
--------------------------------------------------------------------------------
/common/log-values.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef LOG_VALUES_H_
20 | #define LOG_VALUES_H_
21 |
22 | #include
23 | #include
24 |
25 | #include "thread-capture.h"
26 | #include "thread-crosser.h"
27 |
28 | namespace capture_thread {
29 | namespace testing {
30 |
31 | // Captures numerical log entries.
32 | class LogValues : public ThreadCapture {
33 | public:
34 | static void Count(int count);
35 |
36 | protected:
37 | LogValues() = default;
38 | virtual ~LogValues() = default;
39 |
40 | virtual void LogCount(int count) = 0;
41 | };
42 |
43 | // Captures numerical log entries, without automatic thread crossing.
44 | class LogValuesSingleThread : public LogValues {
45 | public:
46 | LogValuesSingleThread() : capture_to_(this) {}
47 |
48 | const std::list& GetCounts() { return counts_; }
49 |
50 | private:
51 | void LogCount(int count) { counts_.emplace_back(count); }
52 |
53 | std::list counts_;
54 | const ScopedCapture capture_to_;
55 | };
56 |
57 | // Captures numerical log entries, with automatic thread crossing.
58 | class LogValuesMultiThread : public LogValues {
59 | public:
60 | LogValuesMultiThread() : cross_and_capture_to_(this) {}
61 |
62 | std::list GetCounts();
63 |
64 | private:
65 | void LogCount(int count);
66 |
67 | std::mutex data_lock_;
68 | std::list counts_;
69 | const AutoThreadCrosser cross_and_capture_to_;
70 | };
71 |
72 | } // namespace testing
73 | } // namespace capture_thread
74 |
75 | #endif // LOG_VALUES_H_
76 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Logging Demo
2 |
3 | This directory contains a small demonstration of a logging system that makes use
4 | of user-specified scope tags for tracing. For testing purposes, it also contains
5 | logic to capture logged content. See [`main.cc`](main.cc) for the main program
6 | and [`test.cc`](test.cc) for the corresponding unit test.
7 |
--------------------------------------------------------------------------------
/demo/logging.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include
20 |
21 | #include "logging.h"
22 | #include "tracing.h"
23 |
24 | namespace demo {
25 |
26 | Logging::LogLine::LogLine() : capture_(GetCurrent()) {
27 | const std::string context = Tracing::GetContext();
28 | if (!context.empty()) {
29 | *this << context << ": ";
30 | } else {
31 | *this << "(unknown context): ";
32 | }
33 | }
34 |
35 | Logging::LogLine::~LogLine() {
36 | *this << '\n';
37 | if (capture_) {
38 | capture_->AppendLine(output_.str());
39 | } else {
40 | DefaultAppendLine(output_.str());
41 | }
42 | }
43 |
44 | // static
45 | void Logging::DefaultAppendLine(const std::string& line) { std::cerr << line; }
46 |
47 | std::list CaptureLogging::CopyLines() {
48 | std::lock_guard lock(data_lock_);
49 | return lines_;
50 | }
51 |
52 | void CaptureLogging::AppendLine(const std::string& line) {
53 | {
54 | std::lock_guard lock(data_lock_);
55 | lines_.emplace_back(line);
56 | }
57 | DefaultAppendLine(line);
58 | }
59 |
60 | } // namespace demo
61 |
--------------------------------------------------------------------------------
/demo/logging.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef LOGGING_H_
20 | #define LOGGING_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "thread-capture.h"
28 |
29 | namespace demo {
30 |
31 | // Provides a text-logging mechanism. By default sends data to stderr. Use
32 | // CaptureLogging to capture logged data.
33 | class Logging : public capture_thread::ThreadCapture {
34 | public:
35 | // Formats and logs a line. Operates as a std::ostream. For example:
36 | //
37 | // Logging::LogLine() << "Log message.";
38 | class LogLine {
39 | public:
40 | LogLine();
41 | ~LogLine();
42 |
43 | template
44 | LogLine& operator<<(Type value) {
45 | static_cast(output_) << value;
46 | return *this;
47 | }
48 |
49 | private:
50 | Logging* const capture_;
51 | std::ostringstream output_;
52 | };
53 |
54 | protected:
55 | Logging() = default;
56 | virtual ~Logging() = default;
57 |
58 | virtual void AppendLine(const std::string& line) = 0;
59 |
60 | static void DefaultAppendLine(const std::string& line);
61 | };
62 |
63 | // Captures lines logged with Logging while in scope.
64 | class CaptureLogging : public Logging {
65 | public:
66 | CaptureLogging() : cross_and_capture_to_(this) {}
67 |
68 | // Returns a copy of all lines captured.
69 | std::list CopyLines();
70 |
71 | protected:
72 | void AppendLine(const std::string& line) override;
73 |
74 | private:
75 | std::mutex data_lock_;
76 | std::list lines_;
77 | const AutoThreadCrosser cross_and_capture_to_;
78 | };
79 |
80 | } // namespace demo
81 |
82 | #endif // LOGGING_H_
83 |
--------------------------------------------------------------------------------
/demo/main.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "callback-queue.h"
27 | #include "logging.h"
28 | #include "tracing.h"
29 |
30 | using capture_thread::ThreadCrosser;
31 | using capture_thread::testing::CallbackQueue;
32 | using demo::CaptureLogging;
33 | using demo::Formatter;
34 | using demo::Logging;
35 | using demo::Tracing;
36 |
37 | namespace {
38 |
39 | // A unit of computation that can be parallelized.
40 | void Compute(int value) {
41 | Tracing context(__func__);
42 | Logging::LogLine() << "Computing " << value;
43 | std::this_thread::sleep_for(std::chrono::milliseconds(value));
44 | }
45 |
46 | // A worker thread that executes whatever is in the queue.
47 | void QueueThread(int index, CallbackQueue* queue) {
48 | assert(queue);
49 | Tracing context((Formatter() << __func__ << '[' << index << ']').String());
50 | Logging::LogLine() << "Thread starting";
51 | while (queue->PopAndCall()) {
52 | }
53 | Logging::LogLine() << "Thread stopping";
54 | }
55 |
56 | // Worker threads don't need to be wrapped with ThreadCrosser::WrapCall if they
57 | // are just executing callbacks from a queue, but it can be helpful, e.g., for
58 | // tracing purposes.
59 | std::unique_ptr NewThread(std::function callback) {
60 | return std::unique_ptr(
61 | new std::thread(ThreadCrosser::WrapCall(std::move(callback))));
62 | }
63 |
64 | } // namespace
65 |
66 | int main() {
67 | Tracing context(__func__);
68 |
69 | // Queue for passing work from the main thread to the worker threads. Created
70 | // in a paused state.
71 | CallbackQueue queue(false /*active*/);
72 |
73 | for (int i = 0; i < 10; ++i) {
74 | // One callback per unit of work that can be parallelized.
75 | queue.Push(ThreadCrosser::WrapCall(std::bind(&Compute, i)));
76 | }
77 |
78 | std::list> threads;
79 | for (int i = 0; i < 3; ++i) {
80 | // An arbitrary number of threads.
81 | threads.emplace_back(NewThread(std::bind(&QueueThread, i, &queue)));
82 | }
83 |
84 | // Perform the computations.
85 |
86 | queue.Activate();
87 | queue.WaitUntilEmpty();
88 | queue.Terminate();
89 |
90 | for (const auto& thread : threads) {
91 | thread->join();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/demo/test.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include
20 |
21 | #include
22 | #include
23 |
24 | #include "callback-queue.h"
25 | #include "logging.h"
26 | #include "tracing.h"
27 |
28 | using capture_thread::ThreadCrosser;
29 | using capture_thread::testing::CallbackQueue;
30 | using testing::ElementsAre;
31 |
32 | namespace demo {
33 |
34 | TEST(DemoTest, IntegrationTest) {
35 | CaptureLogging logger;
36 | Tracing context("test");
37 | CallbackQueue queue;
38 |
39 | for (int i = 0; i < 3; ++i) {
40 | queue.Push(ThreadCrosser::WrapCall([i] {
41 | Tracing context("thread");
42 | Logging::LogLine() << "call " << i;
43 | }));
44 | }
45 |
46 | std::thread worker(ThreadCrosser::WrapCall([&queue] {
47 | Tracing context("worker");
48 | Logging::LogLine() << "start";
49 | while (queue.PopAndCall()) {
50 | }
51 | Logging::LogLine() << "stop";
52 | }));
53 |
54 | queue.WaitUntilEmpty();
55 | queue.Terminate();
56 |
57 | worker.join();
58 |
59 | EXPECT_THAT(logger.CopyLines(),
60 | ElementsAre("test:worker: start\n", "test:thread: call 0\n",
61 | "test:thread: call 1\n", "test:thread: call 2\n",
62 | "test:worker: stop\n"));
63 | }
64 |
65 | } // namespace demo
66 |
67 | int main(int argc, char *argv[]) {
68 | testing::InitGoogleTest(&argc, argv);
69 | return RUN_ALL_TESTS();
70 | }
71 |
--------------------------------------------------------------------------------
/demo/tracing.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #include
20 | #include
21 |
22 | #include "tracing.h"
23 |
24 | namespace demo {
25 |
26 | // static
27 | std::string Tracing::GetContext() {
28 | Formatter formatter;
29 | ReverseTrace(GetCurrent(), &formatter);
30 | return formatter.String();
31 | }
32 |
33 | // static
34 | void Tracing::ReverseTrace(const Tracing* tracer, Formatter* formatter) {
35 | assert(formatter);
36 | if (tracer) {
37 | const auto previous = tracer->cross_and_capture_to_.Previous();
38 | ReverseTrace(previous, formatter);
39 | if (previous) {
40 | *formatter << ":";
41 | }
42 | *formatter << tracer->name();
43 | }
44 | }
45 |
46 | } // namespace demo
47 |
--------------------------------------------------------------------------------
/demo/tracing.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef TRACING_H_
20 | #define TRACING_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "thread-capture.h"
28 |
29 | namespace demo {
30 |
31 | // Formats a string much like std::ostringstream, but returns a type that can
32 | // be directly converted to a string. For example:
33 | //
34 | // std::string message = (Formatter() << "number: " << 1).String();
35 | class Formatter {
36 | public:
37 | std::string String() const { return output_.str(); }
38 |
39 | template
40 | Formatter& operator<<(Type value) {
41 | static_cast(output_) << value;
42 | return *this;
43 | }
44 |
45 | private:
46 | std::ostringstream output_;
47 | };
48 |
49 | // Adds a named tracing scope while the object is in scope.
50 | class Tracing : public capture_thread::ThreadCapture {
51 | public:
52 | explicit Tracing(std::string name)
53 | : name_(std::move(name)), cross_and_capture_to_(this) {}
54 |
55 | // Returns the current context as a ":"-joined concatenation of the names of
56 | // the current Tracing objects in scope. For example:
57 | //
58 | // Tracing scope1("scope1");
59 | // Tracing scope2("scope2");
60 | // std::cerr << Tracing::GetContext(); // "scope1:scope2"
61 | static std::string GetContext();
62 |
63 | private:
64 | const std::string& name() const { return name_; }
65 |
66 | static void ReverseTrace(const Tracing* tracer, Formatter* formatter);
67 |
68 | const std::string name_;
69 | const AutoThreadCrosser cross_and_capture_to_;
70 | };
71 |
72 | } // namespace demo
73 |
74 | #endif // TRACING_H_
75 |
--------------------------------------------------------------------------------
/example/async.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This example demonstrates how you might implement asynchronous reporting,
20 | // e.g., in cases where the reporting process is too costly to be done
21 | // synchronously. The callers of the reporting API aren't blocked by the sending
22 | // of the report, which is handled separately by a dedicated reporting thread.
23 |
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 |
36 | #include "thread-capture.h"
37 |
38 | using capture_thread::ThreadCapture;
39 |
40 | // Captures reports for external reporting. Something like this might be used to
41 | // report usage, access, or performance information to an external resource,
42 | // e.g., remote storage or an auditing server.
43 | class Reporter : public ThreadCapture {
44 | public:
45 | // Arbitrary content to report, which would generally be more structured.
46 | using Report = std::list;
47 |
48 | // Sends the report, if a reporter is in scope; otherwise does nothing. Note
49 | // that noting about this class requires reporting to be asynchronous.
50 | static void Send(Report report) {
51 | if (GetCurrent()) {
52 | GetCurrent()->SendReport(std::move(report));
53 | }
54 | }
55 |
56 | protected:
57 | Reporter() = default;
58 | virtual ~Reporter() = default;
59 |
60 | // Actually performs the reporting.
61 | virtual void SendReport(Report report) = 0;
62 | };
63 |
64 | // Captures reports, but writes them asynchronously to avoid blocking.
65 | class ReportAsync : public Reporter {
66 | public:
67 | ReportAsync() : cross_and_capture_to_(this) {}
68 |
69 | ~ReportAsync() {
70 | {
71 | std::lock_guard lock(queue_lock_);
72 | terminated_ = true;
73 | queue_wait_.notify_all();
74 | }
75 | if (reporter_thread_) {
76 | std::cerr << "Waiting for reporter thread to finish..." << std::endl;
77 | reporter_thread_->join();
78 | std::cerr << "Reporter thread finished." << std::endl;
79 | }
80 | }
81 |
82 | protected:
83 | void SendReport(Report report) override {
84 | std::lock_guard lock(queue_lock_);
85 | if (!terminated_) {
86 | if (!reporter_thread_) {
87 | StartThread();
88 | }
89 | // This assumes that the rate at which SendReport is called does not
90 | // exceed the rate at which reports can be written, on average. Since that
91 | // isn't always a safe assumption, it might be helpful to limit the queue
92 | // size and either wait or drop reports if that limit is exceeded here.
93 | queue_.push(std::move(report));
94 | queue_wait_.notify_all();
95 | }
96 | }
97 |
98 | private:
99 | // The thread is started lazily.
100 | void StartThread() {
101 | reporter_thread_.reset(
102 | new std::thread(std::bind(&ReportAsync::ReporterThread, this)));
103 | }
104 |
105 | // Monitors the queue, and bulk-writes new entries whenever possible.
106 | void ReporterThread() {
107 | bool terminated = false;
108 | std::queue working_queue;
109 | while (!terminated) {
110 | std::unique_lock lock(queue_lock_);
111 | // Wait for new entries to report.
112 | while (!(terminated = terminated_) && queue_.empty()) {
113 | queue_wait_.wait(lock);
114 | }
115 | assert(working_queue.empty());
116 | // Grab them all at once so that the lock can be released, thereby
117 | // minimizing the amount of time callers are blocked.
118 | working_queue.swap(queue_);
119 | lock.unlock();
120 | // Process the entries.
121 | while (!working_queue.empty()) {
122 | WriteToStorage(working_queue.front());
123 | working_queue.pop();
124 | }
125 | }
126 | }
127 |
128 | void WriteToStorage(const Report& report) {
129 | // Simulates an expensive write operation.
130 | std::this_thread::sleep_for(std::chrono::milliseconds(100));
131 | for (const auto& entry : report) {
132 | std::cout << entry << std::endl;
133 | }
134 | }
135 |
136 | bool terminated_ = false;
137 | std::mutex queue_lock_;
138 | std::condition_variable queue_wait_;
139 | std::queue queue_;
140 | std::unique_ptr reporter_thread_;
141 | const AutoThreadCrosser cross_and_capture_to_;
142 | };
143 |
144 | // Simulates a service that process external requests.
145 | class DataService {
146 | public:
147 | DataService() { std::cerr << "Starting DataService." << std::endl; }
148 |
149 | ~DataService() { std::cerr << "Stopping DataService." << std::endl; }
150 |
151 | // An arbitrary latency-sensitive operation that should not be blocked by the
152 | // reporting process.
153 | void AccessSomeResources(int resource_number) {
154 | Reporter::Report report;
155 | std::ostringstream formatted;
156 | formatted << "resource accessed: " << resource_number;
157 | report.emplace_back(formatted.str());
158 | Reporter::Send(std::move(report));
159 | }
160 | };
161 |
162 | int main() {
163 | // Enable reporting globally.
164 | ReportAsync reporter;
165 |
166 | DataService service;
167 | for (int i = 0; i < 10; ++i) {
168 | // Simulates a latency-sensitive request to the service.
169 | service.AccessSomeResources(i);
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/example/connection.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This example demonstrates sharing a client connection (e.g., a socket) within
20 | // the thread so that it doesn't need to be passed around to all functions that
21 | // require access to it. Keep in mind that this design pattern could be
22 | // problematic if multiple threads need to access the client. (This essentially
23 | // turns the current thread into a singleton.)
24 |
25 | #include
26 | #include
27 | #include
28 |
29 | #include "thread-capture.h"
30 |
31 | using capture_thread::ThreadCapture;
32 |
33 | // Serves as a singleton client connection within the thread. The default
34 | // behavior is to act as a disconnected client.
35 | class ClientConnection : public ThreadCapture {
36 | public:
37 | static bool IsActive() {
38 | if (GetCurrent()) {
39 | return GetCurrent()->CheckConnection();
40 | } else {
41 | return false;
42 | }
43 | }
44 |
45 | static bool Send(const std::string& message) {
46 | if (GetCurrent()) {
47 | return GetCurrent()->SendMessage(message);
48 | } else {
49 | return false;
50 | }
51 | }
52 |
53 | static bool Receive(std::string* message) {
54 | if (GetCurrent()) {
55 | return GetCurrent()->ReceiveMessage(message);
56 | } else {
57 | return false;
58 | }
59 | }
60 |
61 | protected:
62 | virtual bool CheckConnection() = 0;
63 | virtual bool SendMessage(const std::string& message) = 0;
64 | virtual bool ReceiveMessage(std::string* message) = 0;
65 | };
66 |
67 | // Provides client functionality from stdin and stdout while in scope.
68 | class ClientFromStandardStreams : public ClientConnection {
69 | public:
70 | ClientFromStandardStreams() : capture_to_(this) {
71 | std::cerr << "Opening ClientFromStandardStreams connection." << std::endl;
72 | }
73 |
74 | ~ClientFromStandardStreams() {
75 | std::cerr << "Closing ClientFromStandardStreams connection." << std::endl;
76 | }
77 |
78 | protected:
79 | bool CheckConnection() { return !!std::cout && !!std::cin; }
80 |
81 | bool SendMessage(const std::string& message) {
82 | return !!(std::cout << "*** Message: " << message << " ***" << std::endl);
83 | }
84 |
85 | bool ReceiveMessage(std::string* message) {
86 | assert(message);
87 | return !!std::getline(std::cin, *message, '\n');
88 | }
89 |
90 | private:
91 | const ScopedCapture capture_to_;
92 | };
93 |
94 | // This is a helper function to send to and recieve from the client.
95 | bool PromptForInfo(const std::string& prompt, std::string* response) {
96 | assert(response);
97 | while (ClientConnection::IsActive()) {
98 | if (!ClientConnection::Send(prompt)) {
99 | return false;
100 | }
101 | if (!ClientConnection::Receive(response)) {
102 | return false;
103 | }
104 | if (!response->empty()) {
105 | return true;
106 | }
107 | }
108 | return false;
109 | }
110 |
111 | // This is the main routine to handle the lifetime of the connection.
112 | void HandleConnection() {
113 | std::string name;
114 | if (!PromptForInfo("What is your name?", &name)) {
115 | std::cerr << "Connection closed without providing a name." << std::endl;
116 | } else {
117 | ClientConnection::Send("Your name is supposedly \"" + name + "\".");
118 | }
119 | }
120 |
121 | int main() {
122 | // While in scope, client will serve as the ClientConnection for all calls.
123 | ClientFromStandardStreams client;
124 | HandleConnection();
125 | }
126 |
--------------------------------------------------------------------------------
/example/function.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is an example of wrapping a general function that takes arguments and
20 | // has a non-void return.
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | #include "thread-capture.h"
31 | #include "thread-crosser.h"
32 |
33 | using capture_thread::ThreadCapture;
34 | using capture_thread::ThreadCrosser;
35 |
36 | // (See threaded.cc for comments.)
37 | class LogText : public ThreadCapture {
38 | public:
39 | LogText() : cross_and_capture_to_(this) {}
40 |
41 | static void Log(std::string line) {
42 | if (GetCurrent()) {
43 | GetCurrent()->LogLine(std::move(line));
44 | } else {
45 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl;
46 | }
47 | }
48 |
49 | std::list CopyLines() {
50 | std::lock_guard lock(data_lock_);
51 | return lines_;
52 | }
53 |
54 | private:
55 | void LogLine(std::string line) {
56 | std::lock_guard lock(data_lock_);
57 | lines_.emplace_back(std::move(line));
58 | }
59 |
60 | std::mutex data_lock_;
61 | std::list lines_;
62 | const AutoThreadCrosser cross_and_capture_to_;
63 | };
64 |
65 | // A function whose calls we want to keep track of.
66 | bool LessThan(const std::string& left, const std::string& right) {
67 | if (left < right) {
68 | LogText::Log("\"" + left + "\" < \"" + right + "\"");
69 | return true;
70 | } else {
71 | LogText::Log("\"" + left + "\" >= \"" + right + "\"");
72 | return false;
73 | }
74 | }
75 |
76 | // This simulates a sorting function that might use multiple threads, as a
77 | // hidden implementation detail.
78 | template
79 | void ThreadedSort(Iterator begin, Iterator end, const Compare& compare) {
80 | std::thread worker(
81 | [&begin, &end, &compare] { std::sort(begin, end, compare); });
82 | worker.join();
83 | }
84 |
85 | int main() {
86 | const std::vector words{
87 | "this", "is", "a", "list", "of", "words", "to", "sort",
88 | };
89 |
90 | std::vector words_copy;
91 |
92 | // Captures log entries while in scope.
93 | LogText logger;
94 |
95 | words_copy = words;
96 | // Here we don't know for sure if LessThan is going to be called in this
97 | // thread or not.
98 | ThreadedSort(words_copy.begin(), words_copy.end(), &LessThan);
99 |
100 | words_copy = words;
101 | // ThreadCrosser::WrapFunction ensures that the scope is captured, regardless
102 | // of how ThreadedSort splits up the process.
103 | ThreadedSort(words_copy.begin(), words_copy.end(),
104 | ThreadCrosser::WrapFunction(&LessThan));
105 |
106 | for (const auto& line : logger.CopyLines()) {
107 | std::cerr << "Captured: " << line << std::endl;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/example/inherit.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // ***Although this example is technically safe, its use is a potential
20 | // indicator of bad design, due to scope ambiguity.*** As such, please treat
21 | // this example as experimental, and seriously reconsider your design before
22 | // using this design pattern.
23 |
24 | // This is a proof-of-concept example of how you might inherit the object that
25 | // is currently being captured to. For example your API might have multiple
26 | // entry points that delegate to each other, but that requires exactly one
27 | // report per call made to the API.
28 |
29 | #include
30 | #include
31 | #include
32 |
33 | #include "thread-capture.h"
34 |
35 | using capture_thread::ThreadCapture;
36 |
37 | // (See simple.cc for general comments.)
38 | class LogText : public ThreadCapture {
39 | public:
40 | enum class InheritType {
41 | kNew, // A new logger should be created.
42 | kInherit, // Logging should be delegated to the existing logger.
43 | };
44 |
45 | LogText(InheritType type) : type_(type), capture_to_(this) {}
46 |
47 | static void Log(std::string line) {
48 | if (GetCurrent()) {
49 | GetCurrent()->LogLine(std::move(line));
50 | }
51 | }
52 |
53 | const std::list& GetLines() const { return Delegate()->lines_; }
54 |
55 | private:
56 | void LogLine(std::string line) {
57 | Delegate()->lines_.emplace_back(std::move(line));
58 | }
59 |
60 | LogText* Delegate() {
61 | if (type_ == InheritType::kInherit && capture_to_.Previous()) {
62 | return capture_to_.Previous()->Delegate();
63 | }
64 | return this;
65 | }
66 |
67 | const LogText* Delegate() const {
68 | if (type_ == InheritType::kInherit && capture_to_.Previous()) {
69 | return capture_to_.Previous()->Delegate();
70 | }
71 | return this;
72 | }
73 |
74 | std::list lines_;
75 | const InheritType type_;
76 | const ScopedCapture capture_to_;
77 | };
78 |
79 | void QueryHandler1(const std::string& query) {
80 | LogText logger(LogText::InheritType::kInherit);
81 | LogText::Log("QueryHandler1 called: " + query);
82 | for (const auto& line : logger.GetLines()) {
83 | std::cerr << "Available from QueryHandler1: \"" << line << "\""
84 | << std::endl;
85 | }
86 | }
87 |
88 | void QueryHandler2(const std::string& query) {
89 | LogText logger(LogText::InheritType::kNew);
90 | LogText::Log("QueryHandler2 called: " + query);
91 | QueryHandler1(query + "!!!");
92 | for (const auto& line : logger.GetLines()) {
93 | std::cerr << "Available from QueryHandler2: \"" << line << "\""
94 | << std::endl;
95 | }
96 | }
97 |
98 | int main() {
99 | std::cerr << "Inherited logger used:" << std::endl;
100 | QueryHandler2("query");
101 | std::cerr << "New logger used:" << std::endl;
102 | QueryHandler1("another query");
103 | }
104 |
--------------------------------------------------------------------------------
/example/limit.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a proof-of-concept example of how the framework could be used to
20 | // limit the amount of effort expended by a computation. We do not expect this
21 | // to be a widely-used use-case.
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include "thread-capture.h"
30 |
31 | using capture_thread::ThreadCapture;
32 |
33 | // The base class just provides an interface for tracking/reporting resources.
34 | class LimitEffort : public ThreadCapture {
35 | public:
36 | static bool ShouldContinue() {
37 | return GetCurrent() ? !GetCurrent()->LimitReached() : true;
38 | }
39 |
40 | static void Consume(int amount) {
41 | if (GetCurrent()) {
42 | GetCurrent()->DecrementResources(amount);
43 | }
44 | }
45 |
46 | protected:
47 | LimitEffort() = default;
48 | virtual ~LimitEffort() = default;
49 |
50 | virtual bool LimitReached() = 0;
51 | virtual void DecrementResources(int amount) {}
52 |
53 | // Don't add a ScopedCapture member to the base class, because that would make
54 | // it impossible to write an implementation that doesn't automatically capture
55 | // logging.
56 | };
57 |
58 | // This implementation imposes a time-based limit.
59 | class LimitTime : public LimitEffort {
60 | public:
61 | explicit LimitTime(double seconds)
62 | : seconds_(seconds),
63 | start_time_(std::chrono::high_resolution_clock::now()),
64 | capture_to_(this) {}
65 |
66 | double ResourcesConsumed() const {
67 | const auto current_time = std::chrono::high_resolution_clock::now();
68 | return std::chrono::duration_cast(current_time -
69 | start_time_)
70 | .count() /
71 | 1000000.0;
72 | }
73 |
74 | protected:
75 | bool LimitReached() override { return ResourcesConsumed() > seconds_; }
76 |
77 | private:
78 | const double seconds_;
79 | const std::chrono::high_resolution_clock::time_point start_time_;
80 | const ScopedCapture capture_to_;
81 | };
82 |
83 | // This implementation imposes a counter-based limit.
84 | class LimitCount : public LimitEffort {
85 | public:
86 | explicit LimitCount(int count) : count_(count), capture_to_(this) {}
87 |
88 | int ResourcesRemaining() const { return count_; }
89 |
90 | protected:
91 | bool LimitReached() override { return ResourcesRemaining() <= 0; }
92 |
93 | void DecrementResources(int amount) override { count_ -= amount; }
94 |
95 | private:
96 | int count_;
97 | const ScopedCapture capture_to_;
98 | };
99 |
100 | // Performs the actual work. It's important to remember that this function, and
101 | // its callers all the way up, need to do something reasonable when stopping
102 | // early. In this case, we're just counting. In other situations, you might have
103 | // a partial result of a computation, which itself might be an acceptable result
104 | // (e.g., an aborted search operation), or it might not be (e.g., a partially-
105 | // completed matrix multiplication.)
106 | void ResourceConsumingWorker() {
107 | for (int i = 0; i < 100 && LimitEffort::ShouldContinue(); ++i) {
108 | std::cerr << i << ' ';
109 | LimitEffort::Consume(i);
110 | std::this_thread::sleep_for(std::chrono::milliseconds(i));
111 | }
112 | std::cerr << std::endl;
113 | }
114 |
115 | void ProcessByTime() {
116 | LimitTime limit(1.0);
117 | ResourceConsumingWorker();
118 | std::cerr << "Resources consumed: " << limit.ResourcesConsumed() << std::endl;
119 | }
120 |
121 | void ProcessByCount() {
122 | LimitCount limit(500);
123 | ResourceConsumingWorker();
124 | std::cerr << "Resources remaining: " << limit.ResourcesRemaining()
125 | << std::endl;
126 | }
127 |
128 | int main() {
129 | std::cerr << "Process with time limit..." << std::endl;
130 | ProcessByTime();
131 | std::cerr << "Process with count limit..." << std::endl;
132 | ProcessByCount();
133 | std::cerr << "Process without limit..." << std::endl;
134 | ResourceConsumingWorker();
135 | }
136 |
--------------------------------------------------------------------------------
/example/manual.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is an example of how you can limit the sharing of instrumentation across
20 | // threads to specific points where you explicitly allow it. This is more
21 | // efficient and makes the code easier to follow than automatic sharing, but it
22 | // doesn't scale well if you have multiple types of instrumentation. (See
23 | // threaded.cc for information about *automatic* sharing of instrumentation.)
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 |
31 | #include "thread-capture.h"
32 | #include "thread-crosser.h"
33 |
34 | using capture_thread::ThreadCapture;
35 | using capture_thread::ThreadCrosser;
36 |
37 | // (See simple.cc and threaded.cc for general comments.)
38 | class LogText : public ThreadCapture {
39 | public:
40 | LogText() : capture_to_(this) {}
41 |
42 | // By default, manually crossing threads is disallowed. To enable it for a
43 | // specific class, add the `using` declarations below to make these two
44 | // classes public.
45 | using ThreadCapture::ThreadBridge;
46 | using ThreadCapture::CrossThreads;
47 |
48 | static void Log(std::string line) {
49 | if (GetCurrent()) {
50 | GetCurrent()->LogLine(std::move(line));
51 | } else {
52 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl;
53 | }
54 | }
55 |
56 | std::list CopyLines() {
57 | std::lock_guard lock(data_lock_);
58 | return lines_;
59 | }
60 |
61 | private:
62 | void LogLine(std::string line) {
63 | std::lock_guard lock(data_lock_);
64 | lines_.emplace_back(std::move(line));
65 | }
66 |
67 | std::mutex data_lock_;
68 | std::list lines_;
69 |
70 | // Unlike AutoThreadCrosser, ScopedCapture prevents ThreadCrosser from
71 | // automatically sharing instrumentation across threads.
72 | const ScopedCapture capture_to_;
73 | };
74 |
75 | int main() {
76 | LogText logger;
77 |
78 | // Instrumentation isn't shared by default.
79 | std::thread unlogged_thread1(
80 | [] { LogText::Log("Logging has not been passed on here."); });
81 | unlogged_thread1.join();
82 |
83 | // Since ScopedCapture is used instead of AutoThreadCrosser, ThreadCrosser::
84 | // WrapCall has no effect on the LogText instrumentation.
85 | std::thread unlogged_thread2(ThreadCrosser::WrapCall([] {
86 | LogText::Log("Logging has not been passed on, even with WrapCall.");
87 | }));
88 | unlogged_thread2.join();
89 |
90 | // Use ThreadBridge to create a bridge point. This captures the current scope
91 | // at the point it's instantiated; therefore, order matters!
92 | const LogText::ThreadBridge bridge;
93 | std::thread logged_thread([&bridge] {
94 | // Connect the threads via the bridge with CrossThreads. The ThreadBridge
95 | // must stay in scope in the other thread until the CrossThreads in this
96 | // thread goes out of scope.
97 | LogText::CrossThreads capture(bridge);
98 | LogText::Log("Logging has been manually passed on here.");
99 | });
100 | logged_thread.join();
101 |
102 | for (const auto& line : logger.CopyLines()) {
103 | std::cerr << "Captured: " << line << std::endl;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/example/mock.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This example demonstrates how the framework can be used to mock resources
20 | // that are otherwise handled by non-trivial default functionality.
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include "thread-capture.h"
30 |
31 | using capture_thread::ThreadCapture;
32 |
33 | // Handles the creation of file objects. Unless an instance of an implementation
34 | // is in scope, uses a default file-open operation. Implementations can override
35 | // if/how files are opened.
36 | class FileFactory : public ThreadCapture {
37 | public:
38 | // Returns an open file, or nullptr.
39 | static std::unique_ptr ReadFile(const std::string& filename) {
40 | if (GetCurrent()) {
41 | return GetCurrent()->GetReadStream(filename);
42 | } else {
43 | return std::unique_ptr(new std::ifstream(filename));
44 | }
45 | }
46 |
47 | protected:
48 | FileFactory() = default;
49 | virtual ~FileFactory() = default;
50 |
51 | // Overrides how files are opened.
52 | virtual std::unique_ptr GetReadStream(
53 | const std::string& filename) = 0;
54 | };
55 |
56 | // Replaces the default file-open behavior of FileFactory when in scope.
57 | class FileMocker : public FileFactory {
58 | public:
59 | FileMocker() : capture_to_(this) {}
60 |
61 | // Registers a string-backed mock file that's used in place of an actual file
62 | // on the filesystem.
63 | void MockFile(std::string filename, std::string content) {
64 | files_.emplace(std::move(filename), std::move(content));
65 | }
66 |
67 | protected:
68 | std::unique_ptr GetReadStream(
69 | const std::string& filename) override {
70 | const auto mock = files_.find(filename);
71 | if (mock == files_.end()) {
72 | return nullptr;
73 | } else {
74 | return std::unique_ptr(
75 | new std::istringstream(mock->second));
76 | }
77 | }
78 |
79 | private:
80 | std::unordered_map files_;
81 | const ScopedCapture capture_to_;
82 | };
83 |
84 | // Some arbitrary function whose API we can't change to add mocking capability.
85 | int OpenConfigAndCountWords() {
86 | const auto input = FileFactory::ReadFile("CMakeLists.txt");
87 | if (!input) {
88 | return -1;
89 | } else {
90 | int count = 0;
91 | std::string sink;
92 | while ((*input >> sink)) {
93 | ++count;
94 | }
95 | return count;
96 | }
97 | }
98 |
99 | int main() {
100 | std::cerr << "Word count *without* mock: " << OpenConfigAndCountWords()
101 | << std::endl;
102 |
103 | {
104 | // Only overrides FileFactory::ReadFile while in scope.
105 | FileMocker mocker;
106 |
107 | std::cerr << "Word count with missing file: " << OpenConfigAndCountWords()
108 | << std::endl;
109 |
110 | mocker.MockFile("CMakeLists.txt", "one two three");
111 | std::cerr << "Word count *with* mock: " << OpenConfigAndCountWords()
112 | << std::endl;
113 | }
114 |
115 | std::cerr << "Word count *without* mock: " << OpenConfigAndCountWords()
116 | << std::endl;
117 | }
118 |
--------------------------------------------------------------------------------
/example/multi.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This example demonstrates how ThreadCrosser::WrapCall will automatically
20 | // capture all ThreadCapture instances currently in scope, as long as the latter
21 | // each have an AutoThreadCrosser as a member. No additional work is required
22 | // for crossing threads if you need to use multiple types of ThreadCapture.
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | #include "thread-capture.h"
29 | #include "thread-crosser.h"
30 |
31 | using capture_thread::ThreadCapture;
32 | using capture_thread::ThreadCrosser;
33 |
34 | // Example implementation, 1 of 2.
35 | class LogTypeOne : public ThreadCapture {
36 | public:
37 | explicit LogTypeOne(const std::string& value)
38 | : value_(value), cross_and_capture_to_(this) {}
39 |
40 | static void Show() {
41 | if (GetCurrent()) {
42 | GetCurrent()->ShowValue();
43 | }
44 | }
45 |
46 | private:
47 | void ShowValue() const { std::cout << value_ << std::endl; }
48 |
49 | const std::string value_;
50 | const AutoThreadCrosser cross_and_capture_to_;
51 | };
52 |
53 | // Example implementation, 2 of 2.
54 | class LogTypeTwo : public ThreadCapture {
55 | public:
56 | explicit LogTypeTwo(const std::string& value)
57 | : value_(value), cross_and_capture_to_(this) {}
58 |
59 | static void Show() {
60 | if (GetCurrent()) {
61 | GetCurrent()->ShowValue();
62 | }
63 | }
64 |
65 | private:
66 | void ShowValue() const { std::cout << value_ << std::endl; }
67 |
68 | const std::string value_;
69 | const AutoThreadCrosser cross_and_capture_to_;
70 | };
71 |
72 | void WorkerThread() {
73 | LogTypeOne::Show();
74 | LogTypeTwo::Show();
75 | }
76 |
77 | int main() {
78 | const LogTypeOne superceded_by_type1("should not print");
79 | const LogTypeOne type1("type1 was captured");
80 | const LogTypeTwo type2("type2 was captured");
81 |
82 | // It doesn't matter how many implementations are in scope; all are captured
83 | // with a single call to ThreadCrosser::WrapCall. Only the most-recent of each
84 | // ThreadCapture type will be used! (See threaded.cc.)
85 | std::thread worker1(ThreadCrosser::WrapCall(&WorkerThread));
86 | worker1.join();
87 | }
88 |
--------------------------------------------------------------------------------
/example/paths.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a proof-of-concept example of how the framework could be used to keep
20 | // track of a directory structure in a decentralized way, e.g., in a tool that
21 | // installs a project in a user-specified root directory.
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include "thread-capture.h"
30 |
31 | using capture_thread::ThreadCapture;
32 |
33 | // A helper class for building paths from components.
34 | class PathBuilder {
35 | public:
36 | std::string String() const { return path_.str(); }
37 |
38 | PathBuilder& Add(const std::string& component) {
39 | if (!component.empty()) {
40 | if (component.front() == '/') {
41 | path_.str("");
42 | path_ << component;
43 | } else {
44 | if (!trailing_slash_) {
45 | path_ << '/';
46 | }
47 | path_ << component;
48 | }
49 | trailing_slash_ = component.back() == '/';
50 | }
51 | return *this;
52 | }
53 |
54 | private:
55 | bool trailing_slash_ = true;
56 | std::ostringstream path_;
57 | };
58 |
59 | // Tracks persistent root and local paths.
60 | class Path : public ThreadCapture {
61 | public:
62 | static std::string Root() {
63 | if (GetCurrent()) {
64 | return GetCurrent()->RootPath();
65 | } else {
66 | return "";
67 | }
68 | }
69 |
70 | static std::string Working() {
71 | if (GetCurrent()) {
72 | PathBuilder builder;
73 | builder.Add(Root());
74 | GetCurrent()->AppendLocalPath(&builder);
75 | return builder.String();
76 | } else {
77 | return Root();
78 | }
79 | }
80 |
81 | protected:
82 | virtual std::string RootPath() const = 0;
83 | virtual void AppendLocalPath(PathBuilder* builder) const = 0;
84 |
85 | static std::string DelegateRootPath(const Path& path) {
86 | return path.RootPath();
87 | }
88 |
89 | static void DelegateLocalPath(const Path& path, PathBuilder* builder) {
90 | path.AppendLocalPath(builder);
91 | }
92 | };
93 |
94 | // Sets the root path, which persists while the instance is in scope.
95 | class InRootPath : public Path {
96 | public:
97 | InRootPath(const std::string& root_path)
98 | : root_path_(root_path), capture_to_(this) {}
99 |
100 | protected:
101 | std::string RootPath() const override { return root_path_; }
102 |
103 | void AppendLocalPath(PathBuilder* builder) const override {
104 | const auto previous = capture_to_.Previous();
105 | if (previous) {
106 | DelegateLocalPath(*previous, builder);
107 | }
108 | }
109 |
110 | private:
111 | const std::string root_path_;
112 | const ScopedCapture capture_to_;
113 | };
114 |
115 | // Sets the local path relative to the current root + local path.
116 | class InLocalPath : public Path {
117 | public:
118 | InLocalPath(const std::string& local_path)
119 | : local_path_(local_path), capture_to_(this) {}
120 |
121 | protected:
122 | std::string RootPath() const override {
123 | const auto previous = capture_to_.Previous();
124 | return previous ? DelegateRootPath(*previous) : "";
125 | }
126 |
127 | void AppendLocalPath(PathBuilder* builder) const override {
128 | assert(builder);
129 | const auto previous = capture_to_.Previous();
130 | if (previous) {
131 | DelegateLocalPath(*previous, builder);
132 | }
133 | builder->Add(local_path_);
134 | }
135 |
136 | private:
137 | const std::string local_path_;
138 | const ScopedCapture capture_to_;
139 | };
140 |
141 | // Simulates installing project binaries.
142 | void InstallBin(const std::vector& targets) {
143 | InLocalPath path("bin");
144 | for (const auto& target : targets) {
145 | InLocalPath file(target);
146 | std::cerr << "Installing binary " << Path::Working() << std::endl;
147 | }
148 | }
149 |
150 | // Simulates installing project libraries.
151 | void InstallLib(const std::vector& targets) {
152 | InLocalPath path("lib");
153 | for (const auto& target : targets) {
154 | InLocalPath file(target);
155 | std::cerr << "Installing library " << Path::Working() << std::endl;
156 | }
157 | }
158 |
159 | // Simulates installing the project.
160 | void InstallProjectIn(const std::string& path) {
161 | InRootPath root(path);
162 | std::cerr << "Installing project in " << Path::Root() << std::endl;
163 | InstallBin({"binary1", "binary2"});
164 | InstallLib({"lib1.so", "lib2.so"});
165 | }
166 |
167 | int main() { InstallProjectIn("/usr/local"); }
168 |
--------------------------------------------------------------------------------
/example/simple.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a minimal example of basic usage of the framework. Use this only in
20 | // cases where you do not need (or want) functionality to cross threads. See
21 | // threaded.cc for an example of crossing threads.
22 |
23 | #include
24 | #include
25 | #include
26 |
27 | #include "thread-capture.h"
28 |
29 | using capture_thread::ThreadCapture;
30 |
31 | // Inherit from ThreadCapture, passing the class name as the template argument.
32 | class LogText : public ThreadCapture {
33 | public:
34 | LogText() : capture_to_(this) {}
35 |
36 | // The public *capturing* API is static, and accesses the private API via
37 | // GetCurrent().
38 | static void Log(std::string line) {
39 | if (GetCurrent()) {
40 | GetCurrent()->LogLine(std::move(line));
41 | } else {
42 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl;
43 | }
44 | }
45 |
46 | // The public *accessing* API is non-static, and provides the accumulated
47 | // information in whatever format happens to be useful.
48 | const std::list& GetLines() const { return lines_; }
49 |
50 | private:
51 | // The private API records data when this logger is being captured to.
52 | void LogLine(std::string line) { lines_.emplace_back(std::move(line)); }
53 |
54 | // Accumulated data.
55 | std::list lines_;
56 |
57 | // ScopedCapture ensures that the static API logs to this object when:
58 | // 1. This object is in scope.
59 | // 2. No logger of the *same* type is higher in the current stack.
60 | const ScopedCapture capture_to_;
61 | };
62 |
63 | void NoLogger() { LogText::Log("No logger is in scope."); }
64 |
65 | void LoggedOp1() { LogText::Log("The logger is in scope."); }
66 |
67 | void LoggedOp2() { LogText::Log("It captures all lines."); }
68 |
69 | int main() {
70 | NoLogger();
71 | {
72 | LogText logger;
73 | LoggedOp1();
74 | LoggedOp2();
75 |
76 | for (const auto& line : logger.GetLines()) {
77 | std::cerr << "Captured: " << line << std::endl;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/example/speed.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a simple (and noisy) test of the overhead required for each
20 | // ThreadCapture captured by ThreadCrosser::WrapCall.
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "thread-capture.h"
28 | #include "thread-crosser.h"
29 |
30 | using capture_thread::ThreadCapture;
31 | using capture_thread::ThreadCrosser;
32 |
33 | constexpr int kRepetitions = 5;
34 | constexpr int kMaxWraps = 4;
35 | constexpr int kMaxScopes = 4;
36 | constexpr int kIterations = 1000000;
37 |
38 | class NoOp : public ThreadCapture {
39 | public:
40 | NoOp() : cross_and_capture_to_(this) {}
41 |
42 | private:
43 | const AutoThreadCrosser cross_and_capture_to_;
44 | };
45 |
46 | static std::chrono::duration GetCurrentTime() {
47 | return std::chrono::duration_cast>(
48 | std::chrono::high_resolution_clock::now().time_since_epoch());
49 | }
50 |
51 | void Execute(const std::function& function) {
52 | const auto start_time = GetCurrentTime();
53 | for (int i = 0; i < kIterations; ++i) {
54 | function(i);
55 | }
56 | const auto finish_time = GetCurrentTime();
57 | const double elapsed_ms = 1000. * (finish_time - start_time).count();
58 | std::cout << '\t' << elapsed_ms << '\t' << elapsed_ms / kIterations
59 | << std::endl;
60 | }
61 |
62 | void ExecuteWithWrapping(std::function function, int wraps) {
63 | if (wraps > 0) {
64 | ExecuteWithWrapping(ThreadCrosser::WrapFunction(function), wraps - 1);
65 | } else {
66 | Execute(function);
67 | }
68 | }
69 |
70 | void ExecuteWithScopesAndWrapping(int scopes, int wraps) {
71 | if (scopes > 0) {
72 | NoOp noop;
73 | ExecuteWithScopesAndWrapping(scopes - 1, wraps);
74 | } else {
75 | ExecuteWithWrapping([](int x) { return x; }, wraps);
76 | }
77 | }
78 |
79 | int main() {
80 | for (int i = 0; i < kRepetitions; ++i) {
81 | for (int wraps = 1; wraps < kMaxWraps + 1; ++wraps) {
82 | for (int scopes = 1; scopes < kMaxScopes + 1; ++scopes) {
83 | std::cout << i << '\t' << scopes << '\t' << wraps;
84 | ExecuteWithScopesAndWrapping(scopes, wraps);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/example/threaded.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a minimal example of creating functionality that can cross threads.
20 | // This is in contrast to simple.cc, which is strictly compartmentalized to
21 | // individual threads.
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | #include "thread-capture.h"
30 | #include "thread-crosser.h"
31 |
32 | using capture_thread::ThreadCapture;
33 | using capture_thread::ThreadCrosser;
34 |
35 | // (See simple.cc for comments. The only comments here relate to threading.)
36 | class LogText : public ThreadCapture {
37 | public:
38 | LogText() : cross_and_capture_to_(this) {}
39 |
40 | static void Log(std::string line) {
41 | if (GetCurrent()) {
42 | GetCurrent()->LogLine(std::move(line));
43 | } else {
44 | std::cerr << "*** Not captured: \"" << line << "\" ***" << std::endl;
45 | }
46 | }
47 |
48 | std::list CopyLines() {
49 | std::lock_guard lock(data_lock_);
50 | return lines_;
51 | }
52 |
53 | private:
54 | void LogLine(std::string line) {
55 | std::lock_guard lock(data_lock_);
56 | lines_.emplace_back(std::move(line));
57 | }
58 |
59 | // To support threading, just add mutex protection and an AutoThreadCrosser.
60 | // AutoThreadCrosser ensures that logging is passed on to worker threads, but
61 | // *only* when the thread function is wrapped with ThreadCrosser::WrapCall.
62 |
63 | // Note that if you implement multiple types of ThreadCapture in this way, a
64 | // single call to ThreadCrosser::WrapCall will pass on *all* of them that are
65 | // currently in scope in the current thread. For example, if you're logging
66 | // strings (e.g., this class) and have a separate ThreadCapture to log timing
67 | // that also uses AutoThreadCrosser, a single call to ThreadCrosser::WrapCall
68 | // will pass on both string logging and timing logging.
69 |
70 | std::mutex data_lock_;
71 | std::list lines_;
72 | const AutoThreadCrosser cross_and_capture_to_;
73 | };
74 |
75 | void NoLogger() { LogText::Log("No logger is in scope."); }
76 |
77 | void LoggedOp() { LogText::Log("The logger is in scope."); }
78 |
79 | void LoggedOpInThread() {
80 | LogText::Log("ThreadCrosser::WrapCall passes on logging.");
81 | }
82 |
83 | void UnloggedOpInThread() {
84 | LogText::Log("Logging has not been passed on here.");
85 | }
86 |
87 | int main() {
88 | NoLogger();
89 | {
90 | LogText logger;
91 | LoggedOp();
92 |
93 | std::thread logged_thread(ThreadCrosser::WrapCall(&LoggedOpInThread));
94 | std::thread unlogged_thread(&UnloggedOpInThread);
95 |
96 | logged_thread.join();
97 | unlogged_thread.join();
98 |
99 | for (const auto& line : logger.CopyLines()) {
100 | std::cerr << "Captured: " << line << std::endl;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/example/throttle.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This is a proof-of-concept example of how you might rate-throttle worker
20 | // threads using a shared timer. For example, you might multithread an operation
21 | // that repeatedly polls a resource where each poll operation is comparatively
22 | // slow, but when combined, the threads would exceed some desired rate limit.
23 |
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | #include "thread-capture.h"
34 | #include "thread-crosser.h"
35 |
36 | using capture_thread::ThreadCapture;
37 | using capture_thread::ThreadCrosser;
38 |
39 | // Base class for throttlers that limit the rate of processing.
40 | class RateThrottler : public ThreadCapture {
41 | public:
42 | static void Wait() {
43 | if (GetCurrent()) {
44 | GetCurrent()->WaitForNextEvent();
45 | }
46 | }
47 |
48 | protected:
49 | RateThrottler() = default;
50 | virtual ~RateThrottler() = default;
51 |
52 | virtual void WaitForNextEvent() = 0;
53 | };
54 |
55 | // Limits the processing rate based on a shared internal timer.
56 | class SharedThrottler : public RateThrottler {
57 | public:
58 | explicit SharedThrottler(double seconds_between_events)
59 | : seconds_between_events_(seconds_between_events),
60 | last_time_(GetCurrentTime() - seconds_between_events_),
61 | cross_and_capture_to_(this) {}
62 |
63 | protected:
64 | void WaitForNextEvent() override {
65 | std::lock_guard lock(time_lock_);
66 | const auto current_time = GetCurrentTime();
67 | std::this_thread::sleep_for(seconds_between_events_ -
68 | (current_time - last_time_));
69 | last_time_ = GetCurrentTime();
70 | }
71 |
72 | private:
73 | static std::chrono::duration GetCurrentTime() {
74 | return std::chrono::duration_cast>(
75 | std::chrono::high_resolution_clock::now().time_since_epoch());
76 | }
77 |
78 | const std::chrono::duration seconds_between_events_;
79 | std::mutex time_lock_;
80 | std::chrono::duration last_time_;
81 | const AutoThreadCrosser cross_and_capture_to_;
82 | };
83 |
84 | // This represents a worker thread that is expected to not exceed the rate limit
85 | // on its own, but might exceed that rate in combination with other workers.
86 | void Worker(int number) {
87 | for (int i = 0; i < 5; ++i) {
88 | RateThrottler::Wait();
89 | std::this_thread::sleep_for(std::chrono::milliseconds(125));
90 | std::ostringstream formatted;
91 | formatted << "Thread #" << number << ": " << i << std::endl;
92 | std::cerr << formatted.str();
93 | }
94 | }
95 |
96 | // The work is parallelized because each individual operation is slow.
97 | void Execute() {
98 | std::list> threads;
99 | for (int i = 0; i < 5; ++i) {
100 | threads.emplace_back(
101 | new std::thread(ThreadCrosser::WrapCall(std::bind(&Worker, i))));
102 | }
103 | for (const auto& thread : threads) {
104 | thread->join();
105 | }
106 | }
107 |
108 | int main() {
109 | {
110 | std::cerr << "Using kMean of 100ms" << std::endl;
111 | SharedThrottler throttler(0.1);
112 | Execute();
113 | }
114 | std::cerr << "Without throttling" << std::endl;
115 | Execute();
116 | }
117 |
--------------------------------------------------------------------------------
/example/trace.cc:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | // This example demonstrates how the framework might be used for call tracing,
20 | // by incrementally creating a call stack.
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include "thread-capture.h"
29 |
30 | using capture_thread::ThreadCapture;
31 | using capture_thread::ThreadCrosser;
32 |
33 | // (See threaded.cc for more info about threading.)
34 | class TraceContext : public ThreadCapture {
35 | public:
36 | explicit TraceContext(std::string name)
37 | : name_(std::move(name)), cross_and_capture_to_(this) {}
38 |
39 | static std::vector GetTrace() {
40 | std::vector trace;
41 | if (GetCurrent()) {
42 | GetCurrent()->AppendTrace(&trace);
43 | }
44 | return trace;
45 | }
46 |
47 | private:
48 | // AutoThreadCrosser::Previous provides access to the next frame on the stack.
49 | // this can be used recursively to create a trace back to the first frame in
50 | // the current scope.
51 |
52 | void AppendTrace(std::vector* trace) const {
53 | assert(trace);
54 | trace->push_back(name_);
55 | if (cross_and_capture_to_.Previous()) {
56 | cross_and_capture_to_.Previous()->AppendTrace(trace);
57 | }
58 | }
59 |
60 | const std::string name_;
61 | const AutoThreadCrosser cross_and_capture_to_;
62 | };
63 |
64 | void PrintTrace() {
65 | const auto trace = TraceContext::GetTrace();
66 | for (unsigned int i = 0; i < trace.size(); ++i) {
67 | std::cerr << "Frame " << i << ": " << trace[i] << std::endl;
68 | }
69 | }
70 |
71 | int main() {
72 | // Each important scope is labeled with a TraceContext. It's important to
73 | // remember that it must be *named*; otherwise, it will go out of scope
74 | // immediately, making it effectively a no-op.
75 | TraceContext trace_frame("main");
76 |
77 | // ThreadCrosser::WrapCall will capture the current frame and pass it on when
78 | // the callback is executed, regardless of where that happens.
79 | const auto execute = ThreadCrosser::WrapCall([] {
80 | TraceContext trace_frame("execute");
81 | PrintTrace();
82 | });
83 |
84 | std::thread worker_thread([execute] {
85 | // Not part of the trace when execute is called.
86 | TraceContext trace_frame("worker_thread");
87 | execute();
88 | });
89 | worker_thread.join();
90 | }
91 |
--------------------------------------------------------------------------------
/include/thread-capture.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef THREAD_CAPTURE_H_
20 | #define THREAD_CAPTURE_H_
21 |
22 | #include
23 |
24 | #include "thread-crosser.h"
25 |
26 | namespace capture_thread {
27 |
28 | // Base class for instrumentation classes that need to be made available within
29 | // a single thread, and possibly shared across threads. Derive a new class Type
30 | // from ThreadCapture to define a new instrumentation type. Scoping is
31 | // managed by adding a ScopedCapture *or* an AutoThreadCrosser member to
32 | // instantiable implementations of the instrumentation. (If Type is abstract,
33 | // add the member to the instantiable subclasses.) See ThreadCrosser for info
34 | // about *automatic* sharing across threads, and ThreadBridge (below) for info
35 | // about *manually* sharing across threads.
36 | template
37 | class ThreadCapture {
38 | protected:
39 | ThreadCapture() = default;
40 | virtual ~ThreadCapture() = default;
41 |
42 | class CrossThreads;
43 |
44 | // Manages scoping of instrumentation within a single thread. Use ThreadBridge
45 | // + CrossThreads to share instrumentation across threads. Alternatively, use
46 | // AutoThreadCrosser *instead* of ScopedCapture to allow ThreadCrosser to
47 | // handle this automatically.
48 | class ScopedCapture {
49 | public:
50 | explicit inline ScopedCapture(Type* capture)
51 | : previous_(GetCurrent()), current_(capture) {
52 | SetCurrent(current_);
53 | }
54 |
55 | inline ~ScopedCapture() { SetCurrent(Previous()); }
56 |
57 | inline Type* Previous() const { return previous_; }
58 |
59 | private:
60 | ScopedCapture(const ScopedCapture&) = delete;
61 | ScopedCapture(ScopedCapture&&) = delete;
62 | ScopedCapture& operator=(const ScopedCapture&) = delete;
63 | ScopedCapture& operator=(ScopedCapture&&) = delete;
64 | void* operator new(std::size_t size) = delete;
65 |
66 | friend class CrossThreads;
67 | Type* const previous_;
68 | Type* const current_;
69 | };
70 |
71 | // Creates a bridge point for sharing a single type of instrumentation between
72 | // threads. Create this in the main thread, then enable crossing in the worker
73 | // thread with CrossThreads. This is only needed if the instrumentation uses
74 | // ScopedCapture instead of AutoThreadCrosser.
75 | //
76 | // NOTE: This isn't visible by default. If you want to make this functionality
77 | // available for a specific instrumentation class, add the following with
78 | // public visibility within the instrumentation class:
79 | //
80 | // using ThreadCapture::ThreadBridge;
81 | class ThreadBridge {
82 | public:
83 | inline ThreadBridge() : capture_(GetCurrent()) {}
84 |
85 | private:
86 | ThreadBridge(const ThreadBridge&) = delete;
87 | ThreadBridge(ThreadBridge&&) = delete;
88 | ThreadBridge& operator=(const ThreadBridge&) = delete;
89 | ThreadBridge& operator=(ThreadBridge&&) = delete;
90 | void* operator new(std::size_t size) = delete;
91 |
92 | friend class CrossThreads;
93 | Type* const capture_;
94 | };
95 |
96 | class AutoThreadCrosser;
97 |
98 | // Connects a worker thread to the main thread via a ThreadBridge for manually
99 | // sharing of a single type of instrumentation.
100 | //
101 | // NOTE: This isn't visible by default. If you want to make this functionality
102 | // available for a specific instrumentation class, add the following with
103 | // public visibility within the instrumentation class:
104 | //
105 | // using ThreadCapture::CrossThreads;
106 | class CrossThreads {
107 | public:
108 | explicit inline CrossThreads(const ThreadBridge& bridge)
109 | : previous_(GetCurrent()) {
110 | SetCurrent(bridge.capture_);
111 | }
112 |
113 | inline ~CrossThreads() { SetCurrent(previous_); }
114 |
115 | private:
116 | explicit inline CrossThreads(const ScopedCapture& capture)
117 | : previous_(GetCurrent()) {
118 | SetCurrent(capture.current_);
119 | }
120 |
121 | CrossThreads(const CrossThreads&) = delete;
122 | CrossThreads(CrossThreads&&) = delete;
123 | CrossThreads& operator=(const CrossThreads&) = delete;
124 | CrossThreads& operator=(CrossThreads&&) = delete;
125 | void* operator new(std::size_t size) = delete;
126 |
127 | friend class AutoThreadCrosser;
128 | Type* const previous_;
129 | };
130 |
131 | // Enables automatic crossing of threads for a specific instrumentation type
132 | // when ThreadCrosser::WrapCall or ThreadCrosser::WrapFunction are used. Add
133 | // this as a member of the implementation that you want to automatically
134 | // share. Use ScopedCapture *instead* if you don't want ThreadCrosser to
135 | // automatically share instrumentation.
136 | class AutoThreadCrosser : public ThreadCrosser {
137 | public:
138 | AutoThreadCrosser(Type* capture)
139 | : cross_with_(this), capture_to_(capture) {}
140 |
141 | inline Type* Previous() const { return capture_to_.Previous(); }
142 |
143 | private:
144 | AutoThreadCrosser(const AutoThreadCrosser&) = delete;
145 | AutoThreadCrosser(AutoThreadCrosser&&) = delete;
146 | AutoThreadCrosser& operator=(const AutoThreadCrosser&) = delete;
147 | AutoThreadCrosser& operator=(AutoThreadCrosser&&) = delete;
148 | void* operator new(std::size_t size) = delete;
149 |
150 | void FindTopAndCall(const std::function& call,
151 | const ReverseScope& reverse_scope) const final;
152 |
153 | void ReconstructContextAndCall(
154 | const std::function& call,
155 | const ReverseScope& reverse_scope) const final;
156 |
157 | const ScopedCrosser cross_with_;
158 | const ScopedCapture capture_to_;
159 | };
160 |
161 | // Gets the most-recent object from the stack of the current thread.
162 | static inline Type* GetCurrent() { return current_; }
163 |
164 | private:
165 | ThreadCapture(const ThreadCapture&) = delete;
166 | ThreadCapture(ThreadCapture&&) = delete;
167 | ThreadCapture& operator=(const ThreadCapture&) = delete;
168 | ThreadCapture& operator=(ThreadCapture&&) = delete;
169 | void* operator new(std::size_t size) = delete;
170 |
171 | static inline void SetCurrent(Type* value) { current_ = value; }
172 |
173 | static thread_local Type* current_;
174 | };
175 |
176 | template
177 | thread_local Type* ThreadCapture::current_(nullptr);
178 |
179 | template
180 | void ThreadCapture::AutoThreadCrosser::FindTopAndCall(
181 | const std::function& call,
182 | const ReverseScope& reverse_scope) const {
183 | if (cross_with_.Parent()) {
184 | cross_with_.Parent()->FindTopAndCall(
185 | call, {cross_with_.Parent(), &reverse_scope});
186 | } else {
187 | ReconstructContextAndCall(call, reverse_scope);
188 | }
189 | }
190 |
191 | template
192 | void ThreadCapture::AutoThreadCrosser::ReconstructContextAndCall(
193 | const std::function& call,
194 | const ReverseScope& reverse_scope) const {
195 | // Makes the ThreadCapture available in this scope so that it overrides the
196 | // default behavior of instrumentation calls made in call.
197 | const CrossThreads capture(capture_to_);
198 | if (reverse_scope.previous) {
199 | const auto current = reverse_scope.previous->current;
200 | assert(current);
201 | if (current) {
202 | current->ReconstructContextAndCall(call, *reverse_scope.previous);
203 | }
204 | } else {
205 | // Makes the ThreadCrosser available in this scope so that call itself can
206 | // cross threads again.
207 | const DelegateCrosser crosser(cross_with_);
208 | assert(call);
209 | if (call) {
210 | call();
211 | }
212 | }
213 | }
214 |
215 | } // namespace capture_thread
216 |
217 | #endif // THREAD_CAPTURE_H_
218 |
--------------------------------------------------------------------------------
/include/thread-crosser.h:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------------
2 | Copyright 2017 Google Inc.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | ----------------------------------------------------------------------------- */
16 |
17 | // Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
18 |
19 | #ifndef THREAD_CROSSER_H_
20 | #define THREAD_CROSSER_H_
21 |
22 | #include
23 | #include
24 |
25 | #include
26 |
27 | namespace capture_thread {
28 |
29 | // Manages automatic thread-crossing for sharing instrumentation classes derived
30 | // from ThreadCapture. The static API allows the caller to automatically share
31 | // all instrumentation types that are in scope, provided they use
32 | // AutoThreadCrosser to manage scoping. Not all classes will have this enabled,
33 | // since it can cause unexpected results.
34 | class ThreadCrosser {
35 | public:
36 | // Wraps a callback to share instrumentation that's currently in scope with a
37 | // worker thread. Call this in the main thread, then pass the returned value
38 | // to the worker thread.
39 | //
40 | // NOTE: The returned function will be invalidated if any instrumentation goes
41 | // out of scope; therefore, the main thread must wait for the worker thread to
42 | // call it before continuing.
43 | static inline std::function WrapCall(std::function call) {
44 | return WrapFunction(std::move(call));
45 | }
46 |
47 | // Wraps an arbitrary function to share instrumentation that's currently in
48 | // scope with a worker thread. Call this in the main thread, then pass the
49 | // returned value to the worker thread.
50 | //
51 | // NOTE: The returned function will be invalidated if any instrumentation goes
52 | // out of scope; therefore, the main thread must wait for the worker thread to
53 | // call it before continuing.
54 | template
55 | static inline std::function WrapFunction(
56 | Return (*function)(Args...)) {
57 | return WrapFunction(std::function(function));
58 | }
59 |
60 | // Wraps an arbitrary function to share instrumentation that's currently in
61 | // scope with a worker thread. Call this in the main thread, then pass the
62 | // returned value to the worker thread.
63 | //
64 | // NOTE: The returned function will be invalidated if any instrumentation goes
65 | // out of scope; therefore, the main thread must wait for the worker thread to
66 | // call it before continuing.
67 | template
68 | static std::function WrapFunction(
69 | std::function function);
70 |
71 | private:
72 | ThreadCrosser(const ThreadCrosser&) = delete;
73 | ThreadCrosser(ThreadCrosser&&) = delete;
74 | ThreadCrosser& operator=(const ThreadCrosser&) = delete;
75 | ThreadCrosser& operator=(ThreadCrosser&&) = delete;
76 | void* operator new(std::size_t size) = delete;
77 |
78 | ThreadCrosser() = default;
79 | virtual ~ThreadCrosser() = default;
80 |
81 | // Keeps track of a reverse call stack when wrapping a callback. (This is used
82 | // to create a linked-list of ThreadCrosser on the stack.)
83 | struct ReverseScope {
84 | const ThreadCrosser* const current;
85 | const ReverseScope* const previous;
86 | };
87 |
88 | // Performs the function call in the full ThreadCapture context above this
89 | // ThreadCrosser.
90 | inline void CallInFullContext(const std::function& call) const {
91 | FindTopAndCall(call, {this, nullptr});
92 | }
93 |
94 | // Traverses to the top of the ThreadCrosser stack to recursively rebuild the
95 | // stack of ThreadCapture, then calls the callback.
96 | virtual void FindTopAndCall(const std::function& call,
97 | const ReverseScope& reverse_scope) const = 0;
98 |
99 | // Instantiates the ThreadCapture context associated with this ThreadCrosser,
100 | // then recursively calls the next ThreadCrosser.
101 | virtual void ReconstructContextAndCall(
102 | const std::function& call,
103 | const ReverseScope& reverse_scope) const = 0;
104 |
105 | class ScopedCrosser;
106 |
107 | // Makes a captured ThreadCrosser available within the current thread. This is
108 | // only to allow thread-crossing to happen again within that thread.
109 | class DelegateCrosser {
110 | public:
111 | explicit inline DelegateCrosser(const ScopedCrosser& crosser)
112 | : parent_(GetCurrent()) {
113 | SetCurrent(crosser.current_);
114 | }
115 |
116 | inline ~DelegateCrosser() { SetCurrent(parent_); }
117 |
118 | private:
119 | DelegateCrosser(const DelegateCrosser&) = delete;
120 | DelegateCrosser(DelegateCrosser&&) = delete;
121 | DelegateCrosser& operator=(const DelegateCrosser&) = delete;
122 | DelegateCrosser& operator=(DelegateCrosser&&) = delete;
123 | void* operator new(std::size_t size) = delete;
124 |
125 | ThreadCrosser* const parent_;
126 | };
127 |
128 | // Captures the ThreadCrosser so it can be used by DelegateCrosser, which will
129 | // make it available in another thread.
130 | class ScopedCrosser {
131 | public:
132 | explicit inline ScopedCrosser(ThreadCrosser* capture)
133 | : parent_(GetCurrent()), current_(capture) {
134 | SetCurrent(capture);
135 | }
136 |
137 | inline ~ScopedCrosser() { SetCurrent(Parent()); }
138 |
139 | inline ThreadCrosser* Parent() const { return parent_; }
140 |
141 | private:
142 | ScopedCrosser(const ScopedCrosser&) = delete;
143 | ScopedCrosser(ScopedCrosser&&) = delete;
144 | ScopedCrosser& operator=(const ScopedCrosser&) = delete;
145 | ScopedCrosser& operator=(ScopedCrosser&&) = delete;
146 | void* operator new(std::size_t size) = delete;
147 |
148 | friend class DelegateCrosser;
149 | ThreadCrosser* const parent_;
150 | ThreadCrosser* const current_;
151 | };
152 |
153 | // AutoMove (and its specializations) ensure that argument-passing when
154 | // calling a std::function doesn't make copies when it would be inappropriate,
155 | // e.g., when Type cannot be copied or moved. This is a class instead of a
156 | // function so that Type can be made explicit, which is necessary for type-
157 | // checking to work.
158 |
159 | template
160 | struct AutoMove;
161 |
162 | // AutoCall (and its specializations) ensure that return values are not
163 | // inappropriately copied, e.g., when returning by reference, or when the
164 | // return type is void. This is a class instead of a function so that the
165 | // types can be made explicit, which is necessary for type-checking to work.
166 |
167 | template
168 | struct AutoCall;
169 |
170 | static ThreadCrosser* GetCurrent();
171 | static void SetCurrent(ThreadCrosser* value);
172 |
173 | template
174 | friend class ThreadCapture;
175 | };
176 |
177 | template
178 | std::function