Skip to content

Commit 53260de

Browse files
authored
chore: add Scarf package to track usage (#35)
1 parent 1112f7a commit 53260de

8 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
"""Scarf analytics utility functions."""
18+
19+
import os
20+
import platform
21+
import threading
22+
import urllib.request
23+
24+
25+
def make_scarf_call(language: str) -> None:
26+
"""Make a call to Scarf for usage analytics.
27+
28+
Args:
29+
language: The language identifier (e.g., 'python', 'adbc', 'dbapi')
30+
"""
31+
32+
def _scarf_request():
33+
try:
34+
# Check for user opt-out
35+
if (
36+
os.environ.get("SCARF_NO_ANALYTICS") is not None
37+
or os.environ.get("DO_NOT_TRACK") is not None
38+
):
39+
return
40+
41+
# Detect architecture and OS
42+
arch = platform.machine().lower().replace(" ", "_")
43+
os_name = platform.system().lower().replace(" ", "_")
44+
45+
# Construct Scarf URL
46+
scarf_url = (
47+
f"https://sedona.gateway.scarf.sh/sedona-db/{arch}/{os_name}/{language}"
48+
)
49+
50+
# Make the request in a non-blocking way
51+
urllib.request.urlopen(scarf_url, timeout=1)
52+
except Exception:
53+
# Silently ignore any errors - we don't want Scarf calls to break user code
54+
pass
55+
56+
# Run in a separate thread to avoid blocking
57+
thread = threading.Thread(target=_scarf_request, daemon=True)
58+
thread.start()

python/sedonadb/python/sedonadb/adbc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
def connect() -> adbc_driver_manager.AdbcDatabase:
2323
"""Create a low level ADBC connection to Sedona."""
24+
# Make Scarf call for usage analytics
25+
from ._scarf import make_scarf_call
26+
27+
make_scarf_call("adbc")
2428
return adbc_driver_manager.AdbcDatabase(
2529
driver=_lib.__file__, entrypoint="AdbcSedonadbDriverInit"
2630
)

python/sedonadb/python/sedonadb/context.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ def sql(self, sql: str) -> DataFrame:
149149

150150
def connect() -> SedonaContext:
151151
"""Create a new [SedonaContext][sedonadb.context.SedonaContext]"""
152+
# Make Scarf call for usage analytics
153+
from ._scarf import make_scarf_call
154+
155+
make_scarf_call("python")
152156
return SedonaContext()
153157

154158

python/sedonadb/python/sedonadb/dbapi.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121

2222
def connect(**kwargs) -> "Connection":
2323
"""Connect to Sedona via ADBC."""
24+
# Make Scarf call for usage analytics
25+
from ._scarf import make_scarf_call
26+
27+
make_scarf_call("dbapi")
28+
2429
db = None
2530
conn = None
2631

rust/sedona/src/context.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ pub struct SedonaContext {
6767
impl SedonaContext {
6868
/// Creates a new context with default options
6969
pub fn new() -> Self {
70+
// Make Scarf call for usage analytics
71+
crate::scarf::make_scarf_call("rust");
72+
7073
// This will panic only if the default build settings are
7174
// incorrect which we test!
7275
Self::new_from_context(SessionContext::new()).unwrap()
@@ -77,6 +80,9 @@ impl SedonaContext {
7780
/// Initializes a context from the current environment and registers access
7881
/// to the local file system.
7982
pub async fn new_local_interactive() -> Result<Self> {
83+
// Make Scarf call for usage analytics
84+
crate::scarf::make_scarf_call("rust");
85+
8086
// These three objects enable configuring various elements of the runtime.
8187
// Eventually we probably want to have a common set of configuration parameters
8288
// exposed via the CLI/Python as arguments, via ADBC as connection options,

rust/sedona/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ mod object_storage;
2222
pub mod random_geometry_provider;
2323
pub mod reader;
2424
pub mod record_batch_reader_provider;
25+
pub mod scarf;
2526
pub mod show;

rust/sedona/src/scarf.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
/// Make a call to Scarf for usage analytics.
19+
///
20+
/// # Arguments
21+
///
22+
/// * `language` - The language identifier (e.g., "rust", "cli")
23+
pub fn make_scarf_call(language: &str) {
24+
let language = language.to_string();
25+
std::thread::spawn(move || {
26+
let _ = scarf_request(&language);
27+
});
28+
}
29+
30+
fn scarf_request(language: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
31+
// Check for user opt-out
32+
if std::env::var("SCARF_NO_ANALYTICS").is_ok() || std::env::var("DO_NOT_TRACK").is_ok() {
33+
return Ok(());
34+
}
35+
36+
// Detect architecture and OS
37+
let arch = std::env::consts::ARCH.to_lowercase().replace(' ', "_");
38+
let os = std::env::consts::OS.to_lowercase().replace(' ', "_");
39+
40+
// Construct Scarf URL
41+
let scarf_url = format!("https://sedona.gateway.scarf.sh/sedona-db/{arch}/{os}/{language}");
42+
43+
// Make the request using std::net::TcpStream for a simple HTTP GET
44+
if let Ok(url) = url::Url::parse(&scarf_url) {
45+
if let Some(host) = url.host_str() {
46+
let port = url.port().unwrap_or(443);
47+
let path = url.path();
48+
49+
// Try to make a simple HTTP request
50+
if let Ok(addr) = format!("{host}:{port}").parse::<std::net::SocketAddr>() {
51+
if let Ok(mut stream) =
52+
std::net::TcpStream::connect_timeout(&addr, std::time::Duration::from_secs(1))
53+
{
54+
let request =
55+
format!("GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n");
56+
let _ = std::io::Write::write_all(&mut stream, request.as_bytes());
57+
}
58+
}
59+
}
60+
}
61+
62+
Ok(())
63+
}

sedona-cli/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ async fn main_inner() -> Result<()> {
128128
println!("Sedona CLI v{DATAFUSION_CLI_VERSION}");
129129
}
130130

131+
// Make Scarf call for usage analytics
132+
sedona::scarf::make_scarf_call("cli");
133+
131134
if let Some(ref path) = args.data_path {
132135
let p = Path::new(path);
133136
env::set_current_dir(p).unwrap();

0 commit comments

Comments
 (0)