1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const cluster = require("cluster");
|
4 | const hot_shots_1 = require("hot-shots");
|
5 | const os = require("os");
|
6 | const trace = require("stack-trace");
|
7 | const internalGraphql = require("../internal/graph/graphQL");
|
8 | const logger_1 = require("../internal/util/logger");
|
9 | const shutdown_1 = require("../internal/util/shutdown");
|
10 | const AutomationEventListener_1 = require("../server/AutomationEventListener");
|
11 | class StatsdAutomationEventListener extends AutomationEventListener_1.AutomationEventListenerSupport {
|
12 | constructor(configuration) {
|
13 | super();
|
14 | this.configuration = configuration;
|
15 | this.registrationName = `${this.configuration.name}/${this.configuration.version}`;
|
16 | this.initStatsd();
|
17 | }
|
18 | registrationSuccessful(handler) {
|
19 | this.increment("counter.registration");
|
20 | this.event("event.registration", `New registration for ${this.registrationName}`);
|
21 | }
|
22 | contextCreated(ctx) {
|
23 | const context = ctx.context;
|
24 | const graphClient = ctx.graphClient;
|
25 |
|
26 | if (graphClient) {
|
27 | const tags = [
|
28 | `atomist_operation:${context.operation}`,
|
29 | `atomist_operation_type:command`,
|
30 | ...this.teamDetail(ctx),
|
31 | ];
|
32 | ctx.graphClient = {
|
33 | endpoint: graphClient.endpoint,
|
34 | executeMutation: (mutation, variables, options) => {
|
35 | const start = Date.now();
|
36 | return graphClient.executeMutation(mutation, variables, options)
|
37 | .then(result => {
|
38 | this.statsd.increment("counter.graphql.mutation.success", 1, 1, tags, this.callback);
|
39 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
40 | return result;
|
41 | })
|
42 | .catch(err => {
|
43 | this.statsd.increment("counter.graphql.mutation.failure", 1, 1, tags, this.callback);
|
44 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
45 | return err;
|
46 | });
|
47 | },
|
48 | executeMutationFromFile: (path, variables, options, current) => {
|
49 | const start = Date.now();
|
50 | return graphClient.executeMutationFromFile(path, variables, options, current)
|
51 | .then(result => {
|
52 | this.statsd.increment("counter.graphql.mutation.success", 1, 1, tags, this.callback);
|
53 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
54 | return result;
|
55 | })
|
56 | .catch(err => {
|
57 | this.statsd.increment("counter.graphql.mutation.failure", 1, 1, tags, this.callback);
|
58 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
59 | return err;
|
60 | });
|
61 | },
|
62 | mutate: (optionsOrName) => {
|
63 | const start = Date.now();
|
64 | if (typeof optionsOrName === "string") {
|
65 | optionsOrName = {
|
66 | name: optionsOrName,
|
67 | };
|
68 | }
|
69 | const m = internalGraphql.mutate({
|
70 | mutation: optionsOrName.mutation,
|
71 | path: optionsOrName.path,
|
72 | name: optionsOrName.name,
|
73 | moduleDir: trace.get()[1].getFileName(),
|
74 | });
|
75 | return graphClient.executeMutation(m, optionsOrName.variables, optionsOrName.options)
|
76 | .then(result => {
|
77 | this.statsd.increment("counter.graphql.mutation.success", 1, 1, tags, this.callback);
|
78 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
79 | return result;
|
80 | })
|
81 | .catch(err => {
|
82 | this.statsd.increment("counter.graphql.mutation.failure", 1, 1, tags, this.callback);
|
83 | this.statsd.timing("timer.graphql.mutation", Date.now() - start, 1, tags, this.callback);
|
84 | return err;
|
85 | });
|
86 | },
|
87 | executeQuery: (query, variables, options) => {
|
88 | const start = Date.now();
|
89 | return graphClient.executeQuery(query, variables, options)
|
90 | .then(result => {
|
91 | this.statsd.increment("counter.graphql.query.success", 1, 1, tags, this.callback);
|
92 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
93 | return result;
|
94 | })
|
95 | .catch(err => {
|
96 | this.statsd.increment("counter.graphql.query.failure", 1, 1, tags, this.callback);
|
97 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
98 | return err;
|
99 | });
|
100 | },
|
101 | executeQueryFromFile: (path, variables, options, current) => {
|
102 | const start = Date.now();
|
103 | return graphClient.executeQueryFromFile(path, variables, options, current)
|
104 | .then(result => {
|
105 | this.statsd.increment("counter.graphql.query.success", 1, 1, tags, this.callback);
|
106 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
107 | return result;
|
108 | })
|
109 | .catch(err => {
|
110 | this.statsd.increment("counter.graphql.query.failure", 1, 1, tags, this.callback);
|
111 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
112 | return err;
|
113 | });
|
114 | },
|
115 | query: (optionsOrName) => {
|
116 | const start = Date.now();
|
117 | if (typeof optionsOrName === "string") {
|
118 | optionsOrName = {
|
119 | name: optionsOrName,
|
120 | };
|
121 | }
|
122 | const q = internalGraphql.query({
|
123 | query: optionsOrName.query,
|
124 | path: optionsOrName.path,
|
125 | name: optionsOrName.name,
|
126 | moduleDir: trace.get()[1].getFileName(),
|
127 | });
|
128 | return graphClient.executeQuery(q, optionsOrName.variables, optionsOrName.options)
|
129 | .then(result => {
|
130 | this.statsd.increment("counter.graphql.query.success", 1, 1, tags, this.callback);
|
131 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
132 | return result;
|
133 | })
|
134 | .catch(err => {
|
135 | this.statsd.increment("counter.graphql.query.failure", 1, 1, tags, this.callback);
|
136 | this.statsd.timing("timer.graphql.query", Date.now() - start, 1, tags, this.callback);
|
137 | return err;
|
138 | });
|
139 | },
|
140 | };
|
141 | }
|
142 | }
|
143 | commandSuccessful(payload, ctx, result) {
|
144 | const tags = [
|
145 | `atomist_operation:${payload.name}`,
|
146 | `atomist_operation_type:command`,
|
147 | ...this.teamDetail(ctx),
|
148 | ];
|
149 | this.increment("counter.operation.success", tags);
|
150 | this.timing("timer.operation", ctx, tags);
|
151 | return Promise.resolve();
|
152 | }
|
153 | commandFailed(payload, ctx, err) {
|
154 | const tags = [
|
155 | `atomist_operation:${payload.name}`,
|
156 | `atomist_operation_type:command`,
|
157 | ...this.teamDetail(ctx),
|
158 | ];
|
159 | this.increment("counter.operation.failure", tags);
|
160 | this.timing("timer.operation", ctx, tags);
|
161 | this.event("event.operation.failure", "Unsuccessfully invoked command", tags);
|
162 | return Promise.resolve();
|
163 | }
|
164 | eventSuccessful(payload, ctx, result) {
|
165 | const tags = [
|
166 | `atomist_operation:${payload.extensions.operationName}`,
|
167 | `atomist_operation_type:event`,
|
168 | ...this.teamDetail(ctx),
|
169 | ];
|
170 | this.increment("counter.operation.success", tags);
|
171 | this.timing("timer.operation", ctx, tags);
|
172 | return Promise.resolve();
|
173 | }
|
174 | eventFailed(payload, ctx, err) {
|
175 | const tags = [
|
176 | `atomist_operation:${payload.extensions.operationName}`,
|
177 | `atomist_operation_type:event`,
|
178 | ...this.teamDetail(ctx),
|
179 | ];
|
180 | this.increment("counter.operation.failure", tags);
|
181 | this.timing("timer.operation", ctx, tags);
|
182 | this.event("event.operation.failure", "Unsuccessfully invoked event", tags);
|
183 | return Promise.resolve();
|
184 | }
|
185 | messageSent(message, destinations, options, ctx) {
|
186 | let type;
|
187 | destinations = Array.isArray(destinations) ? destinations : [destinations];
|
188 | destinations.forEach(d => {
|
189 | if (d.userAgent === "slack") {
|
190 | const sd = d;
|
191 | if (sd.users && sd.users.length > 0) {
|
192 | type = "slack_users";
|
193 | }
|
194 | else if (sd.channels && sd.channels.length > 0) {
|
195 | type = "slack_channels";
|
196 | }
|
197 | else {
|
198 | type = "slack_response";
|
199 | }
|
200 | }
|
201 | });
|
202 | this.increment("counter.message", [
|
203 | `atomist_message_type:${type}`,
|
204 | ...this.teamDetail(ctx),
|
205 | ]);
|
206 | return Promise.resolve();
|
207 | }
|
208 |
|
209 | callback(err) {
|
210 | return;
|
211 | }
|
212 | increment(stat, tags) {
|
213 | if (cluster.isMaster) {
|
214 | this.statsd.increment(stat, 1, 1, tags, this.callback);
|
215 | }
|
216 | }
|
217 | event(title, text, tags) {
|
218 | if (cluster.isMaster) {
|
219 | this.statsd.event(`automation_client.${title}`, text, {}, tags, this.callback);
|
220 | }
|
221 | }
|
222 | timing(stat, ctx, tags) {
|
223 | if (cluster.isMaster &&
|
224 | ctx &&
|
225 | ctx.context &&
|
226 | ctx.context.ts) {
|
227 | const context = ctx.context;
|
228 | this.statsd.timing(stat, Date.now() - context.ts, 1, tags, this.callback);
|
229 | }
|
230 | }
|
231 | initStatsd() {
|
232 | const options = {
|
233 | prefix: "automation_client.",
|
234 | host: this.configuration.statsd.host || "localhost",
|
235 | port: this.configuration.statsd.port || 8125,
|
236 | globalTags: [
|
237 | `atomist_name:${this.configuration.name.replace("@", "").replace("/", ".")}`,
|
238 | `atomist_version:${this.configuration.version}`,
|
239 | `atomist_environment:${this.configuration.environment}`,
|
240 | `atomist_application_id:${this.configuration.application}`,
|
241 | `atomist_process_id:${process.pid}`,
|
242 | `atomist_host:${os.hostname()}`,
|
243 | ],
|
244 | };
|
245 | this.statsd = new hot_shots_1.StatsD(options);
|
246 | this.timer = setInterval(() => {
|
247 | this.submitHeapStats();
|
248 | }, 5000);
|
249 |
|
250 | shutdown_1.registerShutdownHook(() => {
|
251 | this.event("event.shutdown", `Shutting down client ${this.registrationName}`);
|
252 | this.statsd.close(() => {
|
253 | logger_1.logger.debug("Closing StatsD connection");
|
254 | });
|
255 | return Promise.resolve(0);
|
256 | });
|
257 | }
|
258 | teamDetail(ctx) {
|
259 | if (ctx && ctx.context) {
|
260 | const context = ctx.context;
|
261 | const safeTeamName = context.workspaceName ?
|
262 | context.workspaceName.trim().replace(/ /g, "_").replace(/\W/g, "") : undefined;
|
263 | return [
|
264 | `atomist_team_id:${context.workspaceId}`,
|
265 | `atomist_team_name:${safeTeamName}`,
|
266 | ];
|
267 | }
|
268 | else {
|
269 | return [];
|
270 | }
|
271 | }
|
272 | submitHeapStats() {
|
273 | const heap = process.memoryUsage();
|
274 | this.statsd.gauge("heap.rss", heap.rss, 1, [], this.callback);
|
275 | this.statsd.gauge("heap.total", heap.heapTotal, 1, [], this.callback);
|
276 | this.statsd.gauge("heap.used", heap.heapUsed, 1, [], this.callback);
|
277 | }
|
278 | }
|
279 | exports.StatsdAutomationEventListener = StatsdAutomationEventListener;
|
280 |
|
\ | No newline at end of file |