Skip to content
Snippets Groups Projects
Commit 0a374e49 authored by Sameer Al-Sakran's avatar Sameer Al-Sakran
Browse files

first stab at some anonymous usage tracking

parent 62797475
No related branches found
No related tags found
No related merge requests found
(ns metabase.util.stats
"Functions which summarize the usage of an instance"
(:require [clojure.tools.logging :as log]
[clj-http.client :as client]
(metabase [config :as config]
[db :as db])
[metabase.public-settings :as settings]
(metabase.models [field :as field]
[table :as table]
[setting :as setting])
[metabase.util :as u]))
(def ^:private ^:const ^String metabase-usage-url "https://kqatai1z3c.execute-api.us-east-1.amazonaws.com/prod/ServerStatsCollector")
(def ^:private ^Integer anonymous-id
"Generate an anonymous id. Don't worry too much about hash collisions or localhost cases, etc.
The goal is to be able to get a rough sense for how many different hosts are throwing a specific error/event."
(hash (str (java.net.InetAddress/getLocalHost))))
(defn- anon-tracking-enabled?
"To avoid a circular reference"
[]
(require 'metabase.public-settings)
(resolve 'metabase.public-settings/anon-tracking-enabled))
(defn- bin-micro-number
"Return really small bin number. Assumes positive inputs"
[x]
(cond
(= 0 x) "0"
(= 1 x) "1"
(= 2 x) "2"
(> x 2) "3+")
)
(defn- bin-small-number
"Return small bin number. Assumes positive inputs"
[x]
(cond
(= 0 x) "0"
(<= 1 x 5) "1-5"
(<= 6 x 10) "6-10"
(<= 11 x 25) "11-25"
(> x 25) "25+")
)
(defn- bin-medium-number
"Return medium bin number. Assumes positive inputs"
[x]
(cond
(= 0 x) "0"
(<= 1 x 5) "1-5"
(<= 6 x 10) "6-10"
(<= 11 x 25) "11-25"
(<= 26 x 50) "26-50"
(<= 51 x 100) "51-100"
(<= 101 x 250) "101-250"
(> x 250) "250+")
)
(defn- bin-large-number
"Return large bin number. Assumes positive inputs"
[x]
(cond
(= 0 x) "0"
(<= 1 x 10) "1-10"
(<= 11 x 50) "11-50"
(<= 51 x 250) "51-250"
(<= 251 x 1000) "251-1000"
(<= 1001 x 10000) "1001-10000"
(> x 10000) "10000+")
)
(defn- get-settings
"Figure out global info aboutt his instance"
[]
{:version (config/mb-version-info :tag)
:running_on "unknown" ;; HOW DO I GET THIS? beanstalk vs heroku vs mac vs 'unknown'
:application_database (config/config-str :mb-db-type)
:check_for_updates (setting/get :check-for-updates)
:site_name (not= settings/site-name "Metabase")
:report_timezone (setting/get :report-timezone)
:friendly_names true ;; HOW DO I GET THIS?
:email_configured ((resolve 'metabase.email/email-configured?))
:slack_configured ((resolve 'metabase.integrations.slack/slack-configured?))
:sso_configured true ;; HOW DO I GET THIS?
:instance_started (new java.util.Date) ;; HOW DO I GET THIS?
:has_sample_data (db/exists? 'Database, :is_sample true)
}
)
;; util function
(def add-summaries
"add up some dictionaries"
(partial merge-with +)
)
;; User metrics
(defn user-dims
"characterize a user record"
[user]
{:total 1
:active (if (user :is_active) 1 0) ;; HOW DO I GET THE LIST OF ALL USERS INCLUDING INACTIVES?
:admin (if (user :is_superuser) 1 0)
:logged-in (if (nil? (user :last_login)) 0 1)
:sso (if (nil? (user :google_auth)) 0 1)}
)
(defn get-user-metrics
"Get metrics based on user records
TODO: get activity in terms of created questions, pulses and dashboards"
[]
(let [users (db/select 'User)]
{:users (apply add-summaries (map user-dims users))}))
(defn get-group-metrics
"Get metrics based on groups:
TODO characterize by # w/ sql access, # of users, no self-serve data access"
[]
(let [groups (db/select 'PermissionsGroup)]
{:groups (count groups)}))
;; Artifact Metrics
(defn question-dims
"characterize a saved question
TODO: characterize by whether it has params, # of revisions, created by an admin"
[question]
(print question)
{:total 1
:native (if (= (question :iquery_type) "native") 1 0)
:gui (if (not= (question :iquery_type) "native") 1 0)}
)
(defn get-question-metrics
"Get metrics based on questions
TODO characterize by # of labels
characterize by # executions and avg latency"
[]
(let [questions (db/select 'Card)]
{:questions (count questions)}))
(defn get-dashboard-metrics
"Get metrics based on dashboards
TODO characterize by # of cards, # of revisions, and created by an admin"
[]
(let [dashboards (db/select 'Dashboard)]
{:dashboards (count dashboards)}))
(defn get-pulse-metrics
"Get metrics based on pulses
TODO: characterize by # of cards, non-user account emails, slack vs email, # emails"
[]
(let [pulses (db/select 'Pulse)]
{:pulses (count pulses)}))
(defn get-label-metrics
"Get metrics based on labels
TODO: characterize by the # of cards each label has + how many cards are unlabeled"
[]
(let [labels (db/select 'CardLabel)]
{:labels (count labels)}))
;; Metadata Metrics
(defn get-database-metrics
"Get metrics based on databases
TODO: characterize by # of schemas, tables, fields
characterize by in-depth analysis enabled "
[]
(let [databases (db/select 'Database)]
{:databases (count databases)}))
(defn get-schema-metrics
"Get metrics based on schemas
TODO merge this w/ tables?
characterize by # tables"
[]
(let [schemas (db/select 'Table)]
{:schemas (count schemas)}))
(defn get-table-metrics
"Get metrics based on tables
TODO characterize by # fields"
[]
(let [tables (db/select 'Table)]
{:tables (count tables)}))
(defn get-field-metrics
"Get metrics based on fields"
[]
(let [fields (db/select 'Field)]
{:fields (count fields)}))
(defn get-segment-metrics
"Get metrics based on segments"
[]
(let [segments (db/select 'Segment)]
{:segments (count segments)}))
(defn get-metric-metrics
"Get metrics based on metrics"
[]
(let [metrics (db/select 'Metric)]
{:metrics (count metrics)}))
;; Execution Metrics
(defn get-execution-metrics
"Get metrics based on executions.
This should be done in a single pass, as there might
be a LOT of query executions in a normal instance
TODO: characterize by ad hoc vs cards
characterize by latency
characterize by error status"
[]
(let [executions (db/select 'QueryExecution)]
{:executions (count executions)}))
(defn get-map-metrics
"Get metrics based on custom geojson maps
TODO figure out how to get at these"
[]
(let [maps (db/select 'Segment)]
{:maps (count maps)}))
(defn get-anonymous-usage-stats
"generate a map of the usage stats for this instance"
[]
(when [setting/get :anon-tracking-enabled]
;do stuff
(merge (get-settings)
{:uuid anonymous-id :timestamp (new java.util.Date)}
{:stats {
:user (get-user-metrics)
:question (get-question-metrics)
:dashboard (get-dashboard-metrics)
:database (get-database-metrics)
:table (get-table-metrics)
:field (get-field-metrics)
:pulse (get-pulse-metrics)
:segment (get-segment-metrics)
:metric (get-metric-metrics)
:group (get-group-metrics)
:label (get-label-metrics)
:execution (get-execution-metrics)}})))
(defn- send-stats
"send stats to Metabase tracking server"
[stats]
(try
(print (client/post metabase-usage-url {:form-params stats :content-type :json :throw-entire-message? true}))
(catch Throwable e
(log/error "Sending usage stats FAILED: " (.getMessage e)))))
(defn phone-home-stats
"doc-string"
[]
(when (anon-tracking-enabled?)
(send-stats (get-anonymous-usage-stats))))
\ No newline at end of file
(ns metabase.util.stats-test
(:require [expectations :refer :all]
[metabase.util.stats :refer :all]
[metabase.test.util :as tu]))
(tu/resolve-private-vars metabase.util.stats
bin-micro-number bin-small-number bin-medium-number bin-large-number)
(expect "0" (bin-micro-number 0))
(expect "1" (bin-micro-number 1))
(expect "2" (bin-micro-number 2))
(expect "3+" (bin-micro-number 3))
(expect "3+" (bin-micro-number 100))
(expect "0" (bin-small-number 0))
(expect "1-5" (bin-small-number 1))
(expect "1-5" (bin-small-number 5))
(expect "6-10" (bin-small-number 6))
(expect "6-10" (bin-small-number 10))
(expect "11-25" (bin-small-number 11))
(expect "11-25" (bin-small-number 25))
(expect "25+" (bin-small-number 26))
(expect "25+" (bin-small-number 500))
(expect "0" (bin-medium-number 0))
(expect "1-5" (bin-medium-number 1))
(expect "1-5" (bin-medium-number 5))
(expect "6-10" (bin-medium-number 6))
(expect "6-10" (bin-medium-number 10))
(expect "11-25" (bin-medium-number 11))
(expect "11-25" (bin-medium-number 25))
(expect "26-50" (bin-medium-number 26))
(expect "26-50" (bin-medium-number 50))
(expect "51-100" (bin-medium-number 51))
(expect "51-100" (bin-medium-number 100))
(expect "101-250" (bin-medium-number 101))
(expect "101-250" (bin-medium-number 250))
(expect "250+" (bin-medium-number 251))
(expect "250+" (bin-medium-number 5000))
(expect "0" (bin-large-number 0))
(expect "1-10" (bin-large-number 1))
(expect "1-10" (bin-large-number 10))
(expect "11-50" (bin-large-number 11))
(expect "11-50" (bin-large-number 50))
(expect "51-250" (bin-large-number 51))
(expect "51-250" (bin-large-number 250))
(expect "251-1000" (bin-large-number 251))
(expect "251-1000" (bin-large-number 1000))
(expect "1001-10000" (bin-large-number 1001))
(expect "1001-10000" (bin-large-number 10000))
(expect "10000+" (bin-large-number 10001))
(expect "10000+" (bin-large-number 100000))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment