1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include <chrono>
19 #include <ctime>
20 
21 #include "phosphor/platform/thread.h"
22 #include "phosphor/tools/export.h"
23 
24 #include "utils/memory.h"
25 #include "utils/string_utils.h"
26 
27 namespace phosphor {
28     namespace tools {
29 
threadAssociationToString( const std::pair<uint64_t, std::string>& assoc)30         std::string threadAssociationToString(
31                 const std::pair<uint64_t, std::string>& assoc) {
32             return utils::format_string(
33                 "{\"name\": \"thread_name\", \"ph\": \"M\", \"pid\": %d, "
34                 "\"tid\": %d, \"args\": { \"name\" : \"%s\"}}",
35                 platform::getCurrentProcessID(),
36                 assoc.first, assoc.second.c_str());
37         }
38 
JSONExport(const TraceContext& _context)39         JSONExport::JSONExport(const TraceContext& _context)
40             : context(_context),
41               it(context.getBuffer()->begin()),
42               tit(context.getThreadNames().begin()) {
43 
44         }
45 
46         JSONExport::~JSONExport() = default;
47 
read(char* out, size_t length)48         size_t JSONExport::read(char* out, size_t length) {
49             std::string event_json;
50             size_t cursor = 0;
51 
52             while (cursor < length &&
53                    !(state == State::dead && cache.size() == 0)) {
54                 if (cache.size() > 0) {
55                     size_t copied = cache.copy((out + cursor), length - cursor);
56                     cache.erase(0, copied);
57                     cursor += copied;
58 
59                     if (cursor >= length) {
60                         break;
61                     }
62                 }
63                 switch (state) {
64                 case State::opening:
65                     cache = "{\n  \"traceEvents\": [\n";
66                     if (tit != context.getThreadNames().end()) {
67                         state = State::first_thread;
68                     } else if (it != context.getBuffer()->end()) {
69                         state = State::first_event;
70                     } else {
71                         state = State::footer;
72                     }
73                     break;
74                 case State::other_events:
75                     cache += ",\n";
76                 case State::first_event:
77                     event_json = it->to_json(it.getParent().threadID());
78                     ++it;
79                     cache += event_json;
80                     state = State::other_events;
81                     if (it == context.getBuffer()->end()) {
82                         state = State::footer;
83                     }
84                     break;
85                 case State::other_threads:
86                         cache += ",\n";
87                 case State::first_thread:
88                         event_json = threadAssociationToString(*tit);
89                         ++tit;
90                         cache += event_json;
91                         state = State::other_threads;
92                         if (tit == context.getThreadNames().end()) {
93                             if (it != context.getBuffer()->end()) {
94                                 state = State::other_events;
95                             } else {
96                                 state = State::footer;
97                             }
98                         }
99                         break;
100                 case State::footer:
101                     cache =
102                         "\n    ]\n"
103                         "}\n";
104                     state = State::dead;
105                     break;
106                 case State::dead:
107                     break;
108                 }
109             }
110             return cursor;
111         }
112 
read(size_t length)113         StringPtr JSONExport::read(size_t length) {
114             std::string out;
115             out.resize(length, '\0');
116             out.resize(read(&out[0], length));
117             return make_String(out);
118         }
119 
done()120         bool JSONExport::done() {
121             return state == State::dead;
122         }
123 
read()124         StringPtr JSONExport::read() {
125             std::string out;
126 
127             size_t last_wrote;
128             do {
129                 out.resize(out.size() + 4096);
130                 last_wrote = read(&out[out.size() - 4096], 4096);
131             } while (!done());
132 
133             out.resize(out.size() - (4096 - last_wrote));
134             return make_String(out);
135         }
136 
FileStopCallback(const std::string& _file_path)137         FileStopCallback::FileStopCallback(const std::string& _file_path)
138             : file_path(_file_path) {}
139 
140         FileStopCallback::~FileStopCallback() = default;
141 
operator ()(TraceLog& log, std::lock_guard<TraceLog>& lh)142         void FileStopCallback::operator()(TraceLog& log,
143                                           std::lock_guard<TraceLog>& lh) {
144             std::string formatted_path = generateFilePath();
145             auto fp = utils::make_unique_FILE(formatted_path.c_str(), "w");
146             if (fp == nullptr) {
147                 throw std::runtime_error(
148                     "phosphor::tools::ToFileStoppedCallback(): Couldn't"
149                     " Couldn't open file: " +
150                     formatted_path);
151             }
152 
153             const TraceContext context = log.getTraceContext(lh);
154             char chunk[4096];
155             JSONExport exporter(context);
156             while (auto count = exporter.read(chunk, sizeof(chunk))) {
157                 auto ret = fwrite(chunk, sizeof(chunk[0]), count, fp.get());
158                 if (ret != count) {
159                     throw std::runtime_error(
160                         "phosphor::tools::ToFileStoppedCallback(): Couldn't"
161                         " write entire chunk: " +
162                         std::to_string(ret));
163                 }
164             }
165         }
166 
generateFilePath()167         std::string FileStopCallback::generateFilePath() {
168             std::string target = file_path;
169 
170             utils::string_replace(
171                 target, "%p", std::to_string(platform::getCurrentProcessID()));
172 
173             std::time_t now = std::chrono::system_clock::to_time_t(
174                 std::chrono::system_clock::now());
175             std::string timestamp;
176             timestamp.resize(sizeof("YYYY-MM-DDTHH:MM:SSZ"));
177             strftime(&timestamp[0],
178                      timestamp.size(),
179                      "%Y.%m.%dT%H.%M.%SZ",
180                      gmtime(&now));
181             timestamp.resize(timestamp.size() - 1);
182             utils::string_replace(target, "%d", timestamp);
183             return target;
184         }
185 
generateFilePathAsPtr()186         StringPtr FileStopCallback::generateFilePathAsPtr() {
187             return make_String(generateFilePath());
188         }
189     }
190 }
191