Anki/proto/anki/stats.proto
Damien Elmes 37151213cd Move more of the graph processing into the backend
The existing architecture serializes all cards and revlog entries in
the search range into a protobuf message, which the web frontend needs
to decode and then process. The thinking at the time was that this would
make it easier for add-ons to add extra graphs, but in the ~2.5 years
since the new graphs were introduced, no add-ons appear to have taken
advantage of it.

The cards and revlog entries can grow quite large on large collections -
on a collection I tested with approximately 2.5M reviews, the serialized
data is about 110MB, which is a lot to have to deserialize in JavaScript.

This commit shifts the preliminary processing of the data to the Rust end,
which means the data is able to be processed faster, and less needs to
be sent to the frontend. On the test collection above, this reduces the
serialized data from about 110MB to about 160KB, resulting in a more
than 2x performance improvement, and reducing frontend memory usage from
about 400MB to about 40MB.

This also makes #2043 more feasible - while it is still about 50-100%
slower than protobufjs, with the much smaller message size, the difference
is only about 10ms.
2022-12-16 21:42:17 +10:00

176 lines
4.1 KiB
Protocol Buffer

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
syntax = "proto3";
option java_multiple_files = true;
package anki.stats;
import "anki/generic.proto";
import "anki/cards.proto";
service StatsService {
rpc CardStats(cards.CardId) returns (CardStatsResponse);
rpc Graphs(GraphsRequest) returns (GraphsResponse);
rpc GetGraphPreferences(generic.Empty) returns (GraphPreferences);
rpc SetGraphPreferences(GraphPreferences) returns (generic.Empty);
}
message CardStatsResponse {
message StatsRevlogEntry {
int64 time = 1;
RevlogEntry.ReviewKind review_kind = 2;
uint32 button_chosen = 3;
// seconds
uint32 interval = 4;
// per mill
uint32 ease = 5;
float taken_secs = 6;
}
repeated StatsRevlogEntry revlog = 1;
int64 card_id = 2;
int64 note_id = 3;
string deck = 4;
// Unix timestamps
int64 added = 5;
optional int64 first_review = 6;
optional int64 latest_review = 7;
optional int64 due_date = 8;
optional int32 due_position = 9;
// days
uint32 interval = 10;
// per mill
uint32 ease = 11;
uint32 reviews = 12;
uint32 lapses = 13;
float average_secs = 14;
float total_secs = 15;
string card_type = 16;
string notetype = 17;
}
message GraphsRequest {
string search = 1;
uint32 days = 2;
}
message GraphsResponse {
message Added {
map<int32, uint32> added = 1;
}
message Intervals {
map<uint32, uint32> intervals = 1;
}
message Eases {
map<uint32, uint32> eases = 1;
}
message FutureDue {
map<int32, uint32> future_due = 1;
bool have_backlog = 2;
}
message Today {
uint32 answer_count = 1;
uint32 answer_millis = 2;
uint32 correct_count = 3;
uint32 mature_correct = 4;
uint32 mature_count = 5;
uint32 learn_count = 6;
uint32 review_count = 7;
uint32 relearn_count = 8;
uint32 early_review_count = 9;
}
// each bucket is a 24 element vec
message Hours {
message Hour {
uint32 total = 1;
uint32 correct = 2;
}
repeated Hour one_month = 1;
repeated Hour three_months = 2;
repeated Hour one_year = 3;
repeated Hour all_time = 4;
}
message ReviewCountsAndTimes {
message Reviews {
uint32 learn = 1;
uint32 relearn = 2;
uint32 young = 3;
uint32 mature = 4;
uint32 filtered = 5;
}
map<int32, Reviews> count = 1;
map<int32, Reviews> time = 2;
}
// 4 element vecs for buttons 1-4
message Buttons {
message ButtonCounts {
repeated uint32 learning = 1;
repeated uint32 young = 2;
repeated uint32 mature = 3;
}
ButtonCounts one_month = 1;
ButtonCounts three_months = 2;
ButtonCounts one_year = 3;
ButtonCounts all_time = 4;
}
message CardCounts {
message Counts {
uint32 newCards = 1;
uint32 learn = 2;
uint32 relearn = 3;
uint32 young = 4;
uint32 mature = 5;
uint32 suspended = 6;
uint32 buried = 7;
}
// Buried/suspended cards are included in counts; suspended/buried counts
// are 0.
Counts including_inactive = 1;
// Buried/suspended cards are counted separately.
Counts excluding_inactive = 2;
}
Buttons buttons = 1;
CardCounts card_counts = 2;
Hours hours = 3;
Today today = 4;
Eases eases = 5;
Intervals intervals = 6;
FutureDue future_due = 7;
Added added = 8;
ReviewCountsAndTimes reviews = 9;
}
message GraphPreferences {
enum Weekday {
SUNDAY = 0;
MONDAY = 1;
FRIDAY = 5;
SATURDAY = 6;
}
Weekday calendar_first_day_of_week = 1;
bool card_counts_separate_inactive = 2;
bool browser_links_supported = 3;
bool future_due_show_backlog = 4;
}
message RevlogEntry {
enum ReviewKind {
LEARNING = 0;
REVIEW = 1;
RELEARNING = 2;
// Recent Anki versions only use this when rescheduling disabled
FILTERED = 3;
MANUAL = 4;
}
int64 id = 1;
int64 cid = 2;
int32 usn = 3;
uint32 button_chosen = 4;
int32 interval = 5;
int32 last_interval = 6;
uint32 ease_factor = 7;
uint32 taken_millis = 8;
ReviewKind review_kind = 9;
}