Porque falham os sistemas embebidos em produção

Os sistemas embebidos parecem muitas vezes estáveis até saírem de ambientes controlados. O problema é que os sistemas embebidos atuais já não são dispositivos isolados. São ecossistemas de componentes que interagem entre si.

Autor
Interpretica
Publicado
Leitura
12 min
Temas
embedded systems, testing, static analysis, complex systems

Muitos sistemas embebidos parecem estáveis até ao momento em que saem de ambientes controlados.

Dentro da empresa, tudo pode parecer bem durante meses. O dispositivo funciona na validação interna, porta-se bem nas demonstrações e passa os ciclos de teste habituais. Depois a produção começa a crescer e surgem coisas estranhas:

dispositivos que bloqueiam depois de vários dias ligados

instabilidade de rede que só aparece com certos padrões de tráfego

falhas raras de temporização que ninguém consegue reproduzir de forma consistente

degradação aleatória depois de atualizações de firmware

comportamento que muda consoante a revisão do hardware

As equipas que trabalham com sistemas embebidos conhecem bem este padrão.

Em geral, o problema não é um bug óbvio escondido no código à espera de ser descoberto. Os sistemas embebidos atuais já não são dispositivos isolados com comportamento previsível. São ecossistemas de componentes que interagem entre si, em condições que mudam a toda a hora.

A maioria das falhas surge entre os componentes, e não dentro deles

É aqui que o desenvolvimento embebido engana. Cada parte do sistema pode funcionar muito bem sozinha:

Drivers

Comportam-se corretamente isolados, mas podem gerar estados inesperados com acesso concorrente

Serviços

Cada um devolve os resultados esperados, mas a temporização entre eles cria instabilidade em escala

Firmware

Passa a validação nos ambientes de teste, mas comporta-se de outra forma no hardware real

Comunicação em rede

Parece estável nos testes, mas falha com padrões de tráfego reais e com a infraestrutura sob pressão

Mas os sistemas em produção são definidos pela interação entre as partes.

Um pequeno atraso numa camada pode provocar novas tentativas noutro sítio. Um timeout de rede muda a temporização da execução. Um componente sobrecarregado cria instabilidade muito longe da origem do problema.

Quanto maior a infraestrutura, mais difícil é ver estas cadeias de interação com clareza. Isto é ainda mais verdade hoje. Os ambientes embebidos incluem cada vez mais infraestrutura distribuída, serviços cloud, atualizações remotas, camadas de rede e integrações externas.

A certa altura, o sistema deixa de se comportar como um conjunto de módulos. Passa a comportar-se como um ecossistema com padrões de falha próprios. Em geral, é aí que os testes tradicionais começam a ter dificuldades.

Os bugs embebidos raramente se comportam como bugs de software comuns

A depuração embebida é tão frustrante, em parte, porque muitas falhas estão muito ligadas às condições de execução. Uma condição de corrida pode aparecer apenas:

depois de vários dias ligado

com padrões de tráfego invulgares

durante eventos de hardware simultâneos

em certas revisões do dispositivo

quando os recursos estão no limite

Por isso, os engenheiros passam imenso tempo a perseguir comportamentos que, vistos de fora, parecem aleatórios.

O bug desaparece quando se aumenta o logging

As ferramentas de observabilidade mudam a temporização da execução e escondem as condições que provocam a falha

O problema só aparece na instalação de um cliente

Certas configurações de hardware, padrões de carga ou condições do ambiente não se conseguem reproduzir internamente

Um reinício resolve tudo, por algum tempo

O estado acumula-se com o tempo até passar um limiar. A causa raiz fica escondida entre reinícios

O mesmo binário comporta-se de forma diferente em hardware idêntico

Pequenas diferenças na revisão do firmware, na disposição da memória ou na ordem de inicialização mudam o comportamento em execução

Estas situações são muito comuns em ambientes embebidos. A temporização, a concorrência e a interação com o hardware afetam o comportamento a todo o momento. Quando a rede entra em jogo, reproduzir o problema fica ainda mais difícil.

Os ambientes de produção expõem pressupostos que ficaram escondidos durante o desenvolvimento

Os ambientes de desenvolvimento costumam ser muito mais limpos do que as instalações reais. Os sistemas internos têm, em geral, tráfego previsível, hardware estável, tempo de funcionamento controlado e infraestrutura simplificada.

Em produção, essas proteções desaparecem. Os sistemas passam a funcionar sem parar, com carga irregular. A infraestrutura muda com o tempo. Os dispositivos derivam para estados inesperados. As dependências externas ficam instáveis. Os padrões de tráfego deixam de ser "normais".

Pequenos compromissos técnicos que pareciam inofensivos no desenvolvimento começam a acumular-se e geram instabilidade em todo o sistema. Essa acumulação é importante.

As falhas embebidas raramente são acontecimentos súbitos e catastróficos. Formam-se aos poucos:

A pressão sobre a memória cresce devagar

As fugas e a fragmentação acumulam-se durante dias até o sistema ficar sem margem

As novas tentativas ficam mais frequentes

Uma pequena instabilidade numa camada provoca novas tentativas que aumentam a carga no resto do sistema

A sincronização enfraquece sob carga

Pressupostos de temporização que funcionavam em condições normais falham quando a carga se aproxima dos limites do sistema

As filas crescem nos picos de tráfego

Os buffers enchem mais depressa do que esvaziam e acabam por causar timeouts e mensagens perdidas

A latência altera pressupostos de temporização noutros pontos

Um abrandamento num componente empurra para fora dos limites esperados o comportamento dependente de temporização noutras partes do sistema

O sistema pode continuar a parecer operacional enquanto a fiabilidade se degrada em silêncio.

Os testes mudaram muito mais devagar do que a infraestrutura

Muitas metodologias de teste ainda vêm de uma época em que os lançamentos tinham fases mais claras: desenvolvimento, testes, implantação.

Os sistemas embebidos atuais já não evoluem assim. A infraestrutura muda sem parar:

os serviços atualizam-se de forma independente

os pipelines de implantação passam a ser automáticos

o firmware evolui depressa

as dependências multiplicam-se

as condições de execução mudam a toda a hora

Mas muitos processos de validação ainda partem do princípio de que os sistemas ficam quase iguais entre versões. Este desfasamento cria pontos cegos. Isto acontece sobretudo em sistemas grandes, onde já ninguém vê por completo como todos os componentes se comportam em conjunto.

O mais difícil já não é escrever o código

As equipas atuais constroem sistemas muito sofisticados com uma rapidez surpreendente. O difícil, agora, é manter a visão do sistema quando ele fica grande o suficiente:

Várias camadas de hardware

Cada camada traz a sua própria temporização, o seu estado e os seus modos de falha, que interagem com tudo o que está acima e abaixo

Serviços distribuídos

As fronteiras entre serviços criam passagens invisíveis, onde os pressupostos de cada equipa geram comportamentos inesperados

Comportamento assíncrono

Os eventos disparam em sequências difíceis de prever, de reproduzir ou de seguir até à origem

Dependências de infraestrutura externa

Os serviços de terceiros, as APIs cloud e a infraestrutura de rede trazem uma variabilidade que os testes internos não conseguem modelar

A esta escala, as falhas deixam de parecer erros de engenharia isolados. Passam a parecer comportamento que nasce do próprio sistema.

Isto muda o foco dos testes. A pergunta já não é se cada componente funciona tecnicamente. A pergunta é se o ambiente, como um todo, se mantém estável em condições reais.

Os sistemas embebidos estão cada vez mais difíceis de perceber

Uma das maiores mudanças em curso é que o software embebido se comporta cada vez mais como infraestrutura. Os dispositivos já não são pontos isolados que correm o mesmo firmware durante anos. Muitos sistemas funcionam hoje como ambientes em evolução contínua, ligados a ecossistemas maiores.

Por isso, os testes também têm de se aproximar do comportamento em execução:

Validação que conhece a infraestrutura

Testes que têm em conta o ambiente completo, para lá do dispositivo isolado

Ambientes reproduzíveis

Simular condições realistas para encontrar as falhas antes de chegarem à produção

Testes contínuos durante o desenvolvimento

Validação feita a par do desenvolvimento, em vez de numa barreira final antes do lançamento

Análise das interações entre componentes

Perceber como os componentes se comportam em conjunto em condições reais

Quando as falhas se tornam visíveis em produção, a instabilidade de fundo pode já existir há meses. E, nos sistemas embebidos, estes problemas raramente ficam isolados durante muito tempo.