I've been using strprintf() repeatedly for the error messages and exceptions, and I've come up with a better way for it.
First, Ive added a var-args version of strprintf(), in common/Strprintf.h:
string vstrprintf(const char *fmt, va_list ap);
You can use it to create strings from other functions taking the printf-like arguments.
Next go the extensions to the Errors class. The consistent theme there is "check if the Errors reference (Erref) is NULL, if it is, allocate a new Errors, and then add a formatted error message to it". So I've added the new methods not to Errors but to Erref. They check if the Erref object is NULL, allocate a new Errors object into it if needed, and then format the arguments. The simplest one is:
void f(const char *fmt, ...);
It adds a simple formatted message, always marked as an error. You use it like this:
Erref e; // initially NULL
...
e.f("a message with integer %d", n);
The message may be multi-line, it will be split appropriately, like in Errors::appendMultiline().
The next one is even more smart:
bool fAppend(Autoref<Errors> clde, const char *fmt, ...);
It first checks that the child errors object is not NULL and contains an error, and if it does then it does through the dance of allocating a new Errors object if needed, appends the formatted message, and the child errors. The message goes before the child errors, unlike the method signature. So you can use it blindly like this to do the right thing:
Autoref<Errors> checkSubObject(int idx);
...
for (int i = 0; i < sz; i++)
e.fAppend(checkSubObject(i), "error in the sub-object %d:", i);
Same as before, you can use the multi-line error messages.
Next goes the same thing for Exception:
static Exception f(const char *fmt, ...);
static Exception fTrace(const char *fmt, ...);
these are the static factory methods that create an Exception object with the message, and either without and with the stack trace. They are used like
throw Exception::f("a message with integer %d", n);
And the similar methods for construction with the nested errors:
static Exception f(Onceref<Errors> err, const char *fmt, ...);
static Exception fTrace(Onceref<Errors> err, const char *fmt, ...);
Unlike the Erref method, these work unconditionally (since their result is normally used in throw, and it's too late to do anything by that time), so you better make sure in advance that there is a child error. A typical usage would be like this:
try {
...
} catch (Exception e) {
throw Exception(e.getErrors(), "error at stage %d:", n);
}
Again, in the resulting exception the message goes before the nested errors.
This started as my thoughts on the field of Complex Event Processing, mostly about my OpenSource project Triceps. But now it's about all kinds of software-related things.
Showing posts with label print. Show all posts
Showing posts with label print. Show all posts
Monday, March 4, 2013
Thursday, February 16, 2012
More of the manual aggregation
Returning to the last table example, it prints the aggregated information (the average price of two records). This can be fairly easily changed to put the information into the rows and send them on as labels. The function printAverage() morphs into computeAverage():
For the demonstration, the aggregated records sent to $lbAverage get printed. The records being aggregated are printed during the iteration too. And here is a sample run's result, with the input records shown in italics:
There are a couple of things to notice about it: it produces only the INSERT rowops, no DELETEs, and when the last record of the group is removed, that event produces nothing.
The first item is mildly problematic because the processing downstream from here might not be able to handle the updates properly without the DELETE rowops. It can be worked around fairly easily by connecting another table, with the same primary key as the aggregation key, to store the aggregation results. That table would automatically transform the repeated INSERTs on the same key to a DELETE-INSERT sequence.
The second item is actually pretty bad because it means that the last record deleted gets stuck in the aggregation results. The Coral8 solution for this situation is to send a row with all non-key fields set to NULL, to reset them (interestingly, it's a relatively recent addition, that bug took Coral8 years to notice). But with the opcodes available, we can as well send a DELETE rowop with a similar contents, the helper table will fill in the rest of the fields, and produce a clean DELETE.
All this can be done by the following changes. Add the table, remember its input label in $lbAvgPriceHelper. It will be used to send the aggregated rows instead of $tAvgPrice.
Then still use $tAvgPrice to print the records coming out, but now connect it after the helper table:
And in computeAverage() change the destination label and add the case for when the group becomes empty:
Then the output of the same example becomes:
All fixed, the proper DELETEs are coming out.
my $rtAvgPrice = Triceps::RowType->new( symbol => "string", # symbol traded id => "int32", # last trade's id price => "float64", # avg price of the last 2 trades ) or die "$!"; # place to send the average: could be a dummy label, but to keep the # code smalled also print the rows here, instead of in a separate label my $lbAverage = $uTrades->makeLabel($rtAvgPrice, "lbAverage", undef, sub { # (label, rowop) print($_[1]->printP(), "\n"); }) or die "$!"; # Send the average price of the symbol in the last modified row sub computeAverage # (row) { return unless defined $rLastMod; my $rhFirst = $tWindow->findIdx($itSymbol, $rLastMod) or die "$!"; my $rhEnd = $rhFirst->nextGroupIdx($itLast2) or die "$!"; print("Contents:\n"); my $avg; my ($sum, $count); my $rhLast; for (my $rhi = $rhFirst; !$rhi->same($rhEnd); $rhi = $rhi->nextIdx($itLast2)) { print(" ", $rhi->getRow()->printP(), "\n"); $rhLast = $rhi; $count++; $sum += $rhi->getRow()->get("price"); } if ($count) { $avg = $sum/$count; $uTrades->call($lbAverage->makeRowop(&Triceps::OP_INSERT, $rtAvgPrice->makeRowHash( symbol => $rhLast->getRow()->get("symbol"), id => $rhLast->getRow()->get("id"), price => $avg ) )); } }
For the demonstration, the aggregated records sent to $lbAverage get printed. The records being aggregated are printed during the iteration too. And here is a sample run's result, with the input records shown in italics:
OP_INSERT,1,AAA,10,10 Contents: id="1" symbol="AAA" price="10" size="10" lbAverage OP_INSERT symbol="AAA" id="1" price="10" OP_INSERT,3,AAA,20,20 Contents: id="1" symbol="AAA" price="10" size="10" id="3" symbol="AAA" price="20" size="20" lbAverage OP_INSERT symbol="AAA" id="3" price="15" OP_INSERT,5,AAA,30,30 Contents: id="3" symbol="AAA" price="20" size="20" id="5" symbol="AAA" price="30" size="30" lbAverage OP_INSERT symbol="AAA" id="5" price="25" OP_DELETE,3 Contents: id="5" symbol="AAA" price="30" size="30" lbAverage OP_INSERT symbol="AAA" id="5" price="30" OP_DELETE,5 Contents:
There are a couple of things to notice about it: it produces only the INSERT rowops, no DELETEs, and when the last record of the group is removed, that event produces nothing.
The first item is mildly problematic because the processing downstream from here might not be able to handle the updates properly without the DELETE rowops. It can be worked around fairly easily by connecting another table, with the same primary key as the aggregation key, to store the aggregation results. That table would automatically transform the repeated INSERTs on the same key to a DELETE-INSERT sequence.
The second item is actually pretty bad because it means that the last record deleted gets stuck in the aggregation results. The Coral8 solution for this situation is to send a row with all non-key fields set to NULL, to reset them (interestingly, it's a relatively recent addition, that bug took Coral8 years to notice). But with the opcodes available, we can as well send a DELETE rowop with a similar contents, the helper table will fill in the rest of the fields, and produce a clean DELETE.
All this can be done by the following changes. Add the table, remember its input label in $lbAvgPriceHelper. It will be used to send the aggregated rows instead of $tAvgPrice.
my $ttAvgPrice = Triceps::TableType->new($rtAvgPrice) ->addSubIndex("bySymbol", Triceps::IndexType->newHashed(key => [ "symbol" ]) ) or die "$!"; $ttAvgPrice->initialize() or die "$!"; my $tAvgPrice = $uTrades->makeTable($ttAvgPrice, &Triceps::EM_CALL, "tAvgPrice") or die "$!"; my $lbAvgPriceHelper = $tAvgPrice->getInputLabel() or die "$!";
Then still use $tAvgPrice to print the records coming out, but now connect it after the helper table:
$tAvgPrice->getOutputLabel()->chain($lbAverage) or die "$!";
And in computeAverage() change the destination label and add the case for when the group becomes empty:
... if ($count) { $avg = $sum/$count; $uTrades->call($lbAvgPriceHelper->makeRowop(&Triceps::OP_INSERT, $rtAvgPrice->makeRowHash( symbol => $rhLast->getRow()->get("symbol"), id => $rhLast->getRow()->get("id"), price => $avg ) )); } else { $uTrades->call($lbAvgPriceHelper->makeRowop(&Triceps::OP_DELETE, $rtAvgPrice->makeRowHash( symbol => $rLastMod->get("symbol"), ) )); } ...
Then the output of the same example becomes:
OP_INSERT,1,AAA,10,10Contents: id="1" symbol="AAA" price="10" size="10" tAvgPrice.out OP_INSERT symbol="AAA" id="1" price="10" OP_INSERT,3,AAA,20,20 Contents: id="1" symbol="AAA" price="10" size="10" id="3" symbol="AAA" price="20" size="20" tAvgPrice.out OP_DELETE symbol="AAA" id="1" price="10" tAvgPrice.out OP_INSERT symbol="AAA" id="3" price="15" OP_INSERT,5,AAA,30,30 Contents: id="3" symbol="AAA" price="20" size="20" id="5" symbol="AAA" price="30" size="30" tAvgPrice.out OP_DELETE symbol="AAA" id="3" price="15" tAvgPrice.out OP_INSERT symbol="AAA" id="5" price="25" OP_DELETE,3 Contents: id="5" symbol="AAA" price="30" size="30" tAvgPrice.out OP_DELETE symbol="AAA" id="5" price="25" tAvgPrice.out OP_INSERT symbol="AAA" id="5" price="30" OP_DELETE,5 Contents: tAvgPrice.out OP_DELETE symbol="AAA" id="5" price="30"
All fixed, the proper DELETEs are coming out.
Tuesday, December 27, 2011
printing the object contents
When debugging the programs, it's important to find from the error messages, what is going on, what kinds of objects are getting involved. Because of this, most of the Triceps objects provide a way to print out their contents into a string. This is done with the method print(). The simplest use is as follows:
Most of the objects tend to have a pretty complicated internal structure and are printed on multiple lines. They look better when the components are appropriately indented. The default call prints as if the basic message is un-indented, and indents every extra level by 2 spaces.
This can be changed with extra arguments. The general format of print() is:
where indent is the initial indentation, and subindent is the additional indentation for every level. So the default print() is equivalent to print("", " ").
A special case is
It prints the object in a single line, without line breaks.
The row types support the print() method. Here is an example of how a type would get printed:
Then $rt1->print() produces:
With extra arguments $rt1->print("++", "--"):
And finally with an undef argument $rt1->print(undef):
$message = "Error in object " . $object->print();
Most of the objects tend to have a pretty complicated internal structure and are printed on multiple lines. They look better when the components are appropriately indented. The default call prints as if the basic message is un-indented, and indents every extra level by 2 spaces.
This can be changed with extra arguments. The general format of print() is:
$object->print([indent, [subindent] ])
where indent is the initial indentation, and subindent is the additional indentation for every level. So the default print() is equivalent to print("", " ").
A special case is
$object->print(undef)
It prints the object in a single line, without line breaks.
The row types support the print() method. Here is an example of how a type would get printed:
$rt1 = Triceps::RowType->new( a => "uint8", b => "int32", c => "int64", d => "float64", e => "string", );
Then $rt1->print() produces:
row { uint8 a, int32 b, int64 c, float64 d, string e, }
With extra arguments $rt1->print("++", "--"):
row { ++--uint8 a, ++--int32 b, ++--int64 c, ++--float64 d, ++--string e, ++}
And finally with an undef argument $rt1->print(undef):
row { uint8 a, int32 b, int64 c, float64 d, string e, }
Subscribe to:
Posts (Atom)