Errbit-Fehlerhistogramm

Ich möchte ein kleines, praktisches Ruby-Skript vorstellen, mit dessen Hilfe man einen kurzen Überblick über die zeitliche Verteilung von Exceptions in Errbit generieren kann.

Daten aus MongoDB extrahieren

Zunächst muss die Nomenklatur geklärt werden. :) Eine Exception wird in der darunterliegenden MongoDB-Collection problems gespeichert, zusätzliche Informationen dazu in der Collection errs und jedes Auftreten resultiert in einem Eintrag in der Collection notices.

Aus der Errbit-URL zu einem Problem (beispielsweise https://errbit.example.com/apps/5122466fd9d1af0dab004ca6/problems/551cea21d9d1af2ab600013e) kann nun die problem_id aus der URL entnommen werden (der Teil nach problems/). Um an die notices zu gelangen, suchen wir zunächst nach der err_id auf der mongo-Konsole:

var problemId = '551cea21d9d1af2ab600013e';
var errId = db.errs.findOne({problem_id: ObjectId(problemId)})['_id'];

Damit besorgen wir uns von allen zugehörigen notices den Zeitstempel der Erstellung. Wir möchten im Ergebnis daher pro Eintrag nur das created_at-Feld haben, der Rest interessiert uns nicht.

var notices = db.notices.find({err_id: errId}, {"created_at": 1});

Um nun das Ganze tageweise zu gruppieren, kann man db.collection.group() verwenden. Dazu benötigen wir zwei Funktionen, eine stellt pro Dokument in der Collection einen Schlüssel zum Gruppieren bereit, das ist in unserem Fall das Datum:

var keyFunction = function(notice) {
  var date = new Date(notice.created_at);
  var dateKey = (date.getDate() + "." + (date.getMonth()+1) + "."
                  + date.getFullYear() + '');
  return {day: dateKey};
};

Die zweite Funktion ist denkbar einfach, sie summiert pro key einfach immer auf:

var reduceFunction = function(obj, result) {
  result.count++;
};

Damit können wir nun gruppieren:

db.notices.group(
{
  keyf: keyFunction,
  cond: { err_id: errId },
  initial: { count:0 },
  reduce: reduceFunction
});

Ein beispielhafter Output sieht dann so aus:

[
  { "day" : "16.4.2015", "count" : 1 }
]

Abfrage per Ruby-Skript

Nachdem ich das Ganze gerne komfortabel aus meiner Unix-Shell abfragen möchte, habe ich dazu ein kleines Ruby-Skript geschrieben, um diesen Prozess zu beschleunigen. Ich verwende hierzu das net-ssh Gem, um mich auf dem Rechner mit der Errbit-Installation einzuloggen und den Port des MongoDB-Servers an meinen Client weiterzuleiten, da in meinem Fall ein direkter Zugriff auf MongoDB von außen nicht erlaubt ist. Der Zugriff auf die Datenbank erfolgt mittels mongo.

#!/usr/bin/env ruby

require 'mongo'
require 'net/ssh/gateway'
require 'date'

include Mongo

gateway = Net::SSH::Gateway.new('errbit.example.com', 'mein_user_name')
gateway.open '127.0.0.1', 27_017, 27_017
db = MongoClient.new('localhost', 27_017).db('errbit')

err_id = db.collection('errs')
           .find_one(problem_id: BSON::ObjectId(ARGV.first))['_id']

reduce_function =<<-END
  function(obj, result) { result.count++; }
END

key_function = <<-END
  function(notice) {
    var date = new Date(notice.created_at);
    var dateKey = (date.getDate()+"."+(date.getMonth()+1)+"."+date.getFullYear()+'');
    return { 'day':dateKey };
}
END

res = db.collection('notices').group(
  keyf: key_function,
  cond: { err_id: err_id },
  initial: { count: 0 },
  reduce: reduce_function
)

res.each do |item|
  item['count'] = item['count'].to_i
  item['day'] = Date.parse item['day']
end.sort! { |x, y| x['day'] <=> y['day'] }

res.each { |item| puts "#{item['day']}: #{item['count']}" }

Damit erhält man eine GnuPlot-kompatible Ausgabe, die man dann auch noch hübsch graphisch auftragen kann:

~ $ ./errbit_stat.rb 551cea21d9d1af2ab600013e
2015-04-02: 5
2015-04-08: 2
2015-04-09: 277
2015-04-10: 536
2015-04-11: 454
2015-04-12: 560
2015-04-13: 715
2015-04-14: 428
2015-04-15: 440
2015-04-16: 648
2015-04-17: 462
2015-04-18: 303
2015-04-19: 1250
2015-04-20: 1124
2015-04-21: 487
2015-04-22: 316
2015-04-23: 154
2015-04-24: 114
2015-04-25: 112
2015-04-26: 126
2015-04-27: 89

Plotten mit Gnuplot

Zunächst leiten wir die Ausgabe des Ruby-Skripts in eine Textdatei um, die wir anschließend als Datengrundlage für GnuPlot verwenden:

~ $ ./errbit_stat.rb 551cea21d9d1af2ab600013e > 551cea21d9d1af2ab600013e.dat

Ein einfaches GnuPlot-Skript, das auf die Ordinate das Datum und auf die Abszisse die Anzahl der Fehler aufträgt kann z.B. so aussehen:

set title 'Häufigkeit der Exception FooException'

set term png
set output 'foo_exception_plot.png'

set xdata time
set timefmt '%Y-%m-%d'
set xlabel 'Datum'
set ylabel 'Häufigkeit'
set autoscale y
plot '551cea21d9d1af2ab600013e.dat' using 1:2 with linespoints

Plot