Medidas
Noventa y cinco medidas. Cada fila dice cómo se fecha un punto, porque eso es lo que decide qué preguntas puede responder.
Cómo leer las tablas
Sección titulada «Cómo leer las tablas»| Fechado | Significa |
|---|---|
| fechado | El punto lleva el momento en que ocurrió la cosa, así que la historia es real y volver a recoger reescribe las mismas filas |
| diario | Una instantánea sin fecha propia, sellada al inicio del día UTC para que las pasadas de un día converjan en una fila en vez de apilarse |
| ahora | Un estado actual, que solo tiene sentido como “qué es cierto en este momento” |
Toda medida lleva owner, repo y full_name como etiquetas salvo que sea de
ámbito de cuenta, en cuyo caso lleva user. Las tres viajan juntas o no viaja
ninguna, y repo es siempre el nombre corto, tanto en el repositorio de otra
persona como en el tuyo: owner=golang, repo=go, full_name=golang/go. Eso
es lo que permite que un filtro escrito para una medida responda en todas las
demás, y es de donde se construye la variable de repositorio de los dashboards.
El nombre corto no es una identidad, porque dos dueños pueden usar el mismo, así
que una consulta que necesita identidad agrupa por full_name y una que
necesita una persona agrupa por owner.
Esto no siempre fue cierto en todas. Doce no llevaban owner y metían el nombre
completo dentro de repo, y gh_billing_usage tenía un repo corto, ningún
owner y un org. Las tres formas no se solapaban en absoluto, que es peor que
solaparse mal: un filtro construido desde una de ellas no encontraba nada de
nada en las otras, y una unión de dos contaba cada repositorio dos veces. Ahora
son una sola forma. org se ha ido con ellas, y no se pierde nada:
organizationName no es una propiedad del endpoint de facturación que esta
herramienta llama, así que la etiqueta solo podía llevar (none), y en la forma
de organización de ese informe nombra a la organización cuyo informe se pidió,
que es la dueña. owner lo lleva.
Actualizar desde una versión anterior a esta forma es un cambio incompatible con lo ya almacenado, y es más grande que una costura. Lee los cuatro párrafos siguientes antes de actualizar una base de datos que quieras conservar.
Tus filas existentes no se convierten, y la mayoría se vuelven a escribir.
Las filas ya almacenadas conservan las etiquetas viejas y nada las reescribe,
porque en InfluxDB el conjunto de etiquetas forma parte de la identidad de un
punto. Lo que es fácil pasar por alto es que la mayoría de las doce se fechan en
el momento del propio ítem y se vuelven a ofrecer en cada barrido, y el libro de
escrituras se indexa por el conjunto de etiquetas. Cambia el conjunto y cada uno
de esos puntos falla la búsqueda, así que el primer barrido tras la
actualización vuelve a escribir todo el histórico retenido, con las mismas
marcas de tiempo, bajo las etiquetas nuevas, al lado de las filas viejas. Eso
no es una costura que puedas esperar a que pase. Una suma sobre un rango que
cubra el histórico reescrito se duplica de inmediato y sigue duplicada,
mientras GitHub siga sirviendo esos ítems. En la cuenta con la que se desarrolló
esto eran un año entero de gh_contribution_day_repo, que un panel del producto
suma, y varios cientos de filas de gh_issue_comment,
gh_external_contribution y gh_discussion_comment. Solo gh_event y
gh_notification se comportan como una costura, porque son ventanas que GitHub
olvida.
Así que hay dos salidas honestas, y esperar no es una de ellas. Recrear la base de datos, que es lo que hizo el autor; o borrar tú las filas de la forma vieja, lo que en InfluxDB 3 significa eliminar las trece tablas, ya que una etiqueta no se puede renombrar in situ y un predicado de borrado no puede nombrar una etiqueta que las filas nuevas no llevan. No hay migración ni la habrá: renombrar una etiqueta significa reescribir cada fila afectada bajo una identidad nueva, que es una restauración y no una actualización.
El destino PostgreSQL deja de cargar hasta que se cambien sus tablas. Emite
CREATE TABLE IF NOT EXISTS, que no hace nada contra una tabla creada por una
versión anterior, así que el INSERT que viene detrás nombra columnas owner y
full_name que no existen y un ON CONFLICT con una clave que tampoco existe,
y psql lo rechaza en las trece medidas. Elimina esas tablas y deja que el
siguiente fichero las recree, o añade las dos columnas y reconstruye cada clave
primaria:
ALTER TABLE gh_event ADD COLUMN IF NOT EXISTS "owner" TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS "full_name" TEXT NOT NULL DEFAULT '';ALTER TABLE gh_event DROP CONSTRAINT IF EXISTS gh_event_pkey;ALTER TABLE gh_event ADD PRIMARY KEY ("time", "action", "full_name", "owner", "ref_type", "repo", "type");La clave reconstruida tiene que ser exactamente la que declararía esta
herramienta para esa medida: time seguido de las columnas de etiqueta de la
tabla de arriba, por orden de nombre. No la armes con las columnas que tu tabla
tenga. Un gh_billing_usage actualizado aún lleva una columna org de la forma
vieja que ya no escribe nadie y que no forma parte de la clave; si la incluyes,
el ON CONFLICT que emite el destino no coincide con ninguna restricción única
y la carga vuelve a fallar con un mensaje que no apunta a nada. Para comprobar
tu trabajo, mira el CREATE TABLE que el destino escribe para esa medida en un
fichero nuevo: su lista de PRIMARY KEY es la respuesta.
Añadir las columnas conserva las filas viejas pero no las convierte: llevan la cadena vacía donde las nuevas llevan un dueño, así que cuentan doble igual que en InfluxDB. Eliminar es la opción limpia en los dos almacenes.
Las consultas que hayas escrito tú hay que actualizarlas, y solo algunas
fallan en voz alta. gh_billing_usage.org ya no existe, y en InfluxDB 3
nombrar una columna que ninguna fila ha escrito falla al planificar, así que esa
te avisa. Un filtro como gh_event WHERE repo = 'owner/name' sigue siendo
válido y devuelve nada en silencio; pasa a ser full_name = 'owner/name'.
Casi todas llevan además un campo
url: la página de GitHub de aquello de lo que habla la fila, para que una fila
de un dashboard que nombra algo también pueda abrirlo. Una url es absoluta o
está ausente, porque los dashboards enlazan al propio valor. Toda medida que la
lleva se enlaza ítem a ítem desde al menos una tabla, con dos clases de
excepción. Las filas de algunas solo se cuentan o se suman, en una curva, una
barra, un stat o una línea de tabla por repositorio, revisor, job o check, donde
una url por ítem no tiene fila en la que sentarse: gh_pull_request_review,
gh_workflow_job, gh_event, gh_issue_event, gh_artifact,
gh_contribution_day, gh_contribution_day_repo, gh_commit_check,
gh_issue_comment, gh_billing_usage, gh_dependabot_alert,
gh_code_scanning_alert, y las de toda la cuenta gh_account,
gh_account_total, gh_contributions_total y gh_sponsors_listing. Y dos
llevan una url que ninguna tabla enlaza: gh_release_published, que no lee
ningún panel, porque la tabla de releases enlaza cada release desde
gh_release, y gh_upstream_repo, que Work elsewhere une para su columna
Stars mientras cada una de sus filas enlaza el ítem y no el repositorio.
Una etiqueta que GitHub deja vacía se escribe como (none), una sola grafía en
todas las medidas, y ese mismo (none) va en unos pocos campos de texto que no
dicen nada en la mayoría de las filas, el decision de una pull request o el
assigned_to de una issue: InfluxDB 3 crea la columna la primera vez que una
fila la trae, y una consulta que nombra una columna que ninguna fila ha escrito
falla entera, así que esos se escriben en todas las filas. La misma convención
escribe (ghost) donde un login pertenecía a una cuenta que se ha borrado desde
entonces.
Los paréntesis son lo que importa de la grafía. GitHub contesta unknown él
mismo en el relationship de una alerta de Dependabot, y none es un valor de
más de uno de sus enums, así que un valor de reserva escrito de cualquiera de
las dos maneras no se distinguiría de una respuesta; (none) no es nunca un valor que
GitHub devuelva. Un valor sí lee none sin ellos y lo dice en serio: gate en
gh_commit es el estado de la puerta del commit, y none es el estado de un
commit sobre el que no corrió ninguna, junto a SUCCESS y FAILURE.
Un valor que cambia después de la fecha de la fila es un campo, nunca una
etiqueta. Una etiqueta forma parte de la identidad de la fila, así que una
etiqueta que cambia a posteriori abre una segunda serie en el mismo instante y
la fila vieja se queda junto a la nueva para siempre: medido tras once horas de
pasadas, uno de cada cincuenta artefactos tenía una fila con expired=false y
otra con expired=true en el mismo sello, y un commit visto PENDING por una pasada
y FAILURE por la siguiente contaba dos veces. Todas esas etiquetas son campos
ahora, con nombre nuevo porque InfluxDB 3 fija una columna como etiqueta o
campo en la primera escritura: gh_artifact.expired es live,
gh_commit.checks es gate, state en los dos ítems de alerta es
alert_state y el reason de code scanning es resolution, state_reason,
assignee, milestone y parent de gh_issue son resolution,
assigned_to, milestone_title y parent_issue, draft y review_decision
de gh_pull_request son is_draft y decision, gh_discussion.answered es
has_answer, gh_pull_request_review.state es review_state,
gh_deployment.state es outcome y gh_notification.unread es is_unread.
gh_discussion_comment.is_answer, que 2.6.1 dejó de escribir, no necesitó
nombre nuevo: es el campo answers que ya traía cada fila de esa medida, 1
para la respuesta aceptada y 0 para cualquier otro comentario.
state en una pull request, una issue y una contribución externa sigue siendo
etiqueta porque su fecha se mueve con él: la fila abierta se sella al inicio
del día y la cerrada cuando se cerró. El exportador de Prometheus
vuelve a leer los valores degradados como labels de Prometheus; Graphite, que
no guarda texto, no puede agrupar por ellos y sus paneles lo dicen.
Un almacén escrito antes de 2.6.1 y después guarda gh_discussion_comment
con dos formas. Un comentario que una versión anterior leyó antes de que lo
aceptaran y otra vez después son dos filas en el mismo instante, una por cada
valor de is_answer. Un comentario que leyó una versión anterior gana una
fila más sin esa etiqueta cuando una pasada de 2.6.1 lo vuelve a leer: la
segunda para la mayoría, la tercera para uno que ya tenía dos. Las dos tablas
del dashboard sobre esta medida leen una fila por comentario en todos los
almacenes, aceptado si cualquiera de sus filas lo dice, así que una respuesta
aceptada antes de 2.6.1 y retirada después sigue apareciendo como aceptada. Borrar la medida y lanzar un -backfill deja una fila
por comentario: la tabla en InfluxDB 3 y en PostgreSQL, las rutas
github.discussion_comment en Graphite, el índice
ghchronicle-gh_discussion_comment en Elasticsearch. El destino PostgreSQL
que se conecta sigue escribiendo en una tabla creada por una versión
anterior, resolviendo el conflicto sobre la clave de esa misma tabla, y sus
filas nuevas llevan la cadena vacía en is_answer. El fichero SQL no puede
conocer esa clave, así que, reproducido contra una tabla así, sus sentencias de
gh_discussion_comment se rechazan mientras el resto del fichero se carga,
hasta que se borre la tabla. El exportador de Prometheus no conserva nada entre
reinicios y cuenta cada comentario una sola vez. -migrate dice cuáles de los
almacenes configurados guardan las dos formas, bajo
2.6.1/gh_discussion_comment/is_answer: ver
Migraciones.
De una fila a una consulta
Sección titulada «De una fila a una consulta»Cada fila de aquí es una tabla en el almacén, sus etiquetas son columnas por las que filtrar y agrupar, y sus campos son los números. Leídos contra InfluxDB 3 en modo SQL, los tres fechados se convierten en tres formas de consulta.
Una medida fechada es historia, así que se lee sobre un rango:
SELECT time, "count" FROM gh_trafficWHERE kind = 'views' AND repo = 'telemetry' AND time > now() - INTERVAL '90 days'Una instantánea diaria es una fila por día, así que la fila más reciente es la respuesta y la diferencia entre dos días es el movimiento:
SELECT time, downloads FROM gh_release_assetWHERE asset = 'ghchronicle_linux_amd64.tar.gz' ORDER BY time DESC LIMIT 30Un elemento fechado lleva una fila por cada cosa que ocurrió, que es lo que permite preguntar por los elementos y no por una cuenta:
SELECT date_trunc('week', time) AS week, count(*) AS merged, avg(seconds_to_merge) / 3600 AS hoursFROM gh_pull_request WHERE state = 'MERGED' GROUP BY week ORDER BY weekLas tres formas valen en los demás almacenes de historia; los dashboards llevan un juego de consultas por almacén para cada panel, que es el sitio del que copiar.
Todas las medidas, por orden alfabético
Sección titulada «Todas las medidas, por orden alfabético»Noventa y cinco, y cada enlace cae en la tabla en la que está.
gh_account · gh_account_total ·
gh_achievement · gh_achievement_progress ·
gh_actions_cache ·
gh_actions_cache_entry ·
gh_actions_policy · gh_artifact ·
gh_artifact_total · gh_billing_usage ·
gh_branch ·
gh_branch_protection ·
gh_code_scanning_alert ·
gh_code_scanning_alert_item ·
gh_code_scanning_analysis ·
gh_code_scanning_setup ·
gh_collector_family ·
gh_commit ·
gh_commit_check · gh_commit_punchcard ·
gh_commits_week · gh_contribution_day ·
gh_contribution_day_repo · gh_contribution_repo ·
gh_contribution_year · gh_contributions_total ·
gh_dependabot_alert · gh_dependabot_alert_item ·
gh_dependabot_ecosystem ·
gh_dependency ·
gh_dependency_change ·
gh_dependency_license ·
gh_deploy_key ·
gh_deployment · gh_discussion ·
gh_discussion_comment ·
gh_environment · gh_event ·
gh_external_contribution · gh_fork ·
gh_gist · gh_issue ·
gh_issue_comment · gh_issue_event ·
gh_job_log · gh_key ·
gh_label · gh_milestone ·
gh_notification · gh_package ·
gh_package_version · gh_pinned_item ·
gh_policy_file · gh_profile_flag ·
gh_pull_request · gh_pull_request_review ·
gh_rate_limit · gh_release ·
gh_release_asset ·
gh_release_published · gh_repo ·
gh_repo_activity ·
gh_repo_archived · gh_repo_community ·
gh_repo_created · gh_repo_language ·
gh_repo_policy ·
gh_repo_topic · gh_repo_total ·
gh_review_thread · gh_ruleset ·
gh_ruleset_rule ·
gh_ruleset_version · gh_secret ·
gh_security_feature · gh_security_setting ·
gh_social_account · gh_sponsors_listing ·
gh_sponsors_tier · gh_sponsorship ·
gh_star · gh_star_day ·
gh_star_given · gh_star_list ·
gh_traffic ·
gh_traffic_path · gh_traffic_referrer ·
gh_upstream_repo ·
gh_webhook ·
gh_webhook_delivery ·
gh_workflow ·
gh_workflow_job ·
gh_workflow_run ·
gh_workflow_run_total ·
gh_workflow_step
Audiencia
Sección titulada «Audiencia»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado, un punto por día | kind (views, clones) | count, uniques, url |
gh_ | diario | referrer | count, uniques, url, referrer_url |
gh_ | diario | path | count, uniques, title, url |
GitHub sirve catorce días y la ventana entera se reescribe en cada pasada, así que un colector caído un día se repara solo en la siguiente ejecución. Los referrers y las rutas son los diez primeros de esa misma ventana sin fechas, y por eso son una instantánea y no una serie.
Estrellas y forks
Sección titulada «Estrellas y forks»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado, cuando se dio la estrella | user | starred, url, user_url |
gh_ | fechado, un punto por día: el día del Pacífico, a las 00:00 UTC | stars | |
gh_ | fechado | user, language | stars, repo_stars, url |
gh_ | fechado, cuando se creó el fork | by | forks, stars, seconds_to_push, advanced, url |
gh_star_day son las estrellas que ganó un repositorio cada día, leídas del
historial de estrellas
de GitHub para cada repositorio que recoge una pasada, vea lo que vea el token
de sus stargazers. No nombra a nadie, así que no lleva etiqueta propia ni url:
gh_star es la fila por estrella, con quién la dio y el segundo, y solo existe
donde el token puede leer la lista de stargazers, lo que desde julio de 2026
significa los administradores y colaboradores del repositorio. Ningún panel lee
las dos, así que un repositorio que tiene ambas se cuenta una vez, y los paneles
que cuentan estrellas a partir de filas leen gh_star_day, así que uno cuya
lista está oculta se cuenta igual.
Un día es el día natural del propio GitHub en America/Los_Angeles, y su fila se
sella a las 00:00 UTC de esa fecha, como una fila de gh_contribution_day se
sella a las 00:00 UTC de la suya. Medido contra las listas de stargazers de
diecinueve repositorios, 440 estrellas, el día del Pacífico acertó en todas, y
el día UTC equivocaba 38 de los 127 días de un repositorio. Así que una estrella
dada una mañana europea puede quedar un día antes de la fecha que le da
gh_star. Cada día se ancla a la semana que devuelve la API, nunca al reloj, no
se escribe ningún día posterior a la pasada, y hoy puede escribirse como 0 antes
de que haya empezado en la hora del Pacífico, para que lo corrija la pasada
siguiente.
La cuenta son los stargazers actuales del repositorio, cada uno en el día en que
marcó la estrella, así que quitar una estrella la saca del día en que se dio, no
del día en que se retiró. Una pasada lee las treinta semanas más nuevas, una
página, y escribe todos sus días, ceros incluidos, así que un día que baja de
una a ninguna se reescribe la vez siguiente. Las páginas más antiguas se leen la
primera vez que se ve un repositorio, una vez tras actualizar a 2.5.0 y en un
backfill; el fichero de estado registra una lectura entera como history_read,
y solo tras un recorrido que llegó al final del historial, así que un recorrido
cortado, por un error o por una página posterior que responde 403 o 404, se
vuelve a leer entero en la pasada siguiente. Esas páginas escriben solo los días
que tienen estrellas: filas a cero hasta la creación de cada repositorio
multiplicarían los ficheros que abre InfluxDB 3, y por eso un día de hace más de
treinta semanas que perdió su única estrella la conserva, igual que gh_star
conserva cada estrella que vio alguna vez. La suma suele quedar en
gh_repo.stars o un poco por debajo, ya que ese número también cuenta cuentas
que GitHub ya no lista: una menos en tres de esos diecinueve repositorios. Una
estrella dada hace más de treinta semanas y retirada después de leer el
historial puede dejarla por encima: solo un backfill que llegue hasta su día
rebaja ese día, y solo mientras el día conserve otra estrella, así que si era la
única estrella de su día se queda para siempre. Prometheus y un backend OTLP con
raw: false no la ven nunca: quitar una estrella baja un día pasado, cosa que
ningún contador puede hacer, y github_repo_stars ya lleva el número actual de
cada repositorio.
GitHub Enterprise Server no sirve el historial de estrellas: su API REST,
comprobada en 3.21 y 3.22, tiene la lista de stargazers y no
stargazers/history. Allí cada repositorio lo responde con un 404, que se lee
como nada que recoger, así que gh_star_day queda vacía, y con ella los
paneles que cuentan estrellas a partir de ella: Stars gained over time y Stars
over time en InfluxDB y en los almacenes SQL, Stars gained over time en
Graphite y Elasticsearch, y Recent stars en Graphite. Recent stars sigue
listando nombres de gh_star en los demás almacenes, y Prometheus sigue
contando a partir de ella. Un historial que respondió 404 no se registra como
leído, así que un servidor que empiece a servirlo tiene el historial de cada
repositorio leído entero en la pasada siguiente.
gh_star_given es la dirección saliente: lo que esta cuenta marcó en
repositorios ajenos. advanced en un fork separa un derivado real de un
marcador, que es lo que son la mayoría de los forks, y seconds_to_push dice
cuánto después del fork llegó su último empujón. Es negativo cuando GitHub
informa de un empujón anterior al propio fork.
Cuánto lleva un fork parado no se guarda, porque la fila está fechada cuando se
creó el fork: es esa fecha restada de ahora, menos seconds_to_push, y lo
calcula la consulta. Guardado, era un día más cada día escrito sobre una fila
fechada años antes, así que decía “cuándo corrió la pasada” y no algo del fork,
y cada reescritura costaba un fichero en la partición del propio fork.
Repositorios
Sección titulada «Repositorios»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | ahora | language, visibility, license, archived, fork, default_branch | stars, forks, watchers, open_issues, size_kb, age_days, days_since_push, days_since_config_change, network, repo_id, is_template, has_pages, web_commit_signoff_required, allow_update_branch, pull_request_creation_policy, url |
gh_ | ahora | language | bytes |
gh_ | ahora | topic | present, url |
gh_ | ahora | health_percentage, url, has_readme, has_license, has_contributing, has_code_of_conduct, has_issue_template, has_pull_request_template | |
gh_ | fechado, al archivarse el repositorio | archived, age_days_at_archive, url | |
gh_ | ahora | tag, draft, prerelease | downloads, assets, age_days, url |
gh_ | diario | tag, asset | downloads, size_bytes, digest, content_type, uploader, age_days, url |
gh_ | fechado, al publicarse | tag | published, prerelease, url |
open_issues es el campo de GitHub y GitHub cuenta las pull requests dentro.
Usa gh_issue para contar issues.
gh_release_published es cuándo se publicó cada release, al segundo, y es la
medida por la que agrupar un calendario de releases o de la que leer “la
última release estable”, la fila más nueva con prerelease a false.
gh_release sigue sellada en la pasada, porque sus descargas se mueven, y su
age_days son días enteros truncados contados hacia atrás desde la pasada: la
fecha que reconstruye llega un día tarde para toda release publicada más tarde
dentro del día que la hora de la pasada. published vale el entero 1 en
todas las filas, así que contar releases es una suma. prerelease es aquí un
campo booleano, donde en gh_release es una etiqueta: una pre-release se promueve desmarcando
la casilla en la release ya publicada, y como etiqueta una promoción que
conserve la fecha de la publicación escribiría una segunda fila en el mismo
instante, que la suma contaría dos veces. Una release que vuelve a borrador y se
publica de nuevo escribe una segunda fila si GitHub le da un published_at
nuevo, así que la cuenta exacta son los valores distintos de tag de cada
repositorio. Un borrador no tiene publicación, así que no escribe fila aquí, y
su fila de gh_release no lleva age_days:
GitHub envía un borrador con published_at a null, y la edad medida desde la
nada valía 106751 días en cada fila de borrador. La primera pasada tras
actualizar fecha la página de releases que lee; -backfill fecha el resto.
La url de gh_release_asset es la dirección de descarga del binario, no una
página: seguirla baja el fichero. Los assets son inventario, anclados al inicio
del día UTC como las entradas de la caché: sellados en la pasada, cada asset
era una fila nueva cada hora, que era el 15 por ciento de toda la base tras
once horas. Una fila por asset y día sigue respondiendo a “descargas por día”,
y la fila más nueva sigue siendo el valor.
gh_repo_archived es la única fila sobre un repositorio que lleva una fecha en
vez de un estado: fechada en archivedAt, una limpieza se ve como la tanda que
fue, y un repositorio vivo no produce fila alguna, así que contar las filas es
contar el archivo. No necesita include_archived. El listado que una pasada ya
paga dice qué repositorios están archivados, y la familia totals pregunta la
fecha de todos ellos, junto a su fila de toda la vida, en una consulta GraphQL
por cada veinticinco repositorios, en cada pasada de totals: un punto por
cada veinticinco a esa cadencia, y las filas que reescribe son las mismas
filas, que es lo que necesita un exportador que
solo guarda lo que se reescribe; el listado no puede dar la fecha por sí
mismo, porque REST no lleva archived_at y su updated_at se midió entre dos
segundos y ocho minutos después del archivado. Un fork archivado bajo la regla
de forks por omisión es el único tipo sin fila.
Desarrollo
Sección titulada «Desarrollo»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado al cerrarse, diario mientras está abierto | number, state, author | is_draft, decision, title, labels, label_names, author_association, additions, deletions, churn, changed_files, commits, comments, total_comments, reviews, review_requests, review_threads, base_ref, head_ref, merged_by, merge_commit, mergeable, merge_state, stack, stack_size, stack_position, seconds_to_first_review, seconds_to_first_human_review, seconds_to_merge, seconds_open, url |
gh_ | fechado, al enviarse | number, author, reviewer, bot, self | review_state, reviews, seconds_to_review, url |
gh_ | fechado al cerrarse, diario mientras está abierto | number, state, author | resolution, assigned_to, milestone_title, parent_issue, comments, reactions, labels, label_names, sub_issues_total, sub_issues_completed, pull_request, seconds_to_close, seconds_open, url |
gh_ | fechado, cuando se hizo el commit | sha, author, branch, signature | gate, additions, deletions, churn, changed_files, commits, signed, oid, headline, url, pull_request, checks_total, checks_failed |
gh_ | fechado, al terminar la comprobación | sha, app, check, conclusion | checks, failed, url |
gh_ | fechado, cuando ocurrió | event, actor, kind, bot, label, milestone, requested_reviewer, review_requester, mentioned | events, number, title, url, commit_id, rename_from, rename_to |
gh_ | fechado, al escribirse el primer comentario del hilo | thread, number, author, bot | path, comments, resolved, outdated, subject_type, resolved_by |
gh_ | fechado, al crearse | category, answerable, author, number | has_answer, comments, replies, reactions, upvotes, closed, state_reason, seconds_to_answer, seconds_to_close, title, url |
gh_ | diario | label | issues, pull_requests, used, url |
gh_ | diario | milestone, state | progress, issues, pull_requests, days_to_due, seconds_to_close, url |
gh_ | fechado | user, number, kind, state | contributions, merged, private, additions, deletions, changed_files, title, comments, seconds_to_merge, seconds_open, url |
gh_ | ahora | stars, forks, private, language, url |
gh_commit es lo que sustituye a stats/code_frequency, que devuelve 202 con
cuerpo vacío para siempre en una cuenta personal. signature vale unsigned
cuando no hay firma alguna, que es un hecho distinto de una que no se pudo
verificar.
gate es el estado de toda la puerta sobre ese commit, que no es lo mismo que
decir que falló una ejecución de workflow: una ejecución dice que falló un job,
el resumen dice que el commit salió en rojo. Es un campo porque el veredicto
llega después de la fecha del propio commit. gh_commit_check guarda solo las
comprobaciones que no son de GitHub Actions, porque todo lo que corre Actions ya
se recoge con mucho más detalle.
seconds_to_first_review cuenta cualquier revisión, y en una cuenta con bots de
revisión esa es la del bot: medido sobre 140 pull requests, su mediana era de
cinco segundos, porque 132 recibieron la primera revisión de sourcery-ai o
coderabbitai en el minuto siguiente a abrirse. seconds_to_first_human_review
es la espera hasta otra persona, sobre las veinte primeras revisiones que trae
la consulta: ni un bot, ni el autor. La respuesta del autor en un hilo de
revisión llega como una revisión en estado COMMENTED con su propio nombre, y
en esta cuenta era la primera revisión no automática en cada una de las 91 pull
requests que tenían alguna, así que una espera que la contara medía lo rápido
que el propietario contesta a sourcery-ai. Una pull request cuyas revisiones
traídas son todas de bots y respuestas propias no lleva el campo, en vez de
llevar uno falso. Un bot es una GitHub App (__typename Bot) o un login que
termina en [bot]; una cuenta borrada no lo es. gh_pull_request_review.bot
traza la misma línea revisión a revisión y self marca las del propio autor,
para que una tabla de revisores pueda dejar fuera a ambos o mostrarlos aparte;
bot es lo que ya hace gh_review_thread.bot con los hilos.
title, label_names y author_association son campos porque un título no
está acotado y nueve etiquetas en una pull request son una fila, no nueve
series. labels es el recuento y label_names los nombres unidos por comas,
ausente cuando no hay ninguna, tanto en pull requests como en issues.
author_association es OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR,
FIRST_TIME_CONTRIBUTOR o NONE: lo que separa una contribución externa del
trabajo del propio dueño.
mergeable y merge_state solo se escriben mientras una pull request está
abierta. Una ya fusionada sigue respondiendo CONFLICTING mucho después de
fusionarse, que es rancio y no falso, pero se lee como un repositorio lleno de
conflictos. Una pasada solo lee lo que se actualizó en las dos últimas
cadencias, así que una pull request abierta que nadie toca ve reescritos sus
seconds_open, mergeable y merge_state una vez al día, en la lectura
diaria de cada elemento abierto, en vez de cada hora, por mucho que haga que se
movió por última vez; todo lo que mueve updatedAt, una
revisión, un comentario, un push, un cierre, lo reescribe la pasada siguiente.
stack, stack_size y stack_position describen una pila de pull requests
dependientes y no están en una pull request que no está en ninguna. stack es
el número de la pila, no el de un miembro, así que la forma honesta de contar
entregas es contar los valores distintos de stack más las filas que no llevan
ningún campo de pila. review_requests y review_threads son las dos cuentas
con las que se mide un flujo de revisión, y total_comments cuenta todos los
comentarios de la pull request, no los de comments, que son los de la
conversación.
sub_issues_total y sub_issues_completed son hasta dónde ha llegado un épico,
según la lista que GitHub mantiene en el padre. parent_issue es el otro
extremo de esa misma relación, en el hijo, y vale 0 en una issue sin padre.
pull_request es la pull request que cerró la issue, 0 cuando no la cerró
ninguna.
gh_issue_event es la transición, no el estado. gh_issue y gh_pull_request
dicen en qué acabó algo; esto dice cuándo se etiquetó, se cerró, se reabrió, se
renombró o se pidió revisión. Una reapertura no existe en ningún otro sitio.
mentioned es la persona a la que le ocurrió un evento mentioned o
subscribed, que GitHub registra como actor sin decir quién escribió el
comentario; aquí esa persona tiene su propia etiqueta y actor vale (none)
en esos dos tipos, así que la cuenta nombrada en “@coderabbitai” ya no comparte
columna con la app que revisa. Una app se escribe como la escribe REST, con el
sufijo [bot], en todas las medidas.
gh_discussion cuenta aparte comentarios y respuestas: los comentarios
responden a la discusión, las respuestas responden a esos, y el número que
GitHub enseña en la página es la suma de los dos.
gh_external_contribution es el trabajo que la cuenta hizo en repositorios que
no son suyos, sacado de cinco búsquedas, una por kind y state: pull
requests merged, open y closed sin fusionar, e issues open y closed.
Un elemento que sigue abierto se reescribe al principio de cada día que sigue
abierto, así que una pasada lee enteros los dos estados abiertos. Un elemento
cerrado se escribe una vez, fechado cuando se cerró, así que los tres estados
cerrados se leen por cuándo se movió cada elemento por última vez: una pasada
lee cada uno hasta una cadencia antes de la pasada anterior, lo que contiene lo
que se fusionó o se cerró desde entonces por mucho tiempo que haga que se abrió
y por muchos otros elementos que se movieran entre medias, y es una página de
cien salvo que se movieran más de cien. Un relleno sigue leyendo hasta que se
acaban las páginas o llegan a backfill.since.
GitHub sirve mil resultados de cualquier búsqueda y ni uno más, así que una
cuenta que pase de mil en un estado tiene los mil que se movieron más
recientemente, y el log lo dice como aviso.
gh_account_total.pulls_merged_elsewhere e issues_elsewhere son los
recuentos del propio GitHub y aciertan pase lo que pase con ese tope. Un
elemento que estuvo abierto y luego se cerró conserva sus filas open junto a
la cerrada, porque state es una etiqueta.
private dice si el repositorio al que fue el elemento es privado. Las
búsquedas se hacen con el token de la cuenta, así que los repositorios
privados de una organización vuelven junto a los públicos, y este campo es lo
que permite a una página que ven otros dejarlos fuera; es un campo y no una
etiqueta, así que no dio una segunda identidad a las filas ya guardadas. Una
pull request lleva additions, deletions y changed_files, los nombres que
usa gh_pull_request, y un issue no lleva ninguno de los tres. Todo viene en
los nodos que las búsquedas ya devuelven, y medido el 2026-09-26 la consulta
cuesta un punto por página con ello igual que sin ello.
gh_upstream_repo es el lado del repositorio de esas mismas búsquedas: una
fila por repositorio al que llegaron en la pasada, fechada en la pasada, con
sus stars y sus forks como enteros, private como booleano, su language
principal, ausente cuando GitHub no detecta ninguno, y su página. El recuento
de estrellas no está en gh_external_contribution porque esa fila está
fechada cuando se cerró el elemento, y un recuento que se mueve casi cada día
reescribiría una fila del pasado cada vez. En InfluxDB y en PostgreSQL la
tabla “Work elsewhere” une a cada elemento la más reciente de estas filas
dentro del rango, como Stars, y en Prometheus su Stars es el recuento que
guarda el exportador; Graphite y Elasticsearch no pueden unir una medida con
otra, y sus tablas no tienen columna Stars. Un repositorio tiene fila siempre que una búsqueda lee un elemento suyo: cada
estado abierto se lee entero en cada pasada, y cada estado cerrado desde su
página más reciente, así que un repositorio cuyos elementos están todos
cerrados y más atrás que esa página conserva la fila de la última pasada que
leyó uno de ellos, o del último relleno.
Integración continua
Sección titulada «Integración continua»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado, al terminar | workflow (la ruta del fichero), event, conclusion, actor | duration_seconds, queued_seconds, attempt, success, run_id, run_number, pull_request, pull_requests, headline, head_repo, head_sha, head_branch, name, title, workflow_id, initial_actor, url |
gh_ | ahora | runs | |
gh_ | fechado, al terminar | workflow, job_name, attempt, conclusion, runner_group, labels | duration_seconds, queued_seconds, steps, success, runner, run_id, head_sha, head_branch, url |
gh_ | fechado, al terminar | workflow, job_name, attempt, step, conclusion | duration_seconds, step_number |
gh_ | ahora | workflow, path, state | active, age_days, days_since_change, url |
gh_ | fechado, al crearse | artifact | live, size_bytes, retention_days, digest, run_id, head_sha, head_branch, url |
gh_ | ahora | live_bytes, live_count, count, walked | |
gh_ | ahora | size_bytes, count | |
gh_ | diario | cache, ref | size_bytes, caches, key, days_since_use, age_days |
gh_ | fechado | activity, actor | events, id, ref_name |
branch era una etiqueta en gh_workflow_run y en gh_artifact, y ahora es
el campo head_branch en ambas, y también en gh_workflow_job. Una rama es
una identidad, pero no reutilizable: cada pull request y cada subida de
Dependabot acuña un nombre que no vuelve, así que la etiqueta crece sin límite,
y la pregunta acotada que de verdad hace un lector, si esto fue un push o un
pull request, ya es la etiqueta event. Además InfluxDB 3 fija una columna
como etiqueta o como campo la primera vez que la ve y rechaza toda escritura
posterior que la contradiga, así que conservar el nombre habría obligado a
tirar las dos tablas para publicar un valor que la propia API llama
head_branch.
attempt es etiqueta en el job y en el step porque el listado de jobs se pide
ahora para todos los intentos y no solo para el último. Sin ella los dos
intentos de una reejecución son una sola serie, distinguibles solo por el
segundo en que terminaron, y un test inestable no se puede separar de uno roto.
queued_seconds en una ejecución se escribe solo en los primeros intentos.
GitHub conserva el created_at de la ejecución entre reintentos, así que en un
segundo intento la distancia hasta run_started_at es el tiempo que tardó una
persona en pulsar el botón, no una cola de runners: 0,4 s de media en primeros
intentos frente a 1.747 s en segundos, medido. La cola de un reintento existe
solo por job.
run_number es el “#1483” que GitHub muestra y la gente cita; run_id es la
clave de la API. pull_request es el número de la primera pull request que
GitHub enlazó a la ejecución y pull_requests cuántas enlazó, ambos ausentes
cuando no enlazó ninguna. headline es la primera línea del commit que corrió,
y head_repo se escribe solo cuando la ejecución vino de otro repositorio, que
es el aspecto que tiene la pull request de un fork.
gh_workflow_run_total es el propio total_count del listado de ejecuciones,
que es la historia entera y no los pocos cientos de ejecuciones que ve el
recorrido. Es estado actual, así que se sella ahora, y es el único sitio donde
se puede responder “cuántas ejecuciones ha habido en total” sin recorrer la
tabla.
Una pasada ordinaria pide la lista de ejecuciones en páginas de treinta y no de cien. La página son trece kilobytes por ejecución, de los que el colector conserva seiscientos bytes, y a cien ejecuciones era un mega y medio por repositorio activo cada cuarto de hora, el cuarenta y seis por ciento de todo lo que se descarga en un día. Los almacenes no pierden nada: el recorrido sigue pasando de página mientras una página venga llena de ejecuciones más nuevas que la ventana, hasta siete páginas, que son las doscientas diez ejecuciones a las que llegaban dos páginas de cien, y la primera pasada tras el arranque y un backfill siguen pidiendo cien. Lo que cambia es el exportador de Prometheus, que solo tiene lo que recogió la última pasada y muestra las treinta ejecuciones más nuevas entre builds en vez de las cien.
Los jobs de una ejecución se listan una vez. Los jobs de un intento terminado no cambian nunca, y volver a listarlos en cada pasada era una petición por ejecución en la ventana, casi todas 304 que no cuestan cuota pero sí un tercio de segundo de espera cada una, noventa y seis veces al día. El colector recuerda cada intento cuyos jobs escribió, y guarda esa memoria con la caché de ETag en el fichero junto al fichero de estado, así que un reinicio solo pregunta por ejecuciones e intentos nuevos, como habría hecho la pasada anterior; un backfill las lista todas igualmente. Una reejecución conserva el id de la ejecución y es un intento nuevo, así que se lista otra vez. El tope de veinte acota lo que paga una pasada, no qué ejecuciones reciben jobs: una ventana con más ejecuciones que eso se completa a veinte por pasada. Una ejecución queda recordada solo cuando todos los almacenes aceptaron la pasada que listó sus jobs: una pasada que un almacén rechazó olvida sus ejecuciones, y la siguiente las vuelve a listar y a escribir. El fichero guarda una ejecución mientras el listado la siga devolviendo, y la olvida con el mismo horizonte que una respuesta que ya nadie pide. Un arranque que no encuentra un registro de escrituras que diga lo que tienen los almacenes, o que encuentra un almacén añadido desde entonces, no recupera ninguna y vuelve a listar sus jobs.
Los jobs de una ejecución duran más que sus pasos. Medido el 24 de septiembre de
2026, GitHub listaba todos los jobs de una ejecución de hace 278 días, con sus
tiempos y su runner, y devolvía una lista de pasos vacía para toda ejecución
creada antes del 12 de abril, unos cinco meses y medio atrás. Un job que terminó
en éxito, en fallo o por tiempo agotado ejecutó al menos un paso, así que cuando
su lista llega vacía se escribe sin el campo steps, en lugar de con un 0 que
diría que no tuvo ninguno, y sin filas de gh_workflow_step: ese cero
arrastraría hacia nada toda media de steps hasta donde llegó un relleno. Todo
otro job conserva steps como la longitud de su lista, porque su cero puede ser
cierto: un job omitido no ejecuta ningún paso (medido), y uno cancelado puede
pararse antes del primero.
retention_days es la retención que un artefacto tuvo de verdad, que casi
nunca es la configurada por omisión: ochenta y ocho de cada cien artefactos
medidos vivían un día frente a un ajuste de noventa.
gh_repo_activity es una fila por tipo de actividad, actor y segundo. La rama
es el campo ref_name, un nombre por pull request y por bump de Dependabot, y
sin ella en la clave las ramas que un push movió en el mismo segundo serían una
sola fila en todos los almacenes, con la última escrita valiendo por todas:
ocho force pushes en un segundo, medidos. Las entradas que comparten clave se
pliegan en un punto: events las cuenta, ref_name nombra todas las ramas
separadas por comas, id es el de la entrada más reciente, y así una suma de
events es el número de actividades en cualquier almacén.
El tiempo en cola solo existe a nivel de job. La cifra a nivel de ejecución mete la espera dentro de la duración, y la de nivel de job incluye la espera por una dependencia, así que un job que espera diecinueve minutos a que termine otro no es prueba de falta de runners.
runner es un campo, no una etiqueta: un runner alojado se nombra de forma
única en cada ejecución, así que como etiqueta crearía una serie por cada job
ejecutado alguna vez. La etiqueta del job de workflow es job_name y no job,
porque job choca con las etiquetas que Prometheus añade en el scrape.
gh_artifact_total lleva tres cuentas porque su tamaño no está sobre
ninguna de las evidentes. count es el total del propio GitHub para el
repositorio y cuenta los artefactos que GitHub ya ha caducado: medido en
jmrplens/jmrp.io el 2026-09-17, la página 40 del listado eran caducados hasta
la última fila, frente a un total declarado de 29.405. walked es hasta dónde
dejó llegar el tope de cinco páginas. live_bytes es el tamaño de los
artefactos que GitHub todavía guarda entre los recorridos, y live_count
cuántos son, que es la cuenta sobre la que está ese tamaño. Cuando walked es
menor que count, las cifras vivas son un suelo y no un total, que en ese
repositorio se quedaba corto por un factor de cincuenta y seis. Un recorrido que
una página fallida dejó a medias escribe la fila igualmente, con las páginas
anteriores, y su walked se queda por debajo de count del mismo modo; solo
una primera página fallida no escribe ninguna, porque entonces no hay cuenta de
la que quedarse corto.
gh_actions_cache dice que un repositorio ocupa doce gigabytes;
gh_actions_cache_entry dice qué clave los ocupa y cuál lleva una semana sin
tocarse, que es lo que decide qué desaloja GitHub al llegar al techo de diez
gigabytes. La etiqueta cache es la clave sin su hash de contenido, porque la
clave entera es una serie por build.
Una fila de gh_actions_cache_entry es una caché en una ref para el día, con
sus entradas sumadas en ella: caches es cuántas hay, size_bytes su total,
days_since_use y key los de la usada más recientemente, y age_days el de
la más antigua. Hasta la 2.5.2 era una fila por entrada, y todas las entradas
de una caché en una ref tenían las mismas etiquetas y el mismo día, así que el
almacén se quedaba con la última que se escribía: el 2026-09-26 las quince
cachés de CodeQL en main de jmrplens/jmrplens, 57,9 MB entre todas, quedaron
guardadas como una de 3,8 MB. Ninguna API lista las cachés de un día pasado,
así que esos días se quedan como están, y -migrate solo los anota, bajo
2.6.0/gh_actions_cache_entry/sum. El listado se lee en páginas de cien entradas,
hasta diez, donde antes se paraba en la primera: el 2026-09-27 dos
repositorios de la cuenta tenían 118 y 232 entradas. Se lee de la creada más
recientemente a la más antigua, un orden que un acierto de caché no cambia.
Una pasada que lee menos entradas de las que el listado decía tener, porque una
se borró entre dos de sus páginas, no escribe ninguna fila de
gh_actions_cache_entry, y tampoco una a la que le falló una página posterior:
sumada, una parte de una caché se escribiría sobre la caché entera que guardaron
las pasadas anteriores del día, y las filas las escribe la siguiente pasada del
día. El total de gh_actions_cache, leído antes que el listado, se escribe en
los dos casos.
Seguridad
Sección titulada «Seguridad»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | ahora | severity, ecosystem | open, url |
gh_ | fechado, al abrirse | number, severity, ecosystem, package, ghsa, scope, relationship, manifest | alert_state, alerts, cvss, cvss_v4, epss, epss_percentile, cve, cwe, summary, vulnerable_range, first_patched, dismissed_reason, dismissed_by, dismissed_comment, seconds_to_detect, seconds_to_resolve, url |
gh_ | ahora | severity, tool | open, url |
gh_ | fechado, al abrirse | number, severity, tool, rule, path, category, ref | alert_state, resolution, alerts, commit, line, cwe, seconds_to_resolve, url |
gh_ | fechado, cuando corrió el escaneo | tool, version, ref, category | analyses, results, rules, commit |
gh_ | ahora | feature | enabled, open_alerts, alerts, url |
gh_ | ahora | setting, status | enabled |
gh_ | diario | state, query_suite, schedule | setups, languages, days_since_change |
gh_ | diario | kind (actions, dependabot), secret | secrets, age_days, days_since_rotation |
gh_ | diario | permissions | policies, can_approve_pr |
Las etiquetas de las dos medidas de ítem no cuestan nada: los dos listados ya
las traían, y la serie siempre estuvo indexada por number, así que agrupan
filas que ya existen una por alerta en vez de multiplicarlas. Todas se escriben
siempre, cayendo a (none) cuando GitHub omite alguna. Una etiqueta que solo se
escribe a veces le da a la medida dos profundidades de ruta en Graphite, y los
paneles indexan sus nodos desde una única tabla fija. alert_state en los dos
ítems y resolution en el de code scanning son campos, porque una alerta se
fecha al abrirse y los dos cambian al cerrarse; resolution vale open hasta
entonces, para que la columna exista antes de que se cierre ninguna alerta. Una
compilación anterior a la 1.0.0 etiquetaba los dos ítems con state, y el de
code scanning también con reason, así que un almacén que escribió guarda esas
filas junto a las de hoy: -migrate las encuentra, bajo
1.0.0/gh_dependabot_alert_item/state y
1.0.0/gh_code_scanning_alert_item/state, en los almacenes a los que se les
puede preguntar.
Los campos son lo contrario: cve, cwe, first_patched, epss,
epss_percentile y seconds_to_detect se escriben solo cuando el aviso los
trae, porque una puntuación EPSS que falta no es una puntuación de cero. cvss
y cvss_v4 necesitan la misma guarda por otro motivo: GitHub manda siempre las
dos claves y rellena con 0.0 la que no tiene, y un aviso publicado solo con
vector v4 eran 78 de las 225 alertas de un repositorio, suficiente para que el
“peor CVSS” de un grupo de severidad formado por ellas leyera cero. Ninguna de
las dos puntuaciones se escribe si no es mayor que cero; un panel que quiera un
número por alerta lee COALESCE(cvss_v4, cvss).
summary es el título del aviso, vulnerable_range el rango que cubre, que
junto a first_patched es la acción a tomar, y dismissed_reason,
dismissed_by y dismissed_comment dicen por qué una persona cerró una alerta
sin arreglarla; existen solo en una alerta en estado dismissed.
seconds_to_detect es la distancia entre la publicación del aviso y el momento
en que la alerta se abrió aquí. Es negativo cuando la alerta llegó primero, que
es lo que pasa cuando un aviso se redacta a posteriori.
Los dos campos cwe no se pueden unir. Dependabot escribe CWE-400 y code
scanning escribe cwe-079, ambas grafías del propio GitHub, y aquí no se
normaliza ninguna. line es la línea de inicio de la instancia más reciente de
la alerta, y su cero es el valor que da GitHub para una alerta sobre un fichero
entero, no una lectura que falte.
Una alerta de Dependabot se cierra de tres maneras, no de dos:
auto_dismissed_at es como GitHub cierra por su cuenta la alerta de una
dependencia de desarrollo, dejando las otras dos a nulo. Una alerta cerrada así
contaba como abierta para siempre.
Una alerta todavía abierta no lleva ningún campo con cuánto lleva abierta, y es
deliberado. La fila está fechada cuando se abrió la alerta, así que la respuesta
es ahora menos la marca de tiempo de la propia fila y la calcula el panel cuando
se le pregunta. Escrita por el colector, como seconds_open, solo era cierta en
el instante de la pasada que la escribió y se movía en cada pasada: quien
consultara la semana pasada obtenía lo que decidió la última pasada, y cada
reescritura archivaba otro fichero parquet en la partición de la fecha original
de la alerta. Medido el 2026-09-17, las dos familias de alertas escribían unos
234 ficheros al día entre las dos para 1.700 filas, hubiera o no algo nuevo que
contar en GitHub.
gh_security_feature existe para que “sin datos” y “sin alertas” sean
distinguibles. Sin ella, un repositorio con Dependabot apagado es idéntico a uno
sin nada que arreglar. enabled se lee de la primera página completa del
listado, que responde 403 cuando la función está apagada y, en code scanning,
404 cuando todavía no se ha analizado nada.
open_alerts, y open en gh_dependabot_alert y gh_code_scanning_alert,
cuentan todas las alertas del repositorio que siguen abiertas, por antiguas que
sean. Una pasada lee la página más reciente de cada lista, cien alertas en
cualquier estado, y cuando esa página vuelve llena la lista se lee otra vez con
state=open, hasta el final, y las cuentas salen de ahí. Una alerta que sigue
abierta detrás de cien más recientes ya corregidas se cuenta; hasta la 2.5.2 no
se contaba.
alerts no es el mismo número en las dos funciones. En code scanning es el
total del repositorio: cuando la página vuelve llena es la última página que
GitHub declara para una página de una sola alerta, que en un repositorio dio
1.393 donde la página decía 100. La lista de Dependabot pagina por cursor y no
declara última página, así que ahí alerts es lo que leyó la pasada: 100 en una
pasada quiere decir cien o más, y un backfill que recorre la lista entera
escribe el total. Las dos medidas de ítem son las alertas que leyó el recorrido
en ambos casos, y quedan completas tras un backfill.
| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | ahora | followers, following, following_users, public_repos, gists, packages, projects, starred, watching, sponsors, sponsoring, account_age_days, pronouns, url | |
gh_ | ahora | calendar_total, commits, pull_requests, reviews, issues, repositories, restricted, repos_with_commits, repos_with_issues, repos_with_pulls, repos_with_reviews, url | |
gh_ | fechado, un punto por día del calendario | contributions, level, url | |
gh_ | fechado, fin del año; el año en curso a diario | year | contributions, commits, issues, pull_requests, reviews, repositories, restricted, repos_with_commits, repos_with_issues, repos_with_pulls, repos_with_reviews, partial |
gh_ | ahora | kind (commits, issues, pulls, reviews) | contributions, commits, days, commits_dated, url |
gh_ | fechado, el día al que pertenecen los commits | private, own | commits, url |
gh_ | fechado, el domingo de su semana | commits, owner_commits | |
gh_ | ahora | weekday, hour | commits |
gh_ | ahora | package, type, visibility | versions, tagged_versions, age_days, days_since_update, url |
gh_ | fechado, al publicarse | package, type, visibility, tag | digest, published, url |
gh_ | ahora | gist, public | files, comments, size_bytes, description, url, age_days, days_since_update |
gh_ | diario | achievement | name, tier_number, tier_name, present, image, url |
gh_ | diario | achievement | name, count, tier_number, next_threshold, percent, page_tier, agrees, image, url |
gh_ | ahora; la fila orcid a diario | provider | url, present |
gh_ | ahora | pinned, position, kind, stars, days_since_push, url | |
gh_ | ahora | flag | enabled, message, age_days, url |
gh_ | fechado, cuando se hizo el patrocinio | direction (sponsor, maintainer), sponsorable | sponsorship, active, one_time, privacy, tier, amount_cents, url |
gh_ | ahora | has_listing, listing_name, listing_public, listing_age_days, tiers, monthly_income_cents, next_payout_cents, next_payout_date, sponsor_spend_cents, lifetime_received_cents, sponsorships_received, goal_kind, goal_title, goal_target, goal_percent, url | |
gh_ | diario | tier | tiers, price_cents, one_time, retired, age_days, url |
gh_ | diario | list | lists, items, private, name, age_days, days_since_add, url |
gh_ | ahora | pulls_opened, pulls_merged, pulls_open_now, pulls_merged_elsewhere, pulls_reviewed, issues_opened, issues_closed, issues_elsewhere, commented_elsewhere, commits, repositories, url | |
gh_ | fechado, al crearse | fork | created, private, url |
gh_ | diario | kind (ssh, gpg), key | keys, age_days, days_since_use, never_used, days_to_expiry, verified, revoked, can_sign, emails, url |
gh_ | fechado | own, is_reply, author, comment, number | comments, answers, upvotes, title, reply_to, private, discussion_answered, discussion_answerable, discussion_closed, answered_by, answer_chosen_by, state_reason, category, seconds_to_answer, seconds_to_close, url |
gh_ | fechado | own, number | comments, private, url |
following es el número del propio perfil, y cuenta organizaciones además de
personas. La conexión following de GraphQL cuenta solo usuarios, que en esta
cuenta daba cuatro donde el perfil daba nueve, así que más de la mitad era
invisible. Se guardan las dos: following es lo que enseña la página del
perfil, following_users es el recuento de personas de la conexión. Cuando la
petición del perfil falla, el recuento de la conexión rellena las dos, que es la
señal de que no se llegó al perfil.
versions en un paquete es el recuento que declara el propio elemento del
paquete, con el recorrido como respaldo. tagged_versions cuenta solo
publicaciones con nombre. Antes contaba cada tag de contenedor, y cerca de la
mitad de esos son el tag de respaldo de referrers OCI que GitHub publica por
cada manifiesto de atestación y de firma: sha256- seguido del digest que la
fila ya lleva. Nadie se descarga uno, hay uno nuevo en cada build, y excluirlos
redujo a la mitad el recuento en dos paquetes, de ciento veintiséis a cincuenta
y siete en uno. gh_package_version tampoco escribe ya una fila para ellos. Un
paquete que GitHub no asocia a ningún repositorio escribe (none) en las tres
etiquetas que nombran uno, en vez de omitirlas y acabar en una serie sin columna
de repositorio que una consulta pueda nombrar.
gh_pinned_item y gh_profile_flag son la página del perfil convertida en
datos. Un elemento fijado no tiene fecha propia, así que ambas se sellan ahora.
Un gist fijado no está en ningún repositorio: GitHub lo nombra por su hash, así
que repo es ese hash y owner es la cuenta, que es el único dueño que puede
tener un elemento fijado. El campo kind dice cuál de los dos es la fila.
position es un campo y no una etiqueta: un repositorio que pasa del hueco dos
al tres es el mismo elemento fijado, y como etiqueta cada reordenación
bifurcaría la serie. flag es una lista cerrada de ocho: hireable,
developer_program, campus_expert, github_star, bounty_hunter,
employee, sponsors_listing, que es si la cuenta tiene perfil de Sponsors, y
limited_availability, que es el estado de disponibilidad llevado como octava
marca en vez de como medida propia, con el message que muestra y los
age_days desde que se puso ese estado. age_days se escribe solo en esa fila
y es la edad del mensaje de estado, no de la marca; GitHub no dice cuándo se
concedió ninguna de las otras, así que ninguna fila lleva fecha para ellas.
gh_sponsorship es el único registro fechado del dinero. gh_account.sponsors
y gh_account.sponsoring son recuentos de ahora que no dicen ni cuándo ni a
quién, y gh_sponsors_listing.lifetime_received_cents es un total sin fechas
dentro. Las dos conexiones se leen con activeOnly apagado, que es lo que
recupera un patrocinio caducado. sponsorable es la otra parte, y es
literalmente la palabra private cuando el patrocinio la oculta, en cuyo caso
no se escribe URL en vez de inventarse una.
gh_sponsors_tier es inventario estable, igual que una clave SSH. Fechar un
nivel en su creación pondría los ocho en 2021, fuera de todo rango de dashboard,
donde se leerían como “sin niveles”; anclados al inicio del día UTC convergen en
una fila por nivel y día, y age_days mantiene recuperable la fecha de
creación.
gh_star_list tiene la misma forma por la misma razón: las listas en las que
la cuenta archiva las estrellas que da, una fila por lista con cuántas guarda,
anclada al inicio del día UTC. Una lista lleva dos fechas, cuándo se creó y
cuándo entró la última estrella, y las dos sobreviven como age_days y
days_since_add en vez de fechar la fila, que pondría una lista creada en
2024 fuera de todo rango de dashboard. La etiqueta es el slug, que es lo que
direcciona la página de la lista; el nombre visible es un campo. Si el slug
sobrevive a un cambio de nombre no está verificado, porque comprobarlo exige
renombrar una lista. Viaja en la consulta de cuenta que ya se pagaba: medido el
2026-09-11, once listas con sus recuentos no añadieron nada a un coste de uno.
gh_contribution_day es el único sitio donde los cuadrados verdes existen como
datos. Con every.families.history puesto, llega hasta el año en que se creó
la cuenta, a un punto de GraphQL por año. Su level es el tono del cuadrado, el cuartil
del año que calcula el propio GitHub como el 0 a 4 que dibuja el perfil, y no
es función del recuento: en una cuenta, 83 contribuciones un día y 52 otro
fueron ambas el segundo cuartil. El cuartil es el de la ventana que se pidió,
los últimos doce meses en la pasada y el año natural en history, así que un
día que escriben ambas puede cambiar de tono entre una y otra, como en el
perfil al elegir un año. Por eso la rejilla del dashboard tiñe cada día por su
propio recuento: medido el 2026-09-14, la página del perfil usa los quintos del
día más activo de la ventana, una regla que reprodujo los 366 cuadrados a
partir de los recuentos del propio GitHub, mientras que level discrepaba de
la página en 33 de esos días.
gh_achievement es la única medida que no sale de la API. GitHub no lista los
logros en REST ni en GraphQL, así que la familia lee la página pública del
perfil, https://github.com/<login>?tab=achievements, cada hora como
visitante anónimo: ningún token viaja a ella y no se carga a ningún
presupuesto. Una fila por insignia, fechada al inicio del día UTC: name es
la insignia, tier_number el número de su etiqueta (1 sin etiqueta, 2 a 4
para x2 a x4) y tier_name el color que le corresponde (default, bronze,
silver, gold); el número no se llama tier porque Elasticsearch asigna un
tipo a cada nombre de campo una sola vez para los índices de todas las
mediciones y tier ya es una cadena en los patrocinios. El analizador es estricto con el marcado que acepta y contrasta cada parte de
una tarjeta con las demás, así que cuando GitHub cambie la página la familia
avisa una vez y no escribe nada hasta que se actualice el analizador; las
filas anteriores se conservan, y un panel que lea la fila más reciente por
insignia se queda obsoleto en vez de equivocado.
image es la imagen de la insignia que muestra la página en ese nivel, para
que un panel la dibuje.
El sitio del que se lee la página se deriva de github.base_url. Un base_url
que sea un proxy delante de la API tiene que nombrar el sitio con
github.web_url, porque el host de la
API contesta la url de la página con un 404 en JSON, y la familia lo rechaza en
vez de leerlo como que no hay insignias.
Una cuenta sin ninguna insignia no tiene pestaña de logros: su url contesta 404 mientras el perfil contesta 200, lo que son cero filas y ningún aviso, la misma lectura que hace cualquier familia de un 404. Un 200 sin ninguna tarjeta se rechaza como página cambiada en vez de leerse como que no hay ninguna.
gh_achievement_progress la escribe la misma familia junto a las insignias:
una fila por insignia con niveles (Pull Shark, Galaxy Brain, Starstruck, Pair
Extraordinaire), la muestre la página o todavía no, que dice cuánto le falta a
la cuenta para el siguiente nivel. GitHub no publica ni la regla con la que se
gana una insignia ni el recuento alcanzado, así que el recuento se recalcula
desde la API y los umbrales son los de la comunidad, la tabla Tiers de
Schweinepriester/github-profile-achievements
tal y como se leyó el 2026-09-12: Pull Shark cuenta pull requests fusionadas en
cualquier sitio y sus niveles empiezan en 2, 16, 128 y 1024; Galaxy Brain
cuenta las discusiones cuya respuesta aceptada escribió la cuenta, en 2, 8, 16
y 32; Starstruck toma las estrellas del repositorio propio con más estrellas,
sin los forks, en 16, 128, 512 y 4096; Pair Extraordinaire cuenta pull requests
fusionadas en repositorios públicos con un commit coautorizado, una por pull
request, en 1, 10, 24 y 48, contrastado ese mismo día con un recuento a mano
(los dos recuentos diferían en dos, y la diferencia caía en un rango donde el
recuento a mano pasaba de los mil resultados que una búsqueda pagina, y la
página mostraba el mismo nivel en ambos casos; las pull requests
coautorizadas de un repositorio privado no movieron nada). Las insignias de un
solo nivel y las dos que GitHub aún prueba no tienen fila: no hay siguiente
nivel contra el que medir. tier_number es el nivel que implica el recuento (0
por debajo del primer umbral), page_tier el nivel que muestra la página del
perfil (0 cuando la insignia no está en ella) y agrees si los dos coinciden;
cuando coinciden, next_threshold es dónde empieza el siguiente nivel (0 en el
máximo) y percent el recuento frente a él (100 en el máximo). Una fila que no
coincide es una regla que la página contradice, o una página que GitHub aún no
ha recalculado, avisada una vez por proceso en el registro, y no lleva ninguno
de los dos campos, así que no se dibuja ninguna barra a partir de una regla que
la página contradice. Tres de los recuentos son una consulta GraphQL; el cuarto
es un recorrido por las pull requests fusionadas de la cuenta en repositorios
públicos con los mensajes de sus commits, partido por fecha de fusión donde un
rango supera los mil resultados que una búsqueda pagina, un punto por página.
Sobre la vida entera de una cuenta son unas pocas docenas de puntos y 24 MB (35
consultas y 94 segundos sobre 2.315 pull requests, medido el 2026-09-27), así
que se hace una vez y luego se guarda: el fichero de estado tiene el recuento
con el último día UTC que cubre, y cada pasada recorre solo las pull requests
fusionadas desde entonces, una página por pasada: de 468 a 513 KB en esa
cuenta en cada pasada horaria que registró el proxy de producción el
2026-09-27, un tamaño que crece a lo largo del día con lo que se fusiona. En el
día en que corre una pasada todavía se fusiona, así que sus pull requests están
en la fila de ese día y la pasada siguiente las vuelve a recorrer. El historial entero se vuelve a recorrer una vez por semana, porque
el recuento puede bajar (un repositorio que pasa a privado o se borra saca sus
pull requests de is:public), y siempre que haya cambiado la regla con la que
se guardó el recuento.
Una pasada a la que la API no responde escribe las insignias y ninguna fila de
progreso, y dice qué lectura falló con un aviso que lleva el error:
achievement counts unavailable, no progress rows this pass o co-authored pull requests unavailable, no progress rows this pass. Cuesta esa pasada y
nada más. Las filas son las del día, así que las escribe la pasada de una hora
después, y un recorrido que se queda a medias deja el recuento guardado como
estaba, para que la pasada siguiente vuelva a recorrer sus días. Un recuento
que el recorrido no pudo cerrar entero se escribe como un mínimo, y se dice una
vez por proceso mientras sus números no cambien:
level=WARN msg="co-authored pull request count is a floor" capped=false truncated=3capped es un día que por sí solo tenía más de los mil resultados que pagina
una búsqueda, y truncated cuántas pull requests tenían más commits de los que
caben en una página de cien, ningún trailer en los leídos y el resto de sus
commits sin leer porque falló la consulta que los pedía, que es lo más que
puede quedarse corto el recuento. Los dos se guardan en el fichero de estado
con el recuento, así que una pasada que suma a un mínimo sigue diciendo que lo
es.
Una pull request con más de cien commits y ningún trailer en los cien primeros
se sigue leyendo, cien commits cada vez y diez pull requests por consulta, un
punto cada una, hasta que aparece un trailer o se acaban los commits. Hasta la
2.6.0 se quedaba ahí y se contaba como un mínimo: en la cuenta medida, tres
pull requests de 143, 144 y 248 commits hacían que cada pasada avisara de
truncated=3, y leer el resto de sus commits el 2026-09-28, dos consultas y
121 KB, no encontró ningún trailer en ninguna, así que el recuento de 33 había
sido exacto desde el principio. Esas consultas las paga el recorrido entero
semanal, y también cada pasada que recorre el día en que se fusionó una pull
request así.
gh_social_account lleva una fila más que el listado de cuentas sociales: la
página personal, bajo el proveedor website, del blog del perfil. El ORCID
iD que muestra la página del perfil no está en ningún endpoint, así que la
familia achievements, que ya lee esa página cada hora, lo escribe desde
la vcard de la página bajo el proveedor orcid, sellado al comienzo del día
UTC como las insignias. Los enlaces que la API sí lista (Mastodon, LinkedIn,
Bluesky) los escribe la familia profile desde la API y en la página se
saltan, así que ninguna cuenta se escribe dos veces. La lectura es tan
estricta como la de las insignias: una página sin la vcard es una página
cambiada y un aviso, nunca “sin cuentas”.
pronouns es la línea de pronombres del perfil, he/him, como campo en la
fila de cabecera, ausente cuando el perfil no muestra ninguna. Campo y no
etiqueta: es texto libre que el propietario puede editar, y como etiqueta cada
edición bifurcaría la única serie de la cuenta.
gh_issue_comment y gh_discussion_comment se leen desde el extremo más
reciente de sus conexiones, que listan de más antiguo a más nuevo: la única
página de una pasada son los cien comentarios más recientes. Después se leen
aparte las respuestas aceptadas de la cuenta, las quinientas más recientes en
una pasada y todas en un relleno, así que un comentario aceptado como
respuesta cuando ya había salido de esos cien se escribe igualmente con
answers a 1 en la siguiente pasada. Un comentario que devuelven las dos
lecturas se escribe una sola vez. Las dos medidas llevan private, si el
repositorio en el que se dejó el comentario es privado, por la misma razón que
gh_external_contribution: own no lo dice, porque los repositorios propios
de la cuenta pueden ser de las dos clases y los de una organización no son
propios.
gh_contribution_year tiene una fila por año pasado, fechada el treinta y uno
de diciembre, y una por el año en curso, que se pide en cada ejecución desde el
uno de enero hasta ahora. Esa fila es una instantánea, sellada al inicio del
día UTC y marcada partial, para que un panel que compare años distinga una
barra que aún crece de una terminada; se lee como la fila más reciente por
year. La primera ejecución del año siguiente la sustituye por la fila final
fechada en diciembre.
gh_account.packages se cuenta con los listados REST que recorre la familia
profile, no con la conexión packages de GraphQL, que no ve el registro de
contenedores y respondía 0 para una cuenta cuyos cuatro paquetes son todos
contenedores. Si un listado falla, se mantiene el recuento de GraphQL.
gh_account_total es la respuesta a “cuántos ha habido en total”. Todas las demás
medidas de aquí son una fila por hecho, que es la forma correcta para “cuántos
en julio” y la equivocada para un total de por vida. GitHub los cuenta él mismo:
diez son una consulta GraphQL con un alias de búsqueda para cada uno, y
commits, que la búsqueda de GraphQL no sabe contar, es una búsqueda REST
propia. Así que cada número es una fila y es correcto en la primera pasada de
una instalación recién hecha.
Los tres campos que acaban en _elsewhere comparten un mismo sentido de fuera:
en un repositorio que no es de la cuenta, porque el calificador de búsqueda
-user:LOGIN deja fuera los que sí lo son, así que los repositorios de una
organización cuentan como fuera aunque la cuenta pertenezca a ella.
pulls_merged_elsewhere son sus pull requests fusionadas allí e
issues_elsewhere las issues que abrió allí. commented_elsewhere son las
issues y pull requests de allí que llevan al menos un comentario suyo
(commenter:LOGIN -user:LOGIN): hilos, no comentarios, cada uno contado una
vez por muchos que dejara. Uno que abrió solo cuenta si además comentó, porque
el mensaje inicial no es un comentario: medido el 2026-09-26, 33 de los 102
hilos que la cuenta había abierto fuera. En la 2.5.1 y anteriores era
commenter:LOGIN -author:LOGIN, que dejaba fuera los hilos que abrió la cuenta
y contaba todos los de sus propios repositorios: 125 donde la definición de
arriba da 55 en la cuenta con la que se leyó esto, 103 de ellos en casa y 54 de
esos pull requests de Dependabot. Un almacén con filas de antes de actualizar
ve la serie bajar esa diferencia en la primera pasada después.
gh_dependency_change escribe una fila en cada pasada de la familia deps, no
solo cuando una dependencia se movió: un rango sin cambios, una cabeza que no
se movió y la primera pasada, que aún no tiene base, escriben cada uno una fila
con change y ecosystem a (none) y los dos contadores a cero. InfluxDB 3
crea la tabla con su primer punto y responde a una consulta que nombra una
tabla que no ha visto con un error, así que una medida escrita solo cuando hay
cambio no existía hasta el primer bump, y el panel que la lee era un error
hasta entonces. La fila a cero no cuesta ninguna petición y los paneles dejan
fuera la serie (none).
Las tres medidas sobre repositorios ajenos, gh_discussion_comment,
gh_issue_comment y gh_repo_created, existen porque una pasada sobre los
repositorios propios no ve nada de eso. Cada una lleva own para poder
separarlos.
Actividad
Sección titulada «Actividad»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado | type, action, ref_type | events, public, commits, url |
gh_ | fechado, última actualización | reason, private, subject_type | is_unread, notifications, title, url |
Ambas son ventanas, no historias. GitHub guarda los últimos trescientos eventos
de los últimos treinta días, y descarta las notificaciones a los tres meses
salvo las guardadas. Lo que se captura es lo que había cuando corrió la pasada. gh_event no lleva actor,
porque el feed es el de la propia cuenta y el actor era el login en todas las
filas; action y ref_type se escriben en todas las filas, (none) en un
push. is_unread es un campo: leer un hilo no mueve su updated_at, así que la
lectura diaria con all=true, la que lista un hilo leído sin respuesta,
reescribe la misma fila en vez de abrir una segunda al lado.
La url de una notificación se deriva de la dirección de API de su asunto, y un
asunto con una forma que el mapeo no reconoce se queda sin ella en vez de
adivinarla, así que buena parte de las filas no llevan enlace.
Configuración y entrega
Sección titulada «Configuración y entrega»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | diario | hook (el id), host, active | events, hooks |
gh_ | fechado, al entregarse | hook, host, event, status, code, ok | deliveries, duration_seconds, redelivery |
gh_ | diario | ruleset, target, enforcement | rulesets, active, days_since_change, url |
gh_ | diario | ruleset, rule | rules, bypass_actors, bypass_always, bypass_sampled, ref_include, ref_exclude |
gh_ | fechado, al guardarse | ruleset, target, actor_type | versions, version_id, ruleset_id, actor_id, url |
gh_ | diario | pattern | rules, admin_enforced, allows_deletions, allows_force_pushes, blocks_creations, dismisses_stale_reviews, requires_approving_reviews, required_reviews, requires_code_owner_reviews, requires_commit_signatures, requires_conversation_resolution, requires_linear_history, requires_status_checks, requires_strict_status_checks, required_checks, requires_deployments, restricts_pushes, restricts_review_dismissals, url |
gh_ | diario | branch, is_default | branches, oid, days_since_commit |
gh_ | fechado, al crearse el despliegue | deployment, environment, task | outcome, deployments, deployment_state, success, superseded, creator, commit, ref, log_url, environment_url, run_id, seconds_to_status, seconds_live, url |
gh_ | fechado, cuando la ruta cambió por última vez | file (dependabot, codeowners, security, funding) | present, bytes, changes, path, blocks, ecosystems, url |
gh_ | fechado, cuando dependabot.yml cambió por última vez | ecosystem, interval | blocks |
gh_ | diario | environment | environments, days_since_change, age_days, protection_rules, has_branch_policy, protected_branches, custom_branch_policies, can_admins_bypass, url |
gh_ | diario | key, read_only | keys, days_since_use |
gh_ | ahora | security_policy, forking_allowed, discussions, issues, wiki, sponsorships, blank_issues, auto_merge, delete_branch_on_merge, merge_commit, rebase_merge, squash_merge, funding_links, issue_templates, pull_request_templates, branch_protection_rules, codeowners, codeowners_errors, vulnerability_alerts, url | |
gh_ | ahora | visibility, archived, fork | commits, stars, forks, watchers, issues, issues_open, issues_closed, pulls, pulls_open, pulls_merged, pulls_closed, releases, discussions, labels, milestones, branches, tags, size_kb, repo_id, age_days, days_since_push, url |
gh_ | diario | ecosystem | packages |
gh_ | diario | license | packages |
gh_ | ahora | change, ecosystem | packages, vulnerable, base, head |
gh_ | ahora | resource | limit, used, remaining, used_ratio, seconds_to_reset, own_cost, own_queries |
gh_ | ahora | family, scope (family, repo), reason | repos, failed, points, error |
Los webhooks fallan en silencio. Medido, un hook llevaba respondiendo 403 en setenta y ocho de sus últimas cien entregas y no había nada en ningún sitio que lo dijera.
Un 404 de la protección de rama no significa desprotegido: un repositorio puede estar gobernado enteramente por rulesets, de los que ese endpoint no sabe nada.
gh_ruleset_version es el historial detrás de gh_ruleset: una fila por
versión guardada de un ruleset, fechada en el momento en que GitHub la guardó,
con el actor que la guardó. days_since_change solo resume esa historia: un
ruleset apagado un martes y encendido el viernes siguiente se lee como
“cambiado hace tres días”, y nada más de lo recogido dice que una protección
faltó alguna vez. GitHub nombra al actor por id y tipo y no por login, así que
la fila lleva actor_type como etiqueta y actor_id como campo. La familia es
rulesets, diaria: una petición de listado por repositorio y una de historial
por ruleset, las dos con ETag, así que un día en que nadie editó una protección
no cuesta nada del presupuesto. Medido el 2026-09-11 contra el ruleset que
protege el repositorio más activo medido: veinte versiones a lo largo de cinco
meses, 3 KB, una petición core.
Solo se guarda el host de la URL de un webhook. La ruta suele llevar un secreto.
gh_repo_policy y gh_repo_total llegan en una sola consulta de GraphQL por
lotes que cuesta un punto por cada diez repositorios, que es por lo que unos
ajustes que una pasada REST cobraría a ciento noventa y ocho llamadas llegan a
recogerse. codeowners_errors es el que falla en silencio: un CODEOWNERS
roto deja de pedir revisiones y no dice nada. vulnerability_alerts viaja en esa
misma consulta sin coste añadido y es una segunda lectura, independiente, del
mismo interruptor que informa gh_security_feature{feature="dependabot"}.enabled:
uno es el ajuste del propio repositorio, el otro es si el listado respondió de
verdad. Que las dos fuentes discrepen es el caso que merece verse.
gh_repo_total es de donde se leen las estrellas y los forks de un
repositorio archivado, en el Overview y en Every repository, ever, y
gh_repo no lo es. El que el filtro por omisión aparta por estar archivado no
recibe fila de gh_repo de ninguna pasada, porque ninguna familia lo recorre,
y solo tiene la que escribió un relleno histórico, pero sigue recibiendo y
perdiendo estrellas y forks, así que la familia totals escribe su
gh_repo_total en cada pasada desde la consulta que fecha su archivado: las
mismas etiquetas y campos que la fila de un repositorio recogido, archived a
true, sellada en la pasada. Hasta la 2.5.1 solo un relleno histórico la
escribía, una vez, y el 2026-09-26 una de esas filas decía 4 estrellas donde
GitHub decía 3. Las estrellas y los forks de un repositorio vivo en el Overview
siguen saliendo de gh_repo, la fila que leen también la tabla de la sección
Inventory y la tarjeta. No recibe gh_repo_policy. Esa consulta
pregunta por veinticinco repositorios cada vez: medido ese mismo día, la
pasarela respondió a la fila de toda la vida de cincuenta repositorios
archivados una vez en 9,2 segundos y la rechazó dos veces pasados unos once, y
respondió a veinticinco en menos de siete.
Las tres medidas de dependencias están apagadas por omisión. El SBOM es una llamada y un mega o dos por repositorio, y solo se guarda el agregado: una sola subida de dependencia son trescientos setenta cambios, y lo que se almacena son seis filas.
El commit en el que termina un diff, y del que parte el siguiente, se lee como
el SHA desnudo de HEAD bajo el media type application/vnd.github.sha:
cuarenta bytes, donde el listado de un commit que leía antes eran cinco
kilobytes y medio. La respuesta lleva ETag y se pide de forma condicional, así
que en un repositorio al que nadie ha hecho push la lectura del día es un 304
gratuito, como lo era la del listado. El SBOM solo se lee cuando esa cabeza se
movió: GitHub lo regenera en cada petición, así que su ETag nunca acierta y
cada lectura se cobra de su propio cubo, y un repositorio sin commit tiene los
paquetes que tenía.
gh_rate_limit y gh_collector_family son lo que el colector mide de sí
mismo. La primera es lo que le queda por gastar: GitHub lleva quince
presupuestos independientes, y sin ella una familia saltada por falta de
presupuesto es idéntica a una familia sin nada que contar.
La segunda es lo que cada barrido consiguió hacer. Una fila por familia
ejecutada, siempre, con cuántos repositorios se le preguntaron (repos),
cuántos de ellos no pudo recoger (failed) y cuántas filas produjo (points).
En commits, issueevents e issues, repos cuenta también los repositorios
en los que la consulta de movimiento
no encontró nada nuevo, que la familia dejó sin leer y por los que no escribió
ninguna fila. Y una fila más por cada repositorio que perdió, nombrándolo como lo nombra
cualquier otra medida y con reason, una palabra acotada de qué la detuvo (el
estado HTTP, rate limited, query too large, canceled), con el mensaje
entero en el campo error. scope es lo que distingue las dos: family para
la primera clase, cuyas etiquetas de repositorio llevan (none), y repo para
la segunda. error lleva (none) también cuando no hay mensaje, y eso no es
adorno: el protocolo de línea descarta un campo de texto vacío, así que una
columna escrita solo al fallar no existiría hasta que fallara algo, y una
consulta que la nombrara se rechazaría en vez de responder sin filas.
Las filas que siempre llegan son el motivo de todo esto. Una familia sin ninguna fila en un barrido no se ejecutó en ese barrido, que es justo lo que un panel vacío nunca podría decir, y son además lo que hace que la medida exista en una cuenta donde nunca ha fallado nada: una tabla en la que InfluxDB no ha escrito nunca no se dibuja vacía, se rechaza.
Un valor de family no es una familia. discover es el listado de
repositorios, que no se configura ni se puede apagar, y está aquí porque toda
familia depende de él: un barrido que no puede listar los repositorios no
ejecuta ninguna, y sin esto la página mostraría dieciséis familias que nunca
corrieron y ningún motivo para nada de ello. Escribe fila solo cuando falló,
porque un listado que funcionó ya lo dicen todas las demás filas del mismo
barrido.
Esto existe por un fallo medido. El 2026-09-16 gh_workflow_run y
gh_workflow_job no tenían absolutamente nada de los cinco repositorios más
activos de esta cuenta, cada uno porque una llamada a
/repos/<repo>/actions/runs/<id>/jobs había respondido 502 una vez y el
runner tiraba todo lo que esa familia ya había recogido de ese repositorio. La
fila Continuous integration del dashboard se calculaba sobre una cuenta a la
que le faltaban sus cinco repositorios más activos, la fila Cost de la misma
página decía que uno de ellos había quemado 27,6 K minutos de macOS, y el único
registro de la causa era una línea en un diario. Ahora el colector conserva lo
que reunió antes de un fallo, y escribe qué falló.
GET /rate_limit informa de los presupuestos y no cobra por ninguno, que es lo
que hace que casi todo esto sea gratis. No todos los que informa son ciertos:
medido con el token bajo el que corre esto, el endpoint
respondía graphql como used=0, remaining=5000 en el mismo minuto en que
GraphQL respondía used=162 y se movía de uno en uno con cada consulta, y los
dos ni siquiera comparten reloj. Así que la fila graphql se construye con el
bloque rateLimit con el que responde GraphQL, y la versión del endpoint se
descarta en vez de publicarse al lado. Esa lectura es una petición cada quince
minutos y, medido, no cuesta puntos.
graphql no es el único cubo que el endpoint se inventa. Medido el 2026-09-12,
las cabeceras de una petición de SBOM decían dependency_sbom used 1,
remaining 99, reset en 59 s, y GET /rate_limit dos segundos después decía
used 0, remaining 100, con el reset deslizándose un segundo por llamada. Cada
respuesta REST nombra en sus cabeceras el cubo al que cobró, así que una fila se
construye con las cabeceras más recientes que vio el cliente siempre que sigan
dentro de su propia ventana y digan que se gastó más de lo que el endpoint
admite. Las ventanas de dependency_sbom y search son de un minuto, así que
una fila dependency_sbom que lea cero entre ejecuciones de la familia deps
es un cubo rellenado, no el defecto.
La fila graphql puede por tanto faltar, donde las otras se escriben
siempre que el endpoint responda. No se escribe cuando todavía no se ha leído
nada de GraphQL y ninguna lectura anterior sigue dentro de su ventana: una fila
que falta dice “no medido” donde un cero dice “nada gastado”, y el cero era el
defecto.
own_cost y own_queries están solo en esa fila. limit, used y remaining
describen la ventana del token entero, compartida con quien sea que lo tenga;
estos dos son la parte de la que responde este proceso. Cuentan desde el
arranque del proceso, así que un reinicio los devuelve a cero y un panel tiene
que leerlos como contador y no como valor.
Logs de jobs
Sección titulada «Logs de jobs»| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado, cuando se imprimió la línea | workflow, job_name, run | line, head_branch |
Apagado por omisión: pon every.families.joblogs. Es texto y no una medida,
así que se excluye del destino de InfluxDB por omisión y el exportador de Prometheus lo
salta; Loki es donde le corresponde. Los dashboards exportados llevan un
panel de texto, “Where failure output went”, en el sitio que ocuparían las
líneas, porque quien los importa puede no tener Loki;
cmd/publish_dashboard -loki <datasource-uid> publica el dashboard con las líneas
leídas de Loki en el lugar de ese panel (ver
los dashboards).
Solo jobs fallidos, y solo las últimas cuarenta líneas de cada uno. La salida de un job correcto son miles de líneas que nadie va a leer, cada log cuesta una petición, y la cola es donde un fallo se explica. GitHub borra los logs pasado el periodo de retención del repositorio, noventa días por omisión, y responde 410 cuando ya no están; un relleno solo pide los últimos noventa días, diga lo que diga el ajuste, y como mucho 500 jobs fallidos por repositorio.
workflow es aquí la misma ruta de fichero, por el mismo ayudante, así que una
línea de log se une con la ejecución que la imprimió; y branch pasó a ser el
campo head_branch por las mismas dos razones que en la ejecución. run es a
propósito una serie por ejecución, que es a lo que de verdad pertenece una línea
de log, y solo sale a cuenta porque esta familia está apagada por omisión.
Los códigos de color se eliminan y se quita la marca de orden de bytes que GitHub escribe antes de la primera marca de tiempo, para que buscar una palabra no falle porque la palabra estuviera coloreada.
La lista de fallos se pide solo para las ejecuciones creadas en los treinta y un días anteriores a que se abriera la ventana, redondeado al día hacia abajo. Treinta y un días porque una reejecución conserva el created_at de su primer intento y GitHub permite reejecutar durante treinta días: medido el 2026-09-11, el fallo más nuevo del repositorio más activo medido era el tercer intento de una ejecución creada dos horas antes de terminar, y un margen de la duración de un job habría perdido toda reejecución de un fallo de más de una mañana. Sin filtrar eran los cien fallos más nuevos que el repositorio haya tenido nunca, seiscientos kilobytes por repositorio y pasada para una ventana de una hora casi siempre vacía; filtrada, un mes de fallos, sesenta y ocho filas y un megabyte descomprimido en ese repositorio, unas pocas filas o ninguna en la mayoría. El redondeo mantiene la URL, y con ella el ETag, igual entre los pasadas de un día, que es lo que convierte la repetición en un 304 gratuito y no en un 200 cobrado sobre una URL nueva; dentro del día la página solo cambia cuando se crea o se reejecuta un fallo. El corte en la ventana en sí se sigue haciendo aquí, por cuándo terminó la ejecución.
| Medida | Fechado | Etiquetas | Campos |
|---|---|---|---|
gh_ | fechado, por día | product, sku, unit | quantity, price_per_unit, gross, discount, net, url |
unit es el propio unitType de GitHub, con la mayúscula con la que GitHub lo
manda: Minutes, GigabyteHours, AICredits, Requests. Se pasa tal cual, sin
normalizar, y los paneles que leen minutos filtran por esa mayúscula.
No hay etiqueta org. El único endpoint de facturación que puede leer una
cuenta personal es el suyo, y ese informe no tiene organizationName: el campo
es del informe de organización, que necesita una organización a la que
preguntar. Comprobado contra la descripción OpenAPI publicada y contra el
endpoint en vivo, donde ninguno de 487 elementos de uso traía la clave.
Escrita igualmente era (none) en todas las filas recogidas, que es una columna
y una entrada de leyenda que solo dicen que ahí no hay nada.
repo es (none) en un cargo que no pertenece a ningún repositorio, que es lo
que es un asiento de Copilot. Eso es una fila real de la factura y no un
repositorio, así que la tabla de coste por repositorio lo deja fuera; los totales
de gasto de arriba sí lo incluyen.
net no siempre es cero. En la cuenta con la que se desarrolló esto lleva el
crédito mensual, que es por lo que se guardan el bruto, el descuento y el neto
en vez de derivar uno de los otros. La fila se sella al inicio de su día: el
date de GitHub llega como el primer minuto facturado del día en la mitad de
las filas, lo que habría duplicado la fila si GitHub informara de otro minuto
en la siguiente lectura.
Columnas que solo existen una vez escritas
Sección titulada «Columnas que solo existen una vez escritas»InfluxDB 3 crea una columna la primera vez que una fila la trae, y una consulta
que nombra una columna que ninguna fila ha escrito falla al planificarse en vez
de responder nulo: el panel entero se pone en rojo. Así que un campo que solo se
escribe cuando GitHub tiene valor para él no existe en una base donde eso nunca
ha pasado. Los que más probablemente falten en una base recién creada:
gh_discussion.state_reason, seconds_to_answer y seconds_to_close;
gh_milestone.days_to_due y seconds_to_close; gh_ruleset_rule.ref_exclude;
y gh_workflow_run.initial_actor. La misma regla cubre todo seconds_to_* que
necesita un cierre, toda url de un ítem para el que GitHub no envía dirección,
los campos opcionales del aviso en una alerta, checks_total y checks_failed
en un commit por el que pasó una puerta, label_names en un ítem con etiqueta,
resolved_by en un hilo resuelto, queued_seconds en el primer intento de una
ejecución, pull_request, pull_requests, headline y head_repo en una
ejecución que GitHub enlazó, describió o tomó de un fork, merged en el
trabajo fuera que se fusionó y additions, deletions y changed_files en el
trabajo fuera que es una pull request, que una cuenta que solo ha abierto
issues en repositorios ajenos no ha escrito nunca, y language en un
repositorio de fuera en el que GitHub detectó uno. Dos más conviene
nombrarlas, porque las tablas de arriba las listan junto a campos que sí están
siempre: gh_dependabot_alert_item.dismissed_comment, que solo se escribe
cuando quien descartó una alerta tecleó un motivo, y gh_event.commits, que
solo se escribe en un evento de push. Ninguna de las dos columnas existe en la
base de datos de producción contra la que se comprobó esta documentación.
gh_label solo escribe las etiquetas que alguien ha usado;
gh_repo_total.labels es el recuento declarado.
Una de estas la nombra un panel de los que se publican, y es la que más
probablemente falte: gh_pull_request.seconds_to_first_human_review, que solo
existe una vez que alguien que no es el autor ni un bot ha revisado una pull
request. En una base donde eso nunca ha pasado, la tarjeta que la lee informa de
un error de esquema en vez de decir No data, y se lleva por delante los cinco
valores que la acompañan en el mismo panel. Ninguna consulta puede preguntar si
una columna existe, así que esto es una propiedad del almacén y no un defecto
que reparar: la reparación, si llega a morder, es una sola fila de cualquier
tipo que traiga el campo.
Lo raro que es, medido el 2026-09-17 en la cuenta sobre la que se desarrolló esto: catorce filas en todo el almacén, de catorce pull requests de cuatro repositorios, la más reciente abierta el 2026-07-05, y ninguna dentro de los últimos quince días, frente a 703 pull requests de 825 de esos quince días que sí tuvieron una primera revisión de un bot o de su propio autor. El campo no está roto; es la respuesta a una pregunta más estrecha de lo que espera quien lee, que es por lo que el panel se llama por esa pregunta.
La misma lectura vale para los dos de gh_workflow_run. initial_actor solo se
escribe cuando el actor y el triggering_actor de GitHub son distintos, que es
una reejecución que pidió otra persona: 0 de 10.201 ejecuciones en quince días
aquí, y 0 de las 300 ejecuciones más recientes de tres repositorios comprobadas
contra la API al mismo tiempo. head_repo solo se escribe para una ejecución
que vino de otro repositorio: 18 de esas 10.201, todas de la pull request de un
mismo fork. Los dos son correctos y los dos son raros, que es el aspecto que
tiene un campo que solo se escribe cuando GitHub tiene algo que decir.