Skip to content

Commit

Permalink
🐛 fixing bugs with mysql evaluator + made slack calls retry
Browse files Browse the repository at this point in the history
  • Loading branch information
Rohland committed Nov 12, 2023
1 parent 6785372 commit 466dd19
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 123 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "barky",
"version": "1.0.54",
"version": "1.0.55",
"description": "A simple cloud services watchdog with digest notification support & no external dependencies",
"homepage": "https://github.com/Rohland/barky#readme",
"main": "dist/cli.js",
Expand Down
50 changes: 45 additions & 5 deletions src/evaluators/base.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,46 @@ describe("base evaluator", () => {
});
});
});
describe("when app is missing name", () => {
it("should use inferred name by key", async () => {
// arrange
const myApp = {};
const config = {
["web"]: {
myApp
}
};
const e = new WebEvaluator(config);

// act
const apps = e.getAppsToEvaluate()

// assert
expect(apps.length).toEqual(1);
expect(apps[0].name).toEqual("myApp");
});
});
describe("when app is not missing name", () => {
it("should use name", async () => {
// arrange
const myApp = {
name: "test"
};
const config = {
["web"]: {
myApp
}
};
const e = new WebEvaluator(config);

// act
const apps = e.getAppsToEvaluate()

// assert
expect(apps.length).toEqual(1);
expect(apps[0].name).toEqual("test");
});
});
});

describe("given an evaluator", () => {
Expand All @@ -57,9 +97,9 @@ describe("base evaluator", () => {
const apps3 = evaluator.getAppsToEvaluate();

// assert
expect(apps1).toEqual([{ type: "custom" }]);
expect(apps2).toEqual([{ type: "custom" }]);
expect(apps3).toEqual([{ type: "custom" }]);
expect(apps1).toEqual([{ type: "custom", name: "app1" }]);
expect(apps2).toEqual([{ type: "custom", name: "app1" }]);
expect(apps3).toEqual([{ type: "custom", name: "app1" }]);
expect(evaluator.skippedApps).toEqual([]);
});
});
Expand Down Expand Up @@ -351,7 +391,7 @@ describe("base evaluator", () => {
this.results.push(result);
}

configureAndExpandApp(_app: IApp, _name: string): IApp[] {
configureAndExpandApp(_app: IApp): IApp[] {
return [];
}

Expand Down Expand Up @@ -469,7 +509,7 @@ class CustomEvaluator extends BaseEvaluator {
super(config);
}

configureAndExpandApp(app: IApp, _name: string): IApp[] {
configureAndExpandApp(app: IApp): IApp[] {
return [app];
}

Expand Down
17 changes: 8 additions & 9 deletions src/evaluators/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export abstract class BaseEvaluator {

protected abstract dispose(): Promise<void>;

abstract configureAndExpandApp(app: IApp, name: string): IApp[];
abstract configureAndExpandApp(app: IApp): IApp[];

abstract get type(): EvaluatorType;

Expand All @@ -115,10 +115,11 @@ export abstract class BaseEvaluator {
const apps = [];
for (let name of appNames) {
const app = this.config[name];
const expanded = this.configureAndExpandApp(app, name);
app.name ??= name;
const expanded = this.configureAndExpandApp(app);
expanded.forEach(x => {
x.type = this.type;
if (this.shouldEvaluateApp(x, name)) {
if (this.shouldEvaluateApp(x)) {
apps.push(x);
}
});
Expand All @@ -128,23 +129,21 @@ export abstract class BaseEvaluator {
return expanded;
}

private shouldEvaluateApp(
app: IApp,
name: string): boolean {
private shouldEvaluateApp(app: IApp): boolean {
if (!app.every) {
return true;
}
const durationMs = parsePeriodToSeconds(app.every) * 1000;
const everyCount = Math.round(durationMs / LoopMs);
const key = `${ this.type }-${ name }`;
const key = `${ this.type }-${ app.name }`;
const count = executionCounter.get(key) ?? 0;
const shouldEvaluate = count % everyCount === 0;
executionCounter.set(key, count + 1);
if (!shouldEvaluate) {
log(`skipping ${ this.type } check for '${ name }' - every set to: ${ app.every }`);
log(`skipping ${ this.type } check for '${ app.name }' - every set to: ${ app.every }`);
this.skippedApps.push({
...app,
...this.generateSkippedAppUniqueKey(name)
...this.generateSkippedAppUniqueKey(app.name)
});
}
return shouldEvaluate;
Expand Down
24 changes: 17 additions & 7 deletions src/evaluators/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export class MySqlEvaluator extends BaseEvaluator {
return EvaluatorType.mysql;
}

configureAndExpandApp(app: IApp, name: string): IApp[] {
return getAppVariations(app, name).map(variant => {
configureAndExpandApp(app: IApp): IApp[] {
return getAppVariations(app).map(variant => {
return {
timeout: 15000,
...app,
Expand Down Expand Up @@ -54,7 +54,17 @@ async function tryEvaluate(app: IApp): Promise<Result | Result[]> {
const timer = startClock();
const results = await runQuery(connection, app);
app.timeTaken = stopClock(timer);
return validateResults(app, results);
const finalResults = validateResults(app, results);
return finalResults.length > 0
? finalResults
: new MySqlResult( // no results from mysql means OK for all identifiers!
app.name,
"*",
"inferred",
"OK",
app.timeTaken,
true,
app);
} catch (err) {
log(`Error evaluating app ${ app.name }: ${ err.message }`, err);
return new MonitorFailureResult(
Expand All @@ -65,15 +75,15 @@ async function tryEvaluate(app: IApp): Promise<Result | Result[]> {
}
}

export function validateResults(app, results): Result[] {
export function validateResults(app: IApp, results: Result[]): Result[] {
return results.map(row => {
const identifier = row[app.identifier] ?? app.identifier;
const rules = findTriggerRulesFor(identifier, app);
return validateRow(app, identifier, row, rules);
});
}

function generateVariablesAndValues(row, app) {
function generateVariablesAndValues(row, app: IApp) {
const variables = Object.keys(row).filter(x => x !== app.identifier);
const values = {};
const emit: Array<string> = app.emit ?? [];
Expand All @@ -87,8 +97,8 @@ function generateVariablesAndValues(row, app) {
}

export function validateRow(
app,
identifier,
app: IApp,
identifier: string,
row,
rules: IRule[]): MySqlResult {
if (!rules || rules.length === 0) {
Expand Down
4 changes: 2 additions & 2 deletions src/evaluators/sumo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export class SumoEvaluator extends BaseEvaluator {
return EvaluatorType.sumo;
}

configureAndExpandApp(app: IApp, name: string): IApp[] {
return getAppVariations(app, name).map(variant => {
configureAndExpandApp(app: IApp): IApp[] {
return getAppVariations(app).map(variant => {
return {
timeout: 10000,
...app,
Expand Down
4 changes: 2 additions & 2 deletions src/evaluators/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class WebEvaluator extends BaseEvaluator {
return await tryEvaluate(app);
}

configureAndExpandApp(app: IApp, name: string): IApp[] {
return getAppVariations(app, name).map(variant => {
configureAndExpandApp(app: IApp): IApp[] {
return getAppVariations(app).map(variant => {
return {
...app,
...variant
Expand Down
63 changes: 62 additions & 1 deletion src/lib/utility.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { flatten, hash, initLocaleAndTimezone, isToday, shortHash, toLocalTimeString } from "./utility";
import {
flatten,
hash,
initLocaleAndTimezone,
isToday,
shortHash,
toLocalTimeString,
tryExecuteTimes
} from "./utility";

describe("utility functions", () => {
describe("flatten", () => {
Expand Down Expand Up @@ -197,4 +205,57 @@ describe("utility functions", () => {
});
});
});

describe("tryExecuteTimes", () => {
describe("on success", () => {
it("should return", async () => {
// arrange
const func = jest.fn().mockResolvedValue("ok");

// act
const result = await tryExecuteTimes("test", 3, func);

// assert
expect(func).toHaveBeenCalledTimes(1);
expect(result).toEqual("ok");
});
});
describe("on failure but then success", () => {
it("should return success", async () => {
// arrange
const func = jest.fn().mockRejectedValueOnce(new Error("test")).mockResolvedValue("ok");

// act
const result = await tryExecuteTimes("test", 3, func);

// assert
expect(func).toHaveBeenCalledTimes(2);
expect(result).toEqual("ok");
});
});
describe("on successive failure", () => {
it("should retry the number of times and throw", async () => {
// arrange
const func = jest.fn().mockRejectedValue(new Error("test"));

// act
await expect(tryExecuteTimes("test", 3, func)).rejects.toThrow("test");

// assert
expect(func).toHaveBeenCalledTimes(3);
});
describe("with throw set to false", () => {
it("should not throw", async () => {
// arrange
const func = jest.fn().mockRejectedValue(new Error("test"));

// act
await tryExecuteTimes("test", 3, func, false, 0);

// assert
expect(func).toHaveBeenCalledTimes(3);
});
});
});
});
});
26 changes: 26 additions & 0 deletions src/lib/utility.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as crypto from "crypto";
import { log } from "../models/logger";
import { sleepMs } from "./sleep";

Error.stackTraceLimit = Infinity;

Expand Down Expand Up @@ -95,3 +97,27 @@ export function shortHash(key: string) {
.update(key ?? "")
.digest("hex");
}

export async function tryExecuteTimes<T>(
label: string,
times: number,
func: () => Promise<T>,
throwOnEventualFailure: boolean = true,
delayBetweenAttempts: number = 500): Promise<T> {
let counter = 0;
let lastError = null;
while(counter++ < times) {
try {
return await func();
} catch(err) {
const msg = `Error ${ label }: ${ err.message }`;
log(msg, err);
lastError = err;
}
await sleepMs(delayBetweenAttempts);
}
if (throwOnEventualFailure && lastError) {
throw lastError;
}
return null;
}
Loading

0 comments on commit 466dd19

Please sign in to comment.