Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.

Commit 2fff514

Browse files
authored
break down device/nonce mismatch by OS (#2387)
1 parent 7146b9f commit 2fff514

File tree

9 files changed

+228
-108
lines changed

9 files changed

+228
-108
lines changed

assets/server/realmadmin/_stats_codes.html

+38
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,26 @@
7575
</small>
7676
</div>
7777

78+
<div class="card shadow-sm mb-3">
79+
<div class="card-header">
80+
<i class="bi bi-graph-up me-2"></i>
81+
User report device mismatch by OS
82+
</div>
83+
<div id="user_issued_mismatch_dashboard_div">
84+
<div id="user_issued_mismatch_chart_div" class="h-100 w-100" style="min-height:325px;">
85+
<p class="text-center font-italic w-100 mt-5">Loading chart...</p>
86+
</div>
87+
<div id="user_issued_mismatch_filter_div" class="text-end" style="height: 75px;"></div>
88+
</div>
89+
<small class="card-footer d-flex justify-content-between text-muted">
90+
<a href="#" data-bs-toggle="modal" data-bs-target="#realm-user-codes-mismatch-modal">Learn more about this chart</a>
91+
<span>
92+
<a href="/stats/realm.csv" class="me-1">CSV</a>
93+
<a href="/stats/realm.json" target="_blank">JSON</a>
94+
</span>
95+
</small>
96+
</div>
97+
7898
<div class="card shadow-sm mb-3">
7999
<div class="card-header">
80100
<i class="bi bi-graph-up me-2"></i>
@@ -182,6 +202,24 @@ <h5 class="modal-title">User report codes &amp; usage</h5>
182202
</div>
183203
</div>
184204

205+
<div class="modal fade" id="realm-user-codes-modal" data-backdrop="static" tabindex="-1">
206+
<div class="modal-dialog modal-dialog-centered">
207+
<div class="modal-content">
208+
<div class="modal-header">
209+
<h5 class="modal-title">User report device mismatch by OS</h5>
210+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
211+
</div>
212+
<div class="modal-body">
213+
<p>
214+
This graph shows device mismatch statistics by operating system attempting to
215+
claim the code. A mismatch occurs when a code was sent to a device other than
216+
the one that requested it.
217+
</p>
218+
</div>
219+
</div>
220+
</div>
221+
</div>
222+
185223
<div class="modal fade" id="invalid-modal" data-backdrop="static" tabindex="-1">
186224
<div class="modal-dialog modal-dialog-centered">
187225
<div class="modal-content">

assets/server/static/js/stats-codes.js

+20
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@
101101
]);
102102
},
103103
},
104+
{
105+
chartType: 'AreaChart',
106+
chartDiv: '#user_issued_mismatch_chart_div',
107+
dashboardDiv: '#user_issued_mismatch_dashboard_div',
108+
filterDiv: '#user_issued_mismatch_filter_div',
109+
headerFunc: (dataTable, hasKeyServerStats) => {
110+
dataTable.addColumn('date', 'Date');
111+
dataTable.addColumn('number', 'Unknown');
112+
dataTable.addColumn('number', 'iOS');
113+
dataTable.addColumn('number', 'Android');
114+
},
115+
rowFunc: (dataTable, row, hasKeyServerStats) => {
116+
dataTable.addRow([
117+
utcDate(row.date),
118+
row.data.user_reports_invalid_nonce_by_os.unknown_os,
119+
row.data.user_reports_invalid_nonce_by_os.ios,
120+
row.data.user_reports_invalid_nonce_by_os.android,
121+
]);
122+
},
123+
},
104124
{
105125
chartType: 'AreaChart',
106126
chartDiv: '#comparison_chart_div',

pkg/database/composite_stats.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ func (c CompositeStats) MarshalJSON() ([]byte, error) {
9696
data.UserReportsIssued = stat.RealmStats.UserReportsIssued
9797
data.UserReportsClaimed = stat.RealmStats.UserReportsClaimed
9898
data.UserReportsInvalidNonce = stat.RealmStats.UserReportsInvalidNonce
99+
data.UserReportsInvalidNonceByOS = CodesInvalidByOSData{
100+
UnknownOS: stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeUnknown],
101+
IOS: stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeIOS],
102+
Android: stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeAndroid],
103+
}
99104
data.TokensClaimed = stat.RealmStats.TokensClaimed
100105
data.TokensInvalid = stat.RealmStats.TokensInvalid
101106
data.UserReportTokensClaimed = stat.RealmStats.UserReportTokensClaimed
@@ -161,7 +166,8 @@ func (c CompositeStats) MarshalCSV() ([]byte, error) {
161166
"publish_requests_unknown", "publish_requests_android", "publish_requests_ios",
162167
"total_teks_published", "requests_with_revisions", "requests_missing_onset_date", "tek_age_distribution", "onset_to_upload_distribution",
163168
"user_reports_issued", "user_reports_claimed", "user_report_tokens_claimed",
164-
"codes_invalid_unknown_os", "codes_invalid_ios", "codes_invalid_android", "user_reports_invalid_nonce",
169+
"codes_invalid_unknown_os", "codes_invalid_ios", "codes_invalid_android",
170+
"user_reports_invalid_nonce", "user_reports_invalid_nonce_unknown_os", "user_reports_invalid_nonce_ios", "user_reports_invalid_nonce_android",
165171
}); err != nil {
166172
return nil, fmt.Errorf("failed to write CSV header: %w", err)
167173
}
@@ -223,9 +229,12 @@ func (c CompositeStats) MarshalCSV() ([]byte, error) {
223229
}
224230

225231
if stat.RealmStats == nil {
226-
row = append(row, "")
232+
row = append(row, "", "", "", "")
227233
} else {
228234
row = append(row, strconv.FormatUint(uint64(stat.RealmStats.UserReportsInvalidNonce), 10))
235+
row = append(row, strconv.FormatUint(uint64(stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeUnknown]), 10))
236+
row = append(row, strconv.FormatUint(uint64(stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeIOS]), 10))
237+
row = append(row, strconv.FormatUint(uint64(stat.RealmStats.UserReportsInvalidNonceByOS[OSTypeAndroid]), 10))
229238
}
230239

231240
// New stats should always be added to the end to preserve existing external user applications.

pkg/database/composite_stats_test.go

+39-37
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,21 @@ func TestCompositeStats_MarshalCSV(t *testing.T) {
4343
{
4444
Day: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
4545
RealmStats: &RealmStat{
46-
Date: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
47-
RealmID: 1,
48-
CodesIssued: 10,
49-
CodesClaimed: 9,
50-
CodesInvalid: 1,
51-
CodesInvalidByOS: []int64{0, 1, 0},
52-
UserReportsIssued: 3,
53-
UserReportsClaimed: 2,
54-
UserReportsInvalidNonce: 0,
55-
TokensClaimed: 7,
56-
TokensInvalid: 2,
57-
UserReportTokensClaimed: 2,
58-
CodeClaimMeanAge: FromDuration(time.Minute),
59-
CodeClaimAgeDistribution: []int32{1, 3, 4},
46+
Date: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
47+
RealmID: 1,
48+
CodesIssued: 10,
49+
CodesClaimed: 9,
50+
CodesInvalid: 1,
51+
CodesInvalidByOS: []int64{0, 1, 0},
52+
UserReportsIssued: 3,
53+
UserReportsClaimed: 2,
54+
UserReportsInvalidNonce: 0,
55+
UserReportsInvalidNonceByOS: []int64{0, 0, 0},
56+
TokensClaimed: 7,
57+
TokensInvalid: 2,
58+
UserReportTokensClaimed: 2,
59+
CodeClaimMeanAge: FromDuration(time.Minute),
60+
CodeClaimAgeDistribution: []int32{1, 3, 4},
6061
},
6162
KeyServerStats: &keyserver.StatsDay{
6263
Day: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
@@ -73,10 +74,10 @@ func TestCompositeStats_MarshalCSV(t *testing.T) {
7374
},
7475
},
7576
},
76-
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce
77-
2020-02-03,10,9,1,7,2,60,1|3|4,2,39,12,49,3,2,0|1|2|3|4|5|6|7|8|9|10|11|12|13|14,,3,2,2,0,1,0,0
77+
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce,user_reports_invalid_nonce_unknown_os,user_reports_invalid_nonce_ios,user_reports_invalid_nonce_android
78+
2020-02-03,10,9,1,7,2,60,1|3|4,2,39,12,49,3,2,0|1|2|3|4|5|6|7|8|9|10|11|12|13|14,,3,2,2,0,1,0,0,0,0,0
7879
`,
79-
expJSON: `{"realm_id":1,"has_key_server_stats":true,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":10,"codes_claimed":9,"codes_invalid":1,"codes_invalid_by_os":{"unknown_os":0,"ios":1,"android":0},"user_reports_issued":3,"user_reports_claimed":2,"user_reports_invalid_nonce":0,"tokens_claimed":7,"tokens_invalid":2,"user_report_tokens_claimed":2,"code_claim_mean_age_seconds":60,"code_claim_age_distribution":[1,3,4],"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":2,"android":39,"ios":12},"total_teks_published":49,"requests_with_revisions":3,"tek_age_distribution":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"onset_to_upload_distribution":null,"requests_missing_onset_date":2,"total_publish_requests":53}}]}`,
80+
expJSON: `{"realm_id":1,"has_key_server_stats":true,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":10,"codes_claimed":9,"codes_invalid":1,"codes_invalid_by_os":{"unknown_os":0,"ios":1,"android":0},"user_reports_issued":3,"user_reports_claimed":2,"user_reports_invalid_nonce":0,"user_reports_invalid_nonce_by_os":{"unknown_os":0,"ios":0,"android":0},"tokens_claimed":7,"tokens_invalid":2,"user_report_tokens_claimed":2,"code_claim_mean_age_seconds":60,"code_claim_age_distribution":[1,3,4],"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":2,"android":39,"ios":12},"total_teks_published":49,"requests_with_revisions":3,"tek_age_distribution":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"onset_to_upload_distribution":null,"requests_missing_onset_date":2,"total_publish_requests":53}}]}`,
8081
},
8182
{
8283
name: "no_realm_stats",
@@ -98,38 +99,39 @@ func TestCompositeStats_MarshalCSV(t *testing.T) {
9899
},
99100
},
100101
},
101-
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce
102-
2020-02-03,,,,,,,,2,39,12,49,3,2,0|1|2|3|4|5|6|7|8|9|10|11|12|13|14,,,,,,,,
102+
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce,user_reports_invalid_nonce_unknown_os,user_reports_invalid_nonce_ios,user_reports_invalid_nonce_android
103+
2020-02-03,,,,,,,,2,39,12,49,3,2,0|1|2|3|4|5|6|7|8|9|10|11|12|13|14,,,,,,,,,,,
103104
`,
104-
expJSON: `{"realm_id":0,"has_key_server_stats":true,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":0,"codes_claimed":0,"codes_invalid":0,"codes_invalid_by_os":{"unknown_os":0,"ios":0,"android":0},"user_reports_issued":0,"user_reports_claimed":0,"user_reports_invalid_nonce":0,"tokens_claimed":0,"tokens_invalid":0,"user_report_tokens_claimed":0,"code_claim_mean_age_seconds":0,"code_claim_age_distribution":null,"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":2,"android":39,"ios":12},"total_teks_published":49,"requests_with_revisions":3,"tek_age_distribution":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"onset_to_upload_distribution":null,"requests_missing_onset_date":2,"total_publish_requests":53}}]}`,
105+
expJSON: `{"realm_id":0,"has_key_server_stats":true,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":0,"codes_claimed":0,"codes_invalid":0,"codes_invalid_by_os":{"unknown_os":0,"ios":0,"android":0},"user_reports_issued":0,"user_reports_claimed":0,"user_reports_invalid_nonce":0,"user_reports_invalid_nonce_by_os":{"unknown_os":0,"ios":0,"android":0},"tokens_claimed":0,"tokens_invalid":0,"user_report_tokens_claimed":0,"code_claim_mean_age_seconds":0,"code_claim_age_distribution":null,"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":2,"android":39,"ios":12},"total_teks_published":49,"requests_with_revisions":3,"tek_age_distribution":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"onset_to_upload_distribution":null,"requests_missing_onset_date":2,"total_publish_requests":53}}]}`,
105106
},
106107
{
107108
name: "no_keyserver_stats",
108109
stats: []*CompositeDay{
109110
{
110111
Day: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
111112
RealmStats: &RealmStat{
112-
Date: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
113-
RealmID: 1,
114-
CodesIssued: 10,
115-
CodesClaimed: 9,
116-
CodesInvalid: 1,
117-
CodesInvalidByOS: []int64{0, 1, 0},
118-
UserReportsIssued: 3,
119-
UserReportsClaimed: 2,
120-
UserReportsInvalidNonce: 1,
121-
TokensClaimed: 7,
122-
TokensInvalid: 2,
123-
UserReportTokensClaimed: 2,
124-
CodeClaimMeanAge: FromDuration(time.Minute),
125-
CodeClaimAgeDistribution: []int32{1, 3, 4},
113+
Date: time.Date(2020, 2, 3, 0, 0, 0, 0, time.UTC),
114+
RealmID: 1,
115+
CodesIssued: 10,
116+
CodesClaimed: 9,
117+
CodesInvalid: 1,
118+
CodesInvalidByOS: []int64{0, 1, 0},
119+
UserReportsIssued: 3,
120+
UserReportsClaimed: 2,
121+
UserReportsInvalidNonce: 1,
122+
UserReportsInvalidNonceByOS: []int64{0, 0, 1},
123+
TokensClaimed: 7,
124+
TokensInvalid: 2,
125+
UserReportTokensClaimed: 2,
126+
CodeClaimMeanAge: FromDuration(time.Minute),
127+
CodeClaimAgeDistribution: []int32{1, 3, 4},
126128
},
127129
},
128130
},
129-
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce
130-
2020-02-03,10,9,1,7,2,60,1|3|4,,,,,,,,,3,2,2,0,1,0,1
131+
expCSV: `date,codes_issued,codes_claimed,codes_invalid,tokens_claimed,tokens_invalid,code_claim_mean_age_seconds,code_claim_age_distribution,publish_requests_unknown,publish_requests_android,publish_requests_ios,total_teks_published,requests_with_revisions,requests_missing_onset_date,tek_age_distribution,onset_to_upload_distribution,user_reports_issued,user_reports_claimed,user_report_tokens_claimed,codes_invalid_unknown_os,codes_invalid_ios,codes_invalid_android,user_reports_invalid_nonce,user_reports_invalid_nonce_unknown_os,user_reports_invalid_nonce_ios,user_reports_invalid_nonce_android
132+
2020-02-03,10,9,1,7,2,60,1|3|4,,,,,,,,,3,2,2,0,1,0,1,0,0,1
131133
`,
132-
expJSON: `{"realm_id":1,"has_key_server_stats":false,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":10,"codes_claimed":9,"codes_invalid":1,"codes_invalid_by_os":{"unknown_os":0,"ios":1,"android":0},"user_reports_issued":3,"user_reports_claimed":2,"user_reports_invalid_nonce":1,"tokens_claimed":7,"tokens_invalid":2,"user_report_tokens_claimed":2,"code_claim_mean_age_seconds":60,"code_claim_age_distribution":[1,3,4],"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":0,"android":0,"ios":0},"total_teks_published":0,"requests_with_revisions":0,"tek_age_distribution":null,"onset_to_upload_distribution":null,"requests_missing_onset_date":0,"total_publish_requests":0}}]}`,
134+
expJSON: `{"realm_id":1,"has_key_server_stats":false,"statistics":[{"date":"2020-02-03T00:00:00Z","data":{"codes_issued":10,"codes_claimed":9,"codes_invalid":1,"codes_invalid_by_os":{"unknown_os":0,"ios":1,"android":0},"user_reports_issued":3,"user_reports_claimed":2,"user_reports_invalid_nonce":1,"user_reports_invalid_nonce_by_os":{"unknown_os":0,"ios":0,"android":1},"tokens_claimed":7,"tokens_invalid":2,"user_report_tokens_claimed":2,"code_claim_mean_age_seconds":60,"code_claim_age_distribution":[1,3,4],"day":"0001-01-01T00:00:00Z","publish_requests":{"unknown":0,"android":0,"ios":0},"total_teks_published":0,"requests_with_revisions":0,"tek_age_distribution":null,"onset_to_upload_distribution":null,"requests_missing_onset_date":0,"total_publish_requests":0}}]}`,
133135
},
134136
}
135137

pkg/database/migrations.go

+11
Original file line numberDiff line numberDiff line change
@@ -2602,6 +2602,17 @@ func (db *Database) Migrations(ctx context.Context) []*gormigrate.Migration {
26022602
DROP COLUMN IF EXISTS user_reports_invalid_nonce`)
26032603
},
26042604
},
2605+
{
2606+
ID: "00125-AddUserReportInvalidNonceByOS",
2607+
Migrate: func(tx *gorm.DB) error {
2608+
return multiExec(tx,
2609+
`ALTER TABLE realm_stats ADD COLUMN IF NOT EXISTS user_reports_invalid_nonce_by_os BIGINT[]`)
2610+
},
2611+
Rollback: func(tx *gorm.DB) error {
2612+
return multiExec(tx,
2613+
`ALTER TABLE realm_stats DROP COLUMN IF EXISTS user_reports_invalid_nonce_by_os`)
2614+
},
2615+
},
26052616
}
26062617
}
26072618

pkg/database/realm.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1916,7 +1916,8 @@ func (r *Realm) Stats(db *Database) (RealmStats, error) {
19161916
COALESCE(s.code_claim_age_distribution, array[]::integer[]) AS code_claim_age_distribution,
19171917
COALESCE(s.code_claim_mean_age, 0) AS code_claim_mean_age,
19181918
COALESCE(s.codes_invalid_by_os, array[0,0,0]::bigint[]) AS codes_invalid_by_os,
1919-
COALESCE(s.user_reports_invalid_nonce, 0) AS user_reports_invalid_nonce
1919+
COALESCE(s.user_reports_invalid_nonce, 0) AS user_reports_invalid_nonce,
1920+
COALESCE(s.user_reports_invalid_nonce_by_os, array[0,0,0]::bigint[]) AS user_reports_invalid_nonce_by_os
19201921
FROM (
19211922
SELECT date::date FROM generate_series($2, $3, '1 day'::interval) date
19221923
) d
@@ -1930,6 +1931,9 @@ func (r *Realm) Stats(db *Database) (RealmStats, error) {
19301931
}
19311932
return nil, err
19321933
}
1934+
for _, s := range stats {
1935+
s.BackfillBadNonceByOS()
1936+
}
19331937

19341938
return stats, nil
19351939
}

0 commit comments

Comments
 (0)